commit c353699

xplshn  ·  2026-04-08 03:02:51 +0000 UTC
parent de8d436
reorganize, added dep graph & ninja backend

Signed-off-by: xplshn <anto@xplshn.com.ar>
11 files changed,  +570, -252
D main.c
+6, -3
 1@@ -1,10 +1,10 @@
 2 BIN = shin
 3-SRCS = main.c parse.c eval.c util.c
 4-FMTSRCS = $(SRCS) shinobi.h
 5+SRCS = cli/main.c src/parse.c src/eval.c src/graph.c backends/ninja.c src/util.c
 6+FMTSRCS = $(SRCS) src/shinobi.h src/internal.h backends/ninja.h
 7 OBJS = $(SRCS:.c=.o)
 8 
 9 CC = clang
10-CFLAGS = -O2 -Wall -Wextra -pedantic
11+CFLAGS = -O2 -Wall -Wextra -pedantic -Isrc -Ibackends
12 CPPFLAGS =
13 LDFLAGS = -static
14 
15@@ -13,6 +13,9 @@ all: $(BIN)
16 $(BIN): $(OBJS)
17 	$(CC) $(LDFLAGS) -o $(BIN) $(OBJS) $(LDLIBS)
18 
19+%.o: %.c
20+	$(CC) $(CFLAGS) -c -o $@ $<
21+
22 fmt:
23 	clang-format -i $(FMTSRCS)
24 
+52, -0
 1@@ -0,0 +1,52 @@
 2+#include "ninja.h"
 3+#include "internal.h"
 4+
 5+#include <stdio.h>
 6+#include <stdlib.h>
 7+#include <string.h>
 8+
 9+/* ninja backend */
10+
11+static void
12+emitrule(FILE *fp, const struct Target *t, int id)
13+{
14+	size_t i;
15+
16+	fprintf(fp, "rule r%d\n", id);
17+	fprintf(fp, "  command =");
18+	for (i = 0; i < t->recipes.n; i++) {
19+		fprintf(fp, "%s %s", i == 0 ? "" : " &&", t->recipes.v[i]);
20+	}
21+	fprintf(fp, "\n\n");
22+}
23+
24+int
25+genninja(const struct Graph *graph, const char *path)
26+{
27+	FILE *fp;
28+	size_t i, j;
29+	int ruleid;
30+
31+	fp = fopen(path, "w");
32+	if (!fp)
33+		return -1;
34+
35+	ruleid = 0;
36+	for (i = 0; i < graph->n; i++) {
37+		if (graph->v[i].recipes.n > 0) {
38+			emitrule(fp, &graph->v[i], ++ruleid);
39+			fprintf(fp, "build %s: r%d", graph->v[i].name, ruleid);
40+			for (j = 0; j < graph->v[i].prereqs.n; j++)
41+				fprintf(fp, " %s", graph->v[i].prereqs.v[j]);
42+			if (graph->v[i].order_only.n) {
43+				fprintf(fp, " ||");
44+				for (j = 0; j < graph->v[i].order_only.n; j++)
45+					fprintf(fp, " %s", graph->v[i].order_only.v[j]);
46+			}
47+			fprintf(fp, "\n\n");
48+		}
49+	}
50+
51+	fclose(fp);
52+	return 0;
53+}
+8, -0
1@@ -0,0 +1,8 @@
2+#ifndef NINJA_H
3+#define NINJA_H
4+
5+#include "shinobi.h"
6+
7+int genninja(const struct Graph *graph, const char *path);
8+
9+#endif
+88, -0
 1@@ -0,0 +1,88 @@
 2+#include "shinobi.h"
 3+#include "ninja.h"
 4+
 5+#include <stdio.h>
 6+#include <stdlib.h>
 7+#include <string.h>
 8+
 9+static char *
