xref: /Universal-ctags/parsers/make.c (revision 34555344617daccf89deb55939c77d6e359e64e6)
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