commit 3997951

shrub  ·  2026-04-12 12:38:14 +0000 UTC
parent 3ff2026
split eval into two files and make expandstr not be a huge monolithic function, handle addsuffix and sort, check viability of pattern rule
8 files changed,  +836, -718
+0, -715
  1@@ -1,715 +0,0 @@
  2-#include "shinobi.h"
  3-#include "internal.h"
  4-
  5-#include <ctype.h>
  6-#include <stddef.h>
  7-#include <stdio.h>
  8-#include <stdlib.h>
  9-#include <string.h>
 10-
 11-/*
 12- * eval does the second pass over the built ast
 13- *   seed builtins like CC and so on
 14- *   apply assignment semantics
 15- *   expand plain var refs and simple substitution refs
 16- *   execute conditionals and flatten the chosen branch
 17- *
 18- * right now only a small subset of gmake syntax is handled.
 19- */
 20-
 21-static int evalerrs;
 22-
 23-static void
 24-evalerr(const char *msg, const char *detail)
 25-{
 26-	if (detail)
 27-		fprintf(stderr, "eval: unsupported: %s: %s\n", msg, detail);
 28-	else
 29-		fprintf(stderr, "eval: unsupported: %s\n", msg);
 30-	evalerrs++;
 31-}
 32-
 33-struct Var *
 34-findvar(struct Env *env, const char *name)
 35-{
 36-	size_t i;
 37-
 38-	for (i = 0; i < env->n; i++) {
 39-		if (strcmp(env->v[i].name, name) == 0)
 40-			return &env->v[i];
 41-	}
 42-	return 0;
 43-}
 44-
 45-static int
 46-isplainvar(const char *s, size_t n)
 47-{
 48-	size_t i;
 49-
 50-	if (!n)
 51-		return 0;
 52-	for (i = 0; i < n; i++) {
 53-		if (!(isalnum((unsigned char)s[i]) || s[i] == '_'))
 54-			return 0;
 55-	}
 56-	return 1;
 57-}
 58-
 59-static char *
 60-substword(const char *word, size_t n, const char *from, const char *to)
 61-{
 62-	size_t nfrom, nto;
 63-	const char *pct;
 64-	char *out;
 65-
 66-	nfrom = strlen(from);
 67-	nto = strlen(to);
 68-	pct = strchr(from, '%');
 69-	if (!pct) {
 70-		if (n < nfrom || memcmp(word + n - nfrom, from, nfrom) != 0)
 71-			return xstrndup(word, n);
 72-		out = xmalloc(n - nfrom + nto + 1);
 73-		memcpy(out, word, n - nfrom);
 74-		memcpy(out + n - nfrom, to, nto);
 75-		out[n - nfrom + nto] = 0;
 76-		return out;
 77-	}
 78-	{
 79-		size_t pre, suf, stem;
 80-		const char *tpct;
 81-
 82-		pre = (size_t)(pct - from);
 83-		suf = nfrom - pre - 1;
 84-		if (n < pre + suf)
 85-			return xstrndup(word, n);
 86-		if (memcmp(word, from, pre) != 0 || memcmp(word + n - suf, pct + 1, suf) != 0)
 87-			return xstrndup(word, n);
 88-		stem = n - pre - suf;
 89-		tpct = strchr(to, '%');
 90-		if (!tpct) {
 91-			out = xstrdup(to);
 92-			return out;
 93-		}
 94-		{
 95-			size_t tpre, tsuf;
 96-
 97-			tpre = (size_t)(tpct - to);
 98-			tsuf = nto - tpre - 1;
 99-			out = xmalloc(tpre + stem + tsuf + 1);
100-			memcpy(out, to, tpre);
101-			memcpy(out + tpre, word + pre, stem);
102-			memcpy(out + tpre + stem, tpct + 1, tsuf);
103-			out[tpre + stem + tsuf] = 0;
104-			return out;
105-		}
106-	}
107-}
108-
109-static char *
110-substval(const char *val, const char *from, const char *to)
111-{
112-	size_t i, j, n, cap, len, wn;
113-	char *out, *part;
114-
115-	n = strlen(val);
116-	cap = n + 1;
117-	len = 0;
118-	out = xmalloc(cap);
119-	for (i = 0; i < n;) {
120-		if (isspace((unsigned char)val[i])) {
121-			if (len + 2 > cap) {
122-				cap *= 2;
123-				out = xrealloc(out, cap);
124-			}
125-			out[len++] = val[i++];
126-			continue;
127-		}
128-		j = i;
129-		while (j < n && !isspace((unsigned char)val[j]))
130-			j++;
131-		part = substword(val + i, j - i, from, to);
132-		wn = strlen(part);
133-		if (len + wn + 1 > cap) {
134-			cap = len + wn + 1;
135-			out = xrealloc(out, cap);
136-		}
137-		memcpy(out + len, part, wn);
138-		len += wn;
139-		free(part);
140-		i = j;
141-	}
142-	out[len] = 0;
143-	return out;
144-}
145-
146-static int
147-issubstref(const char *s, size_t n, size_t *colon, size_t *eq)
148-{
149-	size_t i;
150-
151-	for (i = 0; i < n; i++) {
152-		if (s[i] == ':') {
153-			*colon = i;
154-			break;
155-		}
156-		if (!(isalnum((unsigned char)s[i]) || s[i] == '_'))
157-			return 0;
158-	}
159-	if (i == 0 || i >= n)
160-		return 0;
161-	for (i = *colon + 1; i < n; i++) {
162-		if (s[i] == '=') {
163-			*eq = i;
164-			return i > *colon + 1;
165-		}
166-	}
167-	return 0;
168-}
169-
170-static ptrdiff_t
171-findargcomma(const char *s, size_t n)
172-{
173-	size_t i, depth;
174-
175-	depth = 0;
176-	for (i = 0; i < n; i++) {
177-		if (s[i] == '$' && i + 1 < n && (s[i + 1] == '(' || s[i + 1] == '{')) {
178-			depth++;
179-			i++;
180-			continue;
181-		}
182-		if ((s[i] == ')' || s[i] == '}') && depth > 0) {
183-			depth--;
184-			continue;
185-		}
186-		if (s[i] == ',' && depth == 0)
187-			return (ptrdiff_t)i;
188-	}
189-	return -1;
190-}
191-
192-char *
193-expandstr(struct Env *env, const char *s)
194-{
195-	size_t i, j, k, n, cap, len, inner, start, colon, eq;
196-	char close;
197-	char *out, *name, *val, *from, *to;
198-	struct Var *v;
199-
200-	n = strlen(s);
201-	cap = n + 1;
202-	len = 0;
203-	out = xmalloc(cap);
204-	for (i = 0; i < n; i++) {
205-		if (s[i] == '$' && i + 1 < n && s[i + 1] == '$') {
206-			if (len + 3 > cap) {
207-				cap *= 2;
208-				out = xrealloc(out, cap);
209-			}
210-			out[len++] = '$';
211-			out[len++] = '$';
212-			i++;
213-			continue;
214-		}
215-		if (s[i] != '$' || i + 1 >= n || (s[i + 1] != '(' && s[i + 1] != '{')) {
216-			if (len + 2 > cap) {
217-				cap *= 2;
218-				out = xrealloc(out, cap);
219-			}
220-			out[len++] = s[i];
221-			continue;
222-		}
223-		close = s[i + 1] == '(' ? ')' : '}';
224-		start = i;
225-		j = i + 2;
226-		inner = 1;
227-		while (j < n && inner) {
228-			if (s[j] == '$' && j + 1 < n && (s[j + 1] == '(' || s[j + 1] == '{')) {
229-				inner++;
230-				j += 2;
231-				continue;
232-			}
233-			if (s[j] == close)
234-				inner--;
235-			j++;
236-		}
237-		if (inner) {
238-			if (len + (n - start) + 1 > cap) {
239-				cap = len + (n - start) + 1;
240-				out = xrealloc(out, cap);
241-			}
242-			memcpy(out + len, s + start, n - start);
243-			len += n - start;
244-			break;
245-		}
246-		if (isplainvar(s + i + 2, j - i - 3)) {
247-			name = xstrndup(s + i + 2, j - i - 3);
248-			v = findvar(env, name);
249-			free(name);
250-			val = v ? expandstr(env, v->val) : xstrdup("");
251-			k = strlen(val);
252-			if (len + k + 1 > cap) {
253-				cap = len + k + 1;
254-				out = xrealloc(out, cap);
255-			}
256-			memcpy(out + len, val, k);
257-			len += k;
258-			free(val);
259-		} else if (issubstref(s + i + 2, j - i - 3, &colon, &eq)) {
260-			name = xstrndup(s + i + 2, colon);
261-			from = xstrndup(s + i + 2 + colon + 1, eq - colon - 1);
262-			to = xstrndup(s + i + 2 + eq + 1, j - i - 4 - eq);
263-			v = findvar(env, name);
264-			free(name);
265-			if (v) {
266-				char *base;
267-
268-				base = expandstr(env, v->val);
269-				val = substval(base, from, to);
270-				free(base);
271-			} else {
272-				val = xstrdup("");
273-			}
274-			free(from);
275-			free(to);
276-			k = strlen(val);
277-			if (len + k + 1 > cap) {
278-				cap = len + k + 1;
279-				out = xrealloc(out, cap);
280-			}
281-			memcpy(out + len, val, k);
282-			len += k;
283-			free(val);
284-		} else if (j - i - 3 >= 10 &&
285-		           memcmp(s + i + 2, "wildcard", 8) == 0 &&
286-		           isspace((unsigned char)s[i + 10])) {
287-			char *pat_raw, *pat_exp, *val;
288-			size_t pstart;
289-
290-			pstart = i + 11;
291-			pat_raw = xstrndup(s + pstart, j - 1 - pstart);
292-			pat_exp = expandstr(env, pat_raw);
293-			free(pat_raw);
294-			val = fnwildcard(pat_exp);
295-			free(pat_exp);
296-			k = strlen(val);
297-			if (len + k + 1 > cap) {
298-				cap = len + k + 1;
299-				out = xrealloc(out, cap);
300-			}
301-			memcpy(out + len, val, k);
302-			len += k;
303-			free(val);
304-		} else if (j - i - 3 >= 7 &&
305-		           memcmp(s + i + 2, "shell", 5) == 0 &&
306-		           isspace((unsigned char)s[i + 7])) {
307-			char *cmd_raw, *cmd_exp, *val;
308-			size_t cstart;
309-
310-			cstart = i + 8;
311-			cmd_raw = xstrndup(s + cstart, j - 1 - cstart);
312-			cmd_exp = expandstr(env, cmd_raw);
313-			free(cmd_raw);
314-			val = fnshell(cmd_exp);
315-			free(cmd_exp);
316-			k = strlen(val);
317-			if (len + k + 1 > cap) {
318-				cap = len + k + 1;
319-				out = xrealloc(out, cap);
320-			}
321-			memcpy(out + len, val, k);
322-			len += k;
323-			free(val);
324-		} else if (j - i - 3 >= 10 &&
325-		           memcmp(s + i + 2, "filter-out", 10) == 0) {
326-			char *args_raw, *pat_raw, *text_raw;
327-			char *pat_exp, *text_exp, *val;
328-			size_t astart;
329-			ptrdiff_t comma;
330-
331-			astart = i + 12;
332-			while (astart < j - 1 && isspace((unsigned char)s[astart]))
333-				astart++;
334-			args_raw = xstrndup(s + astart, j - 1 - astart);
335-			comma = findargcomma(args_raw, strlen(args_raw));
336-			if (comma < 0) {
337-				evalerr("malformed function arguments", "$(filter-out)");
338-				free(args_raw);
339-				val = xstrdup("");
340-			} else {
341-				pat_raw = xstrndup(args_raw, (size_t)comma);
342-				text_raw = xstrdup(args_raw + comma + 1);
343-				free(args_raw);
344-				pat_exp = expandstr(env, pat_raw);
345-				text_exp = expandstr(env, text_raw);
346-				free(pat_raw);
347-				free(text_raw);
348-				val = fnfilterout(pat_exp, text_exp);
349-				free(pat_exp);
350-				free(text_exp);
351-			}
352-			k = strlen(val);
353-			if (len + k + 1 > cap) {
354-				cap = len + k + 1;
355-				out = xrealloc(out, cap);
356-			}
357-			memcpy(out + len, val, k);
358-			len += k;
359-			free(val);
360-		} else if (j - i - 3 >= 6 &&
361-		           memcmp(s + i + 2, "filter", 6) == 0) {
362-			char *args_raw, *pat_raw, *text_raw;
363-			char *pat_exp, *text_exp, *val;
364-			size_t astart;
365-			ptrdiff_t comma;
366-
367-			astart = i + 8;
368-			while (astart < j - 1 && isspace((unsigned char)s[astart]))
369-				astart++;
370-			args_raw = xstrndup(s + astart, j - 1 - astart);
371-			comma = findargcomma(args_raw, strlen(args_raw));
372-			if (comma < 0) {
373-				evalerr("malformed function arguments", "$(filter)");
374-				free(args_raw);
375-				val = xstrdup("");
376-			} else {
377-				pat_raw = xstrndup(args_raw, (size_t)comma);
378-				text_raw = xstrdup(args_raw + comma + 1);
379-				free(args_raw);
380-				pat_exp = expandstr(env, pat_raw);
381-				text_exp = expandstr(env, text_raw);
382-				free(pat_raw);
383-				free(text_raw);
384-				val = fnfilter(pat_exp, text_exp);
385-				free(pat_exp);
386-				free(text_exp);
387-			}
388-			k = strlen(val);
389-			if (len + k + 1 > cap) {
390-				cap = len + k + 1;
391-				out = xrealloc(out, cap);
392-			}
393-			memcpy(out + len, val, k);
394-			len += k;
395-			free(val);
396-		} else if (j - i - 3 >= 9 &&
397-		           memcmp(s + i + 2, "addprefix", 9) == 0) {
398-			char *args_raw, *pre_raw, *names_raw;
399-			char *pre_exp, *names_exp, *val;
400-			size_t astart;
401-			ptrdiff_t comma;
402-
403-			astart = i + 11;
404-			while (astart < j - 1 && isspace((unsigned char)s[astart]))
405-				astart++;
406-			args_raw = xstrndup(s + astart, j - 1 - astart);
407-			comma = findargcomma(args_raw, strlen(args_raw));
408-			if (comma < 0) {
409-				evalerr("malformed function arguments", "$(addprefix)");
410-				free(args_raw);
411-				val = xstrdup("");
412-			} else {
413-				pre_raw = xstrndup(args_raw, (size_t)comma);
414-				names_raw = xstrdup(args_raw + comma + 1);
415-				free(args_raw);
416-				pre_exp = expandstr(env, pre_raw);
417-				names_exp = expandstr(env, names_raw);
418-				free(pre_raw);
419-				free(names_raw);
420-				val = fnaddprefix(pre_exp, names_exp);
421-				free(pre_exp);
422-				free(names_exp);
423-			}
424-			k = strlen(val);
425-			if (len + k + 1 > cap) {
426-				cap = len + k + 1;
427-				out = xrealloc(out, cap);
428-			}
429-			memcpy(out + len, val, k);
430-			len += k;
431-			free(val);
432-		} else if (j - i - 3 >= 4 &&
433-		           memcmp(s + i + 2, "info", 4) == 0) {
434-			char *text_raw, *text_exp, *val;
435-			size_t tstart;
436-
437-			tstart = i + 6;
438-			while (tstart < j - 1 && isspace((unsigned char)s[tstart]))
439-				tstart++;
440-			text_raw = xstrndup(s + tstart, j - 1 - tstart);
441-			text_exp = expandstr(env, text_raw);
442-			free(text_raw);
443-			val = fninfo(text_exp);
444-			free(text_exp);
445-			k = strlen(val);
446-			if (len + k + 1 > cap) {
447-				cap = len + k + 1;
448-				out = xrealloc(out, cap);
449-			}
450-			memcpy(out + len, val, k);
451-			len += k;
452-			free(val);
453-		} else {
454-			char *unsup;
455-
456-			unsup = xstrndup(s + start, j - start);
457-			evalerr("variable reference or function", unsup);
458-			free(unsup);
459-			k = j - start;
460-			if (len + k + 1 > cap) {
461-				cap = len + k + 1;
462-				out = xrealloc(out, cap);
463-			}
464-			memcpy(out + len, s + start, k);
465-			len += k;
466-		}
467-		i = j - 1;
468-	}
469-	out[len] = 0;
470-	return out;
471-}
472-
473-void
474-freeenv(struct Env *env)
475-{
476-	size_t i;
477-
478-	for (i = 0; i < env->n; i++) {
479-		free(env->v[i].name);
480-		free(env->v[i].val);
481-	}
482-	free(env->v);
483-	env->v = 0;
484-	env->n = 0;
485-}
486-
487-void
488-copyenv(struct Env *dst, const struct Env *src)
489-{
490-	size_t i;
491-
492-	memset(dst, 0, sizeof(*dst));
493-	if (src->n)
494-		dst->v = xrealloc(0, src->n * sizeof(dst->v[0]));
495-	for (i = 0; i < src->n; i++) {
496-		dst->v[i].name = xstrdup(src->v[i].name);
497-		dst->v[i].val = xstrdup(src->v[i].val);
498-		dst->v[i].simple = src->v[i].simple;
499-	}
500-	dst->n = src->n;
501-}
502-
503-void
504-evalassign(struct Env *env, const struct AssignNode *in)
505-{
506-	struct Var *v;
507-	char *rhs, *joined;
508-
509-	switch (in->op) {
510-	case ASSIGN_EQ:
511-		envsetvar(env, in->lhs, xstrdup(in->rhs), 0);
512-		break;
513-	case ASSIGN_COLON_EQ:
514-		envsetvar(env, in->lhs, expandstr(env, in->rhs), 1);
515-		break;
516-	case ASSIGN_QMARK_EQ:
517-		if (!findvar(env, in->lhs))
518-			envsetvar(env, in->lhs, xstrdup(in->rhs), 0);
519-		break;
520-	case ASSIGN_PLUS_EQ:
521-		v = findvar(env, in->lhs);
522-		if (!v) {
523-			envsetvar(env, in->lhs, xstrdup(in->rhs), 0);
524-			break;
525-		}
526-		rhs = v->simple ? expandstr(env, in->rhs) : xstrdup(in->rhs);
527-		joined = cat3(v->val, " ", rhs);
528-		free(rhs);
529-		free(v->val);
530-		v->val = joined;
531-		break;
532-	case ASSIGN_BANG_EQ:
533-		envsetvar(env, in->lhs, expandstr(env, in->rhs), 1);
534-		break;
535-	}
536-}
537-
538-static int
539-testcond(struct Env *env, const struct CondNode *cond)
540-{
541-	char *a, *b, *name;
542-	struct Var *v;
543-	int ok;
544-
545-	if (cond->kind == COND_IFDEF || cond->kind == COND_IFNDEF) {
546-		name = expandstr(env, cond->arg1);
547-		v = findvar(env, name);
548-		ok = v != 0;
549-		free(name);
550-		if (cond->kind == COND_IFNDEF)
551-			ok = !ok;
552-		return ok;
553-	}
554-
555-	a = cond->arg1 ? expandstr(env, cond->arg1) : xstrdup("");
556-	b = cond->arg2 ? expandstr(env, cond->arg2) : xstrdup("");
557-	ok = strcmp(a, b) == 0;
558-	free(a);
559-	free(b);
560-	if (cond->kind == COND_IFNEQ)
561-		ok = !ok;
562-	return ok;
563-}
564-
565-static void
566-copywords(struct StrList *out, const struct StrList *in, struct Env *env)
567-{
568-	size_t i;
569-
570-	memset(out, 0, sizeof(*out));
571-	for (i = 0; i < in->n; i++) {
572-		char *s = expandstr(env, in->v[i]);
573-		splitwords(out, s, strlen(s));
574-		free(s);
575-	}
576-}
577-
578-static void
579-copyrecipes(struct RecipeList *out, const struct RecipeList *in)
580-{
581-	size_t i;
582-
583-	memset(out, 0, sizeof(*out));
584-	for (i = 0; i < in->n; i++) {
585-		out->v = xrealloc(out->v, (out->n + 1) * sizeof(out->v[0]));
586-		out->v[out->n++] = xstrdup(in->v[i]);
587-	}
588-}
589-
590-static int
591-evalnodes(const struct NodeList *in, struct NodeList *out, struct Env *env)
592-{
593-	size_t i;
594-
595-	memset(out, 0, sizeof(*out));
596-	for (i = 0; i < in->n; i++) {
597-		const struct Node *src;
598-		struct Node node;
599-
600-		src = &in->v[i];
601-		memset(&node, 0, sizeof(node));
602-		node.kind = src->kind;
603-		node.loc = src->loc;
604-		switch (src->kind) {
605-		case NODE_BLANK:
606-			break;
607-		case NODE_COMMENT:
608-			node.data.raw.text = xstrdup(src->data.raw.text);
609-			break;
610-		case NODE_RAW: {
611-			char *exp;
612-
613-			exp = expandstr(env, src->data.raw.text);
614-			free(exp);
615-			node.kind = NODE_BLANK;
616-			break;
617-		}
618-		case NODE_INCLUDE:
619-			node.data.include.optional = src->data.include.optional;
620-			node.data.include.path = expandstr(env, src->data.include.path);
621-			break;
622-		case NODE_COND:
623-			if (testcond(env, &src->data.cond)) {
624-				if (evalnodes(&src->data.cond.thenpart, out, env) < 0)
625-					return -1;
626-			} else {
627-				if (evalnodes(&src->data.cond.elsepart, out, env) < 0)
628-					return -1;
629-			}
630-			continue;
631-		case NODE_ASSIGN:
632-			node.data.assign.lhs = xstrdup(src->data.assign.lhs);
633-			node.data.assign.op = src->data.assign.op;
634-			node.data.assign.tspec = src->data.assign.tspec;
635-			if (src->data.assign.op == ASSIGN_COLON_EQ || src->data.assign.op == ASSIGN_BANG_EQ)
636-				node.data.assign.rhs = expandstr(env, src->data.assign.rhs);
637-			else
638-				node.data.assign.rhs = xstrdup(src->data.assign.rhs);
639-			copywords(&node.data.assign.targets, &src->data.assign.targets, env);
640-			if (!src->data.assign.tspec)
641-				evalassign(env, &src->data.assign);
642-			break;
643-		case NODE_RULE:
644-			copywords(&node.data.rule.targets, &src->data.rule.targets, env);
645-			copywords(&node.data.rule.prereqs, &src->data.rule.prereqs, env);
646-			copywords(&node.data.rule.order_only, &src->data.rule.order_only, env);
647-			copyrecipes(&node.data.rule.recipes, &src->data.rule.recipes);
648-			break;
649-		}
650-		addnode(out, node);
651-	}
652-	return 0;
653-}
654-
655-int
656-eval(const struct Ast *ast, struct Ast *out)
657-{
658-	struct Env env;
659-	int rc;
660-
661-	memset(&env, 0, sizeof(env));
662-	seedenv(&env);
663-	evalerrs = 0;
664-	rc = evalnodes((const struct NodeList *)ast, (struct NodeList *)out, &env);
665-	freeenv(&env);
666-	if (rc == 0 && evalerrs)
667-		return -1;
668-	return rc;
669-}
670-
671-static void
672-freenodes(struct NodeList *list)
673-{
674-	size_t i;
675-
676-	for (i = 0; i < list->n; i++) {
677-		switch (list->v[i].kind) {
678-		case NODE_COMMENT:
679-		case NODE_RAW:
680-			free(list->v[i].data.raw.text);
681-			break;
682-		case NODE_ASSIGN:
683-			free(list->v[i].data.assign.lhs);
684-			free(list->v[i].data.assign.rhs);
685-			freestrs(&list->v[i].data.assign.targets);
686-			break;
687-		case NODE_RULE:
688-			freestrs(&list->v[i].data.rule.targets);
689-			freestrs(&list->v[i].data.rule.prereqs);
690-			freestrs(&list->v[i].data.rule.order_only);
691-			freerecipes(&list->v[i].data.rule.recipes);
692-			break;
693-		case NODE_INCLUDE:
694-			free(list->v[i].data.include.path);
695-			break;
696-		case NODE_COND:
697-			free(list->v[i].data.cond.arg1);
698-			free(list->v[i].data.cond.arg2);
699-			free(list->v[i].data.cond.raw);
700-			freenodes(&list->v[i].data.cond.thenpart);
701-			freenodes(&list->v[i].data.cond.elsepart);
702-			break;
703-		case NODE_BLANK:
704-			break;
705-		}
706-	}
707-	free(list->v);
708-	list->v = 0;
709-	list->n = 0;
710-}
711-
712-void
713-freeast(struct Ast *ast)
714-{
715-	freenodes((struct NodeList *)ast);
716-}
+408, -0
  1@@ -0,0 +1,408 @@
  2+#include "internal.h"
  3+
  4+#include <ctype.h>
  5+#include <stddef.h>
  6+#include <stdio.h>
  7+#include <stdlib.h>
  8+#include <string.h>
  9+
 10+/* $() ${} expansion is handled here */
 11+
 12+static int evalerrs;
 13+
 14+struct Buf {
 15+	char *s;
 16+	size_t len;
 17+	size_t cap;
 18+};
 19+
 20+static void
 21+evalerr(const char *msg, const char *detail)
 22+{
 23+	if (detail)
 24+		fprintf(stderr, "eval: unsupported: %s: %s\n", msg, detail);
 25+	else
 26+		fprintf(stderr, "eval: unsupported: %s\n", msg);
 27+	evalerrs++;
 28+}
 29+
 30+void
 31+resetevalerrs(void)
 32+{
 33+	evalerrs = 0;
 34+}
 35+
 36+int
 37+getevalerrs(void)
 38+{
 39+	return evalerrs;
 40+}
 41+
 42+static void
 43+bufinit(struct Buf *buf, size_t cap)
 44+{
 45+	buf->cap = cap ? cap : 1;
 46+	buf->len = 0;
 47+	buf->s = xmalloc(buf->cap);
 48+	buf->s[0] = 0;
 49+}
 50+
 51+static void
 52+bufgrow(struct Buf *buf, size_t need)
 53+{
 54+	while (buf->cap < need)
 55+		buf->cap *= 2;
 56+	buf->s = xrealloc(buf->s, buf->cap);
 57+}
 58+
 59+static void
 60+bufappendn(struct Buf *buf, const char *s, size_t n)
 61+{
 62+	if (buf->len + n + 1 > buf->cap)
 63+		bufgrow(buf, buf->len + n + 1);
 64+	memcpy(buf->s + buf->len, s, n);
 65+	buf->len += n;
 66+	buf->s[buf->len] = 0;
 67+}
 68+
 69+static void
 70+bufappend(struct Buf *buf, const char *s)
 71+{
 72+	bufappendn(buf, s, strlen(s));
 73+}
 74+
 75+static void
 76+bufappendc(struct Buf *buf, char c)
 77+{
 78+	if (buf->len + 2 > buf->cap)
 79+		bufgrow(buf, buf->len + 2);
 80+	buf->s[buf->len++] = c;
 81+	buf->s[buf->len] = 0;
 82+}
 83+
 84+static char *
 85+bufdone(struct Buf *buf)
 86+{
 87+	return buf->s;
 88+}
 89+
 90+static int
 91+isplainvar(const char *s, size_t n)
 92+{
 93+	size_t i;
 94+
 95+	if (!n)
 96+		return 0;
 97+	for (i = 0; i < n; i++) {
 98+		if (!(isalnum((unsigned char)s[i]) || s[i] == '_'))
 99+			return 0;
100+	}
101+	return 1;
102+}
103+
104+static char *
105+substword(const char *word, size_t n, const char *from, const char *to)
106+{
107+	size_t nfrom, nto;
108+	const char *pct;
109+	char *out;
110+
111+	nfrom = strlen(from);
112+	nto = strlen(to);
113+	pct = strchr(from, '%');
114+	if (!pct) {
115+		if (n < nfrom || memcmp(word + n - nfrom, from, nfrom) != 0)
116+			return xstrndup(word, n);
117+		out = xmalloc(n - nfrom + nto + 1);
118+		memcpy(out, word, n - nfrom);
119+		memcpy(out + n - nfrom, to, nto);
120+		out[n - nfrom + nto] = 0;
121+		return out;
122+	}
123+	{
124+		size_t pre, suf, stem;
125+		const char *tpct;
126+
127+		pre = (size_t)(pct - from);
128+		suf = nfrom - pre - 1;
129+		if (n < pre + suf)
130+			return xstrndup(word, n);
131+		if (memcmp(word, from, pre) != 0 || memcmp(word + n - suf, pct + 1, suf) != 0)
132+			return xstrndup(word, n);
133+		stem = n - pre - suf;
134+		tpct = strchr(to, '%');
135+		if (!tpct)
136+			return xstrdup(to);
137+		{
138+			size_t tpre, tsuf;
139+
140+			tpre = (size_t)(tpct - to);
141+			tsuf = nto - tpre - 1;
142+			out = xmalloc(tpre + stem + tsuf + 1);
143+			memcpy(out, to, tpre);
144+			memcpy(out + tpre, word + pre, stem);
145+			memcpy(out + tpre + stem, tpct + 1, tsuf);
146+			out[tpre + stem + tsuf] = 0;
147+			return out;
148+		}
149+	}
150+}
151+
152+static char *
153+substval(const char *val, const char *from, const char *to)
154+{
155+	size_t i, j, n;
156+	char *part;
157+	struct Buf out;
158+
159+	n = strlen(val);
160+	bufinit(&out, n + 1);
161+	for (i = 0; i < n;) {
162+		if (isspace((unsigned char)val[i])) {
163+			bufappendc(&out, val[i]);
164+			i++;
165+			continue;
166+		}
167+		j = i;
168+		while (j < n && !isspace((unsigned char)val[j]))
169+			j++;
170+		part = substword(val + i, j - i, from, to);
171+		bufappend(&out, part);
172+		free(part);
173+		i = j;
174+	}
175+	return bufdone(&out);
176+}
177+
178+static int
179+issubstref(const char *s, size_t n, size_t *colon, size_t *eq)
180+{
181+	size_t i;
182+
183+	for (i = 0; i < n; i++) {
184+		if (s[i] == ':') {
185+			*colon = i;
186+			break;
187+		}
188+		if (!(isalnum((unsigned char)s[i]) || s[i] == '_'))
189+			return 0;
190+	}
191+	if (i == 0 || i >= n)
192+		return 0;
193+	for (i = *colon + 1; i < n; i++) {
194+		if (s[i] == '=') {
195+			*eq = i;
196+			return i > *colon + 1;
197+		}
198+	}
199+	return 0;
200+}
201+
202+static ptrdiff_t
203+findargcomma(const char *s, size_t n)
204+{
205+	size_t i, depth;
206+
207+	depth = 0;
208+	for (i = 0; i < n; i++) {
209+		if (s[i] == '$' && i + 1 < n && (s[i + 1] == '(' || s[i + 1] == '{')) {
210+			depth++;
211+			i++;
212+			continue;
213+		}
214+		if ((s[i] == ')' || s[i] == '}') && depth > 0) {
215+			depth--;
216+			continue;
217+		}
218+		if (s[i] == ',' && depth == 0)
219+			return (ptrdiff_t)i;
220+	}
221+	return -1;
222+}
223+
224+static size_t
225+findclose(const char *s, size_t i, size_t n, char close)
226+{
227+	size_t j, inner;
228+
229+	j = i + 2;
230+	inner = 1;
231+	while (j < n && inner) {
232+		if (s[j] == '$' && j + 1 < n && (s[j + 1] == '(' || s[j + 1] == '{')) {
233+			inner++;
234+			j += 2;
235+			continue;
236+		}
237+		if (s[j] == close)
238+			inner--;
239+		j++;
240+	}
241+	return inner ? 0 : j;
242+}
243+
244+static char *
245+expandvarref(struct Env *env, const char *s, size_t n)
246+{
247+	char *name, *val;
248+	struct Var *v;
249+
250+	name = xstrndup(s, n);
251+	v = findvar(env, name);
252+	free(name);
253+	val = v ? expandstr(env, v->val) : xstrdup("");
254+	return val;
255+}
256+
257+static char *
258+expandsubstref(struct Env *env, const char *s, size_t colon, size_t eq, size_t n)
259+{
260+	char *name, *from, *to, *val;
261+	struct Var *v;
262+
263+	name = xstrndup(s, colon);
264+	from = xstrndup(s + colon + 1, eq - colon - 1);
265+	to = xstrndup(s + eq + 1, n - eq - 1);
266+	v = findvar(env, name);
267+	free(name);
268+	if (v) {
269+		char *base;
270+
271+		base = expandstr(env, v->val);
272+		val = substval(base, from, to);
273+		free(base);
274+	} else {
275+		val = xstrdup("");
276+	}
277+	free(from);
278+	free(to);
279+	return val;
280+}
281+
282+typedef char *(*fn1_t)(const char *);
283+typedef char *(*fn2_t)(const char *, const char *);
284+
285+struct func {
286+	const char *name;
287+	int arity;
288+	union {
289+		fn1_t f1;
290+		fn2_t f2;
291+	} fn;
292+};
293+
294+static const struct func funcs[] = {
295+	{"wildcard",   1, {.f1 = fnwildcard}},
296+	{"shell",      1, {.f1 = fnshell}},
297+	{"sort",       1, {.f1 = fnsort}},
298+	{"info",       1, {.f1 = fninfo}},
299+	{"filter-out", 2, {.f2 = fnfilterout}},
300+	{"filter",     2, {.f2 = fnfilter}},
301+	{"addprefix",  2, {.f2 = fnaddprefix}},
302+	{"addsuffix",  2, {.f2 = fnaddsuffix}},
303+	{0, 0, {.f1 = 0}},
304+};
305+
306+static char *
307+funcref(struct Env *env, const char *s, size_t n)
308+{
309+	size_t i, namelen, start;
310+	const struct func *f;
311+	char *val;
312+
313+	for (i = 0; funcs[i].name; i++) {
314+		f = &funcs[i];
315+		namelen = strlen(f->name);
316+		if (n < namelen || memcmp(s, f->name, namelen) != 0)
317+			continue;
318+		start = namelen;
319+		while (start < n && isspace((unsigned char)s[start]))
320+			start++;
321+		if (f->arity == 1) {
322+			char *raw, *exp;
323+
324+			raw = xstrndup(s + start, n - start);
325+			exp = expandstr(env, raw);
326+			free(raw);
327+			val = f->fn.f1(exp);
328+			free(exp);
329+		} else {
330+			char *args, *lhs_raw, *rhs_raw, *lhs_exp, *rhs_exp, *detail;
331+			ptrdiff_t comma;
332+
333+			args = xstrndup(s + start, n - start);
334+			comma = findargcomma(args, strlen(args));
335+			if (comma < 0) {
336+				detail = cat3("$(", f->name, ")");
337+				evalerr("malformed function arguments", detail);
338+				free(detail);
339+				free(args);
340+				return xstrdup("");
341+			}
342+			lhs_raw = xstrndup(args, (size_t)comma);
343+			rhs_raw = xstrdup(args + comma + 1);
344+			free(args);
345+			lhs_exp = expandstr(env, lhs_raw);
346+			rhs_exp = expandstr(env, rhs_raw);
347+			free(lhs_raw);
348+			free(rhs_raw);
349+			val = f->fn.f2(lhs_exp, rhs_exp);
350+			free(lhs_exp);
351+			free(rhs_exp);
352+		}
353+		return val;
354+	}
355+	return 0;
356+}
357+
358+static char *
359+expandref(struct Env *env, const char *s, size_t n)
360+{
361+	size_t colon, eq;
362+	char *val, *unsup;
363+
364+	if (isplainvar(s, n))
365+		return expandvarref(env, s, n);
366+	if (issubstref(s, n, &colon, &eq))
367+		return expandsubstref(env, s, colon, eq, n);
368+	val = funcref(env, s, n);
369+	if (val)
370+		return val;
371+	unsup = xstrndup(s - 2, n + 3);
372+	evalerr("variable reference or function", unsup);
373+	free(unsup);
374+	return xstrndup(s - 2, n + 3);
375+}
376+
377+char *
378+expandstr(struct Env *env, const char *s)
379+{
380+	size_t i, j, n;
381+	char close;
382+	char *val;
383+	struct Buf out;
384+
385+	n = strlen(s);
386+	bufinit(&out, n + 1);
387+	for (i = 0; i < n; i++) {
388+		if (s[i] == '$' && i + 1 < n && s[i + 1] == '$') {
389+			bufappend(&out, "$$");
390+			i++;
391+			continue;
392+		}
393+		if (s[i] != '$' || i + 1 >= n || (s[i + 1] != '(' && s[i + 1] != '{')) {
394+			bufappendc(&out, s[i]);
395+			continue;
396+		}
397+		close = s[i + 1] == '(' ? ')' : '}';
398+		j = findclose(s, i, n, close);
399+		if (!j) {
400+			bufappendn(&out, s + i, n - i);
401+			break;
402+		}
403+		val = expandref(env, s + i + 2, j - i - 3);
404+		bufappend(&out, val);
405+		free(val);
406+		i = j - 1;
407+	}
408+	return bufdone(&out);
409+}
+271, -0
  1@@ -0,0 +1,271 @@
  2+#include "shinobi.h"
  3+#include "internal.h"
  4+
  5+#include <stdlib.h>
  6+#include <string.h>
  7+
  8+/*
  9+ * eval does the second pass over the built ast
 10+ *   seed builtins like CC and so on
 11+ *   apply assignment semantics
 12+ *   execute conditionals and flatten the chosen branch
 13+ */
 14+
 15+struct Var *
 16+findvar(struct Env *env, const char *name)
 17+{
 18+	size_t i;
 19+
 20+	for (i = 0; i < env->n; i++) {
 21+		if (strcmp(env->v[i].name, name) == 0)
 22+			return &env->v[i];
 23+	}
 24+	return 0;
 25+}
 26+
 27+void
 28+freeenv(struct Env *env)
 29+{
 30+	size_t i;
 31+
 32+	for (i = 0; i < env->n; i++) {
 33+		free(env->v[i].name);
 34+		free(env->v[i].val);
 35+	}
 36+	free(env->v);
 37+	env->v = 0;
 38+	env->n = 0;
 39+}
 40+
 41+void
 42+copyenv(struct Env *dst, const struct Env *src)
 43+{
 44+	size_t i;
 45+
 46+	memset(dst, 0, sizeof(*dst));
 47+	if (src->n)
 48+		dst->v = xrealloc(0, src->n * sizeof(dst->v[0]));
 49+	for (i = 0; i < src->n; i++) {
 50+		dst->v[i].name = xstrdup(src->v[i].name);
 51+		dst->v[i].val = xstrdup(src->v[i].val);
 52+		dst->v[i].simple = src->v[i].simple;
 53+	}
 54+	dst->n = src->n;
 55+}
 56+
 57+void
 58+evalassign(struct Env *env, const struct AssignNode *in)
 59+{
 60+	struct Var *v;
 61+	char *rhs, *joined;
 62+
 63+	switch (in->op) {
 64+	case ASSIGN_EQ:
 65+		envsetvar(env, in->lhs, xstrdup(in->rhs), 0);
 66+		break;
 67+	case ASSIGN_COLON_EQ:
 68+		envsetvar(env, in->lhs, expandstr(env, in->rhs), 1);
 69+		break;
 70+	case ASSIGN_QMARK_EQ:
 71+		if (!findvar(env, in->lhs))
 72+			envsetvar(env, in->lhs, xstrdup(in->rhs), 0);
 73+		break;
 74+	case ASSIGN_PLUS_EQ:
 75+		v = findvar(env, in->lhs);
 76+		if (!v) {
 77+			envsetvar(env, in->lhs, xstrdup(in->rhs), 0);
 78+			break;
 79+		}
 80+		rhs = v->simple ? expandstr(env, in->rhs) : xstrdup(in->rhs);
 81+		joined = cat3(v->val, " ", rhs);
 82+		free(rhs);
 83+		free(v->val);
 84+		v->val = joined;
 85+		break;
 86+	case ASSIGN_BANG_EQ:
 87+		envsetvar(env, in->lhs, expandstr(env, in->rhs), 1);
 88+		break;
 89+	}
 90+}
 91+
 92+static int
 93+testcond(struct Env *env, const struct CondNode *cond)
 94+{
 95+	char *a, *b, *name;
 96+	struct Var *v;
 97+	int ok;
 98+
 99+	if (cond->kind == COND_IFDEF || cond->kind == COND_IFNDEF) {
100+		name = expandstr(env, cond->arg1);
101+		v = findvar(env, name);
102+		ok = v != 0;
103+		free(name);
104+		if (cond->kind == COND_IFNDEF)
105+			ok = !ok;
106+		return ok;
107+	}
108+
109+	a = cond->arg1 ? expandstr(env, cond->arg1) : xstrdup("");
110+	b = cond->arg2 ? expandstr(env, cond->arg2) : xstrdup("");
111+	ok = strcmp(a, b) == 0;
112+	free(a);
113+	free(b);
114+	if (cond->kind == COND_IFNEQ)
115+		ok = !ok;
116+	return ok;
117+}
118+
119+static void
120+copywords(struct StrList *out, const struct StrList *in, struct Env *env)
121+{
122+	size_t i;
123+
124+	memset(out, 0, sizeof(*out));
125+	for (i = 0; i < in->n; i++) {
126+		char *s;
127+
128+		s = expandstr(env, in->v[i]);
129+		splitwords(out, s, strlen(s));
130+		free(s);
131+	}
132+}
133+
134+static void
135+copyrecipes(struct RecipeList *out, const struct RecipeList *in)
136+{
137+	size_t i;
138+
139+	memset(out, 0, sizeof(*out));
140+	for (i = 0; i < in->n; i++) {
141+		out->v = xrealloc(out->v, (out->n + 1) * sizeof(out->v[0]));
142+		out->v[out->n++] = xstrdup(in->v[i]);
143+	}
144+}
145+
146+static int
147+evalnodes(const struct NodeList *in, struct NodeList *out, struct Env *env)
148+{
149+	size_t i;
150+
151+	memset(out, 0, sizeof(*out));
152+	for (i = 0; i < in->n; i++) {
153+		const struct Node *src;
154+		struct Node node;
155+
156+		src = &in->v[i];
157+		memset(&node, 0, sizeof(node));
158+		node.kind = src->kind;
159+		node.loc = src->loc;
160+		switch (src->kind) {
161+		case NODE_BLANK:
162+			break;
163+		case NODE_COMMENT:
164+			node.data.raw.text = xstrdup(src->data.raw.text);
165+			break;
166+		case NODE_RAW: {
167+			char *exp;
168+
169+			exp = expandstr(env, src->data.raw.text);
170+			free(exp);
171+			node.kind = NODE_BLANK;
172+			break;
173+		}
174+		case NODE_INCLUDE:
175+			node.data.include.optional = src->data.include.optional;
176+			node.data.include.path = expandstr(env, src->data.include.path);
177+			break;
178+		case NODE_COND:
179+			if (testcond(env, &src->data.cond)) {
180+				if (evalnodes(&src->data.cond.thenpart, out, env) < 0)
181+					return -1;
182+			} else {
183+				if (evalnodes(&src->data.cond.elsepart, out, env) < 0)
184+					return -1;
185+			}
186+			continue;
187+		case NODE_ASSIGN:
188+			node.data.assign.lhs = xstrdup(src->data.assign.lhs);
189+			node.data.assign.op = src->data.assign.op;
190+			node.data.assign.tspec = src->data.assign.tspec;
191+			if (src->data.assign.op == ASSIGN_COLON_EQ || src->data.assign.op == ASSIGN_BANG_EQ)
192+				node.data.assign.rhs = expandstr(env, src->data.assign.rhs);
193+			else
194+				node.data.assign.rhs = xstrdup(src->data.assign.rhs);
195+			copywords(&node.data.assign.targets, &src->data.assign.targets, env);
196+			if (!src->data.assign.tspec)
197+				evalassign(env, &src->data.assign);
198+			break;
199+		case NODE_RULE:
200+			copywords(&node.data.rule.targets, &src->data.rule.targets, env);
201+			copywords(&node.data.rule.prereqs, &src->data.rule.prereqs, env);
202+			copywords(&node.data.rule.order_only, &src->data.rule.order_only, env);
203+			copyrecipes(&node.data.rule.recipes, &src->data.rule.recipes);
204+			break;
205+		}
206+		addnode(out, node);
207+	}
208+	return 0;
209+}
210+
211+int
212+eval(const struct Ast *ast, struct Ast *out)
213+{
214+	struct Env env;
215+	int rc;
216+
217+	memset(&env, 0, sizeof(env));
218+	seedenv(&env);
219+	resetevalerrs();
220+	rc = evalnodes((const struct NodeList *)ast, (struct NodeList *)out, &env);
221+	freeenv(&env);
222+	if (rc == 0 && getevalerrs())
223+		return -1;
224+	return rc;
225+}
226+
227+static void
228+freenodes(struct NodeList *list)
229+{
230+	size_t i;
231+
232+	for (i = 0; i < list->n; i++) {
233+		switch (list->v[i].kind) {
234+		case NODE_COMMENT:
235+		case NODE_RAW:
236+			free(list->v[i].data.raw.text);
237+			break;
238+		case NODE_ASSIGN:
239+			free(list->v[i].data.assign.lhs);
240+			free(list->v[i].data.assign.rhs);
241+			freestrs(&list->v[i].data.assign.targets);
242+			break;
243+		case NODE_RULE:
244+			freestrs(&list->v[i].data.rule.targets);
245+			freestrs(&list->v[i].data.rule.prereqs);
246+			freestrs(&list->v[i].data.rule.order_only);
247+			freerecipes(&list->v[i].data.rule.recipes);
248+			break;
249+		case NODE_INCLUDE:
250+			free(list->v[i].data.include.path);
251+			break;
252+		case NODE_COND:
253+			free(list->v[i].data.cond.arg1);
254+			free(list->v[i].data.cond.arg2);
255+			free(list->v[i].data.cond.raw);
256+			freenodes(&list->v[i].data.cond.thenpart);
257+			freenodes(&list->v[i].data.cond.elsepart);
258+			break;
259+		case NODE_BLANK:
260+			break;
261+		}
262+	}
263+	free(list->v);
264+	list->v = 0;
265+	list->n = 0;
266+}
267+
268+void
269+freeast(struct Ast *ast)
270+{
271+	freenodes((struct NodeList *)ast);
272+}
+114, -0
  1@@ -306,6 +306,120 @@ fnaddprefix(const char *prefix, const char *names)
  2 	return out;
  3 }
  4 
  5+char *
  6+fnaddsuffix(const char *suffix, const char *names)
  7+{
  8+	char *out;
  9+	size_t cap, len, i, j, nsuffix;
 10+
 11+	nsuffix = strlen(suffix);
 12+	cap = strlen(names) + nsuffix + 1;
 13+	if (cap < 64)
 14+		cap = 64;
 15+	len = 0;
 16+	out = xmalloc(cap);
 17+	out[0] = 0;
 18+
 19+	for (i = 0; names[i];) {
 20+		size_t wn, need;
 21+
 22+		while (names[i] && isspace((unsigned char)names[i]))
 23+			i++;
 24+		if (!names[i])
 25+			break;
 26+		j = i;
 27+		while (names[j] && !isspace((unsigned char)names[j]))
 28+			j++;
 29+		wn = j - i;
 30+		need = len + wn + nsuffix + 2;
 31+		if (need > cap) {
 32+			while (cap < need)
 33+				cap *= 2;
 34+			out = xrealloc(out, cap);
 35+		}
 36+		if (len)
 37+			out[len++] = ' ';
 38+		memcpy(out + len, names + i, wn);
 39+		len += wn;
 40+		memcpy(out + len, suffix, nsuffix);
 41+		len += nsuffix;
 42+		out[len] = 0;
 43+		i = j;
 44+	}
 45+
 46+	return out;
 47+}
 48+
 49+static int
 50+cmpstr(const void *a, const void *b)
 51+{
 52+	const char *const *sa;
 53+	const char *const *sb;
 54+
 55+	sa = a;
 56+	sb = b;
 57+	return strcmp(*sa, *sb);
 58+}
 59+
 60+char *
 61+fnsort(const char *text)
 62+{
 63+	char **words;
 64+	char *out;
 65+	size_t i, j, n, cap, len;
 66+
 67+	words = 0;
 68+	n = 0;
 69+	for (i = 0; text[i];) {
 70+		size_t start;
 71+
 72+		while (text[i] && isspace((unsigned char)text[i]))
 73+			i++;
 74+		if (!text[i])
 75+			break;
 76+		start = i;
 77+		while (text[i] && !isspace((unsigned char)text[i]))
 78+			i++;
 79+		words = xrealloc(words, (n + 1) * sizeof(words[0]));
 80+		words[n++] = xstrndup(text + start, i - start);
 81+	}
 82+
 83+	if (n == 0)
 84+		return xstrdup("");
 85+
 86+	qsort(words, n, sizeof(words[0]), cmpstr);
 87+
 88+	cap = strlen(text) + 1;
 89+	if (cap < 64)
 90+		cap = 64;
 91+	len = 0;
 92+	out = xmalloc(cap);
 93+	out[0] = 0;
 94+	for (i = 0; i < n; i++) {
 95+		size_t wn, need;
 96+
 97+		if (i > 0 && strcmp(words[i - 1], words[i]) == 0)
 98+			continue;
 99+		wn = strlen(words[i]);
100+		need = len + wn + 2;
101+		if (need > cap) {
102+			while (cap < need)
103+				cap *= 2;
104+			out = xrealloc(out, cap);
105+		}
106+		if (len)
107+			out[len++] = ' ';
108+		memcpy(out + len, words[i], wn);
109+		len += wn;
110+		out[len] = 0;
111+	}
112+
113+	for (j = 0; j < n; j++)
114+		free(words[j]);
115+	free(words);
116+	return out;
117+}
118+
119 char *
120 fninfo(const char *text)
121 {
+37, -1
 1@@ -1,5 +1,6 @@
 2 #include "gnu/pattern.h"
 3 
 4+#include <unistd.h>
 5 #include <stdlib.h>
 6 #include <string.h>
 7 
 8@@ -124,8 +125,39 @@ collectpat(struct PatRules *rules, const struct RuleNode *rule)
 9 	}
