1 /*
2 * Copyright (c) 2000-2005, Darren Hiebert
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 functions for generating tags for makefiles.
8 */
9
10 /*
11 * INCLUDE FILES
12 */
13 #include "general.h" /* must always come first */
14
15 #include <string.h>
16 #include <ctype.h>
17
18 #include "make.h"
19
20 #include "entry.h"
21 #include "kind.h"
22 #include "numarray.h"
23 #include "parse.h"
24 #include "read.h"
25 #include "routines.h"
26 #include "strlist.h"
27 #include "vstring.h"
28 #include "xtag.h"
29
30
31 /*
32 * DATA DEFINITIONS
33 */
34 typedef enum {
35 K_MACRO, K_TARGET, K_INCLUDE,
36 } makeKind;
37
38 typedef enum {
39 R_INCLUDE_GENERIC,
40 R_INCLUDE_OPTIONAL,
41 } makeMakefileRole;
42
43 static roleDefinition MakeMakefileRoles [] = {
44 { true, "included", "included" },
45 { true, "optional", "optionally included"},
46 };
47
48 static kindDefinition MakeKinds [] = {
49 { true, 'm', "macro", "macros"},
50 { true, 't', "target", "targets"},
51 { true, 'I', "makefile", "makefiles",
52 .referenceOnly = true, ATTACH_ROLES(MakeMakefileRoles)},
53 };
54
55
56 /*
57 * FUNCTION DEFINITIONS
58 */
59
nextChar(void)60 static int nextChar (void)
61 {
62 int c = getcFromInputFile ();
63 if (c == '\\')
64 {
65 c = getcFromInputFile ();
66 if (c == '\n')
67 c = nextChar ();
68 }
69 return c;
70 }
71
skipLine(void)72 static void skipLine (void)
73 {
74 int c;
75 do
76 c = nextChar ();
77 while (c != EOF && c != '\n');
78 if (c == '\n')
79 ungetcToInputFile (c);
80 }
81
skipToNonWhite(int c)82 static int skipToNonWhite (int c)
83 {
84 while (c != '\n' && isspace (c))
85 c = nextChar ();
86 return c;
87 }
88
isIdentifier(int c)89 static bool isIdentifier (int c)
90 {
91 return (bool)(c != '\0' && (isalnum (c) || strchr (".-_/$(){}%", c) != NULL));
92 }
93
isSpecialTarget(vString * const name)94 static bool isSpecialTarget (vString *const name)
95 {
96 size_t i = 0;
97 /* All special targets begin with '.'. */
98 if (vStringLength (name) < 1 || vStringChar (name, i++) != '.') {
99 return false;
100 }
101 while (i < vStringLength (name)) {
102 char ch = vStringChar (name, i++);
103 if (ch != '_' && !isupper (ch))
104 {
105 return false;
106 }
107 }
108 return true;
109 }
110
makeSimpleMakeTag(vString * const name,makeKind kind)111 static int makeSimpleMakeTag (vString *const name, makeKind kind)
112 {
113 if (!isLanguageEnabled (getInputLanguage ()))
114 return CORK_NIL;
115
116 return makeSimpleTag (name, kind);
117 }
118
makeSimpleMakeRefTag(const vString * const name,const int kind,int roleIndex)119 static void makeSimpleMakeRefTag (const vString* const name, const int kind,
120 int roleIndex)
121 {
122 if (!isLanguageEnabled (getInputLanguage ()))
123 return;
124
125 makeSimpleRefTag (name, kind, roleIndex);
126 }
127
newTarget(vString * const name)128 static int newTarget (vString *const name)
129 {
130 /* Ignore GNU Make's "special targets". */
131 if (isSpecialTarget (name))
132 {
133 return CORK_NIL;
134 }
135 return makeSimpleMakeTag (name, K_TARGET);
136 }
137
newMacro(vString * const name,bool with_define_directive,bool appending)138 static int newMacro (vString *const name, bool with_define_directive, bool appending)
139 {
140 int r = CORK_NIL;
141 subparser *s;
142
143 if (!appending)
144 r = makeSimpleMakeTag (name, K_MACRO);
145
146 foreachSubparser(s, false)
147 {
148 makeSubparser *m = (makeSubparser *)s;
149 enterSubparser(s);
150 if (m->newMacroNotify)
151 m->newMacroNotify (m, vStringValue(name), with_define_directive, appending);
152 leaveSubparser();
153 }
154
155 return r;
156 }
157
valueFound(vString * const name)158 static void valueFound (vString *const name)
159 {
160 subparser *s;
161 foreachSubparser(s, false)
162 {
163 makeSubparser *m = (makeSubparser *)s;
164 enterSubparser(s);
165 if (m->valueNotify)
166 m->valueNotify (m, vStringValue (name));
167 leaveSubparser();
168 }
169 }
170
directiveFound(vString * const name)171 static void directiveFound (vString *const name)
172 {
173 subparser *s;
174 foreachSubparser (s, false)
175 {
176 makeSubparser *m = (makeSubparser *)s;
177 enterSubparser(s);
178 if (m->directiveNotify)
179 m->directiveNotify (m, vStringValue (name));
180 leaveSubparser();
181 }
182 }
183
newInclude(vString * const name,bool optional)184 static void newInclude (vString *const name, bool optional)
185 {
186 makeSimpleMakeRefTag (name, K_INCLUDE,
187 optional? R_INCLUDE_OPTIONAL: R_INCLUDE_GENERIC);
188 }
189
isAcceptableAsInclude(vString * const name)190 static bool isAcceptableAsInclude (vString *const name)
191 {
192 if (strcmp (vStringValue (name), "$") == 0)
193 return false;
194 return true;
195 }
196
readIdentifier(const int first,vString * const id)197 static void readIdentifier (const int first, vString *const id)
198 {
199 int depth = 0;
200 int c = first;
201 vStringClear (id);
202 while (isIdentifier (c) || (depth > 0 && c != EOF && c != '\n'))
203 {
204 if (c == '(' || c == '{')
205 depth++;
206 else if (depth > 0 && (c == ')' || c == '}'))
207 depth--;
208 vStringPut (id, c);
209 c = nextChar ();
210 }
211 ungetcToInputFile (c);
212 }
213
endTargets(intArray * targets,unsigned long lnum)214 static void endTargets (intArray *targets, unsigned long lnum)
215 {
216 for (unsigned int i = 0; i < intArrayCount (targets); i++)
217 {
218 int cork_index = intArrayItem (targets, i);
219 tagEntryInfo *e = getEntryInCorkQueue (cork_index);
220 if (e)
221 e->extensionFields.endLine = lnum;
222 }
223 intArrayClear (targets);
224 }
225
findMakeTags(void)226 static void findMakeTags (void)
227 {
228 stringList *identifiers = stringListNew ();
229 bool newline = true;
230 int current_macro = CORK_NIL;
231 bool in_value = false;
232 intArray *current_targets = intArrayNew ();
233 bool variable_possible = true;
234 bool appending = false;
235 int c;
236 subparser *sub;
237
238 sub = getSubparserRunningBaseparser();
239 if (sub)
240 chooseExclusiveSubparser (sub, NULL);
241
242 while ((c = nextChar ()) != EOF)
243 {
244 if (newline)
245 {
246 if (!intArrayIsEmpty (current_targets))
247 {
248 if (c == '\t' || (c = skipToNonWhite (c)) == '#')
249 {
250 skipLine (); /* skip rule or comment */
251 c = nextChar ();
252 }
253 else if (c != '\n')
254 endTargets (current_targets, getInputLineNumber () - 1);
255 }
256 else if (in_value)
257 in_value = false;
258
259 stringListClear (identifiers);
260 variable_possible = intArrayIsEmpty (current_targets);
261 newline = false;
262 }
263 if (c == '\n')
264 newline = true;
265 else if (isspace (c))
266 continue;
267 else if (c == '#')
268 skipLine ();
269 else if (variable_possible && c == '?')
270 {
271 c = nextChar ();
272 ungetcToInputFile (c);
273 variable_possible = (c == '=');
274 }
275 else if (variable_possible && c == '+')
276 {
277 c = nextChar ();
278 ungetcToInputFile (c);
279 variable_possible = (c == '=');
280 appending = true;
281 }
282 else if ((! in_value) && variable_possible && c == ':' &&
283 stringListCount (identifiers) > 0)
284 {
285 c = nextChar ();
286 ungetcToInputFile (c);
287 if (c != '=')
288 {
289 unsigned int i;
290 for (i = 0; i < stringListCount (identifiers); i++)
291 {
292 int r = newTarget (stringListItem (identifiers, i));
293 if (r != CORK_NIL)
294 intArrayAdd (current_targets, r);
295 }
296 stringListClear (identifiers);
297 }
298 }
299 else if (variable_possible && c == '=' &&
300 stringListCount (identifiers) == 1)
301 {
302 newMacro (stringListItem (identifiers, 0), false, appending);
303
304 in_value = true;
305 endTargets (current_targets, getInputLineNumber () - 1);
306 appending = false;
307 }
308 else if (variable_possible && isIdentifier (c))
309 {
310 vString *name = vStringNew ();
311 readIdentifier (c, name);
312 stringListAdd (identifiers, name);
313
314 if (in_value)
315 valueFound(name);
316
317 if (stringListCount (identifiers) == 1)
318 {
319 if ((current_macro != CORK_NIL) && ! strcmp (vStringValue (name), "endef"))
320 {
321 tagEntryInfo *e = getEntryInCorkQueue(current_macro);
322
323 current_macro = CORK_NIL;
324 if (e)
325 e->extensionFields.endLine = getInputLineNumber ();
326 }
327 else if (current_macro != CORK_NIL)
328 skipLine ();
329 else if (! strcmp (vStringValue (name), "define"))
330 {
331 c = skipToNonWhite (nextChar ());
332 vStringClear (name);
333 /* all remaining characters on the line are the name -- even spaces */
334 while (c != EOF && c != '\n')
335 {
336 vStringPut (name, c);
337 c = nextChar ();
338 }
339 if (c == '\n')
340 ungetcToInputFile (c);
341 vStringStripTrailing (name);
342
343 current_macro = newMacro (name, true, false);
344 }
345 else if (! strcmp (vStringValue (name), "export"))
346 stringListClear (identifiers);
347 else if (! strcmp (vStringValue (name), "include")
348 || ! strcmp (vStringValue (name), "sinclude")
349 || ! strcmp (vStringValue (name), "-include"))
350 {
351 bool optional = (vStringValue (name)[0] == 'i')? false: true;
352 while (1)
353 {
354 c = skipToNonWhite (nextChar ());
355 readIdentifier (c, name);
356 vStringStripTrailing (name);
357 if (isAcceptableAsInclude(name))
358 newInclude (name, optional);
359
360 /* non-space characters after readIdentifier() may
361 * be rejected by the function:
362 * e.g.
363 * include $*
364 *
365 * Here, remove such characters from input stream.
366 */
367 do
368 c = nextChar ();
369 while (c != EOF && c != '\n' && (!isspace (c)));
370 if (c == '\n')
371 ungetcToInputFile (c);
372
373 if (c == EOF || c == '\n')
374 break;
375 }
376 }
377 else
378 directiveFound (name);
379 }
380 }
381 else
382 variable_possible = false;
383 }
384
385 endTargets (current_targets, getInputLineNumber ());
386
387 intArrayDelete (current_targets);
388 stringListDelete (identifiers);
389 }
390
391
MakefileParser(void)392 extern parserDefinition* MakefileParser (void)
393 {
394 static const char *const patterns [] = { "[Mm]akefile", "GNUmakefile", NULL };
395 static const char *const extensions [] = { "mak", "mk", NULL };
396 static const char *const aliases [] = {
397 /* the mode name in emacs */
398 "makefile",
399 NULL };
400 parserDefinition* const def = parserNew ("Make");
401 def->kindTable = MakeKinds;
402 def->kindCount = ARRAY_SIZE (MakeKinds);
403 def->patterns = patterns;
404 def->extensions = extensions;
405 def->aliases = aliases;
406 def->parser = findMakeTags;
407 def->useCork = CORK_QUEUE;
408 return def;
409 }
410