commit e7c76d3

shrub  ·  2026-04-12 08:32:33 +0000 UTC
parent 91483b0
organise a bit and add single-suffix rule handling
11 files changed,  +672, -458
+10, -2
 1@@ -1,6 +1,10 @@
 2+PREFIX = /usr
 3+DESTDIR =
 4+BINDIR = $(DESTDIR)$(PREFIX)/bin
 5+
 6 BIN = shin
 7-SRCS = cli/main.c src/parse.c src/eval.c src/graph.c backends/ninja.c src/util.c src/functions.c
 8-FMTSRCS = $(SRCS) src/shinobi.h src/internal.h backends/ninja.h
 9+SRCS = cli/main.c src/parse.c src/eval.c src/graph.c src/posix.c src/gnu/pattern.c backends/ninja.c src/util.c src/gnu/functions.c
10+FMTSRCS = $(SRCS) src/shinobi.h src/internal.h src/posix.h src/gnu/pattern.h backends/ninja.h
11 OBJS = $(SRCS:.c=.o)
12 
13 CC = clang
14@@ -22,5 +26,9 @@ fmt:
15 test: $(BIN)
16 	tests/shintest.sh
17 
18+install: $(BIN)
19+	install -d $(BINDIR)
20+	install -m 755 $(BIN) $(BINDIR)/$(BIN)
21+
22 clean:
23 	rm -f $(BIN) $(OBJS)
+2, -13
 1@@ -51,6 +51,7 @@ joinrecipes(const struct Target *t)
 2 	return s;
 3 }
 4 
 5+/*turn remaining automatic vars into ninja equivalents*/ 
 6 static char *
 7 translateauto(const char *s, const struct Target *t)
 8 {
 9@@ -130,24 +131,12 @@ findrule(struct Rule *rules, size_t n, const char *cmd)
10 	return 0;
11 }
12 
13-static const struct Target *
14-findtarget(const struct Graph *graph, const char *name)
15-{
16-	size_t i;
17-
18-	for (i = 0; i < graph->n; i++) {
19-		if (strcmp(graph->v[i].name, name) == 0)
20-			return &graph->v[i];
21-	}
22-	return 0;
23-}
24-
25 static const char *
26 defaulttarget(const struct Graph *graph)
27 {
28 	size_t i;
29 
30-	if (findtarget(graph, "all"))
31+	if (findctarget(graph, "all"))
32 		return "all";
33 	for (i = 0; i < graph->n; i++) {
34 		if (strcmp(graph->v[i].name, "clean") == 0 || strcmp(graph->v[i].name, "fmt") == 0)
+5, -39
 1@@ -469,40 +469,6 @@ expandstr(struct Env *env, const char *s)
 2 	return out;
 3 }
 4 
 5-static void
 6-setvar(struct Env *env, const char *name, char *val, int simple)
 7-{
 8-	struct Var *v;
 9-
10-	v = findvar(env, name);
11-	if (v) {
12-		free(v->val);
13-		v->val = val;
14-		v->simple = simple;
15-		return;
16-	}
17-	env->v = xrealloc(env->v, (env->n + 1) * sizeof(env->v[0]));
18-	env->v[env->n].name = xstrdup(name);
19-	env->v[env->n].val = val;
20-	env->v[env->n].simple = simple;
21-	env->n++;
22-}
23-
24-void
25-seedenv(struct Env *env)
26-{
27-	setvar(env, "CC", xstrdup("cc"), 1);
28-	setvar(env, "CXX", xstrdup("c++"), 1);
29-	setvar(env, "CPP", xstrdup("$(CC) -E"), 0);
30-	setvar(env, "AR", xstrdup("ar"), 1);
31-	setvar(env, "ARFLAGS", xstrdup("-rv"), 1);
32-	setvar(env, "AS", xstrdup("as"), 1);
33-	setvar(env, "LD", xstrdup("ld"), 1);
34-	setvar(env, "LEX", xstrdup("lex"), 1);
35-	setvar(env, "YACC", xstrdup("yacc"), 1);
36-	setvar(env, "SHELL", xstrdup("/bin/sh"), 1);
37-}
38-
39 void
40 freeenv(struct Env *env)
41 {
42@@ -541,19 +507,19 @@ evalassign(struct Env *env, const struct AssignNode *in)
43 
44 	switch (in->op) {
45 	case ASSIGN_EQ:
46-		setvar(env, in->lhs, xstrdup(in->rhs), 0);
47+		envsetvar(env, in->lhs, xstrdup(in->rhs), 0);
48 		break;
49 	case ASSIGN_COLON_EQ:
50-		setvar(env, in->lhs, expandstr(env, in->rhs), 1);
51+		envsetvar(env, in->lhs, expandstr(env, in->rhs), 1);
52 		break;
53 	case ASSIGN_QMARK_EQ:
54 		if (!findvar(env, in->lhs))
55-			setvar(env, in->lhs, xstrdup(in->rhs), 0);
56+			envsetvar(env, in->lhs, xstrdup(in->rhs), 0);
57 		break;
58 	case ASSIGN_PLUS_EQ:
59 		v = findvar(env, in->lhs);
60 		if (!v) {
61-			setvar(env, in->lhs, xstrdup(in->rhs), 0);
62+			envsetvar(env, in->lhs, xstrdup(in->rhs), 0);
63 			break;
64 		}
65 		rhs = v->simple ? expandstr(env, in->rhs) : xstrdup(in->rhs);
66@@ -563,7 +529,7 @@ evalassign(struct Env *env, const struct AssignNode *in)
67 		v->val = joined;
68 		break;
69 	case ASSIGN_BANG_EQ:
70-		setvar(env, in->lhs, expandstr(env, in->rhs), 1);
71+		envsetvar(env, in->lhs, expandstr(env, in->rhs), 1);
72 		break;
73 	}
74 }
R src/functions.c => src/gnu/functions.c
+2, -2
 1@@ -285,7 +285,7 @@ fnaddprefix(const char *prefix, const char *names)
 2 char *
 3 fninfo(const char *text)
 4 {
 5-	fputs(text, stdout);
 6-	fputc('\n', stdout);
 7+	fputs(text, stderr);
 8+	fputc('\n', stderr);
 9 	return xstrdup("");
10 }
+188, -0
  1@@ -0,0 +1,188 @@
  2+#include "gnu/pattern.h"
  3+
  4+#include <stdlib.h>
  5+#include <string.h>
  6+
  7+/*helpers for handling gnu % pattern rules*/
  8+
  9+static char *
 10+matchpat(const char *pat, const char *name)
 11+{
 12+	const char *p;
 13+	size_t pre, suf, n, plen;
 14+
 15+	p = strchr(pat, '%');
 16+	if (!p)
 17+		return 0;
 18+	pre = (size_t)(p - pat);
 19+	suf = strlen(p + 1);
 20+	n = strlen(name);
 21+	plen = strlen(pat);
 22+
 23+	if (n < plen - 1)
 24+		return 0;
 25+	if (memcmp(pat, name, pre) != 0)
 26+		return 0;
 27+	if (memcmp(p + 1, name + n - suf, suf) != 0)
 28+		return 0;
 29+	return xstrndup(name + pre, n - pre - suf);
 30+}
 31+
 32+static char *
 33+applystem(const char *s, const char *stem)
 34+{
 35+	const char *p;
 36+	size_t pre, suf, stemlen, slen;
 37+	char *out;
 38+
 39+	p = strchr(s, '%');
 40+	if (!p)
 41+		return xstrdup(s);
 42+	pre = (size_t)(p - s);
 43+	slen = strlen(s);
 44+	suf = slen - pre - 1;
 45+	stemlen = strlen(stem);
 46+	out = xmalloc(pre + stemlen + suf + 1);
 47+	memcpy(out, s, pre);
 48+	memcpy(out + pre, stem, stemlen);
 49+	memcpy(out + pre + stemlen, p + 1, suf);
 50+	out[pre + stemlen + suf] = 0;
 51+	return out;
 52+}
 53+
 54+static char *
 55+expandstem(const char *s, const char *stem)
 56+{
 57+	size_t i, n, cap, len;
 58+	char *out;
 59+
 60+	n = strlen(s);
 61+	cap = n + 1;
 62+	len = 0;
 63+	out = xmalloc(cap);
 64+	for (i = 0; i < n; i++) {
 65+		if (s[i] == '$' && i + 1 < n && s[i + 1] == '*') {
 66+			size_t slen;
 67+
 68+			slen = stem ? strlen(stem) : 0;
 69+			if (len + slen + 1 > cap) {
 70+				cap = len + slen + (n - i) + 1;
 71+				out = xrealloc(out, cap);
 72+			}
 73+			if (slen) {
 74+				memcpy(out + len, stem, slen);
 75+				len += slen;
 76+			}
 77+			i++;
 78+			continue;
 79+		}
 80+		if (len + 2 > cap) {
 81+			cap *= 2;
 82+			out = xrealloc(out, cap);
 83+		}
 84+		out[len++] = s[i];
 85+	}
 86+	out[len] = 0;
 87+	return out;
 88+}
 89+
 90+int
 91+ispat(const char *s)
 92+{
 93+	return strchr(s, '%') != 0;
 94+}
 95+
 96+int
 97+patmatches(const char *pat, const char *name)
 98+{
 99+	char *stem;
100+
101+	stem = matchpat(pat, name);
102+	if (!stem)
103+		return 0;
104+	free(stem);
105+	return 1;
106+}
107+
108+void
109+collectpat(struct PatRules *rules, const struct RuleNode *rule)
110+{
111+	size_t i;
112+
113+	for (i = 0; i < rule->targets.n; i++) {
114+		struct PatRule *p;
115+
116+		if (!ispat(rule->targets.v[i]))
117+			continue;
118+		rules->v = xrealloc(rules->v, (rules->n + 1) * sizeof(rules->v[0]));
119+		p = &rules->v[rules->n++];
120+		memset(p, 0, sizeof(*p));
121+		p->target = xstrdup(rule->targets.v[i]);
122+		addwords(&p->prereqs, &rule->prereqs);
123+		addwords(&p->order_only, &rule->order_only);
124+		addrecipes(&p->recipes, &rule->recipes);
125+	}
126+}
127+
128+int
129+instpatrule(const struct PatRules *rules, struct Target *t, struct Env *env)
130+{
131+	size_t i, j;
132+
133+	for (i = 0; i < rules->n; i++) {
134+		char *stem;
135+
136+		stem = matchpat(rules->v[i].target, t->name);
137+		if (!stem)
138+			continue;
139+		for (j = 0; j < rules->v[i].prereqs.n; j++) {
140+			char *s;
141+
142+			s = applystem(rules->v[i].prereqs.v[j], stem);
143+			t->prereqs.v = xrealloc(t->prereqs.v, (t->prereqs.n + 1) * sizeof(t->prereqs.v[0]));
144+			t->prereqs.v[t->prereqs.n++] = s;
145+		}
146+		for (j = 0; j < rules->v[i].order_only.n; j++) {
147+			char *s;
148+
149+			s = applystem(rules->v[i].order_only.v[j], stem);
150+			t->order_only.v = xrealloc(t->order_only.v,
151+			                           (t->order_only.n + 1) * sizeof(t->order_only.v[0]));
152+			t->order_only.v[t->order_only.n++] = s;
153+		}
154+		for (j = 0; j < rules->v[i].recipes.n; j++) {
155+			char *s, *vars, *exp;
156+
157+			s = applystem(rules->v[i].recipes.v[j], stem);
158+			vars = expandstr(env, s);
159+			exp = expandstem(vars, stem);
160+			free(vars);
161+			free(s);
162+			if (!exp[0]) {
163+				free(exp);
164+				continue;
165+			}
166+			t->recipes.v = xrealloc(t->recipes.v, (t->recipes.n + 1) * sizeof(t->recipes.v[0]));
167+			t->recipes.v[t->recipes.n++] = exp;
168+		}
169+		free(stem);
170+		return 1;
171+	}
172+	return 0;
173+}
174+
175+void
176+freepatrule(struct PatRules *rules)
177+{
178+	size_t i;
179+
180+	for (i = 0; i < rules->n; i++) {
181+		free(rules->v[i].target);
182+		freestrs(&rules->v[i].prereqs);
183+		freestrs(&rules->v[i].order_only);
184+		freerecipes(&rules->v[i].recipes);
185+	}
186+	free(rules->v);
187+	rules->v = 0;
188+	rules->n = 0;
189+}
+24, -0
 1@@ -0,0 +1,24 @@
 2+#ifndef GNU_PATTERN_H
 3+#define GNU_PATTERN_H
 4+
 5+#include "internal.h"
 6+
 7+struct PatRule {
 8+	char *target;
 9+	struct StrList prereqs;
10+	struct StrList order_only;
11+	struct RecipeList recipes;
12+};
13+
14+struct PatRules {
15+	struct PatRule *v;
16+	size_t n;
17+};
18+
19+int ispat(const char *s);
20+int patmatches(const char *pat, const char *name);
21+void collectpat(struct PatRules *rules, const struct RuleNode *rule);
22+int instpatrule(const struct PatRules *rules, struct Target *t, struct Env *env);
23+void freepatrule(struct PatRules *rules);
24+
25+#endif
+48, -402
  1@@ -1,58 +1,13 @@
  2 #include "shinobi.h"
  3 #include "internal.h"
  4+#include "posix.h"
  5+#include "gnu/pattern.h"
  6 
  7 #include <stdlib.h>
  8 #include <string.h>
  9 
 10 /* graph builder */
 11 
 12-static struct Target *
 13-findtarget(struct Graph *graph, const char *name)
 14-{
 15-	size_t i;
 16-
 17-	for (i = 0; i < graph->n; i++) {
 18-		if (strcmp(graph->v[i].name, name) == 0)
 19-			return &graph->v[i];
 20-	}
 21-	return 0;
 22-}
 23-
 24-static void
 25-addwords(struct StrList *dest, const struct StrList *src)
 26-{
 27-	size_t i;
 28-
 29-	for (i = 0; i < src->n; i++) {
 30-		dest->v = xrealloc(dest->v, (dest->n + 1) * sizeof(dest->v[0]));
 31-		dest->v[dest->n++] = xstrdup(src->v[i]);
 32-	}
 33-}
 34-
 35-static void
 36-addrecipes(struct RecipeList *dest, const struct RecipeList *src)
 37-{
 38-	size_t i;
 39-
 40-	for (i = 0; i < src->n; i++) {
 41-		dest->v = xrealloc(dest->v, (dest->n + 1) * sizeof(dest->v[0]));
 42-		dest->v[dest->n++] = xstrdup(src->v[i]);
 43-	}
 44-}
 45-
 46-struct Pattern {
 47-	char *target;
 48-	struct StrList prereqs;
 49-	struct StrList order_only;
 50-	struct RecipeList recipes;
 51-};
 52-
 53-struct Suffix {
 54-	char *from;
 55-	char *to;
 56-	struct RecipeList recipes;
 57-};
 58-
 59 struct TAssign {
 60 	struct StrList targets;
 61 	char *lhs;
 62@@ -65,10 +20,8 @@ struct GraphState {
 63 	struct Env env;
 64 	const struct RuleNode **rules;
 65 	size_t nrules;
 66-	struct Pattern *pats;
 67-	size_t npats;
 68-	struct Suffix *sufs;
 69-	size_t nsufs;
 70+	struct PatRules patterns;
 71+	struct SufRules sufs;
 72 	struct TAssign *tas;
 73 	size_t ntas;
 74 };
 75@@ -80,142 +33,12 @@ collectrule(struct GraphState *gs, const struct RuleNode *rule)
 76 	gs->rules[gs->nrules++] = rule;
 77 }
 78 
 79-static char *
 80-matchpat(const char *pat, const char *name)
 81-{
 82-	const char *p;
 83-	size_t pre, suf, n, plen;
 84-
 85-	p = strchr(pat, '%');
 86-	if (!p)
 87-		return 0;
 88-	pre = (size_t)(p - pat);
 89-	suf = strlen(p + 1);
 90-	n = strlen(name);
 91-	plen = strlen(pat);
 92-
 93-	if (n < plen - 1)
 94-		return 0;
 95-	if (memcmp(pat, name, pre) != 0)
 96-		return 0;
 97-	if (memcmp(p + 1, name + n - suf, suf) != 0)
 98-		return 0;
 99-	return xstrndup(name + pre, n - pre - suf);
100-}
101-
102-static int
103-matchsuf(const char *suf, const char *name, char **stem)
104-{
105-	size_t nsuf, nname;
106-
107-	nsuf = strlen(suf);
108-	nname = strlen(name);
109-	if (nname < nsuf || strcmp(name + nname - nsuf, suf) != 0)
110-		return 0;
111-	*stem = xstrndup(name, nname - nsuf);
112-	return 1;
113-}
114-
115 static int
116 targetmatches(const char *pat, const char *name)
117 {
118-	char *stem;
119-
120-	if (!strchr(pat, '%'))
121+	if (!ispat(pat))
122 		return strcmp(pat, name) == 0;
123-	stem = matchpat(pat, name);
124-	if (!stem)
125-		return 0;
126-	free(stem);
127-	return 1;
128-}
129-
130-static char *
131-applystem(const char *s, const char *stem)
132-{
133-	const char *p;
134-	size_t pre, suf, stemlen, slen;
135-	char *out;
136-
137-	p = strchr(s, '%');
138-	if (!p)
139-		return xstrdup(s);
140-	pre = (size_t)(p - s);
141-	slen = strlen(s);
142-	suf = slen - pre - 1;
143-	stemlen = strlen(stem);
144-	out = xmalloc(pre + stemlen + suf + 1);
145-	memcpy(out, s, pre);
146-	memcpy(out + pre, stem, stemlen);
147-	memcpy(out + pre + stemlen, p + 1, suf);
148-	out[pre + stemlen + suf] = 0;
149-	return out;
150-}
151-
152-static int
153-issuffix(const char *s, char **from, char **to)
154-{
155-	const char *mid;
156-
157-	if (!s || s[0] != '.')
158-		return 0;
159-	mid = strchr(s + 1, '.');
160-	if (!mid || mid == s + 1 || !mid[1] || strchr(mid + 1, '.'))
161-		return 0;
162-	*from = xstrndup(s, (size_t)(mid - s));
163-	*to = xstrdup(mid);
164-	return 1;
165-}
166-
167-static char *
168-joinprereqs(const struct Target *t)
169-{
170-	size_t k;
171-	char *joined, *next;
172-
173-	joined = xstrdup("");
174-	for (k = 0; k < t->prereqs.n; k++) {
175-		next = joined[0] ? cat3(joined, " ", t->prereqs.v[k]) : xstrdup(t->prereqs.v[k]);
176-		free(joined);
177-		joined = next;
178-	}
179-	return joined;
180-}
181-
182-static char *
183-expandstem(const char *s, const char *stem)
184-{
185-	size_t i, n, cap, len;
186-	char *out;
187-
188-	n = strlen(s);
189-	cap = n + 1;
190-	len = 0;
191-	out = xmalloc(cap);
192-	for (i = 0; i < n; i++) {
193-		if (s[i] == '$' && i + 1 < n && s[i + 1] == '*') {
194-			size_t slen;
195-
196-			slen = stem ? strlen(stem) : 0;
197-			if (len + slen + 1 > cap) {
198-				cap = len + slen + (n - i) + 1;
199-				out = xrealloc(out, cap);
200-			}
201-			if (slen) {
202-				memcpy(out + len, stem, slen);
203-				len += slen;
204-			}
205-			i++;
206-			continue;
207-		}
208-		if (len + 2 > cap) {
209-			cap *= 2;
210-			out = xrealloc(out, cap);
211-		}
212-		out[len++] = s[i];
213-	}
214-	out[len] = 0;
215-	return out;
216+	return patmatches(pat, name);
217 }
218 
219 static void
220@@ -236,81 +59,6 @@ addexprecipes(struct RecipeList *dest, const struct RecipeList *src, struct Env
221 	}
222 }
223 
224-/*
225- * expand gnu make automatic variables
226- *   $@  target
227- *   $<  first prereq
228- *   $^  all prereqs
229- *   $+  all prereqs
230- *   $?  all prereqs, let ninja decide
231- *   $*  stem
232- *   $$  literal $
233- */
234-static char *
235-expandauto(const char *s, const struct Target *t, const char *stem)
236-{
237-	size_t i, n, cap, len;
238-	char *out;
239-
240-	n = strlen(s);
241-	cap = n + 1;
242-	len = 0;
243-	out = xmalloc(cap);
244-	for (i = 0; i < n; i++) {
245-		if (s[i] == '$' && i + 1 < n) {
246-			const char *val;
247-			int mustfree;
248-
249-			val = 0;
250-			mustfree = 0;
251-
252-			if (s[i + 1] == '$') {
253-				if (len + 3 > cap) {
254-					cap = len + 3 + (n - i);
255-					out = xrealloc(out, cap);
256-				}
257-				out[len++] = '$';
258-				out[len++] = '$';
259-				i++;
260-				continue;
261-			}
262-			if (s[i + 1] == '@') {
263-				val = t->name;
264-			} else if (s[i + 1] == '<') {
265-				if (t->prereqs.n > 0)
266-					val = t->prereqs.v[0];
267-				else
268-					val = "";
269-			} else if (s[i + 1] == '^' || s[i + 1] == '+' || s[i + 1] == '?') {
270-				val = joinprereqs(t);
271-				mustfree = 1;
272-			} else if (s[i + 1] == '*') {
273-				val = stem ? stem : "";
274-			}
275-
276-			if (val) {
277-				size_t vlen = strlen(val);
278-				if (len + vlen + 1 > cap) {
279-					cap = len + vlen + (n - i) + 1;
280-					out = xrealloc(out, cap);
281-				}
282-				memcpy(out + len, val, vlen);
283-				len += vlen;
284-				if (mustfree)
285-					free((char *)val);
286-				i++;
287-				continue;
288-			}
289-		}
290-		if (len + 2 > cap) {
291-			cap *= 2;
292-			out = xrealloc(out, cap);
293-		}
294-		out[len++] = s[i];
295-	}
296-	out[len] = 0;
297-	return out;
298-}
299 /* we apply target assignments from the graphstate (gs.tas) to those specific
300  * targets, for some target like
301  *
302@@ -343,6 +91,10 @@ targetenv(struct GraphState *gs, struct Env *env, const char *name)
303 	applytassigns(gs, env, name);
304 }
305 
306+
307+/* add an explicit target rule to the graph, and add all 
308+ * non-pattern prereqs also as placeholder nodes. later 
309+ * we discorver how to build them if they need to be built */ 
310 static void
311 addrule(struct GraphState *gs, const char *name, const struct RuleNode *rule)
312 {
313@@ -374,52 +126,6 @@ addrule(struct GraphState *gs, const char *name, const struct RuleNode *rule)
314 		}
315 	}
316 }
317-/* if the target is a pattern rule, store it in the graphstate
318- * instead of making a target immediately */
319-static void
320-addpattern(struct GraphState *gs, const struct RuleNode *rule)
321-{
322-	size_t i;
323-
324-	for (i = 0; i < rule->targets.n; i++) {
325-		if (strchr(rule->targets.v[i], '%')) {
326-			struct Pattern *p;
327-
328-			gs->pats = xrealloc(gs->pats, (gs->npats + 1) * sizeof(gs->pats[0]));
329-			p = &gs->pats[gs->npats++];
330-			memset(p, 0, sizeof(*p));
331-			p->target = xstrdup(rule->targets.v[i]);
332-			addwords(&p->prereqs, &rule->prereqs);
333-			addwords(&p->order_only, &rule->order_only);
334-			addrecipes(&p->recipes, &rule->recipes);
335-		} else {
336-			addrule(gs, rule->targets.v[i], rule);
337-		}
338-	}
339-}
340-
341-static void
342-addsuffix(struct GraphState *gs, const struct RuleNode *rule)
343-{
344-	char *from, *to;
345-	struct Suffix *sr;
346-
347-	if (rule->targets.n != 1 || rule->prereqs.n != 0 || rule->order_only.n != 0) {
348-		addpattern(gs, rule);
349-		return;
350-	}
351-	if (!issuffix(rule->targets.v[0], &from, &to)) {
352-		addpattern(gs, rule);
353-		return;
354-	}
355-	gs->sufs = xrealloc(gs->sufs, (gs->nsufs + 1) * sizeof(gs->sufs[0]));
356-	sr = &gs->sufs[gs->nsufs++];
357-	memset(sr, 0, sizeof(*sr));
358-	sr->from = from;
359-	sr->to = to;
360-	addrecipes(&sr->recipes, &rule->recipes);
361-}
362-
363 static void
364 addtassign(struct GraphState *gs, const struct AssignNode *assign)
365 {
366@@ -434,76 +140,10 @@ addtassign(struct GraphState *gs, const struct AssignNode *assign)
367 	ta->op = assign->op;
368 }
369 
370-static void
371-instantiate(struct GraphState *gs, struct Target *t, struct Pattern *p, const char *stem)
372-{
373-	size_t i;
374-	struct Env env;
375-
376-	for (i = 0; i < p->prereqs.n; i++) {
377-		char *s = applystem(p->prereqs.v[i], stem);
378-		t->prereqs.v = xrealloc(t->prereqs.v, (t->prereqs.n + 1) * sizeof(t->prereqs.v[0]));
379-		t->prereqs.v[t->prereqs.n++] = s;
380-	}
381-	for (i = 0; i < p->order_only.n; i++) {
382-		char *s = applystem(p->order_only.v[i], stem);
383-		t->order_only.v = xrealloc(t->order_only.v, (t->order_only.n + 1) * sizeof(t->order_only.v[0]));
384-		t->order_only.v[t->order_only.n++] = s;
385-	}
386-	targetenv(gs, &env, t->name);
387-	for (i = 0; i < p->recipes.n; i++) {
388-		char *s, *vars, *exp;
389-
390-		s = applystem(p->recipes.v[i], stem);
391-		vars = expandstr(&env, s);
392-		exp = expandstem(vars, stem);
393-		free(vars);
394-		free(s);
395-		if (!exp[0]) {
396-			free(exp);
397-			continue;
398-		}
399-		t->recipes.v = xrealloc(t->recipes.v, (t->recipes.n + 1) * sizeof(t->recipes.v[0]));
400-		t->recipes.v[t->recipes.n++] = exp;
401-	}
402-	freeenv(&env);
403-}
404-
405-static int
406-instantiatesuffix(struct GraphState *gs, struct Target *t)
407-{
408-	size_t i, k;
409-
410-	for (i = 0; i < gs->nsufs; i++) {
411-		char *stem, *src;
412-		struct Env env;
413-
414-		if (!matchsuf(gs->sufs[i].to, t->name, &stem))
415-			continue;
416-		src = cat3(stem, "", gs->sufs[i].from);
417-		t->prereqs.v = xrealloc(t->prereqs.v, (t->prereqs.n + 1) * sizeof(t->prereqs.v[0]));
418-		t->prereqs.v[t->prereqs.n++] = src;
419-		targetenv(gs, &env, t->name);
420-		for (k = 0; k < gs->sufs[i].recipes.n; k++) {
421-			char *vars, *exp;
422-
423-			vars = expandstr(&env, gs->sufs[i].recipes.v[k]);
424-			exp = expandauto(vars, t, stem);
425-			free(vars);
426-			if (!exp[0]) {
427-				free(exp);
428-				continue;
429-			}
430-			t->recipes.v = xrealloc(t->recipes.v, (t->recipes.n + 1) * sizeof(t->recipes.v[0]));
431-			t->recipes.v[t->recipes.n++] = exp;
432-		}
433-		freeenv(&env);
434-		free(stem);
435-		return 1;
436-	}
437-	return 0;
438-}
439-
440+/*generate the graph from the evaluated ast
441+ * get the env and target specific assignments,
442+ * then expand placeholder targets until all prereqs 
443+ * we can find are in the graph. */
444 int
445 buildgraph(const struct Ast *ast, struct Graph *graph)
446 {
447@@ -515,6 +155,8 @@ buildgraph(const struct Ast *ast, struct Graph *graph)
448 	gs.graph = graph;
449 	seedenv(&gs.env);
450 
451+	/* get the state, env vars in gs.env, target-specific var in gs.tas,
452+	 * and rules in gs.rules */
453 	for (i = 0; i < ast->n; i++) {
454 		if (ast->v[i].kind == NODE_ASSIGN) {
455 			if (ast->v[i].data.assign.tspec)
456@@ -526,59 +168,63 @@ buildgraph(const struct Ast *ast, struct Graph *graph)
457 		}
458 	}
459 
460-	for (i = 0; i < gs.nrules; i++)
461-		addsuffix(&gs, gs.rules[i]);
462+	for (i = 0; i < gs.nrules; i++) {
463+		size_t k;
464+
465+		if (collectsufrule(&gs.sufs, gs.rules[i]))
466+			continue;
467+		collectpat(&gs.patterns, gs.rules[i]);
468+		for (k = 0; k < gs.rules[i]->targets.n; k++) {
469+			if (!ispat(gs.rules[i]->targets.v[k]))
470+				addrule(&gs, gs.rules[i]->targets.v[k], gs.rules[i]);
471+		}
472+	}
473 
474 	start = 0;
475 	while (start < graph->n) {
476 		size_t current_n = graph->n;
477 		for (i = start; i < current_n; i++) {
478 			struct Target *t = &graph->v[i];
479-			if (t->recipes.n == 0) {
480-				for (j = 0; j < gs.npats; j++) {
481-					char *stem = matchpat(gs.pats[j].target, t->name);
482-					if (stem) {
483-						instantiate(&gs, t, &gs.pats[j], stem);
484-						free(stem);
485-						break;
486-					}
487+			size_t nprereqs;
488+			/* for placeholder targets with no recipe or prereqs,
489+			 * try a pattern rule or suffix rule */
490+			if (t->recipes.n == 0 && t->prereqs.n == 0 && t->order_only.n == 0) {
491+				int matched;
492+				struct Env env;
493+
494+				targetenv(&gs, &env, t->name);
495+				instpatrule(&gs.patterns, t, &env);
496+				matched = t->recipes.n > 0;
497+				if (!matched) {
498+					instsufrule(&gs.sufs, graph, t, &env);
499 				}
500-				if (t->recipes.n == 0)
501-					instantiatesuffix(&gs, t);
502+				freeenv(&env);
503 			}
504-			for (j = 0; j < t->prereqs.n; j++) {
505-				if (!findtarget(graph, t->prereqs.v[j])) {
506+			nprereqs = t->prereqs.n;
507+			for (j = 0; j < nprereqs; j++) {
508+				const char *prereq = t->prereqs.v[j];
509+
510+				if (!findtarget(graph, prereq)) {
511 					struct Target *nt;
512 
513 					graph->v = xrealloc(graph->v, (graph->n + 1) * sizeof(graph->v[0]));
514 					nt = &graph->v[graph->n++];
515 					memset(nt, 0, sizeof(*nt));
516-					nt->name = xstrdup(t->prereqs.v[j]);
517+					nt->name = xstrdup(prereq);
518 				}
519 			}
520 		}
521 		start = current_n;
522 	}
523 
524-	for (i = 0; i < gs.npats; i++) {
525-		free(gs.pats[i].target);
526-		freestrs(&gs.pats[i].prereqs);
527-		freestrs(&gs.pats[i].order_only);
528-		freerecipes(&gs.pats[i].recipes);
529-	}
530-	for (i = 0; i < gs.nsufs; i++) {
531-		free(gs.sufs[i].from);
532-		free(gs.sufs[i].to);
533-		freerecipes(&gs.sufs[i].recipes);
534-	}
535 	for (i = 0; i < gs.ntas; i++) {
536 		freestrs(&gs.tas[i].targets);
537 		free(gs.tas[i].lhs);
538 		free(gs.tas[i].rhs);
539 	}
540 	free(gs.tas);
541-	free(gs.pats);
542-	free(gs.sufs);
543+	freepatrule(&gs.patterns);
544+	freesufrules(&gs.sufs);
545 	free(gs.rules);
546 	freeenv(&gs.env);
547 
+5, -0
 1@@ -21,8 +21,13 @@ char *xstrdup(const char *s);
 2 void addnode(struct NodeList *list, struct Node node);
 3 void splitwords(struct StrList *out, const char *s, size_t n);
 4 char *cat3(const char *a, const char *b, const char *c);
 5+void addwords(struct StrList *dest, const struct StrList *src);
 6+void addrecipes(struct RecipeList *dest, const struct RecipeList *src);
 7 void freestrs(struct StrList *list);
 8 void freerecipes(struct RecipeList *list);
 9+void envsetvar(struct Env *env, const char *name, char *val, int simple);
10+struct Target *findtarget(struct Graph *graph, const char *name);
11+const struct Target *findctarget(const struct Graph *graph, const char *name);
12 
13 struct Var *findvar(struct Env *env, const char *name);
14 char *expandstr(struct Env *env, const char *s);
+285, -0
  1@@ -0,0 +1,285 @@
  2+#include "posix.h"
  3+
  4+#include <unistd.h>
  5+#include <stdlib.h>
  6+#include <string.h>
  7+
  8+static int
  9+pathorargetexists(const struct Graph *graph, const char *name)
 10+{
 11+	size_t i;
 12+
 13+	for (i = 0; i < graph->n; i++) {
 14+		if (strcmp(graph->v[i].name, name) == 0)
 15+			return 1;
 16+	}
 17+	return access(name, F_OK) == 0;
 18+}
 19+
 20+static int
 21+issuf(const char *s, char **from, char **to)
 22+{
 23+	const char *mid;
 24+
 25+	if (!s || s[0] != '.')
 26+		return 0;
 27+	mid = strchr(s + 1, '.');
 28+	if (!mid || mid == s + 1 || !mid[1] || strchr(mid + 1, '.'))
 29+		return 0;
 30+	*from = xstrndup(s, (size_t)(mid - s));
 31+	*to = xstrdup(mid);
 32+	return 1;
 33+}
 34+
 35+static int
 36+issinglesuf(const char *s, char **from)
 37+{
 38+	if (!s || s[0] != '.')
 39+		return 0;
 40+	if (!s[1] || strchr(s + 1, '.'))
 41+		return 0;
 42+	*from = xstrdup(s);
 43+	return 1;
 44+}
 45+
 46+static int
 47+matchsuf(const char *suf, const char *name, char **stem)
 48+{
 49+	size_t nsuf, nname;
 50+
 51+	nsuf = strlen(suf);
 52+	nname = strlen(name);
 53+	if (nname < nsuf || strcmp(name + nname - nsuf, suf) != 0)
 54+		return 0;
 55+	*stem = xstrndup(name, nname - nsuf);
 56+	return 1;
 57+}
 58+
 59+static char *
 60+joinprereqs(const struct Target *t)
 61+{
 62+	size_t i;
 63+	char *joined, *next;
 64+
 65+	joined = xstrdup("");
 66+	for (i = 0; i < t->prereqs.n; i++) {
 67+		next = joined[0] ? cat3(joined, " ", t->prereqs.v[i]) : xstrdup(t->prereqs.v[i]);
 68+		free(joined);
 69+		joined = next;
 70+	}
 71+	return joined;
 72+}
 73+
 74+
 75+/*
 76+ * expand automatic variables like so:
 77+ *   $@  target
 78+ *   $<  first prereq
 79+ *   $^  all prereqs
 80+ *   $+  all prereqs
 81+ *   $?  all prereqs, let ninja decide
 82+ *   $*  stem
 83+ *   $$  literal $
 84+ *
 85+ *  this turns them into concrete strings,
 86+ *  later during ninja generation
 87+ *  translateauto() turns any remaining ones
 88+ *  into ninja variables if possible
 89+ */
 90+static char *
 91+expandauto(const char *s, const struct Target *t, const char *stem)
 92+{
 93+	size_t i, n, cap, len;
 94+	char *out;
 95+
 96+	n = strlen(s);
 97+	cap = n + 1;
 98+	len = 0;
 99+	out = xmalloc(cap);
100+	for (i = 0; i < n; i++) {
101+		if (s[i] == '$' && i + 1 < n) {
102+			const char *val;
103+			int mustfree;
104+
105+			val = 0;
106+			mustfree = 0;
107+			if (s[i + 1] == '$') {
108+				if (len + 3 > cap) {
109+					cap = len + 3 + (n - i);
110+					out = xrealloc(out, cap);
111+				}
112+				out[len++] = '$';
113+				out[len++] = '$';
114+				i++;
115+				continue;
116+			}
117+			if (s[i + 1] == '@') {
118+				val = t->name;
119+			} else if (s[i + 1] == '<') {
120+				val = t->prereqs.n > 0 ? t->prereqs.v[0] : "";
121+			} else if (s[i + 1] == '^' || s[i + 1] == '+' || s[i + 1] == '?') {
122+				val = joinprereqs(t);
123+				mustfree = 1;
124+			} else if (s[i + 1] == '*') {
125+				val = stem ? stem : "";
126+			}
127+			if (val) {
128+				size_t vlen = strlen(val);
129+				if (len + vlen + 1 > cap) {
130+					cap = len + vlen + (n - i) + 1;
131+					out = xrealloc(out, cap);
132+				}
133+				memcpy(out + len, val, vlen);
134+				len += vlen;
135+				if (mustfree)
136+					free((char *)val);
137+				i++;
138+				continue;
139+			}
140+		}
141+		if (len + 2 > cap) {
142+			cap *= 2;
143+			out = xrealloc(out, cap);
144+		}
145+		out[len++] = s[i];
146+	}
147+	out[len] = 0;
148+	return out;
149+}
150+
151+void
152+seedenv(struct Env *env)
153+{
154+	envsetvar(env, "CC", xstrdup("cc"), 1);
155+	envsetvar(env, "CFLAGS", xstrdup(""), 1);
156+	envsetvar(env, "CXX", xstrdup("c++"), 1);
157+	envsetvar(env, "CPP", xstrdup("$(CC) -E"), 0);
158+	envsetvar(env, "AR", xstrdup("ar"), 1);
159+	envsetvar(env, "ARFLAGS", xstrdup("-rv"), 1);
160+	envsetvar(env, "AS", xstrdup("as"), 1);
161+	envsetvar(env, "GET", xstrdup("get"), 1);
162+	envsetvar(env, "GFLAGS", xstrdup(""), 1);
163+	envsetvar(env, "LD", xstrdup("ld"), 1);
164+	envsetvar(env, "LDFLAGS", xstrdup(""), 1);
165+	envsetvar(env, "LEX", xstrdup("lex"), 1);
166+	envsetvar(env, "LFLAGS", xstrdup(""), 1);
167+	envsetvar(env, "SCCSFLAGS", xstrdup(""), 1);
168+	envsetvar(env, "SCCSGETFLAGS", xstrdup("-s"), 1);
169+	envsetvar(env, "YACC", xstrdup("yacc"), 1);
170+	envsetvar(env, "YFLAGS", xstrdup(""), 1);
171+	envsetvar(env, "SHELL", xstrdup("/bin/sh"), 1);
172+}
173+
174+int
175+collectsufrule(struct SufRules *rules, const struct RuleNode *rule)
176+{
177+	char *from, *to;
178+
179+	if (rule->targets.n != 1 || rule->prereqs.n != 0 || rule->order_only.n != 0)
180+		return 0;
181+	if (issinglesuf(rule->targets.v[0], &from)) {
182+		struct SingleSufRule *sr;
183+
184+		rules->singlesuf = xrealloc(rules->singlesuf,
185+		                            (rules->nsinglesuf + 1) * sizeof(rules->singlesuf[0]));
186+		sr = &rules->singlesuf[rules->nsinglesuf++];
187+		memset(sr, 0, sizeof(*sr));
188+		sr->from = from;
189+		addrecipes(&sr->recipes, &rule->recipes);
190+		return 1;
191+	}
192+	if (!issuf(rule->targets.v[0], &from, &to))
193+		return 0;
194+	rules->sufs = xrealloc(rules->sufs, (rules->nsufs + 1) * sizeof(rules->sufs[0]));
195+	memset(&rules->sufs[rules->nsufs], 0, sizeof(rules->sufs[rules->nsufs]));
196+	rules->sufs[rules->nsufs].from = from;
197+	rules->sufs[rules->nsufs].to = to;
198+	addrecipes(&rules->sufs[rules->nsufs].recipes, &rule->recipes);
199+	rules->nsufs++;
200+	return 1;
201+}
202+
203+int
204+instsufrule(const struct SufRules *rules,
205+            const struct Graph *graph,
206+            struct Target *t,
207+            struct Env *env)
208+{
209+	size_t i, k;
210+
211+	for (i = 0; i < rules->nsufs; i++) {
212+		char *stem, *src;
213+
214+		if (!matchsuf(rules->sufs[i].to, t->name, &stem))
215+			continue;
216+		src = cat3(stem, "", rules->sufs[i].from);
217+		t->prereqs.v = xrealloc(t->prereqs.v, (t->prereqs.n + 1) * sizeof(t->prereqs.v[0]));
218+		t->prereqs.v[t->prereqs.n++] = src;
219+		for (k = 0; k < rules->sufs[i].recipes.n; k++) {
220+			char *vars, *exp;
221+
222+			vars = expandstr(env, rules->sufs[i].recipes.v[k]);
223+			exp = expandauto(vars, t, stem);
224+			free(vars);
225+			if (!exp[0]) {
226+				free(exp);
227+				continue;
228+			}
229+			t->recipes.v = xrealloc(t->recipes.v, (t->recipes.n + 1) * sizeof(t->recipes.v[0]));
230+			t->recipes.v[t->recipes.n++] = exp;
231+		}
232+		free(stem);
233+		return 1;
234+	}
235+
236+	if (strchr(t->name, '.'))
237+		return 0;
238+	for (i = 0; i < rules->nsinglesuf; i++) {
239+		char *src;
240+
241+		src = cat3(t->name, "", rules->singlesuf[i].from);
242+		if (!pathorargetexists(graph, src)) {
243+			free(src);
244+			continue;
245+		}
246+		t->prereqs.v = xrealloc(t->prereqs.v, (t->prereqs.n + 1) * sizeof(t->prereqs.v[0]));
247+		t->prereqs.v[t->prereqs.n++] = src;
248+		for (k = 0; k < rules->singlesuf[i].recipes.n; k++) {
249+			char *vars, *exp;
250+
251+			vars = expandstr(env, rules->singlesuf[i].recipes.v[k]);
252+			exp = expandauto(vars, t, t->name);
253+			free(vars);
254+			if (!exp[0]) {
255+				free(exp);
256+				continue;
257+			}
258+			t->recipes.v = xrealloc(t->recipes.v, (t->recipes.n + 1) * sizeof(t->recipes.v[0]));
259+			t->recipes.v[t->recipes.n++] = exp;
260+		}
261+		return 1;
262+	}
263+	return 0;
264+}
265+
266+void
267+freesufrules(struct SufRules *rules)
268+{
269+	size_t i;
270+
271+	for (i = 0; i < rules->nsinglesuf; i++) {
272+		free(rules->singlesuf[i].from);
273+		freerecipes(&rules->singlesuf[i].recipes);
274+	}
275+	for (i = 0; i < rules->nsufs; i++) {
276+		free(rules->sufs[i].from);
277+		free(rules->sufs[i].to);
278+		freerecipes(&rules->sufs[i].recipes);
279+	}
280+	free(rules->singlesuf);
281+	free(rules->sufs);
282+	rules->singlesuf = 0;
283+	rules->nsinglesuf = 0;
284+	rules->sufs = 0;
285+	rules->nsufs = 0;
286+}
+38, -0
 1@@ -0,0 +1,38 @@
 2+#ifndef POSIX_H
 3+#define POSIX_H
 4+
 5+#include "shinobi.h"
 6+#include "internal.h"
 7+
 8+/* https://pubs.opengroup.org/onlinepubs/9799919799/ */
 9+
10+/* single suffix rule like .sh */
11+struct SingleSufRule {
12+	char *from;
13+	struct RecipeList recipes;
14+};
15+
16+/* normal suffix like .c.o */
17+struct SufRule {
18+	char *from;
19+	char *to;
20+	struct RecipeList recipes;
21+};
22+
23+/*holds all stored suffix rules*/
24+struct SufRules {
25+	struct SingleSufRule *singlesuf;
26+	size_t nsinglesuf;
27+	struct SufRule *sufs;
28+	size_t nsufs;
29+};
30+
31+void seedenv(struct Env *env);
32+int collectsufrule(struct SufRules *rules, const struct RuleNode *rule);
33+int instsufrule(const struct SufRules *rules,
34+                const struct Graph *graph,
35+                struct Target *t,
36+                struct Env *env);
37+void freesufrules(struct SufRules *rules);
38+
39+#endif
+65, -0
 1@@ -73,6 +73,52 @@ addnode(struct NodeList *list, struct Node node)
 2 	list->v[list->n++] = node;
 3 }
 4 
 5+void
 6+addwords(struct StrList *dest, const struct StrList *src)
 7+{
 8+	size_t i;
 9+
10+	for (i = 0; i < src->n; i++) {
11+		dest->v = xrealloc(dest->v, (dest->n + 1) * sizeof(dest->v[0]));
12+		dest->v[dest->n++] = xstrdup(src->v[i]);
13+	}
14+}
15+
16+void
17+addrecipes(struct RecipeList *dest, const struct RecipeList *src)
18+{
19+	size_t i;
20+
21+	for (i = 0; i < src->n; i++) {
22+		dest->v = xrealloc(dest->v, (dest->n + 1) * sizeof(dest->v[0]));
23+		dest->v[dest->n++] = xstrdup(src->v[i]);
24+	}
25+}
26+
27+static const struct Target *
28+findtarget0(const struct Graph *graph, const char *name)
29+{
30+	size_t i;
31+
32+	for (i = 0; i < graph->n; i++) {
33+		if (strcmp(graph->v[i].name, name) == 0)
34+			return &graph->v[i];
35+	}
36+	return 0;
37+}
38+
39+struct Target *
40+findtarget(struct Graph *graph, const char *name)
41+{
42+	return (struct Target *)findtarget0(graph, name);
43+}
44+
45+const struct Target *
46+findctarget(const struct Graph *graph, const char *name)
47+{
48+	return findtarget0(graph, name);
49+}
50+
51 void
52 freestrs(struct StrList *list)
53 {
54@@ -100,3 +146,22 @@ freerecipes(struct RecipeList *list)
55 	list->v = 0;
56 	list->n = 0;
57 }
58+
59+void
60+envsetvar(struct Env *env, const char *name, char *val, int simple)
61+{
62+	struct Var *v;
63+
64+	v = findvar(env, name);
65+	if (v) {
66+		free(v->val);
67+		v->val = val;
68+		v->simple = simple;
69+		return;
70+	}
71+	env->v = xrealloc(env->v, (env->n + 1) * sizeof(env->v[0]));
72+	env->v[env->n].name = xstrdup(name);
73+	env->v[env->n].val = val;
74+	env->v[env->n].simple = simple;
75+	env->n++;
76+}