10 }
11 
12+static int
13+pattargetexists(const struct Graph *graph, const char *name)
14+{
15+	const struct Target *t;
16+
17+	if (access(name, F_OK) == 0)
18+		return 1;
19+	t = findctarget(graph, name);
20+	if (!t)
21+		return 0;
22+	return t->recipes.n > 0 || t->prereqs.n > 0 || t->order_only.n > 0;
23+}
24+
25+static int
26+patruleviable(const struct PatRule *rule, const struct Graph *graph, const char *stem)
27+{
28+	size_t i;
29+
30+	for (i = 0; i < rule->prereqs.n; i++) {
31+		char *s;
32+		int ok;
33+
34+		s = applystem(rule->prereqs.v[i], stem);
35+		ok = pattargetexists(graph, s);
36+		free(s);
37+		if (!ok)
38+			return 0;
39+	}
40+	return 1;
41+}
42+
43 int
44-instpatrule(const struct PatRules *rules, struct Target *t, struct Env *env)
45+instpatrule(const struct PatRules *rules, const struct Graph *graph, struct Target *t, struct Env *env)
46 {
47 	size_t i, j;
48 
49@@ -135,6 +167,10 @@ instpatrule(const struct PatRules *rules, struct Target *t, struct Env *env)
50 		stem = matchpat(rules->v[i].target, t->name);
51 		if (!stem)
52 			continue;
53+		if (!patruleviable(&rules->v[i], graph, stem)) {
54+			free(stem);
55+			continue;
56+		}
57 		for (j = 0; j < rules->v[i].prereqs.n; j++) {
58 			char *s;
59 
+1, -1
1@@ -18,7 +18,7 @@ struct PatRules {
2 int ispat(const char *s);
3 int patmatches(const char *pat, const char *name);
4 void collectpat(struct PatRules *rules, const struct RuleNode *rule);
5-int instpatrule(const struct PatRules *rules, struct Target *t, struct Env *env);
6+int instpatrule(const struct PatRules *rules, const struct Graph *graph, struct Target *t, struct Env *env);
7 void freepatrule(struct PatRules *rules);
8 
9 #endif
+1, -1
1@@ -192,7 +192,7 @@ buildgraph(const struct Ast *ast, struct Graph *graph)
2 				struct Env env;
3 
4 				targetenv(&gs, &env, t->name);
5-				instpatrule(&gs.patterns, t, &env);
6+				instpatrule(&gs.patterns, graph, t, &env);
7 				matched = t->recipes.n > 0;
8 				if (!matched) {
9 					instsufrule(&gs.sufs, graph, t, &env);
+4, -0
 1@@ -31,6 +31,8 @@ const struct Target *findctarget(const struct Graph *graph, const char *name);
 2 
 3 struct Var *findvar(struct Env *env, const char *name);
 4 char *expandstr(struct Env *env, const char *s);
 5+void resetevalerrs(void);
 6+int getevalerrs(void);
 7 void seedenv(struct Env *env);
 8 void freeenv(struct Env *env);
 9 void copyenv(struct Env *dst, const struct Env *src);
10@@ -41,6 +43,8 @@ char *fnshell(const char *cmd);
11 char *fnfilter(const char *patterns, const char *text);
12 char *fnfilterout(const char *patterns, const char *text);
13 char *fnaddprefix(const char *prefix, const char *names);
14+char *fnaddsuffix(const char *suffix, const char *names);
15+char *fnsort(const char *text);
16 char *fninfo(const char *text);
17 
18 #endif