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