commit cde0ae1

shrub  ·  2026-04-12 22:05:49 +0000 UTC
parents 2d98333, 81a73c9
Merge branch 'xplshn-main'
8 files changed,  +368, -28
+15, -3
 1@@ -22,13 +22,25 @@ emitrule(FILE *fp, const char *cmd, int id)
 2 static char *
 3 cleanrecipe(const char *s)
 4 {
 5+	size_t n;
 6+
 7 	while (*s == ' ' || *s == '\t')
 8 		s++;
 9-	while (*s == '@' || *s == '+' || *s == '-')
10-		s++;
11+	while (*s == '@' || *s == '+' || *s == '-') {
12+		char next = s[1];
13+
14+		if (next == '@' || next == '+' || next == '-' ||
15+		    next == ' ' || next == '\t' || next == '\0')
16+			s++;
17+		else
18+			break;
19+	}
20 	while (*s == ' ' || *s == '\t')
21 		s++;
22-	return xstrdup(s);
23+	n = strlen(s);
24+	while (n > 0 && (s[n - 1] == ' ' || s[n - 1] == '\t'))
25+		n--;
26+	return xstrndup(s, n);
27 }
28 
29 static char *
+66, -15
  1@@ -259,11 +259,19 @@ expandsubstref(struct Env *env, const char *s, size_t colon, size_t eq, size_t n
  2 	char *name, *from, *to, *val;
  3 	struct Var *v;
  4 
  5-	name = xstrndup(s, colon);
  6-	from = xstrndup(s + colon + 1, eq - colon - 1);
  7-	to = xstrndup(s + eq + 1, n - eq - 1);
  8-	v = findvar(env, name);
  9-	free(name);
 10+	{
 11+		char *toraw, *fromraw;
 12+
 13+		name = xstrndup(s, colon);
 14+		fromraw = xstrndup(s + colon + 1, eq - colon - 1);
 15+		toraw = xstrndup(s + eq + 1, n - eq - 1);
 16+		from = expandstr(env, fromraw);
 17+		to = expandstr(env, toraw);
 18+		free(fromraw);
 19+		free(toraw);
 20+		v = findvar(env, name);
 21+		free(name);
 22+	}
 23 	if (v) {
 24 		char *base;
 25 
 26@@ -280,6 +288,7 @@ expandsubstref(struct Env *env, const char *s, size_t colon, size_t eq, size_t n
 27 
 28 typedef char *(*fn1_t)(const char *);
 29 typedef char *(*fn2_t)(const char *, const char *);
 30+typedef char *(*fn3_t)(const char *, const char *, const char *);
 31 
 32 struct func {
 33 	const char *name;
 34@@ -287,19 +296,26 @@ struct func {
 35 	union {
 36 		fn1_t f1;
 37 		fn2_t f2;
 38+		fn3_t f3;
 39 	} fn;
 40 };
 41 
 42 static const struct func funcs[] = {
 43-    {"wildcard", 1, {.f1 = fnwildcard}},
 44-    {"shell", 1, {.f1 = fnshell}},
 45-    {"sort", 1, {.f1 = fnsort}},
 46-    {"info", 1, {.f1 = fninfo}},
 47-    {"filter-out", 2, {.f2 = fnfilterout}},
 48-    {"filter", 2, {.f2 = fnfilter}},
 49-    {"addprefix", 2, {.f2 = fnaddprefix}},
 50-    {"addsuffix", 2, {.f2 = fnaddsuffix}},
 51-    {0, 0, {.f1 = 0}},
 52+	{"wildcard",   1, {.f1 = fnwildcard}},
 53+	{"shell",      1, {.f1 = fnshell}},
 54+	{"sort",       1, {.f1 = fnsort}},
 55+	{"info",       1, {.f1 = fninfo}},
 56+	{"notdir",     1, {.f1 = fnnotdir}},
 57+	{"dir",        1, {.f1 = fndir}},
 58+	{"basename",   1, {.f1 = fnbasename}},
 59+	{"filter-out", 2, {.f2 = fnfilterout}},
 60+	{"filter",     2, {.f2 = fnfilter}},
 61+	{"addprefix",  2, {.f2 = fnaddprefix}},
 62+	{"addsuffix",  2, {.f2 = fnaddsuffix}},
 63+	{"subst",      3, {.f3 = fnsubst}},
 64+	{"patsubst",   3, {.f3 = fnpatsubst}},
 65+	{"if",         3, {.f3 = fnif}},
 66+	{0, 0, {.f1 = 0}},
 67 };
 68 
 69 static char *
 70@@ -325,7 +341,7 @@ funcref(struct Env *env, const char *s, size_t n)
 71 			free(raw);
 72 			val = f->fn.f1(exp);
 73 			free(exp);
 74-		} else {
 75+		} else if (f->arity == 2) {
 76 			char *args, *lhs_raw, *rhs_raw, *lhs_exp, *rhs_exp, *detail;
 77 			ptrdiff_t comma;
 78 
 79@@ -348,6 +364,41 @@ funcref(struct Env *env, const char *s, size_t n)
 80 			val = f->fn.f2(lhs_exp, rhs_exp);
 81 			free(lhs_exp);
 82 			free(rhs_exp);
 83+		} else {
 84+			char *args, *a1r, *a2r, *a3r, *a1e, *a2e, *a3e, *detail;
 85+			ptrdiff_t c1, c2;
 86+			size_t rest;
 87+
 88+			args = xstrndup(s + start, n - start);
 89+			c1 = findargcomma(args, strlen(args));
 90+			if (c1 < 0) {
 91+				detail = cat3("$(", f->name, ")");
 92+				evalerr("malformed function arguments", detail);
 93+				free(detail);
 94+				free(args);
 95+				return xstrdup("");
 96+			}
 97+			a1r = xstrndup(args, (size_t)c1);
 98+			rest = (size_t)c1 + 1;
 99+			c2 = findargcomma(args + rest, strlen(args + rest));
100+			if (c2 < 0) {
101+				a2r = xstrdup(args + rest);
102+				a3r = xstrdup("");
103+			} else {
104+				a2r = xstrndup(args + rest, (size_t)c2);
105+				a3r = xstrdup(args + rest + (size_t)c2 + 1);
106+			}
107+			free(args);
108+			a1e = expandstr(env, a1r);
109+			a2e = expandstr(env, a2r);
110+			a3e = expandstr(env, a3r);
111+			free(a1r);
112+			free(a2r);
113+			free(a3r);
114+			val = f->fn.f3(a1e, a2e, a3e);
115+			free(a1e);
116+			free(a2e);
117+			free(a3e);
118 		}
119 		return val;
120 	}
+1, -1
 1@@ -147,7 +147,6 @@ evalnodes(const struct NodeList *in, struct NodeList *out, struct Env *env)
 2 {
 3 	size_t i;
 4 
 5-	memset(out, 0, sizeof(*out));
 6 	for (i = 0; i < in->n; i++) {
 7 		const struct Node *src;
 8 		struct Node node;
 9@@ -213,6 +212,7 @@ eval(const struct Ast *ast, struct Ast *out)
10 	struct Env env;
11 	int rc;
12 
13+	memset(out, 0, sizeof(*out));
14 	memset(&env, 0, sizeof(env));
15 	seedenv(&env);
16 	resetevalerrs();
+262, -0
  1@@ -427,3 +427,265 @@ fninfo(const char *text)
  2 	fputc('\n', stderr);
  3 	return xstrdup("");
  4 }
  5+
  6+char *
  7+fnnotdir(const char *names)
  8+{
  9+	size_t i, j, k, len, cap, need, wn;
 10+	const char *slash;
 11+	char *out;
 12+
 13+	cap = strlen(names) + 1;
 14+	if (cap < 16)
 15+		cap = 16;
 16+	len = 0;
 17+	out = xmalloc(cap);
 18+	out[0] = 0;
 19+	for (i = 0; names[i];) {
 20+		while (names[i] && isspace((unsigned char)names[i]))
 21+			i++;
 22+		if (!names[i])
 23+			break;
 24+		j = i;
 25+		while (names[j] && !isspace((unsigned char)names[j]))
 26+			j++;
 27+		slash = 0;
 28+		for (k = i; k < j; k++) {
 29+			if (names[k] == '/')
 30+				slash = names + k;
 31+		}
 32+		wn = slash ? (size_t)(names + j - slash - 1) : j - i;
 33+		need = len + wn + 2;
 34+		if (need > cap) {
 35+			while (cap < need)
 36+				cap *= 2;
 37+			out = xrealloc(out, cap);
 38+		}
 39+		if (len)
 40+			out[len++] = ' ';
 41+		memcpy(out + len, slash ? slash + 1 : names + i, wn);
 42+		len += wn;
 43+		out[len] = 0;
 44+		i = j;
 45+	}
 46+	return out;
 47+}
 48+
 49+char *
 50+fndir(const char *names)
 51+{
 52+	size_t i, j, k, len, cap, need, dlen;
 53+	const char *slash;
 54+	char *out;
 55+
 56+	cap = strlen(names) + 1;
 57+	if (cap < 16)
 58+		cap = 16;
 59+	len = 0;
 60+	out = xmalloc(cap);
 61+	out[0] = 0;
 62+	for (i = 0; names[i];) {
 63+		while (names[i] && isspace((unsigned char)names[i]))
 64+			i++;
 65+		if (!names[i])
 66+			break;
 67+		j = i;
 68+		while (names[j] && !isspace((unsigned char)names[j]))
 69+			j++;
 70+		slash = 0;
 71+		for (k = i; k < j; k++) {
 72+			if (names[k] == '/')
 73+				slash = names + k;
 74+		}
 75+		dlen = slash ? (size_t)(slash - (names + i)) + 1 : 2;
 76+		need = len + dlen + 2;
 77+		if (need > cap) {
 78+			while (cap < need)
 79+				cap *= 2;
 80+			out = xrealloc(out, cap);
 81+		}
 82+		if (len)
 83+			out[len++] = ' ';
 84+		if (slash) {
 85+			memcpy(out + len, names + i, dlen);
 86+		} else {
 87+			memcpy(out + len, "./", 2);
 88+		}
 89+		len += dlen;
 90+		out[len] = 0;
 91+		i = j;
 92+	}
 93+	return out;
 94+}
 95+
 96+char *
 97+fnbasename(const char *names)
 98+{
 99+	size_t i, j, k, len, cap, need, wn;
100+	const char *fnstart, *dot;
101+	char *out;
102+
103+	cap = strlen(names) + 1;
104+	if (cap < 16)
105+		cap = 16;
106+	len = 0;
107+	out = xmalloc(cap);
108+	out[0] = 0;
109+	for (i = 0; names[i];) {
110+		while (names[i] && isspace((unsigned char)names[i]))
111+			i++;
112+		if (!names[i])
113+			break;
114+		j = i;
115+		while (names[j] && !isspace((unsigned char)names[j]))
116+			j++;
117+		wn = j - i;
118+		fnstart = names + i;
119+		dot = 0;
120+		for (k = i; k < j; k++) {
121+			if (names[k] == '/')
122+				fnstart = names + k + 1;
123+			else if (names[k] == '.' && names + k > fnstart)
124+				dot = names + k;
125+		}
126+		if (dot)
127+			wn = (size_t)(dot - (names + i));
128+		need = len + wn + 2;
129+		if (need > cap) {
130+			while (cap < need)
131+				cap *= 2;
132+			out = xrealloc(out, cap);
133+		}
134+		if (len)
135+			out[len++] = ' ';
136+		memcpy(out + len, names + i, wn);
137+		len += wn;
138+		out[len] = 0;
139+		i = j;
140+	}
141+	return out;
142+}
143+
144+char *
145+fnsubst(const char *from, const char *to, const char *text)
146+{
147+	size_t nfrom, nto, i, len, cap, need;
148+	char *out;
149+
150+	nfrom = strlen(from);
151+	nto = strlen(to);
152+	if (!nfrom)
153+		return xstrdup(text);
154+	cap = strlen(text) + 1;
155+	if (cap < 16)
156+		cap = 16;
157+	len = 0;
158+	out = xmalloc(cap);
159+	out[0] = 0;
160+	for (i = 0; text[i];) {
161+		if (strncmp(text + i, from, nfrom) == 0) {
162+			need = len + nto + 1;
163+			if (need > cap) {
164+				while (cap < need)
165+					cap *= 2;
166+				out = xrealloc(out, cap);
167+			}
168+			memcpy(out + len, to, nto);
169+			len += nto;
170+			i += nfrom;
171+		} else {
172+			need = len + 2;
173+			if (need > cap) {
174+				cap *= 2;
175+				out = xrealloc(out, cap);
176+			}
177+			out[len++] = text[i++];
178+		}
179+	}
180+	out[len] = 0;
181+	return out;
182+}
183+
184+static char *
185+patsubstword(const char *word, size_t wn, const char *pattern, const char *replacement)
186+{
187+	size_t npat, nrep, pre, suf, stem, rpre, rsuf;
188+	const char *pct, *rpct;
189+	char *out;
190+
191+	npat = strlen(pattern);
192+	nrep = strlen(replacement);
193+	pct = strchr(pattern, '%');
194+	if (!pct) {
195+		if (wn == npat && memcmp(word, pattern, wn) == 0)
196+			return xstrdup(replacement);
197+		return xstrndup(word, wn);
198+	}
199+	pre = (size_t)(pct - pattern);
200+	suf = npat - pre - 1;
201+	if (wn < pre + suf)
202+		return xstrndup(word, wn);
203+	if (memcmp(word, pattern, pre) != 0 || memcmp(word + wn - suf, pct + 1, suf) != 0)
204+		return xstrndup(word, wn);
205+	stem = wn - pre - suf;
206+	rpct = strchr(replacement, '%');
207+	if (!rpct)
208+		return xstrdup(replacement);
209+	rpre = (size_t)(rpct - replacement);
210+	rsuf = nrep - rpre - 1;
211+	out = xmalloc(rpre + stem + rsuf + 1);
212+	memcpy(out, replacement, rpre);
213+	memcpy(out + rpre, word + pre, stem);
214+	memcpy(out + rpre + stem, rpct + 1, rsuf);
215+	out[rpre + stem + rsuf] = 0;
216+	return out;
217+}
218+
219+char *
220+fnpatsubst(const char *pattern, const char *replacement, const char *text)
221+{
222+	size_t i, j, wlen, len, cap, need;
223+	char *w, *out;
224+
225+	cap = strlen(text) + 1;
226+	if (cap < 16)
227+		cap = 16;
228+	len = 0;
229+	out = xmalloc(cap);
230+	out[0] = 0;
231+	for (i = 0; text[i];) {
232+		while (text[i] && isspace((unsigned char)text[i]))
233+			i++;
234+		if (!text[i])
235+			break;
236+		j = i;
237+		while (text[j] && !isspace((unsigned char)text[j]))
238+			j++;
239+		w = patsubstword(text + i, j - i, pattern, replacement);
240+		wlen = strlen(w);
241+		need = len + wlen + 2;
242+		if (need > cap) {
243+			while (cap < need)
244+				cap *= 2;
245+			out = xrealloc(out, cap);
246+		}
247+		if (len)
248+			out[len++] = ' ';
249+		memcpy(out + len, w, wlen);
250+		len += wlen;
251+		out[len] = 0;
252+		free(w);
253+		i = j;
254+	}
255+	return out;
256+}
257+
258+char *
259+fnif(const char *cond, const char *then, const char *otherwise)
260+{
261+	const char *p;
262+
263+	for (p = cond; *p && isspace((unsigned char)*p); p++)
264+		;
265+	return xstrdup(*p ? then : otherwise);
266+}
+12, -7
 1@@ -135,7 +135,7 @@ pattargetexists(const struct Graph *graph, const char *name)
 2 	t = findctarget(graph, name);
 3 	if (!t)
 4 		return 0;
 5-	return t->recipes.n > 0 || t->prereqs.n > 0 || t->order_only.n > 0;
 6+	return t->recipes.n > 0;
 7 }
 8 
 9 static int
10@@ -171,12 +171,17 @@ instpatrule(const struct PatRules *rules, const struct Graph *graph, struct Targ
11 			free(stem);
12 			continue;
13 		}
14-		for (j = 0; j < rules->v[i].prereqs.n; j++) {
15-			char *s;
16-
17-			s = applystem(rules->v[i].prereqs.v[j], stem);
18-			t->prereqs.v = xrealloc(t->prereqs.v, (t->prereqs.n + 1) * sizeof(t->prereqs.v[0]));
19-			t->prereqs.v[t->prereqs.n++] = s;
20+		/* prepend so $< is the pattern source, not an extra dep
21+		 * added by an explicit rule for the same target */
22+		if (rules->v[i].prereqs.n > 0) {
23+			size_t np = rules->v[i].prereqs.n;
24+			t->prereqs.v = xrealloc(t->prereqs.v,
25+			                        (t->prereqs.n + np) * sizeof(t->prereqs.v[0]));
26+			memmove(t->prereqs.v + np, t->prereqs.v,
27+			        t->prereqs.n * sizeof(t->prereqs.v[0]));
28+			for (j = 0; j < np; j++)
29+				t->prereqs.v[j] = applystem(rules->v[i].prereqs.v[j], stem);
30+			t->prereqs.n += np;
31 		}
32 		for (j = 0; j < rules->v[i].order_only.n; j++) {
33 			char *s;
+3, -1
 1@@ -185,7 +185,9 @@ buildgraph(const struct Ast *ast, struct Graph *graph)
 2 		for (i = start; i < current_n; i++) {
 3 			struct Target *t = &graph->v[i];
 4 			size_t nprereqs;
 5-			/* for targets with no recipe, try a pattern rule or suffix rule */
 6+			/* for targets with no recipe, try a pattern rule or
 7+			 * suffix rule. existing prereqs dont block this,
 8+			 * the rule may still supply the recipe */
 9 			if (t->recipes.n == 0) {
10 				int matched;
11 				struct Env env;
+6, -0
 1@@ -46,5 +46,11 @@ char *fnaddprefix(const char *prefix, const char *names);
 2 char *fnaddsuffix(const char *suffix, const char *names);
 3 char *fnsort(const char *text);
 4 char *fninfo(const char *text);
 5+char *fnnotdir(const char *names);
 6+char *fndir(const char *names);
 7+char *fnbasename(const char *names);
 8+char *fnsubst(const char *from, const char *to, const char *text);
 9+char *fnpatsubst(const char *pattern, const char *replacement, const char *text);
10+char *fnif(const char *cond, const char *then, const char *otherwise);
11 
12 #endif
+3, -1
 1@@ -280,10 +280,12 @@ readline(const char **src, int *lineno, struct PreLine *line)
 2 		len += chunk;
 3 		buf[len] = 0;
 4 
 5-		if (line->isrecipe || !hascont(buf, len))
 6+		if (!hascont(buf, len))
 7 			break;
 8 		len--;
 9 		buf[len++] = ' ';
10+		while (*p == ' ' || *p == '\t')
11+			p++;
12 		line1++;
13 		first = 0;
14 		if (!*p)