commit b1a8a94

shrub  ·  2026-04-08 19:43:09 +0000 UTC
parent 59a6db8
add targ-specific assingments and cli ops to dump ast/graph
5 files changed,  +434, -82
+1, -1
1@@ -13,7 +13,7 @@ all: $(BIN)
2 $(BIN): $(OBJS)
3 	$(CC) $(LDFLAGS) -o $(BIN) $(OBJS) $(LDLIBS)
4 
5-%.o: %.c
6+.c.o:
7 	$(CC) $(CFLAGS) -c -o $@ $<
8 
9 fmt:
+198, -1
  1@@ -5,6 +5,12 @@
  2 #include <stdlib.h>
  3 #include <string.h>
  4 
  5+static void
  6+usage(FILE *fp, const char *argv0)
  7+{
  8+	fprintf(fp, "usage: %s [-ag] [makefile]\n", argv0);
  9+}
 10+
 11 static char *
 12 readfile(const char *path)
 13 {
 14@@ -39,16 +45,203 @@ readfile(const char *path)
 15 	return buf;
 16 }
 17 
 18+static const char *
 19+assignopname(enum AssignOp op)
 20+{
 21+	switch (op) {
 22+	case ASSIGN_EQ:
 23+		return "=";
 24+	case ASSIGN_PLUS_EQ:
 25+		return "+=";
 26+	case ASSIGN_COLON_EQ:
 27+		return ":=";
 28+	case ASSIGN_QMARK_EQ:
 29+		return "?=";
 30+	case ASSIGN_BANG_EQ:
 31+		return "!=";
 32+	}
 33+	return "?";
 34+}
 35+
 36+static const char *
 37+condkindname(enum CondKind kind)
 38+{
 39+	switch (kind) {
 40+	case COND_IFEQ:
 41+		return "ifeq";
 42+	case COND_IFNEQ:
 43+		return "ifneq";
 44+	case COND_IFDEF:
 45+		return "ifdef";
 46+	case COND_IFNDEF:
 47+		return "ifndef";
 48+	case COND_ELSE:
 49+		return "else";
 50+	case COND_ENDIF:
 51+		return "endif";
 52+	}
 53+	return "?";
 54+}
 55+
 56+static void
 57+printindent(int depth)
 58+{
 59+	int i;
 60+
 61+	for (i = 0; i < depth; i++)
 62+		fputs("  ", stdout);
 63+}
 64+
 65+static void
 66+dumpstrlist(const char *label, const struct StrList *list, int depth)
 67+{
 68+	size_t i;
 69+
 70+	printindent(depth);
 71+	printf("%s (%zu):", label, list->n);
 72+	for (i = 0; i < list->n; i++)
 73+		printf(" %s", list->v[i]);
 74+	putchar('\n');
 75+}
 76+
 77+static void
 78+dumprecipes(const struct RecipeList *list, int depth)
 79+{
 80+	size_t i;
 81+
 82+	printindent(depth);
 83+	printf("recipes (%zu):\n", list->n);
 84+	for (i = 0; i < list->n; i++) {
 85+		printindent(depth + 1);
 86+		printf("%s\n", list->v[i]);
 87+	}
 88+}
 89+
 90+static void dumpnodes(const struct Node *nodes, size_t n, int depth);
 91+
 92+static void
 93+dumpnode(const struct Node *node, int depth)
 94+{
 95+	printindent(depth);
 96+	printf("%d-%d ", node->loc.line0, node->loc.line1);
 97+	switch (node->kind) {
 98+	case NODE_BLANK:
 99+		printf("blank\n");
100+		break;
101+	case NODE_COMMENT:
102+		printf("comment %s\n", node->data.raw.text);
103+		break;
104+	case NODE_RAW:
105+		printf("raw %s\n", node->data.raw.text);
106+		break;
107+	case NODE_INCLUDE:
108+		printf("include%s %s\n",
109+		       node->data.include.optional ? " optional" : "",
110+		       node->data.include.path);
111+		break;
112+	case NODE_ASSIGN:
113+		printf("assign %s %s %s\n",
114+		       node->data.assign.lhs,
115+		       assignopname(node->data.assign.op),
116+		       node->data.assign.rhs);
117+		if (node->data.assign.tspec)
118+			dumpstrlist("targets", &node->data.assign.targets, depth + 1);
119+		break;
120+	case NODE_RULE:
121+		printf("rule\n");
122+		dumpstrlist("targets", &node->data.rule.targets, depth + 1);
123+		dumpstrlist("prereqs", &node->data.rule.prereqs, depth + 1);
124+		dumpstrlist("order-only", &node->data.rule.order_only, depth + 1);
125+		dumprecipes(&node->data.rule.recipes, depth + 1);
126+		break;
127+	case NODE_COND:
128+		printf("cond %s", condkindname(node->data.cond.kind));
129+		if (node->data.cond.arg1)
130+			printf(" arg1=%s", node->data.cond.arg1);
131+		if (node->data.cond.arg2)
132+			printf(" arg2=%s", node->data.cond.arg2);
133+		putchar('\n');
134+		printindent(depth + 1);
135+		printf("then:\n");
136+		dumpnodes(node->data.cond.thenpart.v, node->data.cond.thenpart.n, depth + 2);
137+		if (node->data.cond.elsepart.n) {
138+			printindent(depth + 1);
139+			printf("else:\n");
140+			dumpnodes(node->data.cond.elsepart.v, node->data.cond.elsepart.n, depth + 2);
141+		}
142+		break;
143+	}
144+}
145+
146+static void
147+dumpnodes(const struct Node *nodes, size_t n, int depth)
148+{
149+	size_t i;
150+
151+	for (i = 0; i < n; i++)
152+		dumpnode(&nodes[i], depth);
153+}
154+
155+static void
156+dumpast(const struct Ast *ast)
157+{
158+	printf("ast (%zu nodes)\n", ast->n);
159+	dumpnodes(ast->v, ast->n, 1);
160+}
161+
162+static void
163+dumpgraph(const struct Graph *graph)
164+{
165+	size_t i;
166+
167+	printf("graph (%zu targets)\n", graph->n);
168+	for (i = 0; i < graph->n; i++) {
169+		const struct Target *t = &graph->v[i];
170+
171+		printindent(1);
172+		printf("target %s\n", t->name);
173+		dumpstrlist("prereqs", &t->prereqs, 2);
174+		dumpstrlist("order-only", &t->order_only, 2);
175+		dumprecipes(&t->recipes, 2);
176+	}
177+}
178+
179 int
180 main(int argc, char **argv)
181 {
182 	const char *path;
183 	char *src;
184+	int dump_ast;
185+	int dump_graph;
186+	int have_path;
187+	int i;
188 	struct Ast ast;
189 	struct Ast out;
190 	struct Graph graph;
191 
192-	path = argc > 1 ? argv[1] : "Makefile";
193+	path = "Makefile";
194+	dump_ast = 0;
195+	dump_graph = 0;
196+	have_path = 0;
197+	for (i = 1; i < argc; i++) {
198+		if (strcmp(argv[i], "-a") == 0) {
199+			dump_ast = 1;
200+		} else if (strcmp(argv[i], "-g") == 0) {
201+			dump_graph = 1;
202+		} else if (strcmp(argv[i], "-h") == 0 || strcmp(argv[i], "--help") == 0) {
203+			usage(stdout, argv[0]);
204+			return 0;
205+		} else if (argv[i][0] == '-') {
206+			usage(stderr, argv[0]);
207+			return 1;
208+		} else if (have_path) {
209+			usage(stderr, argv[0]);
210+			return 1;
211+		} else {
212+			path = argv[i];
213+			have_path = 1;
214+		}
215+	}
216 	src = readfile(path);
217 	if (!src) {
218 		fprintf(stderr, "could not read %s\n", path);
219@@ -65,6 +258,8 @@ main(int argc, char **argv)
220 		free(src);
221 		return 1;
222 	}
223+	if (dump_ast)
224+		dumpast(&out);
225 	if (buildgraph(&out, &graph) < 0) {
226 		fprintf(stderr, "graph error in %s\n", path);
227 		freeast(&out);
228@@ -72,6 +267,8 @@ main(int argc, char **argv)
229 		free(src);
230 		return 1;
231 	}
232+	if (dump_graph)
233+		dumpgraph(&graph);
234 	if (genninja(&graph, "build.ninja") < 0) {
235 		fprintf(stderr, "ninja generation error\n");
236 		freegraph(&graph);
+36, -24
  1@@ -17,17 +17,6 @@
  2  * right now only a small subset of gmake syntax is handled.
  3  */
  4 
  5-struct Var {
  6-	char *name;
  7-	char *val;
  8-	int simple;
  9-};
 10-
 11-struct Env {
 12-	struct Var *v;
 13-	size_t n;
 14-};
 15-
 16 static int evalerrs;
 17 
 18 static void
 19@@ -40,7 +29,7 @@ evalerr(const char *msg, const char *detail)
 20 	evalerrs++;
 21 }
 22 
 23-static struct Var *
 24+struct Var *
 25 findvar(struct Env *env, const char *name)
 26 {
 27 	size_t i;
 28@@ -177,7 +166,7 @@ issubstref(const char *s, size_t n, size_t *colon, size_t *eq)
 29 	return 0;
 30 }
 31 
 32-static char *
 33+char *
 34 expandstr(struct Env *env, const char *s)
 35 {
 36 	size_t i, j, k, n, cap, len, inner, start, colon, eq;
 37@@ -190,6 +179,16 @@ expandstr(struct Env *env, const char *s)
 38 	len = 0;
 39 	out = xmalloc(cap);
 40 	for (i = 0; i < n; i++) {
 41+		if (s[i] == '$' && i + 1 < n && s[i + 1] == '$') {
 42+			if (len + 3 > cap) {
 43+				cap *= 2;
 44+				out = xrealloc(out, cap);
 45+			}
 46+			out[len++] = '$';
 47+			out[len++] = '$';
 48+			i++;
 49+			continue;
 50+		}
 51 		if (s[i] != '$' || i + 1 >= n || (s[i + 1] != '(' && s[i + 1] != '{')) {
 52 			if (len + 2 > cap) {
 53 				cap *= 2;
 54@@ -318,7 +317,7 @@ setvar(struct Env *env, const char *name, char *val, int simple)
 55 	env->n++;
 56 }
 57 
 58-static void
 59+void
 60 seedenv(struct Env *env)
 61 {
 62 	setvar(env, "CC", xstrdup("cc"), 1);
 63@@ -333,7 +332,7 @@ seedenv(struct Env *env)
 64 	setvar(env, "SHELL", xstrdup("/bin/sh"), 1);
 65 }
 66 
 67-static void
 68+void
 69 freeenv(struct Env *env)
 70 {
 71 	size_t i;
 72@@ -347,16 +346,28 @@ freeenv(struct Env *env)
 73 	env->n = 0;
 74 }
 75 
 76-static void
 77+void
 78+copyenv(struct Env *dst, const struct Env *src)
 79+{
 80+	size_t i;
 81+
 82+	memset(dst, 0, sizeof(*dst));
 83+	if (src->n)
 84+		dst->v = xrealloc(0, src->n * sizeof(dst->v[0]));
 85+	for (i = 0; i < src->n; i++) {
 86+		dst->v[i].name = xstrdup(src->v[i].name);
 87+		dst->v[i].val = xstrdup(src->v[i].val);
 88+		dst->v[i].simple = src->v[i].simple;
 89+	}
 90+	dst->n = src->n;
 91+}
 92+
 93+void
 94 evalassign(struct Env *env, const struct AssignNode *in)
 95 {
 96 	struct Var *v;
 97 	char *rhs, *joined;
 98 
 99-	if (in->tspec) {
100-		evalerr("target-specific assignment", in->lhs);
101-		return;
102-	}
103 	switch (in->op) {
104 	case ASSIGN_EQ:
105 		setvar(env, in->lhs, xstrdup(in->rhs), 0);
106@@ -427,14 +438,14 @@ copywords(struct StrList *out, const struct StrList *in, struct Env *env)
107 }
108 
109 static void
110-copyrecipes(struct RecipeList *out, const struct RecipeList *in, struct Env *env)
111+copyrecipes(struct RecipeList *out, const struct RecipeList *in)
112 {
113 	size_t i;
114 
115 	memset(out, 0, sizeof(*out));
116 	for (i = 0; i < in->n; i++) {
117 		out->v = xrealloc(out->v, (out->n + 1) * sizeof(out->v[0]));
118-		out->v[out->n++] = expandstr(env, in->v[i]);
119+		out->v[out->n++] = xstrdup(in->v[i]);
120 	}
121 }
122 
123@@ -481,13 +492,14 @@ evalnodes(const struct NodeList *in, struct NodeList *out, struct Env *env)
124 			else
125 				node.data.assign.rhs = xstrdup(src->data.assign.rhs);
126 			copywords(&node.data.assign.targets, &src->data.assign.targets, env);
127-			evalassign(env, &src->data.assign);
128+			if (!src->data.assign.tspec)
129+				evalassign(env, &src->data.assign);
130 			break;
131 		case NODE_RULE:
132 			copywords(&node.data.rule.targets, &src->data.rule.targets, env);
133 			copywords(&node.data.rule.prereqs, &src->data.rule.prereqs, env);
134 			copywords(&node.data.rule.order_only, &src->data.rule.order_only, env);
135-			copyrecipes(&node.data.rule.recipes, &src->data.rule.recipes, env);
136+			copyrecipes(&node.data.rule.recipes, &src->data.rule.recipes);
137 			break;
138 		}
139 		addnode(out, node);
+181, -56
  1@@ -40,35 +40,6 @@ addrecipes(struct RecipeList *dest, const struct RecipeList *src)
  2 	}
  3 }
  4 
  5-static void
  6-addrule(struct Graph *graph, const char *name, const struct RuleNode *rule)
  7-{
  8-	size_t i;
  9-	struct Target *t;
 10-
 11-	t = findtarget(graph, name);
 12-	if (!t) {
 13-		graph->v = xrealloc(graph->v, (graph->n + 1) * sizeof(graph->v[0]));
 14-		t = &graph->v[graph->n++];
 15-		memset(t, 0, sizeof(*t));
 16-		t->name = xstrdup(name);
 17-	}
 18-	addwords(&t->prereqs, &rule->prereqs);
 19-	addwords(&t->order_only, &rule->order_only);
 20-	addrecipes(&t->recipes, &rule->recipes);
 21-
 22-	for (i = 0; i < rule->prereqs.n; i++) {
 23-		if (strchr(rule->prereqs.v[i], '%'))
 24-			continue;
 25-		if (!findtarget(graph, rule->prereqs.v[i])) {
 26-			graph->v = xrealloc(graph->v, (graph->n + 1) * sizeof(graph->v[0]));
 27-			t = &graph->v[graph->n++];
 28-			memset(t, 0, sizeof(*t));
 29-			t->name = xstrdup(rule->prereqs.v[i]);
 30-		}
 31-	}
 32-}
 33-
 34 struct Pattern {
 35 	char *target;
 36 	struct StrList prereqs;
 37@@ -76,30 +47,29 @@ struct Pattern {
 38 	struct RecipeList recipes;
 39 };
 40 
 41+struct TAssign {
 42+	struct StrList targets;
 43+	char *lhs;
 44+	char *rhs;
 45+	enum AssignOp op;
 46+};
 47+
 48 struct GraphState {
 49 	struct Graph *graph;
 50+	struct Env env;
 51+	const struct RuleNode **rules;
 52+	size_t nrules;
 53 	struct Pattern *pats;
 54 	size_t npats;
 55+	struct TAssign *tas;
 56+	size_t ntas;
 57 };
 58 
 59 static void
 60-addpats(struct GraphState *gs, const struct RuleNode *rule)
 61+collectrule(struct GraphState *gs, const struct RuleNode *rule)
 62 {
 63-	size_t i;
 64-
 65-	for (i = 0; i < rule->targets.n; i++) {
 66-		if (strchr(rule->targets.v[i], '%')) {
 67-			gs->pats = xrealloc(gs->pats, (gs->npats + 1) * sizeof(gs->pats[0]));
 68-			struct Pattern *p = &gs->pats[gs->npats++];
 69-			memset(p, 0, sizeof(*p));
 70-			p->target = xstrdup(rule->targets.v[i]);
 71-			addwords(&p->prereqs, &rule->prereqs);
 72-			addwords(&p->order_only, &rule->order_only);
 73-			addrecipes(&p->recipes, &rule->recipes);
 74-		} else {
 75-			addrule(gs->graph, rule->targets.v[i], rule);
 76-		}
 77-	}
 78+	gs->rules = xrealloc(gs->rules, (gs->nrules + 1) * sizeof(gs->rules[0]));
 79+	gs->rules[gs->nrules++] = rule;
 80 }
 81 
 82 static char *
 83@@ -125,6 +95,20 @@ matchpat(const char *pat, const char *name)
 84 	return xstrndup(name + pre, n - pre - suf);
 85 }
 86 
 87+static int
 88+targetmatches(const char *pat, const char *name)
 89+{
 90+	char *stem;
 91+
 92+	if (!strchr(pat, '%'))
 93+		return strcmp(pat, name) == 0;
 94+	stem = matchpat(pat, name);
 95+	if (!stem)
 96+		return 0;
 97+	free(stem);
 98+	return 1;
 99+}
100+
101 static char *
102 applystem(const char *s, const char *stem)
103 {
104@@ -162,14 +146,28 @@ joinprereqs(const struct Target *t)
105 	return joined;
106 }
107 
108+static void
109+addexprecipes(struct RecipeList *dest, const struct RecipeList *src, struct Env *env)
110+{
111+	size_t i;
112+
113+	for (i = 0; i < src->n; i++) {
114+		char *exp;
115+
116+		exp = expandstr(env, src->v[i]);
117+		dest->v = xrealloc(dest->v, (dest->n + 1) * sizeof(dest->v[0]));
118+		dest->v[dest->n++] = exp;
119+	}
120+}
121+
122 /*
123- * expand gnu make automatic variables 
124+ * expand gnu make automatic variables
125  *   $@  target
126  *   $<  first prereq
127- *   $^  all prereqs 
128+ *   $^  all prereqs
129  *   $+  all prereqs
130- *   $?  all prereqs, let ninja decide 
131- *   $*  stem 
132+ *   $?  all prereqs, let ninja decide
133+ *   $*  stem
134  *   $$  literal $
135  */
136 static char *
137@@ -237,12 +235,113 @@ expandauto(const char *s, const struct Target *t, const char *stem)
138 	out[len] = 0;
139 	return out;
140 }
141+/* we apply target assignments from the graphstate (gs.tas) to those specific 
142+ * targets, for some target like 
143+ *
144+ * binary : LDFLAGS += -static
145+ */
146+static void
147+applytassigns(struct GraphState *gs, struct Env *env, const char *name)
148+{
149+	size_t i, j;
150+
151+	for (i = 0; i < gs->ntas; i++) {
152+		struct AssignNode in;
153 
154+		for (j = 0; j < gs->tas[i].targets.n; j++) {
155+			if (!targetmatches(gs->tas[i].targets.v[j], name))
156+				continue;
157+			memset(&in, 0, sizeof(in));
158+			in.lhs = gs->tas[i].lhs;
159+			in.rhs = gs->tas[i].rhs;
160+			in.op = gs->tas[i].op;
161+			evalassign(env, &in);
162+		}
163+	}
164+}
165+
166+static void
167+targetenv(struct GraphState *gs, struct Env *env, const char *name)
168+{
169+	copyenv(env, &gs->env);
170+	applytassigns(gs, env, name);
171+}
172+
173+static void
174+addrule(struct GraphState *gs, const char *name, const struct RuleNode *rule)
175+{
176+	size_t i;
177+	struct Target *t;
178+	struct Env env;
179+
180+	t = findtarget(gs->graph, name);
181+	if (!t) {
182+		gs->graph->v = xrealloc(gs->graph->v, (gs->graph->n + 1) * sizeof(gs->graph->v[0]));
183+		t = &gs->graph->v[gs->graph->n++];
184+		memset(t, 0, sizeof(*t));
185+		t->name = xstrdup(name);
186+	}
187+	addwords(&t->prereqs, &rule->prereqs);
188+	addwords(&t->order_only, &rule->order_only);
189+	targetenv(gs, &env, name);
190+	addexprecipes(&t->recipes, &rule->recipes, &env);
191+	freeenv(&env);
192+
193+	for (i = 0; i < rule->prereqs.n; i++) {
194+		if (strchr(rule->prereqs.v[i], '%'))
195+			continue;
196+		if (!findtarget(gs->graph, rule->prereqs.v[i])) {
197+			gs->graph->v = xrealloc(gs->graph->v, (gs->graph->n + 1) * sizeof(gs->graph->v[0]));
198+			t = &gs->graph->v[gs->graph->n++];
199+			memset(t, 0, sizeof(*t));
200+			t->name = xstrdup(rule->prereqs.v[i]);
201+		}
202+	}
203+}
204+/* if the target is a pattern rule, store it in the graphstate 
205+ * instead of making a target immediately */
206 static void
207-instantiate(struct Target *t, struct Pattern *p, const char *stem)
208+addpattern(struct GraphState *gs, const struct RuleNode *rule)
209 {
210 	size_t i;
211 
212+	for (i = 0; i < rule->targets.n; i++) {
213+		if (strchr(rule->targets.v[i], '%')) {
214+			struct Pattern *p;
215+
216+			gs->pats = xrealloc(gs->pats, (gs->npats + 1) * sizeof(gs->pats[0]));
217+			p = &gs->pats[gs->npats++];
218+			memset(p, 0, sizeof(*p));
219+			p->target = xstrdup(rule->targets.v[i]);
220+			addwords(&p->prereqs, &rule->prereqs);
221+			addwords(&p->order_only, &rule->order_only);
222+			addrecipes(&p->recipes, &rule->recipes);
223+		} else {
224+			addrule(gs, rule->targets.v[i], rule);
225+		}
226+	}
227+}
228+
229+static void
230+addtassign(struct GraphState *gs, const struct AssignNode *assign)
231+{
232+	struct TAssign *ta;
233+
234+	gs->tas = xrealloc(gs->tas, (gs->ntas + 1) * sizeof(gs->tas[0]));
235+	ta = &gs->tas[gs->ntas++];
236+	memset(ta, 0, sizeof(*ta));
237+	addwords(&ta->targets, &assign->targets);
238+	ta->lhs = xstrdup(assign->lhs);
239+	ta->rhs = xstrdup(assign->rhs);
240+	ta->op = assign->op;
241+}
242+
243+static void
244+instantiate(struct GraphState *gs, struct Target *t, struct Pattern *p, const char *stem)
245+{
246+	size_t i;
247+	struct Env env;
248+
249 	for (i = 0; i < p->prereqs.n; i++) {
250 		char *s = applystem(p->prereqs.v[i], stem);
251 		t->prereqs.v = xrealloc(t->prereqs.v, (t->prereqs.n + 1) * sizeof(t->prereqs.v[0]));
252@@ -253,13 +352,19 @@ instantiate(struct Target *t, struct Pattern *p, const char *stem)
253 		t->order_only.v = xrealloc(t->order_only.v, (t->order_only.n + 1) * sizeof(t->order_only.v[0]));
254 		t->order_only.v[t->order_only.n++] = s;
255 	}
256+	targetenv(gs, &env, t->name);
257 	for (i = 0; i < p->recipes.n; i++) {
258-		char *s = applystem(p->recipes.v[i], stem);
259-		char *exp = expandauto(s, t, stem);
260+		char *s, *vars, *exp;
261+
262+		s = applystem(p->recipes.v[i], stem);
263+		vars = expandstr(&env, s);
264+		exp = expandauto(vars, t, stem);
265+		free(vars);
266 		free(s);
267 		t->recipes.v = xrealloc(t->recipes.v, (t->recipes.n + 1) * sizeof(t->recipes.v[0]));
268 		t->recipes.v[t->recipes.n++] = exp;
269 	}
270+	freeenv(&env);
271 }
272 
273 int
274@@ -271,12 +376,22 @@ buildgraph(const struct Ast *ast, struct Graph *graph)
275 	memset(graph, 0, sizeof(*graph));
276 	memset(&gs, 0, sizeof(gs));
277 	gs.graph = graph;
278+	seedenv(&gs.env);
279 
280 	for (i = 0; i < ast->n; i++) {
281-		if (ast->v[i].kind == NODE_RULE)
282-			addpats(&gs, &ast->v[i].data.rule);
283+		if (ast->v[i].kind == NODE_ASSIGN) {
284+			if (ast->v[i].data.assign.tspec)
285+				addtassign(&gs, &ast->v[i].data.assign);
286+			else
287+				evalassign(&gs.env, &ast->v[i].data.assign);
288+		} else if (ast->v[i].kind == NODE_RULE) {
289+			collectrule(&gs, &ast->v[i].data.rule);
290+		}
291 	}
292 
293+	for (i = 0; i < gs.nrules; i++)
294+		addpattern(&gs, gs.rules[i]);
295+
296 	start = 0;
297 	while (start < graph->n) {
298 		size_t current_n = graph->n;
299@@ -286,7 +401,7 @@ buildgraph(const struct Ast *ast, struct Graph *graph)
300 				for (j = 0; j < gs.npats; j++) {
301 					char *stem = matchpat(gs.pats[j].target, t->name);
302 					if (stem) {
303-						instantiate(t, &gs.pats[j], stem);
304+						instantiate(&gs, t, &gs.pats[j], stem);
305 						free(stem);
306 						break;
307 					}
308@@ -294,8 +409,10 @@ buildgraph(const struct Ast *ast, struct Graph *graph)
309 			}
310 			for (j = 0; j < t->prereqs.n; j++) {
311 				if (!findtarget(graph, t->prereqs.v[j])) {
312+					struct Target *nt;
313+
314 					graph->v = xrealloc(graph->v, (graph->n + 1) * sizeof(graph->v[0]));
315-					struct Target *nt = &graph->v[graph->n++];
316+					nt = &graph->v[graph->n++];
317 					memset(nt, 0, sizeof(*nt));
318 					nt->name = xstrdup(t->prereqs.v[j]);
319 				}
320@@ -321,7 +438,15 @@ buildgraph(const struct Ast *ast, struct Graph *graph)
321 		freestrs(&gs.pats[i].order_only);
322 		freerecipes(&gs.pats[i].recipes);
323 	}
324+	for (i = 0; i < gs.ntas; i++) {
325+		freestrs(&gs.tas[i].targets);
326+		free(gs.tas[i].lhs);
327+		free(gs.tas[i].rhs);
328+	}
329+	free(gs.tas);
330 	free(gs.pats);
331+	free(gs.rules);
332+	freeenv(&gs.env);
333 
334 	return 0;
335 }
+18, -0
 1@@ -3,6 +3,17 @@
 2 
 3 #include "shinobi.h"
 4 
 5+struct Var {
 6+	char *name;
 7+	char *val;
 8+	int simple;
 9+};
10+
11+struct Env {
12+	struct Var *v;
13+	size_t n;
14+};
15+
16 void *xmalloc(size_t n);
17 void *xrealloc(void *p, size_t n);
18 char *xstrndup(const char *s, size_t n);
19@@ -13,6 +24,13 @@ char *cat3(const char *a, const char *b, const char *c);
20 void freestrs(struct StrList *list);
21 void freerecipes(struct RecipeList *list);
22 
23+struct Var *findvar(struct Env *env, const char *name);
24+char *expandstr(struct Env *env, const char *s);
25+void seedenv(struct Env *env);
26+void freeenv(struct Env *env);
27+void copyenv(struct Env *dst, const struct Env *src);
28+void evalassign(struct Env *env, const struct AssignNode *in);
29+
30 char *fnwildcard(const char *patterns);
31 
32 #endif