commit 248e4cc

shrub  ·  2026-04-07 17:59:42 +0000 UTC
parent b427801
feat: cond block handling
3 files changed,  +216, -88
M main.c
M main.c
+29, -9
 1@@ -87,19 +87,23 @@ printwords(const char *name, const struct StrList *list)
 2 	printf("\n");
 3 }
 4 
 5-/*dump ast, TODO make it actually do something useful*/
 6-/*also TODO
 7- * variable eval
 8- * build a proper graph
 9- * a lot more
10- */
11 static void
12-dump(const struct Ast *ast)
13+indent(size_t n)
14+{
15+	size_t i;
16+
17+	for (i = 0; i < n; i++)
18+		printf("  ");
19+}
20+
21+static void
22+dumpnodes(const struct Node *v, size_t n, size_t depth)
23 {
24 	size_t i, j;
25 
26-	for (i = 0; i < ast->n; i++) {
27-		const struct Node *s = &ast->v[i];
28+	for (i = 0; i < n; i++) {
29+		const struct Node *s = &v[i];
30+		indent(depth);
31 		printf("%d-%d ", s->loc.line0, s->loc.line1);
32 		switch (s->kind) {
33 		case NODE_BLANK:
34@@ -118,6 +122,16 @@ dump(const struct Ast *ast)
35 			if (s->data.cond.arg2)
36 				printf(" arg2=%s", s->data.cond.arg2);
37 			printf("\n");
38+			if (s->data.cond.thenpart.n) {
39+				indent(depth);
40+				puts("then");
41+				dumpnodes(s->data.cond.thenpart.v, s->data.cond.thenpart.n, depth + 1);
42+			}
43+			if (s->data.cond.elsepart.n) {
44+				indent(depth);
45+				puts("else");
46+				dumpnodes(s->data.cond.elsepart.v, s->data.cond.elsepart.n, depth + 1);
47+			}
48 			break;
49 		case NODE_ASSIGN:
50 			printf("assign%s lhs=%s op=%s rhs=%s\n",
51@@ -143,6 +157,12 @@ dump(const struct Ast *ast)
52 	}
53 }
54 
55+static void
56+dump(const struct Ast *ast)
57+{
58+	dumpnodes(ast->v, ast->n, 0);
59+}
60+
61 int
62 main(int argc, char **argv)
63 {
+173, -74
  1@@ -72,6 +72,13 @@ xstrdup(const char *s)
  2 	return xstrndup(s, strlen(s));
  3 }
  4 
  5+static void
  6+addnode(struct NodeList *list, struct Node node)
  7+{
  8+	list->v = xrealloc(list->v, (list->n + 1) * sizeof(list->v[0]));
  9+	list->v[list->n++] = node;
 10+}
 11+
 12 static char *
 13 trimdup(const char *s, size_t n)
 14 {
 15@@ -552,11 +559,6 @@ parsecond(const struct PreLine *line, const char *s)
 16 	} else if (haskw(s, "ifneq")) {
 17 		state.data.cond.kind = COND_IFNEQ;
 18 		p = s + 5;
 19-	} else if (haskw(s, "else")) {
 20-		state.data.cond.kind = COND_ELSE;
 21-		p = s + 4;
 22-		state.data.cond.arg1 = trimdup(p, strlen(p));
 23-		return state;
 24 	} else {
 25 		state.data.cond.kind = COND_ENDIF;
 26 		return state;
 27@@ -707,22 +709,18 @@ parseline(const struct PreLine *line)
 28 	return state;
 29 }
 30 
 31-/*TODO the ast we build is very flat. we probably want actual nested blocks for conditionals*/
 32-int
 33-buildast(const char *path, const struct Pre *pre, struct Ast *ast)
 34+static int
 35+parseblock(const struct Pre *pre, size_t *i, struct NodeList *out)
 36 {
 37 	struct Node state;
 38 	struct Node *last_rule;
 39-	size_t i;
 40 
 41-	(void)path;
 42-	memset(ast, 0, sizeof(*ast));
 43 	last_rule = 0;
 44-
 45-	for (i = 0; i < pre->n; i++) {
 46+	while (*i < pre->n) {
 47 		struct PreLine *line;
 48+		char *trim;
 49 
 50-		line = &pre->v[i];
 51+		line = &pre->v[*i];
 52 		if (line->isrecipe) {
 53 			if (last_rule) {
 54 				last_rule->data.rule.recipes.v = xrealloc(last_rule->data.rule.recipes.v,
 55@@ -730,19 +728,75 @@ buildast(const char *path, const struct Pre *pre, struct Ast *ast)
 56 				last_rule->data.rule.recipes.v[last_rule->data.rule.recipes.n++] =
 57 				    xstrndup(line->text + 1, strlen(line->text + 1));
 58 				last_rule->loc.line1 = line->line1;
 59+				(*i)++;
 60 				continue;
 61 			}
 62 			memmove(line->text, line->text + 1, strlen(line->text + 1) + 1);
 63 		}
 64+		trim = trimdup(line->text, strlen(line->text));
 65+		if (haskw(trim, "else") || haskw(trim, "endif")) {
 66+			free(trim);
 67+			break;
 68+		}
 69+		if (haskw(trim, "ifeq") || haskw(trim, "ifneq")) {
 70+			state = parsecond(line, trim);
 71+			free(trim);
 72+			(*i)++;
 73+			if (parseblock(pre, i, &state.data.cond.thenpart) < 0)
 74+				return -1;
 75+			if (*i < pre->n) {
 76+				struct PreLine *endline;
 77+				char *endtrim;
 78+
 79+				endline = &pre->v[*i];
 80+				endtrim = trimdup(endline->text, strlen(endline->text));
 81+				if (haskw(endtrim, "else")) {
 82+					free(endtrim);
 83+					(*i)++;
 84+					if (parseblock(pre, i, &state.data.cond.elsepart) < 0)
 85+						return -1;
 86+					if (*i >= pre->n)
 87+						return -1;
 88+					endline = &pre->v[*i];
 89+					endtrim = trimdup(endline->text, strlen(endline->text));
 90+				}
 91+				if (!haskw(endtrim, "endif")) {
 92+					free(endtrim);
 93+					return -1;
 94+				}
 95+				state.loc.line1 = endline->line1;
 96+				free(endtrim);
 97+				(*i)++;
 98+			} else {
 99+				return -1;
100+			}
101+			addnode(out, state);
102+			last_rule = 0;
103+			continue;
104+		}
105+		free(trim);
106 
107 		state = parseline(line);
108-		ast->v = xrealloc(ast->v, (ast->n + 1) * sizeof(ast->v[0]));
109-		ast->v[ast->n++] = state;
110+		addnode(out, state);
111 		if (state.kind == NODE_RULE)
112-			last_rule = &ast->v[ast->n - 1];
113+			last_rule = &out->v[out->n - 1];
114 		else if (state.kind != NODE_BLANK && state.kind != NODE_COMMENT)
115 			last_rule = 0;
116+		(*i)++;
117 	}
118+	return 0;
119+}
120+
121+int
122+buildast(const char *path, const struct Pre *pre, struct Ast *ast)
123+{
124+	size_t i;
125+
126+	(void)path;
127+	memset(ast, 0, sizeof(*ast));
128+	i = 0;
129+	if (parseblock(pre, &i, (struct NodeList *)ast) < 0)
130+		return -1;
131 
132 	return 0;
133 }
134@@ -910,6 +964,20 @@ seedenv(struct Env *env)
135 	setvar(env, "SHELL", xstrdup("/bin/sh"), 1);
136 }
137 
138+static void
139+freeenv(struct Env *env)
140+{
141+	size_t i;
142+
143+	for (i = 0; i < env->n; i++) {
144+		free(env->v[i].name);
145+		free(env->v[i].val);
146+	}
147+	free(env->v);
148+	env->v = 0;
149+	env->n = 0;
150+}
151+
152 static void
153 evalassign(struct Env *env, const struct AssignNode *in)
154 {
155@@ -947,6 +1015,22 @@ evalassign(struct Env *env, const struct AssignNode *in)
156 	}
157 }
158 
159+static int
160+testcond(struct Env *env, const struct CondNode *cond)
161+{
162+	char *a, *b;
163+	int ok;
164+
165+	a = cond->arg1 ? expandstr(env, cond->arg1) : xstrdup("");
166+	b = cond->arg2 ? expandstr(env, cond->arg2) : xstrdup("");
167+	ok = strcmp(a, b) == 0;
168+	free(a);
169+	free(b);
170+	if (cond->kind == COND_IFNEQ)
171+		ok = !ok;
172+	return ok;
173+}
174+
175 static void
176 copywords(struct StrList *out, const struct StrList *in, struct Env *env)
177 {
178@@ -971,69 +1055,76 @@ copyrecipes(struct RecipeList *out, const struct RecipeList *in, struct Env *env
179 	}
180 }
181 
182-int
183-eval(const struct Ast *ast, struct Ast *out)
184+static int
185+evalnodes(const struct NodeList *in, struct NodeList *out, struct Env *env)
186 {
187-	struct Env env;
188 	size_t i;
189 
190 	memset(out, 0, sizeof(*out));
191-	memset(&env, 0, sizeof(env));
192-	seedenv(&env);
193-	for (i = 0; i < ast->n; i++) {
194-		const struct Node *in;
195+	for (i = 0; i < in->n; i++) {
196+		const struct Node *src;
197 		struct Node node;
198 
199-		in = &ast->v[i];
200+		src = &in->v[i];
201 		memset(&node, 0, sizeof(node));
202-		node.kind = in->kind;
203-		node.loc = in->loc;
204-		switch (in->kind) {
205+		node.kind = src->kind;
206+		node.loc = src->loc;
207+		switch (src->kind) {
208 		case NODE_BLANK:
209 			break;
210 		case NODE_COMMENT:
211 		case NODE_RAW:
212-			node.data.raw.text = xstrdup(in->data.raw.text);
213+			node.data.raw.text = xstrdup(src->data.raw.text);
214 			break;
215 		case NODE_INCLUDE:
216-			node.data.include.optional = in->data.include.optional;
217-			node.data.include.path = expandstr(&env, in->data.include.path);
218+			node.data.include.optional = src->data.include.optional;
219+			node.data.include.path = expandstr(env, src->data.include.path);
220 			break;
221 		case NODE_COND:
222-			node.data.cond.kind = in->data.cond.kind;
223-			node.data.cond.arg1 = in->data.cond.arg1 ? expandstr(&env, in->data.cond.arg1) : 0;
224-			node.data.cond.arg2 = in->data.cond.arg2 ? expandstr(&env, in->data.cond.arg2) : 0;
225-			node.data.cond.raw = in->data.cond.raw ? expandstr(&env, in->data.cond.raw) : 0;
226-			break;
227+			if (testcond(env, &src->data.cond)) {
228+				if (evalnodes(&src->data.cond.thenpart, out, env) < 0)
229+					return -1;
230+			} else {
231+				if (evalnodes(&src->data.cond.elsepart, out, env) < 0)
232+					return -1;
233+			}
234+			continue;
235 		case NODE_ASSIGN:
236-			node.data.assign.lhs = xstrdup(in->data.assign.lhs);
237-			node.data.assign.op = in->data.assign.op;
238-			node.data.assign.tspec = in->data.assign.tspec;
239-			if (in->data.assign.op == ASSIGN_COLON_EQ || in->data.assign.op == ASSIGN_BANG_EQ)
240-				node.data.assign.rhs = expandstr(&env, in->data.assign.rhs);
241+			node.data.assign.lhs = xstrdup(src->data.assign.lhs);
242+			node.data.assign.op = src->data.assign.op;
243+			node.data.assign.tspec = src->data.assign.tspec;
244+			if (src->data.assign.op == ASSIGN_COLON_EQ || src->data.assign.op == ASSIGN_BANG_EQ)
245+				node.data.assign.rhs = expandstr(env, src->data.assign.rhs);
246 			else
247-				node.data.assign.rhs = xstrdup(in->data.assign.rhs);
248-			copywords(&node.data.assign.targets, &in->data.assign.targets, &env);
249-			evalassign(&env, &in->data.assign);
250+				node.data.assign.rhs = xstrdup(src->data.assign.rhs);
251+			copywords(&node.data.assign.targets, &src->data.assign.targets, env);
252+			evalassign(env, &src->data.assign);
253 			break;
254 		case NODE_RULE:
255-			copywords(&node.data.rule.targets, &in->data.rule.targets, &env);
256-			copywords(&node.data.rule.prereqs, &in->data.rule.prereqs, &env);
257-			copywords(&node.data.rule.order_only, &in->data.rule.order_only, &env);
258-			copyrecipes(&node.data.rule.recipes, &in->data.rule.recipes, &env);
259+			copywords(&node.data.rule.targets, &src->data.rule.targets, env);
260+			copywords(&node.data.rule.prereqs, &src->data.rule.prereqs, env);
261+			copywords(&node.data.rule.order_only, &src->data.rule.order_only, env);
262+			copyrecipes(&node.data.rule.recipes, &src->data.rule.recipes, env);
263 			break;
264 		}
265-		out->v = xrealloc(out->v, (out->n + 1) * sizeof(out->v[0]));
266-		out->v[out->n++] = node;
267-	}
268-	for (i = 0; i < env.n; i++) {
269-		free(env.v[i].name);
270-		free(env.v[i].val);
271+		addnode(out, node);
272 	}
273-	free(env.v);
274 	return 0;
275 }
276 
277+int
278+eval(const struct Ast *ast, struct Ast *out)
279+{
280+	struct Env env;
281+	int rc;
282+
283+	memset(&env, 0, sizeof(env));
284+	seedenv(&env);
285+	rc = evalnodes((const struct NodeList *)ast, (struct NodeList *)out, &env);
286+	freeenv(&env);
287+	return rc;
288+}
289+
290 static void
291 freestrs(struct StrList *list)
292 {
293@@ -1054,41 +1145,49 @@ freerec(struct RecipeList *list)
294 	free(list->v);
295 }
296 
297-void
298-freeast(struct Ast *ast)
299+static void
300+freenodes(struct NodeList *list)
301 {
302 	size_t i;
303 
304-	for (i = 0; i < ast->n; i++) {
305-		switch (ast->v[i].kind) {
306+	for (i = 0; i < list->n; i++) {
307+		switch (list->v[i].kind) {
308 		case NODE_COMMENT:
309 		case NODE_RAW:
310-			free(ast->v[i].data.raw.text);
311+			free(list->v[i].data.raw.text);
312 			break;
313 		case NODE_ASSIGN:
314-			free(ast->v[i].data.assign.lhs);
315-			free(ast->v[i].data.assign.rhs);
316-			freestrs(&ast->v[i].data.assign.targets);
317+			free(list->v[i].data.assign.lhs);
318+			free(list->v[i].data.assign.rhs);
319+			freestrs(&list->v[i].data.assign.targets);
320 			break;
321 		case NODE_RULE:
322-			freestrs(&ast->v[i].data.rule.targets);
323-			freestrs(&ast->v[i].data.rule.prereqs);
324-			freestrs(&ast->v[i].data.rule.order_only);
325-			freerec(&ast->v[i].data.rule.recipes);
326+			freestrs(&list->v[i].data.rule.targets);
327+			freestrs(&list->v[i].data.rule.prereqs);
328+			freestrs(&list->v[i].data.rule.order_only);
329+			freerec(&list->v[i].data.rule.recipes);
330 			break;
331 		case NODE_INCLUDE:
332-			free(ast->v[i].data.include.path);
333+			free(list->v[i].data.include.path);
334 			break;
335 		case NODE_COND:
336-			free(ast->v[i].data.cond.arg1);
337-			free(ast->v[i].data.cond.arg2);
338-			free(ast->v[i].data.cond.raw);
339+			free(list->v[i].data.cond.arg1);
340+			free(list->v[i].data.cond.arg2);
341+			free(list->v[i].data.cond.raw);
342+			freenodes(&list->v[i].data.cond.thenpart);
343+			freenodes(&list->v[i].data.cond.elsepart);
344 			break;
345 		case NODE_BLANK:
346 			break;
347 		}
348 	}
349-	free(ast->v);
350-	ast->v = 0;
351-	ast->n = 0;
352+	free(list->v);
353+	list->v = 0;
354+	list->n = 0;
355+}
356+
357+void
358+freeast(struct Ast *ast)
359+{
360+	freenodes((struct NodeList *)ast);
361 }
+14, -5
 1@@ -8,7 +8,7 @@
 2  * PreLine: one preprocessed line with source location
 3  * Pre: flattened preprocessed makefile input
 4  * Node: one parsed syntax node
 5- * Ast: flat list of parsed nodes in source order
 6+ * Ast: parsed nodes in source order with nested cond blocks
 7  */
 8 
 9 enum NodeKind {
10@@ -84,15 +84,24 @@ struct IncludeNode {
11 	char *path;
12 };
13 
14+struct RawNode {
15+	char *text;
16+};
17+
18+struct Node;
19+
20+struct NodeList {
21+	struct Node *v;
22+	size_t n;
23+};
24+
25 struct CondNode {
26 	enum CondKind kind;
27 	char *arg1;
28 	char *arg2;
29 	char *raw;
30-};
31-
32-struct RawNode {
33-	char *text;
34+	struct NodeList thenpart;
35+	struct NodeList elsepart;
36 };
37 
38 struct Node {