xref: /Universal-ctags/parsers/clojure.c (revision c18ddfad91670a09f3c9b4891ad7a49730cd9c1b)
1 /**
2  *   Copyright (c) 2015, Miloslav Nenadál <nenadalm@gmail.com>
3  *
4  *   This source code is released for free distribution under the terms of the
5  *   GNU General Public License version 2 or (at your option) any later version.
6  *
7  *   This module contains code for generating tags for the Clojure language.
8  */
9 
10 #include "general.h"
11 
12 #include <string.h>
13 
14 #include "parse.h"
15 #include "read.h"
16 #include "routines.h"
17 #include "vstring.h"
18 #include "entry.h"
19 
20 typedef enum {
21 	K_FUNCTION,
22 	K_NAMESPACE
23 } clojureKind;
24 
25 static kindDefinition ClojureKinds[] = {
26 	{true, 'f', "function", "functions"},
27 	{true, 'n', "namespace", "namespaces"}
28 };
29 
isNamespace(const char * strp)30 static int isNamespace (const char *strp)
31 {
32 	return strncmp (++strp, "ns", 2) == 0 && isspace (strp[2]);
33 }
34 
isCoreNamespace(const char * strp)35 static int isCoreNamespace (const char *strp)
36 {
37 	return strncmp (++strp, "clojure.core/ns", 15) == 0 && isspace (strp[15]);
38 }
39 
isFunction(const char * strp)40 static int isFunction (const char *strp)
41 {
42 	return (strncmp (++strp, "defn", 4) == 0 && isspace (strp[4]));
43 }
44 
isCoreFunction(const char * strp)45 static int isCoreFunction (const char *strp)
46 {
47 	return (strncmp (++strp, "clojure.core/defn", 17) == 0 && isspace (strp[17]));
48 }
49 
isQuote(const char * strp)50 static int isQuote (const char *strp)
51 {
52 	return strncmp (++strp, "quote", 5) == 0 && isspace (strp[5]);
53 }
54 
functionName(vString * const name,const char * dbp)55 static void functionName (vString * const name, const char *dbp)
56 {
57 	const char *p;
58 
59 	if (*dbp == '\'')
60 		dbp++;
61 	else if (*dbp == '(' && isQuote (dbp))
62 	{
63 		dbp += 7;
64 		while (isspace (*dbp))
65 			dbp++;
66 	}
67 
68 	for (p = dbp; *p != '\0' && *p != '(' && !isspace ((int) *p) && *p != ')';
69 		p++)
70 		vStringPut (name, *p);
71 }
72 
skipMetadata(const char * dbp)73 const char* skipMetadata (const char *dbp)
74 {
75 	while (1)
76 	{
77 		if (*dbp == '^')
78 		{
79 			dbp++;
80 			if (*dbp == '{')
81 			{
82 				/* skipping an arraymap */
83 				for (; *dbp != '\0' && *dbp != '}'; dbp++)
84 					;
85 			}
86 			else
87 			{
88 				/* skip a keyword or a symbol */
89 				for (; *dbp != '\0' && !isspace((unsigned char)*dbp); dbp++)
90 					;
91 			}
92 
93 			if (*dbp == '\0')
94 				break;
95 
96 			dbp++;
97 			while (isspace ((unsigned char)*dbp))
98 				dbp++;
99 		}
100 		else
101 			break;
102 	}
103 
104 	return dbp;
105 }
106 
makeNamespaceTag(vString * const name,const char * dbp)107 static int makeNamespaceTag (vString * const name, const char *dbp)
108 {
109 	dbp = skipMetadata (dbp);
110 	functionName (name, dbp);
111 	if (vStringLength (name) > 0 && ClojureKinds[K_NAMESPACE].enabled)
112 	{
113 		tagEntryInfo e;
114 		initTagEntry (&e, vStringValue (name), K_NAMESPACE);
115 		e.lineNumber = getInputLineNumber ();
116 		e.filePosition = getInputFilePosition ();
117 
118 		return makeTagEntry (&e);
119 	}
120 	else
121 		return CORK_NIL;
122 }
123 
makeFunctionTag(vString * const name,const char * dbp,int scope_index)124 static void makeFunctionTag (vString * const name, const char *dbp, int scope_index)
125 {
126 	dbp = skipMetadata (dbp);
127 	functionName (name, dbp);
128 	if (vStringLength (name) > 0 && ClojureKinds[K_FUNCTION].enabled)
129 	{
130 		tagEntryInfo e;
131 		initTagEntry (&e, vStringValue (name), K_FUNCTION);
132 		e.lineNumber = getInputLineNumber ();
133 		e.filePosition = getInputFilePosition ();
134 
135 		e.extensionFields.scopeIndex =  scope_index;
136 		makeTagEntry (&e);
137 	}
138 }
139 
skipToSymbol(const char ** p)140 static void skipToSymbol (const char **p)
141 {
142 	while (**p != '\0' && !isspace ((int) **p))
143 		*p = *p + 1;
144 	while (isspace ((int) **p))
145 		*p = *p + 1;
146 }
147 
findClojureTags(void)148 static void findClojureTags (void)
149 {
150 	vString *name = vStringNew ();
151 	const char *p;
152 	int scope_index = CORK_NIL;
153 
154 	while ((p = (char *)readLineFromInputFile ()) != NULL)
155 	{
156 		vStringClear (name);
157 
158 		while (isspace (*p))
159 			p++;
160 
161 		if (*p == '(')
162 		{
163 			if (isNamespace (p) || isCoreNamespace (p))
164 			{
165 				skipToSymbol (&p);
166 				scope_index = makeNamespaceTag (name, p);
167 			}
168 			else if (isFunction (p) || isCoreFunction (p))
169 			{
170 				skipToSymbol (&p);
171 				makeFunctionTag (name, p, scope_index);
172 			}
173 		}
174 	}
175 	vStringDelete (name);
176 }
177 
ClojureParser(void)178 extern parserDefinition *ClojureParser (void)
179 {
180 	static const char *const extensions[] = {
181 		"clj", "cljs", "cljc", NULL
182 	};
183 	static const char *const aliases[] = {
184 		NULL
185 	};
186 
187 	parserDefinition *def = parserNew ("Clojure");
188 	def->kindTable = ClojureKinds;
189 	def->kindCount = ARRAY_SIZE (ClojureKinds);
190 	def->extensions = extensions;
191 	def->aliases = aliases;
192 	def->parser = findClojureTags;
193 	def->useCork = CORK_QUEUE;
194 	return def;
195 }
196