main shinobi / src / eval / dollar.c
  1#include "internal.h"
  2
  3#include <ctype.h>
  4#include <stddef.h>
  5#include <stdio.h>
  6#include <stdlib.h>
  7#include <string.h>
  8
  9/* $() ${} expansion is handled here */
 10
 11struct Buf {
 12	char *s;
 13	size_t len;
 14	size_t cap;
 15};
 16
 17static void
 18evalerr(struct EvalCtx *ctx, const char *msg, const char *detail)
 19{
 20	dielikemake(ctx->cur_path, ctx->cur_line, msg, detail);
 21	ctx->errors++;
 22}
 23
 24static void
 25bufinit(struct Buf *buf, size_t cap)
 26{
 27	buf->cap = cap ? cap : 1;
 28	buf->len = 0;
 29	buf->s = xmalloc(buf->cap);
 30	buf->s[0] = 0;
 31}
 32
 33static void
 34bufgrow(struct Buf *buf, size_t need)
 35{
 36	while (buf->cap < need)
 37		buf->cap *= 2;
 38	buf->s = xrealloc(buf->s, buf->cap);
 39}
 40
 41static void
 42bufappendn(struct Buf *buf, const char *s, size_t n)
 43{
 44	if (buf->len + n + 1 > buf->cap)
 45		bufgrow(buf, buf->len + n + 1);
 46	memcpy(buf->s + buf->len, s, n);
 47	buf->len += n;
 48	buf->s[buf->len] = 0;
 49}
 50
 51static void
 52bufappend(struct Buf *buf, const char *s)
 53{
 54	bufappendn(buf, s, strlen(s));
 55}
 56
 57static void
 58bufappendc(struct Buf *buf, char c)
 59{
 60	if (buf->len + 2 > buf->cap)
 61		bufgrow(buf, buf->len + 2);
 62	buf->s[buf->len++] = c;
 63	buf->s[buf->len] = 0;
 64}
 65
 66static char *
 67bufdone(struct Buf *buf)
 68{
 69	return buf->s;
 70}
 71
 72static int
 73isplainvar(const char *s, size_t n)
 74{
 75	size_t i;
 76
 77	if (!n)
 78		return 0;
 79	for (i = 0; i < n; i++) {
 80		if (!(isalnum((unsigned char)s[i]) || s[i] == '_'))
 81			return 0;
 82	}
 83	return 1;
 84}
 85
 86static char *
 87substword(const char *word, size_t n, const char *from, const char *to)
 88{
 89	size_t nfrom, nto;
 90	const char *pct;
 91	char *out;
 92
 93	nfrom = strlen(from);
 94	nto = strlen(to);
 95	pct = strchr(from, '%');
 96	if (!pct) {
 97		if (n < nfrom || memcmp(word + n - nfrom, from, nfrom) != 0)
 98			return xstrndup(word, n);
 99		out = xmalloc(n - nfrom + nto + 1);
100		memcpy(out, word, n - nfrom);
101		memcpy(out + n - nfrom, to, nto);
102		out[n - nfrom + nto] = 0;
103		return out;
104	}
105	{
106		size_t pre, suf, stem;
107		const char *tpct;
108
109		pre = (size_t)(pct - from);
110		suf = nfrom - pre - 1;
111		if (n < pre + suf)
112			return xstrndup(word, n);
113		if (memcmp(word, from, pre) != 0 || memcmp(word + n - suf, pct + 1, suf) != 0)
114			return xstrndup(word, n);
115		stem = n - pre - suf;
116		tpct = strchr(to, '%');
117		if (!tpct)
118			return xstrdup(to);
119		{
120			size_t tpre, tsuf;
121
122			tpre = (size_t)(tpct - to);
123			tsuf = nto - tpre - 1;
124			out = xmalloc(tpre + stem + tsuf + 1);
125			memcpy(out, to, tpre);
126			memcpy(out + tpre, word + pre, stem);
127			memcpy(out + tpre + stem, tpct + 1, tsuf);
128			out[tpre + stem + tsuf] = 0;
129			return out;
130		}
131	}
132}
133
134static char *
135substval(const char *val, const char *from, const char *to)
136{
137	size_t i, j, n;
138	char *part;
139	struct Buf out;
140
141	n = strlen(val);
142	bufinit(&out, n + 1);
143	for (i = 0; i < n;) {
144		if (isspace((unsigned char)val[i])) {
145			bufappendc(&out, val[i]);
146			i++;
147			continue;
148		}
149		j = i;
150		while (j < n && !isspace((unsigned char)val[j]))
151			j++;
152		part = substword(val + i, j - i, from, to);
153		bufappend(&out, part);
154		free(part);
155		i = j;
156	}
157	return bufdone(&out);
158}
159
160static int
161issubstref(const char *s, size_t n, size_t *colon, size_t *eq)
162{
163	size_t i;
164
165	for (i = 0; i < n; i++) {
166		if (s[i] == ':') {
167			*colon = i;
168			break;
169		}
170		if (isspace((unsigned char)s[i]))
171			return 0;
172	}
173	if (i == 0 || i >= n)
174		return 0;
175	for (i = *colon + 1; i < n; i++) {
176		if (s[i] == '=') {
177			*eq = i;
178			return i > *colon + 1;
179		}
180	}
181	return 0;
182}
183
184static ptrdiff_t
185findargcomma(const char *s, size_t n)
186{
187	size_t i, depth;
188
189	depth = 0;
190	for (i = 0; i < n; i++) {
191		if (s[i] == '$' && i + 1 < n && (s[i + 1] == '(' || s[i + 1] == '{')) {
192			depth++;
193			i++;
194			continue;
195		}
196		if ((s[i] == ')' || s[i] == '}') && depth > 0) {
197			depth--;
198			continue;
199		}
200		if (s[i] == ',' && depth == 0)
201			return (ptrdiff_t)i;
202	}
203	return -1;
204}
205
206static size_t
207findclose(const char *s, size_t i, size_t n, char close)
208{
209	size_t j, inner;
210
211	j = i + 2;
212	inner = 1;
213	while (j < n && inner) {
214		if (s[j] == '$' && j + 1 < n && (s[j + 1] == '(' || s[j + 1] == '{')) {
215			inner++;
216			j += 2;
217			continue;
218		}
219		if (s[j] == close)
220			inner--;
221		j++;
222	}
223	return inner ? 0 : j;
224}
225
226static char *expandvarref(struct EvalCtx *ctx, const char *s, size_t n);
227static char *expandref(struct EvalCtx *ctx, const char *s, size_t n);
228
229static char *
230autoprereqsval(const struct StrList *prereqs, char kind)
231{
232	if (kind == '+')
233		return joinstrs(prereqs, " ");
234
235	{
236		size_t i;
237		struct StrList uniq;
238		char *joined;
239
240		memset(&uniq, 0, sizeof(uniq));
241		for (i = 0; i < prereqs->n; i++) {
242			if (!hasword(&uniq, prereqs->v[i]))
243				addstr(&uniq, prereqs->v[i]);
244		}
245		joined = joinstrs(&uniq, " ");
246		freestrs(&uniq);
247		return joined;
248	}
249}
250
251static char *
252expandname(struct EvalCtx *ctx, const char *s)
253{
254	size_t i, j, n;
255	char close;
256	char *val;
257	struct Buf out;
258
259	n = strlen(s);
260	bufinit(&out, n + 1);
261	for (i = 0; i < n; i++) {
262		if (s[i] != '$' || i + 1 >= n) {
263			bufappendc(&out, s[i]);
264			continue;
265		}
266		if (s[i + 1] == '$') {
267			bufappendc(&out, '$');
268			i++;
269			continue;
270		}
271		if (s[i + 1] == '@' && ctx->auto_target) {
272			bufappend(&out, ctx->auto_target);
273			i++;
274			continue;
275		}
276		if ((s[i + 1] == '<') && ctx->auto_prereqs) {
277			if (ctx->auto_prereqs->n > 0)
278				bufappend(&out, ctx->auto_prereqs->v[0]);
279			i++;
280			continue;
281		}
282		if ((s[i + 1] == '^' || s[i + 1] == '+' || s[i + 1] == '?') &&
283		    ctx->auto_prereqs) {
284			char *joined;
285
286			joined = autoprereqsval(ctx->auto_prereqs, s[i + 1]);
287			bufappend(&out, joined);
288			free(joined);
289			i++;
290			continue;
291		}
292		if (s[i + 1] == '*' && ctx->auto_stem) {
293			bufappend(&out, ctx->auto_stem);
294			i++;
295			continue;
296		}
297		if (s[i + 1] != '(' && s[i + 1] != '{') {
298			val = expandvarref(ctx, s + i + 1, 1);
299			bufappend(&out, val);
300			free(val);
301			i++;
302			continue;
303		}
304		close = s[i + 1] == '(' ? ')' : '}';
305		j = findclose(s, i, n, close);
306		if (!j) {
307			bufappendn(&out, s + i, n - i);
308			break;
309		}
310		val = expandref(ctx, s + i + 2, j - i - 3);
311		bufappend(&out, val);
312		free(val);
313		i = j - 1;
314	}
315	return bufdone(&out);
316}
317
318static char *
319expandvarref(struct EvalCtx *ctx, const char *s, size_t n)
320{
321	char *name, *raw, *val;
322	struct Var *v;
323
324	raw = xstrndup(s, n);
325	name = expandname(ctx, raw);
326	free(raw);
327
328	if (n > 0 && ctx->call) {
329		size_t i;
330		int numeric;
331
332		numeric = 1;
333		for (i = 0; name[i]; i++) {
334			if (!isdigit((unsigned char)name[i])) {
335				numeric = 0;
336				break;
337			}
338		}
339		if (numeric) {
340			unsigned long idx;
341			char *end;
342
343			idx = strtoul(name, &end, 10);
344			if (*end == 0 && idx < ctx->call->nargs) {
345				val = expandstr(ctx, ctx->call->args[idx]);
346				free(name);
347				return val;
348			}
349		}
350	}
351
352	v = findvar(ctx->env, name);
353	free(name);
354	if (!v)
355		return xstrdup("");
356	if (v->simple)
357		return xstrdup(v->val);
358	val = expandstr(ctx, v->val);
359	return val;
360}
361
362static char *
363expandsubstref(struct EvalCtx *ctx, const char *s, size_t colon, size_t eq, size_t n)
364{
365	char *name, *from, *to, *val;
366	struct Var *v;
367	char *base;
368	int auto_name;
369
370	{
371		char *nameraw, *toraw, *fromraw;
372
373		nameraw = xstrndup(s, colon);
374		name = expandname(ctx, nameraw);
375		free(nameraw);
376		fromraw = xstrndup(s + colon + 1, eq - colon - 1);
377		toraw = xstrndup(s + eq + 1, n - eq - 1);
378		from = expandstr(ctx, fromraw);
379		to = expandstr(ctx, toraw);
380		free(fromraw);
381		free(toraw);
382		v = findvar(ctx->env, name);
383	}
384	base = 0;
385	auto_name = 0;
386	if (v) {
387		base = expandstr(ctx, v->val);
388	} else if (name[0] && name[1] == 0) {
389		/* we handle some substitution refs on automatic vars like $(@:.o=.c). */
390		switch (name[0]) {
391		case '@':
392			auto_name = 1;
393			if (ctx->auto_target)
394				base = xstrdup(ctx->auto_target);
395			break;
396		case '<':
397			auto_name = 1;
398			if (ctx->auto_prereqs && ctx->auto_prereqs->n > 0)
399				base = xstrdup(ctx->auto_prereqs->v[0]);
400			break;
401		case '^':
402		case '+':
403		case '?':
404			auto_name = 1;
405			if (ctx->auto_prereqs)
406				base = autoprereqsval(ctx->auto_prereqs, name[0]);
407			break;
408		case '*':
409			auto_name = 1;
410			if (ctx->auto_stem)
411				base = xstrdup(ctx->auto_stem);
412			break;
413		}
414	}
415	free(name);
416
417	if (base) {
418		if (ctx->mode == MODE_POSIX_2008 && (strchr(from, '%') || strchr(to, '%'))) {
419			evalerr(ctx,
420			        "pattern macros in substitution references are not valid in POSIX 2008",
421			        0);
422			free(base);
423			free(from);
424			free(to);
425			return xstrdup("");
426		}
427		val = substval(base, from, to);
428		free(base);
429	} else if (auto_name) {
430		char *inner;
431
432		inner = xstrndup(s, n);
433		val = cat3("$(", inner, ")");
434		free(inner);
435	} else {
436		val = xstrdup("");
437	}
438	free(from);
439	free(to);
440	return val;
441}
442
443typedef char *(*fn1_t)(const char *);
444typedef char *(*fn2_t)(const char *, const char *);
445typedef char *(*fn3_t)(const char *, const char *, const char *);
446typedef char *(*ctxfn_t)(struct EvalCtx *, const char *);
447
448enum FuncMode {
449	FNEXP1,
450	FNEXP2,
451	FNEXP3,
452	FNCTX,
453};
454
455/* posix2008 and posix2024 both have no builtin functions, gnu has all of them */
456
457struct func {
458	const char *name;
459	enum FuncMode mode;
460	union {
461		fn1_t f1;
462		fn2_t f2;
463		fn3_t f3;
464		ctxfn_t ctx;
465	} fn;
466};
467
468static const char *const allfuncs[] = {
469    "abspath",
470    "addprefix",
471    "addsuffix",
472    "and",
473    "basename",
474    "call",
475    "dir",
476    "error",
477    "eval",
478    "file",
479    "filter",
480    "filter-out",
481    "findstring",
482    "firstword",
483    "flavor",
484    "foreach",
485    "guile",
486    "if",
487    "info",
488    "intcmp",
489    "join",
490    "lastword",
491    "let",
492    "notdir",
493    "or",
494    "origin",
495    "patsubst",
496    "realpath",
497    "shell",
498    "sort",
499    "strip",
500    "subst",
501    "suffix",
502    "value",
503    "warning",
504    "wildcard",
505    "word",
506    "wordlist",
507    "words",
508    0,
509};
510
511static const struct func funcs[] = {
512    {"wildcard",   FNEXP1, {.f1 = fnwildcard}},
513    {"shell",      FNEXP1, {.f1 = fnshell}},
514    {"sort",       FNEXP1, {.f1 = fnsort}},
515    {"info",       FNCTX,  {.ctx = fninfo}},
516    {"origin",     FNCTX,  {.ctx = fnorigin}},
517    {"notdir",     FNEXP1, {.f1 = fnnotdir}},
518    {"dir",        FNEXP1, {.f1 = fndir}},
519    {"basename",   FNEXP1, {.f1 = fnbasename}},
520    {"filter-out", FNEXP2, {.f2 = fnfilterout}},
521    {"filter",     FNEXP2, {.f2 = fnfilter}},
522    {"findstring", FNEXP2, {.f2 = fnfindstring}},
523    {"addprefix",  FNEXP2, {.f2 = fnaddprefix}},
524    {"addsuffix",  FNEXP2, {.f2 = fnaddsuffix}},
525    {"join",       FNEXP2, {.f2 = fnjoin}},
526    {"strip",      FNEXP1, {.f1 = fnstrip}},
527    {"subst",      FNEXP3, {.f3 = fnsubst}},
528    {"patsubst",   FNEXP3, {.f3 = fnpatsubst}},
529    {"if",         FNEXP3, {.f3 = fnif}},
530    {"call",       FNCTX,  {.ctx = fncall}},
531    {"foreach",    FNCTX,  {.ctx = fnforeach}},
532    {"eval",       FNCTX,  {.ctx = fneval}},
533    {"value",      FNCTX,  {.ctx = fnvalue}},
534    {"words",      FNEXP1, {.f1 = fnwords}},
535    {"word",       FNEXP2, {.f2 = fnword}},
536    {"wordlist",   FNEXP3, {.f3 = fnwordlist}},
537    {"firstword",  FNEXP1, {.f1 = fnfirstword}},
538    {"lastword",   FNEXP1, {.f1 = fnlastword}},
539    {"realpath",   FNEXP1, {.f1 = fnrealpath}},
540    {"abspath",    FNEXP1, {.f1 = fnabspath}},
541    {0,            FNEXP1, {.f1 = 0}},
542};
543
544static char *
545funcref(struct EvalCtx *ctx, const char *s, size_t n)
546{
547	size_t i, namelen, start;
548	const struct func *f;
549	char *val;
550
551	if (ctx->mode != MODE_GNU)
552		return 0;
553	for (i = 0; funcs[i].name; i++) {
554		f = &funcs[i];
555		namelen = strlen(f->name);
556		if (n < namelen || memcmp(s, f->name, namelen) != 0)
557			continue;
558		start = namelen;
559		while (start < n && isspace((unsigned char)s[start]))
560			start++;
561		if (f->mode == FNCTX) {
562			char *args;
563
564			args = xstrndup(s + start, n - start);
565			val = f->fn.ctx(ctx, args);
566			free(args);
567		} else if (f->mode == FNEXP1) {
568			char *raw, *exp;
569
570			raw = xstrndup(s + start, n - start);
571			exp = expandstr(ctx, raw);
572			free(raw);
573			val = f->fn.f1(exp);
574			free(exp);
575		} else if (f->mode == FNEXP2) {
576			char *args, *lhs_raw, *rhs_raw, *lhs_exp, *rhs_exp, *detail;
577			ptrdiff_t comma;
578
579			args = xstrndup(s + start, n - start);
580			comma = findargcomma(args, strlen(args));
581			if (comma < 0) {
582				detail = cat3("$(", f->name, ")");
583				evalerr(ctx, "malformed function arguments", detail);
584				free(detail);
585				free(args);
586				return xstrdup("");
587			}
588			lhs_raw = xstrndup(args, (size_t)comma);
589			rhs_raw = xstrdup(args + comma + 1);
590			free(args);
591			lhs_exp = expandstr(ctx, lhs_raw);
592			rhs_exp = expandstr(ctx, rhs_raw);
593			free(lhs_raw);
594			free(rhs_raw);
595			val = f->fn.f2(lhs_exp, rhs_exp);
596			free(lhs_exp);
597			free(rhs_exp);
598		} else {
599			char *args, *a1r, *a2r, *a3r, *a1e, *a2e, *a3e, *detail;
600			ptrdiff_t c1, c2;
601			size_t rest;
602
603			args = xstrndup(s + start, n - start);
604			c1 = findargcomma(args, strlen(args));
605			if (c1 < 0) {
606				detail = cat3("$(", f->name, ")");
607				evalerr(ctx, "malformed function arguments", detail);
608				free(detail);
609				free(args);
610				return xstrdup("");
611			}
612			a1r = xstrndup(args, (size_t)c1);
613			rest = (size_t)c1 + 1;
614			c2 = findargcomma(args + rest, strlen(args + rest));
615			if (c2 < 0) {
616				a2r = xstrdup(args + rest);
617				a3r = xstrdup("");
618			} else {
619				a2r = xstrndup(args + rest, (size_t)c2);
620				a3r = xstrdup(args + rest + (size_t)c2 + 1);
621			}
622			free(args);
623			a1e = expandstr(ctx, a1r);
624			a2e = expandstr(ctx, a2r);
625			a3e = expandstr(ctx, a3r);
626			free(a1r);
627			free(a2r);
628			free(a3r);
629			val = f->fn.f3(a1e, a2e, a3e);
630			free(a1e);
631			free(a2e);
632			free(a3e);
633		}
634		return val;
635	}
636	return 0;
637}
638
639static int
640isfunc(const char *s, size_t n)
641{
642	size_t i;
643
644	for (i = 0; allfuncs[i]; i++) {
645		if (strlen(allfuncs[i]) == n && strncmp(allfuncs[i], s, n) == 0)
646			return 1;
647	}
648	return 0;
649}
650
651static char *
652expandref(struct EvalCtx *ctx, const char *s, size_t n)
653{
654	size_t colon, eq, i;
655	char *val;
656
657	if (isplainvar(s, n))
658		return expandvarref(ctx, s, n);
659	if (issubstref(s, n, &colon, &eq))
660		return expandsubstref(ctx, s, colon, eq, n);
661	val = funcref(ctx, s, n);
662	if (val)
663		return val;
664	for (i = 0; i < n && !isspace((unsigned char)s[i]); i++)
665		;
666	if (i < n && isfunc(s, i)) {
667		char *detail;
668
669		detail = xstrndup(s, n);
670		if (ctx->mode != MODE_GNU)
671			evalerr(ctx, "functions are only valid in GNU", detail);
672		else
673			evalerr(ctx, "i don't know how to handle that yet", detail);
674		free(detail);
675		return xstrdup("");
676	}
677	return expandvarref(ctx, s, n);
678}
679
680char *
681expandstr(struct EvalCtx *ctx, const char *s)
682{
683	size_t i, j, n;
684	char close;
685	char *val;
686	struct Buf out;
687
688	n = strlen(s);
689	bufinit(&out, n + 1);
690	for (i = 0; i < n; i++) {
691		if (s[i] == '$' && i + 1 < n && s[i + 1] == '$') {
692			bufappendc(&out, '$');
693			i++;
694			continue;
695		}
696		if (s[i] != '$' || i + 1 >= n) {
697			bufappendc(&out, s[i]);
698			continue;
699		}
700		if (s[i + 1] != '(' && s[i + 1] != '{') {
701			if (s[i + 1] == '@' && ctx->auto_target) {
702				bufappend(&out, ctx->auto_target);
703				i++;
704				continue;
705			}
706			if (s[i + 1] == '<' && ctx->auto_prereqs) {
707				if (ctx->auto_prereqs->n > 0)
708					bufappend(&out, ctx->auto_prereqs->v[0]);
709				i++;
710				continue;
711			}
712			if ((s[i + 1] == '^' || s[i + 1] == '+' || s[i + 1] == '?') &&
713			    ctx->auto_prereqs) {
714				char *joined;
715
716				joined = autoprereqsval(ctx->auto_prereqs, s[i + 1]);
717				bufappend(&out, joined);
718				free(joined);
719				i++;
720				continue;
721			}
722			if (s[i + 1] == '*' && ctx->auto_stem) {
723				bufappend(&out, ctx->auto_stem);
724				i++;
725				continue;
726			}
727			/* leave unresolved automatic vars for later translation */
728			if (s[i + 1] == '@' || s[i + 1] == '<' || s[i + 1] == '^' ||
729			    s[i + 1] == '+' || s[i + 1] == '?' || s[i + 1] == '*' ||
730			    s[i + 1] == '%') {
731				bufappendc(&out, s[i]);
732				continue;
733			}
734			/* some single char variable like $x */
735			val = expandvarref(ctx, s + i + 1, 1);
736			bufappend(&out, val);
737			free(val);
738			i++;
739			continue;
740		}
741		close = s[i + 1] == '(' ? ')' : '}';
742		j = findclose(s, i, n, close);
743		if (!j) {
744			bufappendn(&out, s + i, n - i);
745			break;
746		}
747		val = expandref(ctx, s + i + 2, j - i - 3);
748		bufappend(&out, val);
749		free(val);
750		i = j - 1;
751	}
752	return bufdone(&out);
753}