10+readfile(const char *path)
11+{
12+	FILE *fp;
13+	long n;
14+	char *buf;
15+
16+	fp = fopen(path, "rb");
17+	if (!fp)
18+		return 0;
19+	if (fseek(fp, 0, SEEK_END) < 0) {
20+		fclose(fp);
21+		return 0;
22+	}
23+	n = ftell(fp);
24+	if (n < 0) {
25+		fclose(fp);
26+		return 0;
27+	}
28+	if (fseek(fp, 0, SEEK_SET) < 0) {
29+		fclose(fp);
30+		return 0;
31+	}
32+	buf = malloc((size_t)n + 1);
33+	if (fread(buf, 1, (size_t)n, fp) != (size_t)n) {
34+		fclose(fp);
35+		free(buf);
36+		return 0;
37+	}
38+	buf[n] = 0;
39+	fclose(fp);
40+	return buf;
41+}
42+
43+int
44+main(int argc, char **argv)
45+{
46+	const char *path;
47+	char *src;
48+	struct Ast ast;
49+	struct Ast out;
50+	struct Graph graph;
51+
52+	path = argc > 1 ? argv[1] : "Makefile";
53+	src = readfile(path);
54+	if (!src) {
55+		fprintf(stderr, "could not read %s\n", path);
56+		return 1;
57+	}
58+	if (parse(path, src, &ast) < 0) {
59+		fprintf(stderr, "parse error in %s\n", path);
60+		free(src);
61+		return 1;
62+	}
63+	if (eval(&ast, &out) < 0) {
64+		fprintf(stderr, "eval error in %s\n", path);
65+		freeast(&ast);
66+		free(src);
67+		return 1;
68+	}
69+	if (buildgraph(&out, &graph) < 0) {
70+		fprintf(stderr, "graph error in %s\n", path);
71+		freeast(&out);
72+		freeast(&ast);
73+		free(src);
74+		return 1;
75+	}
76+	if (genninja(&graph, "build.ninja") < 0) {
77+		fprintf(stderr, "ninja generation error\n");
78+		freegraph(&graph);
79+		freeast(&out);
80+		freeast(&ast);
81+		free(src);
82+		return 1;
83+	}
84+	freegraph(&graph);
85+	freeast(&out);
86+	freeast(&ast);
87+	free(src);
88+	return 0;
89+}
D main.c
+0, -196
  1@@ -1,196 +0,0 @@
  2-#include "shinobi.h"
  3-
  4-#include <stdio.h>
  5-#include <stdlib.h>
  6-#include <string.h>
  7-
  8-static char *
  9-readfile(const char *path)
 10-{
 11-	FILE *fp;
 12-	long n;
 13-	char *buf;
 14-
 15-	fp = fopen(path, "rb");
 16-	if (!fp)
 17-		return 0;
 18-	if (fseek(fp, 0, SEEK_END) < 0) {
 19-		fclose(fp);
 20-		return 0;
 21-	}
 22-	n = ftell(fp);
 23-	if (n < 0) {
 24-		fclose(fp);
 25-		return 0;
 26-	}
 27-	if (fseek(fp, 0, SEEK_SET) < 0) {
 28-		fclose(fp);
 29-		return 0;
 30-	}
 31-	buf = malloc((size_t)n + 1);
 32-	if (!buf) {
 33-		fclose(fp);
 34-		return 0;
 35-	}
 36-	if (fread(buf, 1, (size_t)n, fp) != (size_t)n) {
 37-		fclose(fp);
 38-		free(buf);
 39-		return 0;
 40-	}
 41-	buf[n] = 0;
 42-	fclose(fp);
 43-	return buf;
 44-}
 45-
 46-static const char *
 47-assignname(enum AssignOp op)
 48-{
 49-	switch (op) {
 50-	case ASSIGN_EQ:
 51-		return "=";
 52-	case ASSIGN_PLUS_EQ:
 53-		return "+=";
 54-	case ASSIGN_COLON_EQ:
 55-		return ":=";
 56-	case ASSIGN_QMARK_EQ:
 57-		return "?=";
 58-	case ASSIGN_BANG_EQ:
 59-		return "!=";
 60-	}
 61-	return "?";
 62-}
 63-
 64-static const char *
 65-condname(enum CondKind kind)
 66-{
 67-	switch (kind) {
 68-	case COND_IFEQ:
 69-		return "ifeq";
 70-	case COND_IFNEQ:
 71-		return "ifneq";
 72-	case COND_ELSE:
 73-		return "else";
 74-	case COND_ENDIF:
 75-		return "endif";
 76-	}
 77-	return "?";
 78-}
 79-
 80-static void
 81-printwords(const char *name, const struct StrList *list)
 82-{
 83-	size_t i;
 84-
 85-	printf("  %s:", name);
 86-	for (i = 0; i < list->n; i++)
 87-		printf(" [%s]", list->v[i]);
 88-	printf("\n");
 89-}
 90-
 91-static void
 92-indent(size_t n)
 93-{
 94-	size_t i;
 95-
 96-	for (i = 0; i < n; i++)
 97-		printf("  ");
 98-}
 99-
