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 <stdio.h>
16 #include <string.h>
17 #include <ctype.h>
18 
19 #include "options.h"
20 #include "parse.h"
21 #include "read.h"
22 #include "routines.h"
23 #include "vstring.h"
24 #include "xtag.h"
25 
26 /*
27 *   DATA DEFINITIONS
28 */
29 typedef enum {
30 	K_MACRO, K_TARGET
31 } shKind;
32 
33 static kindDefinition MakeKinds [] = {
34 	{ true, 'm', "macro",  "macros"},
35 	{ true, 't', "target", "targets"}
36 };
37 
38 /*
39 *   FUNCTION DEFINITIONS
40 */
41 
nextChar(void)42 static int nextChar (void)
43 {
44 	int c = getcFromInputFile ();
45 	if (c == '\\')
46 	{
47 		c = getcFromInputFile ();
48 		if (c == '\n')
49 			c = nextChar ();
50 	}
51 	return c;
52 }
53 
skipLine(void)54 static void skipLine (void)
55 {
56 	int c;
57 	do
58 		c = nextChar ();
59 	while (c != EOF  &&  c != '\n');
60 	if (c == '\n')
61 		ungetcToInputFile (c);
62 }
63 
skipToNonWhite(int c)64 static int skipToNonWhite (int c)
65 {
66 	while (c != '\n' && isspace (c))
67 		c = nextChar ();
68 	return c;
69 }
70 
isIdentifier(int c)71 static bool isIdentifier (int c)
72 {
73 	return (bool)(c != '\0' && (isalnum (c)  ||  strchr (".-_/$(){}%", c) != NULL));
74 }
75 
isSpecialTarget(vString * const name)76 static bool isSpecialTarget (vString *const name)
77 {
78 	size_t i = 0;
79 	/* All special targets begin with '.'. */
80 	if (vStringLength (name) < 1 || vStringChar (name, i++) != '.') {
81 		return false;
82 	}
83 	while (i < vStringLength (name)) {
84 		char ch = vStringChar (name, i++);
85 		if (ch != '_' && !isupper (ch))
86 		{
87 			return false;
88 		}
89 	}
90 	return true;
91 }
92 
newTarget(vString * const name)93 static void newTarget (vString *const name)
94 {
95 	/* Ignore GNU Make's "special targets". */
96 	if  (isSpecialTarget (name))
97 	{
98 		return;
99 	}
100 	makeSimpleTag (name, K_TARGET);
101 }
102 
newMacro(vString * const name)103 static void newMacro (vString *const name)
104 {
105 	makeSimpleTag (name, K_MACRO);
106 }
107 
readIdentifier(const int first,vString * const id)108 static void readIdentifier (const int first, vString *const id)
109 {
110 	int depth = 0;
111 	int c = first;
112 	vStringClear (id);
113 	while (isIdentifier (c) || (depth > 0 && c != EOF && c != '\n'))
114 	{
115 		if (c == '(' || c == '{')
116 			depth++;
117 		else if (depth > 0 && (c == ')' || c == '}'))
118 			depth--;
119 		vStringPut (id, c);
120 		c = nextChar ();
121 	}
122 	ungetcToInputFile (c);
123 }
124 
findMakeTags(void)125 static void findMakeTags (void)
126 {
127 	stringList *identifiers = stringListNew ();
128 	bool newline = true;
129 	bool in_define = false;
130 	bool in_rule = false;
131 	bool variable_possible = true;
132 	int c;
133 
134 	while ((c = nextChar ()) != EOF)
135 	{
136 		if (newline)
137 		{
138 			if (in_rule)
139 			{
140 				if (c == '\t' || (c = skipToNonWhite (c)) == '#')
141 				{
142 					skipLine ();  /* skip rule or comment */
143 					c = nextChar ();
144 				}
145 				else if (c != '\n')
146 					in_rule = false;
147 			}
148 			stringListClear (identifiers);
149 			variable_possible = (bool)(!in_rule);
150 			newline = false;
151 		}
152 		if (c == '\n')
153 			newline = true;
154 		else if (isspace (c))
155 			continue;
156 		else if (c == '#')
157 			skipLine ();
158 		else if (variable_possible && c == '?')
159 		{
160 			c = nextChar ();
161 			ungetcToInputFile (c);
162 			variable_possible = (c == '=');
163 		}
164 		else if (variable_possible && c == ':' &&
165 				 stringListCount (identifiers) > 0)
166 		{
167 			c = nextChar ();
168 			ungetcToInputFile (c);
169 			if (c != '=')
170 			{
171 				unsigned int i;
172 				for (i = 0; i < stringListCount (identifiers); i++)
173 					newTarget (stringListItem (identifiers, i));
174 				stringListClear (identifiers);
175 				in_rule = true;
176 			}
177 		}
178 		else if (variable_possible && c == '=' &&
179 				 stringListCount (identifiers) == 1)
180 		{
181 			newMacro (stringListItem (identifiers, 0));
182 			skipLine ();
183 			in_rule = false;
184 		}
185 		else if (variable_possible && isIdentifier (c))
186 		{
187 			vString *name = vStringNew ();
188 			readIdentifier (c, name);
189 			stringListAdd (identifiers, name);
190 
191 			if (stringListCount (identifiers) == 1)
192 			{
193 				if (in_define && ! strcmp (vStringValue (name), "endef"))
194 					in_define = false;
195 				else if (in_define)
196 					skipLine ();
197 				else if (! strcmp (vStringValue (name), "define"))
198 				{
199 					in_define = true;
200 					c = skipToNonWhite (nextChar ());
201 					vStringClear (name);
202 					/* all remaining characters on the line are the name -- even spaces */
203 					while (c != EOF && c != '\n')
204 					{
205 						vStringPut (name, c);
206 						c = nextChar ();
207 					}
208 					if (c == '\n')
209 						ungetcToInputFile (c);
210 					vStringStripTrailing (name);
211 					newMacro (name);
212 				}
213 				else if (! strcmp (vStringValue (name), "export"))
214 					stringListClear (identifiers);
215 			}
216 		}
217 		else
218 			variable_possible = false;
219 	}
220 	stringListDelete (identifiers);
221 }
222 
MakefileParser(void)223 extern parserDefinition* MakefileParser (void)
224 {
225 	static const char *const patterns [] = { "[Mm]akefile", "GNUmakefile", NULL };
226 	static const char *const extensions [] = { "mak", "mk", NULL };
227 	parserDefinition* const def = parserNew ("Make");
228 	def->kindTable  = MakeKinds;
229 	def->kindCount  = ARRAY_SIZE (MakeKinds);
230 	def->patterns   = patterns;
231 	def->extensions = extensions;
232 	def->parser     = findMakeTags;
233 	return def;
234 }
235