100-static void
101-dumpnodes(const struct Node *v, size_t n, size_t depth)
102-{
103-	size_t i, j;
104-
105-	for (i = 0; i < n; i++) {
106-		const struct Node *s = &v[i];
107-		indent(depth);
108-		printf("%d-%d ", s->loc.line0, s->loc.line1);
109-		switch (s->kind) {
110-		case NODE_BLANK:
111-			puts("blank");
112-			break;
113-		case NODE_COMMENT:
114-			printf("comment %s\n", s->data.raw.text);
115-			break;
116-		case NODE_INCLUDE:
117-			printf("include optional=%d path=%s\n", s->data.include.optional, s->data.include.path);
118-			break;
119-		case NODE_COND:
120-			printf("cond %s", condname(s->data.cond.kind));
121-			if (s->data.cond.arg1)
122-				printf(" arg1=%s", s->data.cond.arg1);
123-			if (s->data.cond.arg2)
124-				printf(" arg2=%s", s->data.cond.arg2);
125-			printf("\n");
126-			if (s->data.cond.thenpart.n) {
127-				indent(depth);
128-				puts("then");
129-				dumpnodes(s->data.cond.thenpart.v, s->data.cond.thenpart.n, depth + 1);
130-			}
131-			if (s->data.cond.elsepart.n) {
132-				indent(depth);
133-				puts("else");
134-				dumpnodes(s->data.cond.elsepart.v, s->data.cond.elsepart.n, depth + 1);
135-			}
136-			break;
137-		case NODE_ASSIGN:
138-			printf("assign%s lhs=%s op=%s rhs=%s\n",
139-			       s->data.assign.tspec ? " target" : "",
140-			       s->data.assign.lhs,
141-			       assignname(s->data.assign.op),
142-			       s->data.assign.rhs);
143-			if (s->data.assign.tspec)
144-				printwords("targets", &s->data.assign.targets);
145-			break;
146-		case NODE_RULE:
147-			puts("rule");
148-			printwords("targets", &s->data.rule.targets);
149-			printwords("prereqs", &s->data.rule.prereqs);
150-			printwords("order_only", &s->data.rule.order_only);
151-			for (j = 0; j < s->data.rule.recipes.n; j++)
152-				printf("  recipe: %s\n", s->data.rule.recipes.v[j]);
153-			break;
154-		case NODE_RAW:
155-			printf("raw %s\n", s->data.raw.text);
156-			break;
157-		}
158-	}
159-}
160-
161-static void
162-dump(const struct Ast *ast)
163-{
164-	dumpnodes(ast->v, ast->n, 0);
165-}
166-
167-int
168-main(int argc, char **argv)
169-{
170-	const char *path;
171-	char *src;
172-	struct Ast ast;
173-	struct Ast out;
174-
175-	path = argc > 1 ? argv[1] : "Makefile";
176-	src = readfile(path);
177-	if (!src) {
178-		fprintf(stderr, "could not read %s\n", path);
179-		return 1;
180-	}
181-	if (parse(path, src, &ast) < 0) {
182-		fprintf(stderr, "parse error in %s\n", path);
183-		free(src);
184-		return 1;
185-	}
186-	if (eval(&ast, &out) < 0) {
187-		fprintf(stderr, "eval error in %s\n", path);
188-		freeast(&ast);
189-		free(src);
190-		return 1;
191-	}
192-	dump(&out);
193-	freeast(&out);
194-	freeast(&ast);
195-	free(src);
196-	return 0;
197-}
R eval.c => src/eval.c
+17, -40
  1@@ -40,21 +40,6 @@ findvar(struct Env *env, const char *name)
  2 	return 0;
  3 }
  4 
  5-static char *
  6-cat2(const char *a, const char *b)
  7-{
  8-	size_t na, nb;
  9-	char *s;
 10-
 11-	na = strlen(a);
 12-	nb = strlen(b);
 13-	s = xmalloc(na + nb + 1);
 14-	memcpy(s, a, na);
 15-	memcpy(s + na, b, nb);
 16-	s[na + nb] = 0;
 17-	return s;
 18-}
 19-
 20 static int
 21 isplainvar(const char *s, size_t n)
 22 {
 23@@ -351,7 +336,7 @@ evalassign(struct Env *env, const struct AssignNode *in)
 24 			break;
 25 		}
 26 		rhs = v->simple ? expandstr(env, in->rhs) : xstrdup(in->rhs);
 27-		joined = cat2(v->val, rhs);
 28+		joined = cat3(v->val, " ", rhs);
 29 		free(rhs);
 30 		free(v->val);
 31 		v->val = joined;
 32@@ -365,9 +350,20 @@ evalassign(struct Env *env, const struct AssignNode *in)
 33 static int
 34 testcond(struct Env *env, const struct CondNode *cond)
 35 {
 36-	char *a, *b;
 37+	char *a, *b, *name;
 38+	struct Var *v;
 39 	int ok;
 40 
 41+	if (cond->kind == COND_IFDEF || cond->kind == COND_IFNDEF) {
 42+		name = expandstr(env, cond->arg1);
 43+		v = findvar(env, name);
 44+		ok = v != 0;
 45+		free(name);
 46+		if (cond->kind == COND_IFNDEF)
 47+			ok = !ok;
 48+		return ok;
 49+	}
 50+
 51 	a = cond->arg1 ? expandstr(env, cond->arg1) : xstrdup("");
 52 	b = cond->arg2 ? expandstr(env, cond->arg2) : xstrdup("");
 53 	ok = strcmp(a, b) == 0;
 54@@ -385,8 +381,9 @@ copywords(struct StrList *out, const struct StrList *in, struct Env *env)
 55 
 56 	memset(out, 0, sizeof(*out));
 57 	for (i = 0; i < in->n; i++) {
 58-		out->v = xrealloc(out->v, (out->n + 1) * sizeof(out->v[0]));
 59-		out->v[out->n++] = expandstr(env, in->v[i]);
 60+		char *s = expandstr(env, in->v[i]);
 61+		splitwords(out, s, strlen(s));
 62+		free(s);
 63 	}
 64 }
 65 
 66@@ -472,26 +469,6 @@ eval(const struct Ast *ast, struct Ast *out)
 67 	return rc;
 68 }
 69 
 70-static void
 71-freestrs(struct StrList *list)
 72-{
 73-	size_t i;
 74-
 75-	for (i = 0; i < list->n; i++)
 76-		free(list->v[i]);
 77-	free(list->v);
 78-}
 79-
 80-static void
 81-freerec(struct RecipeList *list)
 82-{
 83-	size_t i;
 84-
 85-	for (i = 0; i < list->n; i++)
 86-		free(list->v[i]);
 87-	free(list->v);
 88-}
 89-
 90 static void
 91 freenodes(struct NodeList *list)
 92 {
 93@@ -512,7 +489,7 @@ freenodes(struct NodeList *list)
 94 			freestrs(&list->v[i].data.rule.targets);
 95 			freestrs(&list->v[i].data.rule.prereqs);
 96 			freestrs(&list->v[i].data.rule.order_only);
 97-			freerec(&list->v[i].data.rule.recipes);
 98+			freerecipes(&list->v[i].data.rule.recipes);
 99 			break;
100 		case NODE_INCLUDE:
101 			free(list->v[i].data.include.path);
+299, -0
  1@@ -0,0 +1,299 @@
  2+#include "shinobi.h"
  3+#include "internal.h"
  4+
  5+#include <stdlib.h>
  6+#include <string.h>
  7+
  8+/* graph builder */
  9+
 10+static struct Target *
 11+findtarget(struct Graph *graph, const char *name)
 12+{
 13+	size_t i;
 14+
 15+	for (i = 0; i < graph->n; i++) {
 16+		if (strcmp(graph->v[i].name, name) == 0)
 17+			return &graph->v[i];
 18+	}
 19+	return 0;
 20+}
 21+
 22+static void
 23+addwords(struct StrList *dest, const struct StrList *src)
 24+{
 25+	size_t i;
 26+
 27+	for (i = 0; i < src->n; i++) {
 28+		dest->v = xrealloc(dest->v, (dest->n + 1) * sizeof(dest->v[0]));
 29+		dest->v[dest->n++] = xstrdup(src->v[i]);
 30+	}
 31+}
 32+
 33+static void
 34+addrecipes(struct RecipeList *dest, const struct RecipeList *src)
 35+{
 36+	size_t i;
 37+
 38+	for (i = 0; i < src->n; i++) {
 39+		dest->v = xrealloc(dest->v, (dest->n + 1) * sizeof(dest->v[0]));
 40+		dest->v[dest->n++] = xstrdup(src->v[i]);
 41+	}
 42+}
 43+
 44+static void
 45+addrule(struct Graph *graph, const char *name, const struct RuleNode *rule)
 46+{
 47+	size_t i;
 48+	struct Target *t;
 49+
 50+	t = findtarget(graph, name);
 51+	if (!t) {
 52+		graph->v = xrealloc(graph->v, (graph->n + 1) * sizeof(graph->v[0]));
 53+		t = &graph->v[graph->n++];
 54+		memset(t, 0, sizeof(*t));
 55+		t->name = xstrdup(name);
 56+	}
 57+	addwords(&t->prereqs, &rule->prereqs);
 58+	addwords(&t->order_only, &rule->order_only);
 59+	addrecipes(&t->recipes, &rule->recipes);
 60+
 61+	for (i = 0; i < rule->prereqs.n; i++) {
 62+		if (strchr(rule->prereqs.v[i], '%'))
 63+			continue;
 64+		if (!findtarget(graph, rule->prereqs.v[i])) {
 65+			graph->v = xrealloc(graph->v, (graph->n + 1) * sizeof(graph->v[0]));
 66+			t = &graph->v[graph->n++];
 67+			memset(t, 0, sizeof(*t));
 68+			t->name = xstrdup(rule->prereqs.v[i]);
 69+		}
 70+	}
 71+}
 72+
 73+struct Pattern {
 74+	char *target;
 75+	struct StrList prereqs;
 76+	struct StrList order_only;
 77+	struct RecipeList recipes;
 78+};
 79+
 80+struct GraphState {
 81+	struct Graph *graph;
 82+	struct Pattern *pats;
 83+	size_t npats;
 84+};
 85+
 86+static void
 87+addpats(struct GraphState *gs, const struct RuleNode *rule)
 88+{
 89+	size_t i;
 90+
 91+	for (i = 0; i < rule->targets.n; i++) {
 92+		if (strchr(rule->targets.v[i], '%')) {
 93+			gs->pats = xrealloc(gs->pats, (gs->npats + 1) * sizeof(gs->pats[0]));
 94+			struct Pattern *p = &gs->pats[gs->npats++];
 95+			memset(p, 0, sizeof(*p));
 96+			p->target = xstrdup(rule->targets.v[i]);
 97+			addwords(&p->prereqs, &rule->prereqs);
 98+			addwords(&p->order_only, &rule->order_only);
 99+			addrecipes(&p->recipes, &rule->recipes);
100+		} else {
101+			addrule(gs->graph, rule->targets.v[i], rule);
102+		}
103+	}
104+}
105+
106+static char *
107+matchpat(const char *pat, const char *name)
108+{
109+	const char *p;
110+	size_t pre, suf, n, plen;
111+
112+	p = strchr(pat, '%');
113+	if (!p)
114+		return 0;
115+	pre = (size_t)(p - pat);
116+	suf = strlen(p + 1);
117+	n = strlen(name);
118+	plen = strlen(pat);
119+
120+	if (n < plen - 1)
121+		return 0;
122+	if (memcmp(pat, name, pre) != 0)
123+		return 0;
124+	if (memcmp(p + 1, name + n - suf, suf) != 0)
125+		return 0;
126+	return xstrndup(name + pre, n - pre - suf);
127+}
128+
129+static char *
130+applystem(const char *s, const char *stem)
131+{
132+	const char *p;
133+	size_t pre, suf, stemlen, slen;
134+	char *out;
135+
136+	p = strchr(s, '%');
137+	if (!p)
138+		return xstrdup(s);
139+	pre = (size_t)(p - s);
140+	slen = strlen(s);
141+	suf = slen - pre - 1;
142+	stemlen = strlen(stem);
143+	out = xmalloc(pre + stemlen + suf + 1);
144+	memcpy(out, s, pre);
145+	memcpy(out + pre, stem, stemlen);
146+	memcpy(out + pre + stemlen, p + 1, suf);
147+	out[pre + stemlen + suf] = 0;
148+	return out;
149+}
150+
151+static char *
152+expandauto(const char *s, const struct Target *t)
153+{
154+	size_t i, n, cap, len;
155+	char *out;
156+
157+	n = strlen(s);
158+	cap = n + 1;
159+	len = 0;
160+	out = xmalloc(cap);
161+	for (i = 0; i < n; i++) {
162+		if (s[i] == '$' && i + 1 < n) {
163+			char *val = 0;
164+			int mustfree = 0;
165+
166+			if (s[i + 1] == '@') {
167+				val = t->name;
168+			} else if (s[i + 1] == '<') {
169+				if (t->prereqs.n > 0)
170+					val = t->prereqs.v[0];
171+			} else if (s[i + 1] == '^') {
172+				size_t k;
173+				char *joined = xstrdup("");
174+				for (k = 0; k < t->prereqs.n; k++) {
175+					char *next = joined[0] ? cat3(joined, " ", t->prereqs.v[k]) : xstrdup(t->prereqs.v[k]);
176+					free(joined);
177+					joined = next;
178+				}
179+				val = joined;
180+				mustfree = 1;
181+			}
182+
183+			if (val) {
184+				size_t vlen = strlen(val);
185+				if (len + vlen + 1 > cap) {
186+					cap = len + vlen + n - i + 1;
187+					out = xrealloc(out, cap);
188+				}
189+				memcpy(out + len, val, vlen);
190+				len += vlen;
191+				if (mustfree)
192+					free(val);
193+				i++;
194+				continue;
195+			}
196+		}
197+		if (len + 2 > cap) {
198+			cap *= 2;
199+			out = xrealloc(out, cap);
200+		}
201+		out[len++] = s[i];
202+	}
203+	out[len] = 0;
204+	return out;
205+}
206+
207+static void
208+instantiate(struct Target *t, struct Pattern *p, const char *stem)
209+{
210+	size_t i;
211+
212+	for (i = 0; i < p->prereqs.n; i++) {
213+		char *s = applystem(p->prereqs.v[i], stem);
214+		t->prereqs.v = xrealloc(t->prereqs.v, (t->prereqs.n + 1) * sizeof(t->prereqs.v[0]));
215+		t->prereqs.v[t->prereqs.n++] = s;
216+	}
217+	for (i = 0; i < p->order_only.n; i++) {
218+		char *s = applystem(p->order_only.v[i], stem);
219+		t->order_only.v = xrealloc(t->order_only.v, (t->order_only.n + 1) * sizeof(t->order_only.v[0]));
220+		t->order_only.v[t->order_only.n++] = s;
221+	}
222+	for (i = 0; i < p->recipes.n; i++) {
223+		char *s = applystem(p->recipes.v[i], stem);
224+		char *exp = expandauto(s, t);
225+		free(s);
226+		t->recipes.v = xrealloc(t->recipes.v, (t->recipes.n + 1) * sizeof(t->recipes.v[0]));
227+		t->recipes.v[t->recipes.n++] = exp;
228+	}
229+}
230+
231+int
232+buildgraph(const struct Ast *ast, struct Graph *graph)
233+{
234+	size_t i, j, start;
235+	struct GraphState gs;
236+
237+	memset(graph, 0, sizeof(*graph));
238+	memset(&gs, 0, sizeof(gs));
239+	gs.graph = graph;
240+
241+	for (i = 0; i < ast->n; i++) {
242+		if (ast->v[i].kind == NODE_RULE)
243+			addpats(&gs, &ast->v[i].data.rule);
244+	}
245+
246+	start = 0;
247+	while (start < graph->n) {
248+		size_t current_n = graph->n;
249+		for (i = start; i < current_n; i++) {
250+			struct Target *t = &graph->v[i];
251+			if (t->recipes.n == 0) {
252+				for (j = 0; j < gs.npats; j++) {
253+					char *stem = matchpat(gs.pats[j].target, t->name);
254+					if (stem) {
255+						instantiate(t, &gs.pats[j], stem);
256+						free(stem);
257+						break;
258+					}
259+				}
260+			}
261+			for (j = 0; j < t->prereqs.n; j++) {
262+				if (!findtarget(graph, t->prereqs.v[j])) {
263+					graph->v = xrealloc(graph->v, (graph->n + 1) * sizeof(graph->v[0]));
264+					struct Target *nt = &graph->v[graph->n++];
265+					memset(nt, 0, sizeof(*nt));
266+					nt->name = xstrdup(t->prereqs.v[j]);
267+				}
268+			}
269+		}
270+		start = current_n;
271+	}
272+
273+	for (i = 0; i < gs.npats; i++) {
274+		free(gs.pats[i].target);
275+		freestrs(&gs.pats[i].prereqs);
276+		freestrs(&gs.pats[i].order_only);
277+		freerecipes(&gs.pats[i].recipes);
278+	}
279+	free(gs.pats);
280+
281+	return 0;
282+}
283+
284+void
285+freegraph(struct Graph *graph)
286+{
287+	size_t i;
288+
289+	if (!graph)
290+		return;
291+	for (i = 0; i < graph->n; i++) {
292+		free(graph->v[i].name);
293+		freestrs(&graph->v[i].prereqs);
294+		freestrs(&graph->v[i].order_only);
295+		freerecipes(&graph->v[i].recipes);
296+	}
297+	free(graph->v);
298+	graph->v = 0;
299+	graph->n = 0;
300+}
R internal.h => src/internal.h
+4, -0
 1@@ -8,5 +8,9 @@ void *xrealloc(void *p, size_t n);
 2 char *xstrndup(const char *s, size_t n);
 3 char *xstrdup(const char *s);
 4 void addnode(struct NodeList *list, struct Node node);
 5+void splitwords(struct StrList *out, const char *s, size_t n);
 6+char *cat3(const char *a, const char *b, const char *c);
 7+void freestrs(struct StrList *list);
 8+void freerecipes(struct RecipeList *list);
 9 
10 #endif
R parse.c => src/parse.c
+35, -13
  1@@ -197,7 +197,7 @@ findtop(const char *s, size_t n, char want)
  2 	return -1;
  3 }
  4 
  5-static void
  6+void
  7 splitwords(struct StrList *out, const char *s, size_t n)
  8 {
  9 	size_t i, j, start;
 10@@ -377,7 +377,7 @@ joinpath(const char *dir, const char *name)
 11 }
 12 
 13 static int
 14-preprocfile(const char *path, struct Pre *pre, struct Inc *inc)
 15+preprocfile(const char *path, const char *src_override, struct Pre *pre, struct Inc *inc)
 16 {
 17 	char *src, *dir;
 18 	const char *p;
 19@@ -388,9 +388,13 @@ preprocfile(const char *path, struct Pre *pre, struct Inc *inc)
 20 		fprintf(stderr, "include cycle: %s\n", path);
 21 		return -1;
 22 	}
 23-	src = readfile(path);
 24-	if (!src)
 25-		return -1;
 26+	if (src_override) {
 27+		src = xstrdup(src_override);
 28+	} else {
 29+		src = readfile(path);
 30+		if (!src)
 31+			return -1;
 32+	}
 33 	inc->v = xrealloc(inc->v, (inc->n + 1) * sizeof(inc->v[0]));
 34 	inc->v[inc->n++] = xstrdup(path);
 35 	dir = dirpart(path);
 36@@ -416,7 +420,7 @@ preprocfile(const char *path, struct Pre *pre, struct Inc *inc)
 37 					free(line.text);
 38 					free(trim);
 39 					free(incarg);
 40-					rc = preprocfile(full, pre, inc);
 41+					rc = preprocfile(full, 0, pre, inc);
 42 					free(full);
 43 					if (rc < 0 && !opt) {
 44 						free(dir);
 45@@ -446,7 +450,7 @@ preproc(const char *path, struct Pre *pre)
 46 
 47 	memset(pre, 0, sizeof(*pre));
 48 	memset(&inc, 0, sizeof(inc));
 49-	if (preprocfile(path, pre, &inc) < 0) {
 50+	if (preprocfile(path, 0, pre, &inc) < 0) {
 51 		free(inc.v);
 52 		freepre(pre);
 53 		return -1;
 54@@ -509,6 +513,12 @@ parsecond(const struct PreLine *line, const char *s)
 55 	} else if (haskw(s, "ifneq")) {
 56 		state.data.cond.kind = COND_IFNEQ;
 57 		p = s + 5;
 58+	} else if (haskw(s, "ifdef")) {
 59+		state.data.cond.kind = COND_IFDEF;
 60+		p = s + 5;
 61+	} else if (haskw(s, "ifndef")) {
 62+		state.data.cond.kind = COND_IFNDEF;
 63+		p = s + 6;
 64 	} else {
 65 		state.data.cond.kind = COND_ENDIF;
 66 		return state;
 67@@ -522,6 +532,11 @@ parsecond(const struct PreLine *line, const char *s)
 68 		state.data.cond.arg2 = xstrndup("", 0);
 69 		return state;
 70 	}
 71+	if (state.data.cond.kind == COND_IFDEF || state.data.cond.kind == COND_IFNDEF) {
 72+		state.data.cond.arg1 = xstrndup(p, n);
 73+		state.data.cond.arg2 = xstrndup("", 0);
 74+		return state;
 75+	}
 76 	if ((p[0] == '(' && p[n - 1] == ')') || (p[0] == '"' && p[n - 1] == '"')) {
 77 		mid = n / 2;
 78 		state.data.cond.arg1 = xstrndup(p, n);
 79@@ -583,7 +598,7 @@ parserule(const struct PreLine *line, const char *s, size_t n, size_t colon)
 80 	return state;
 81 }
 82 
 83-/*unclassified line*/
 84+/* unclassified line */
 85 static struct Node
 86 parseraw(const struct PreLine *line, const char *s)
 87 {
 88@@ -626,6 +641,7 @@ parseline(const struct PreLine *line)
 89 		return state;
 90 	}
 91 	if (haskw(trim, "ifeq") || haskw(trim, "ifneq") ||
 92+	    haskw(trim, "ifdef") || haskw(trim, "ifndef") ||
 93 	    haskw(trim, "else") || haskw(trim, "endif")) {
 94 		state = parsecond(line, trim);
 95 		free(trim);
 96@@ -688,7 +704,8 @@ parseblock(const struct Pre *pre, size_t *i, struct NodeList *out)
 97 			free(trim);
 98 			break;
 99 		}
100-		if (haskw(trim, "ifeq") || haskw(trim, "ifneq")) {
101+		if (haskw(trim, "ifeq") || haskw(trim, "ifneq") ||
102+		    haskw(trim, "ifdef") || haskw(trim, "ifndef")) {
103 			state = parsecond(line, trim);
104 			free(trim);
105 			(*i)++;
106@@ -751,17 +768,22 @@ buildast(const char *path, const struct Pre *pre, struct Ast *ast)
107 	return 0;
108 }
109 
110-/* pre procces build ast, */
111+/* preprocess and parse */
112 int
113 parse(const char *path, const char *src, struct Ast *ast)
114 {
115 	struct Pre pre;
116+	struct Inc inc;
117 	int rc;
118 
119-	(void)src;
120-	rc = preproc(path, &pre);
121-	if (rc < 0)
122+	memset(&pre, 0, sizeof(pre));
123+	memset(&inc, 0, sizeof(inc));
124+	rc = preprocfile(path, src, &pre, &inc);
125+	free(inc.v);
126+	if (rc < 0) {
127+		freepre(&pre);
128 		return rc;
129+	}
130 	rc = buildast(path, &pre, ast);
131 	freepre(&pre);
132 	return rc;
R shinobi.h => src/shinobi.h
+16, -0
 1@@ -32,6 +32,8 @@ enum AssignOp {
 2 enum CondKind {
 3 	COND_IFEQ,
 4 	COND_IFNEQ,
 5+	COND_IFDEF,
 6+	COND_IFNDEF,
 7 	COND_ELSE,
 8 	COND_ENDIF,
 9 };
10@@ -121,11 +123,25 @@ struct Ast {
11 	size_t n;
12 };
13 
14+struct Target {
15+	char *name;
16+	struct StrList prereqs;
17+	struct StrList order_only;
18+	struct RecipeList recipes;
19+};
20+
21+struct Graph {
22+	struct Target *v;
23+	size_t n;
24+};
25+
26 int preproc(const char *path, struct Pre *pre);
27 void freepre(struct Pre *pre);
28 int buildast(const char *path, const struct Pre *pre, struct Ast *ast);
29 int eval(const struct Ast *ast, struct Ast *out);
30 int parse(const char *path, const char *src, struct Ast *ast);
31 void freeast(struct Ast *ast);
32+int buildgraph(const struct Ast *ast, struct Graph *graph);
33+void freegraph(struct Graph *graph);
34 
35 #endif
R util.c => src/util.c
+45, -0
 1@@ -49,9 +49,54 @@ xstrdup(const char *s)
 2 	return xstrndup(s, strlen(s));
 3 }
 4 
 5+char *
 6+cat3(const char *a, const char *b, const char *c)
 7+{
 8+	size_t na, nb, nc;
 9+	char *s;
10+
11+	na = strlen(a);
12+	nb = strlen(b);
13+	nc = strlen(c);
14+	s = xmalloc(na + nb + nc + 1);
15+	memcpy(s, a, na);
16+	memcpy(s + na, b, nb);
17+	memcpy(s + na + nb, c, nc);
18+	s[na + nb + nc] = 0;
19+	return s;
20+}
21+
22 void
23 addnode(struct NodeList *list, struct Node node)
24 {
25 	list->v = xrealloc(list->v, (list->n + 1) * sizeof(list->v[0]));
26 	list->v[list->n++] = node;
27 }
28+
29+void
30+freestrs(struct StrList *list)
31+{
32+	size_t i;
33+
34+	if (!list)
35+		return;
36+	for (i = 0; i < list->n; i++)
37+		free(list->v[i]);
38+	free(list->v);
39+	list->v = 0;
40+	list->n = 0;
41+}
42+
43+void
44+freerecipes(struct RecipeList *list)
45+{
46+	size_t i;
47+
48+	if (!list)
49+		return;
50+	for (i = 0; i < list->n; i++)
51+		free(list->v[i]);
52+	free(list->v);
53+	list->v = 0;
54+	list->n = 0;
55+}