main shrub/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, rawdepth;
188
189	depth = 0;
190	rawdepth = 0;
191	for (i = 0; i < n; i++) {
192		if (s[i] == '$' && i + 1 < n && (s[i + 1] == '(' || s[i + 1] == '{')) {
193			depth++;
194			i++;
195			continue;
196		}
197		if (s[i] == '(') {
198			rawdepth++;
199			continue;
200		}
201		if (s[i] == ')' && rawdepth > 0) {
202			rawdepth--;
203			continue;
204		}
205		if ((s[i] == ')' || s[i] == '}') && depth > 0) {
206			depth--;
207			continue;
208		}
209		if (s[i] == ',' && depth == 0 && rawdepth == 0)
210			return (ptrdiff_t)i;
211	}
212	return -1;
213}
214
215static size_t
216findclose(const char *s, size_t i, size_t n, char close)
217{
218	size_t j, inner;
219	char open;
220
221	j = i + 2;
222	inner = 1;
223	open = close == ')' ? '(' : '{';
224	while (j < n && inner) {
225		if (s[j] == '$' && j + 1 < n && (s[j + 1] == '(' || s[j + 1] == '{')) {
226			inner++;
227			j += 2;
228			continue;
229		}
230		if (s[j] == open) {
231			inner++;
232			j++;
233			continue;
234		}
235		if (s[j] == close)
236			inner--;
237		j++;
238	}
239	return inner ? 0 : j;
240}
241
242static char *expandvarref(struct EvalCtx *ctx, const char *s, size_t n);
243static char *expandref(struct EvalCtx *ctx, const char *s, size_t n);
244
245static char *
246autoprereqsval(const struct StrList *prereqs, char kind)
247{
248	if (kind == '+')
249		return joinstrs(prereqs, " ");
250
251	{
252		size_t i;
253		struct StrList uniq;
254		char *joined;
255
256		memset(&uniq, 0, sizeof(uniq));
257		for (i = 0; i < prereqs->n; i++) {
258			if (!hasword(&uniq, prereqs->v[i]))
259				addstr(&uniq, prereqs->v[i]);
260		}
261		joined = joinstrs(&uniq, " ");
262		freestrs(&uniq);
263		return joined;
264	}
265}
266
267static char *
268expandname(struct EvalCtx *ctx, const char *s)
269{
270	size_t i, j, n;
271	char close;
272	char *val;
273	struct Buf out;
274
275	n = strlen(s);
276	bufinit(&out, n + 1);
277	for (i = 0; i < n; i++) {
278		if (s[i] != '$' || i + 1 >= n) {
279			bufappendc(&out, s[i]);
280			continue;
281		}
282		if (s[i + 1] == '$') {
283			bufappendc(&out, '$');
284			i++;
285			continue;
286		}
287		if (s[i + 1] == '@' && ctx->auto_target) {
288			bufappend(&out, ctx->auto_target);
289			i++;
290			continue;
291		}
292		if ((s[i + 1] == '<') && ctx->auto_prereqs) {
293			if (ctx->auto_prereqs->n > 0)
294				bufappend(&out, ctx->auto_prereqs->v[0]);
295			i++;
296			continue;
297		}
298		if ((s[i + 1] == '^' || s[i + 1] == '+' || s[i + 1] == '?') &&
299		    ctx->auto_prereqs) {
300			char *joined;
301
302			joined = autoprereqsval(ctx->auto_prereqs, s[i + 1]);
303			bufappend(&out, joined);
304			free(joined);
305			i++;
306			continue;
307		}
308		if (s[i + 1] == '*' && ctx->auto_stem) {
309			bufappend(&out, ctx->auto_stem);
310			i++;
311			continue;
312		}
313		if (s[i + 1] != '(' && s[i + 1] != '{') {
314			val = expandvarref(ctx, s + i + 1, 1);
315			bufappend(&out, val);
316			free(val);
317			i++;
318			continue;
319		}
320		close = s[i + 1] == '(' ? ')' : '}';
321		j = findclose(s, i, n, close);
322		if (!j) {
323			bufappendn(&out, s + i, n - i);
324			break;
325		}
326		val = expandref(ctx, s + i + 2, j - i - 3);
327		bufappend(&out, val);
328		free(val);
329		i = j - 1;
330	}
331	return bufdone(&out);
332}
333
334static char *
335expandvarref(struct EvalCtx *ctx, const char *s, size_t n)
336{
337	char *name, *raw, *val;
338	struct Var *v;
339
340	raw = xstrndup(s, n);
341	name = expandname(ctx, raw);
342	free(raw);
343
344	if (n > 0 && ctx->call) {
345		size_t i;
346		int numeric;
347
348		numeric = 1;
349		for (i = 0; name[i]; i++) {
350			if (!isdigit((unsigned char)name[i])) {
351				numeric = 0;
352				break;
353			}
354		}
355		if (numeric) {
356			unsigned long idx;
357			char *end;
358
359			idx = strtoul(name, &end, 10);
360			if (*end == 0 && idx < ctx->call->nargs) {
361				val = expandstr(ctx, ctx->call->args[idx]);
362				free(name);
363				return val;
364			}
365		}
366	}
367
368	v = findvar(ctx->env, name);
369	free(name);
370	if (!v)
371		return xstrdup("");
372	if (v->simple)
373		return xstrdup(v->val);
374	/* expansion could be $(eval ...) and replace this var, freeing its
375	 * current value so keep the input alive for the whole expansion */
376	raw = xstrdup(v->val);
377	val = expandstr(ctx, raw);
378	free(raw);
379	return val;
380}
381
382static char *
383expandsubstref(struct EvalCtx *ctx, const char *s, size_t colon, size_t eq, size_t n)
384{
385	char *name, *from, *to, *val;
386	struct Var *v;
387	char *base;
388	int auto_name;
389
390	{
391		char *nameraw, *toraw, *fromraw;
392
393		nameraw = xstrndup(s, colon);
394		name = expandname(ctx, nameraw);
395		free(nameraw);
396		fromraw = xstrndup(s + colon + 1, eq - colon - 1);
397		toraw = xstrndup(s + eq + 1, n - eq - 1);
398		from = expandstr(ctx, fromraw);
399		to = expandstr(ctx, toraw);
400		free(fromraw);
401		free(toraw);
402		v = findvar(ctx->env, name);
403	}
404	base = 0;
405	auto_name = 0;
406	if (v) {
407		char *rawval;
408
409		rawval = xstrdup(v->val);
410		base = expandstr(ctx, rawval);
411		free(rawval);
412	} else if (name[0] && name[1] == 0) {
413		/* we handle some substitution refs on automatic vars like $(@:.o=.c). */
414		switch (name[0]) {
415		case '@':
416			auto_name = 1;
417			if (ctx->auto_target)
418				base = xstrdup(ctx->auto_target);
419			break;
420		case '<':
421			auto_name = 1;
422			if (ctx->auto_prereqs && ctx->auto_prereqs->n > 0)
423				base = xstrdup(ctx->auto_prereqs->v[0]);
424			break;
425		case '^':
426		case '+':
427		case '?':
428			auto_name = 1;
429			if (ctx->auto_prereqs)
430				base = autoprereqsval(ctx->auto_prereqs, name[0]);
431			break;
432		case '*':
433			auto_name = 1;
434			if (ctx->auto_stem)
435				base = xstrdup(ctx->auto_stem);
436			break;
437		}
438	}
439	free(name);
440
441	if (base) {
442		if (ctx->mode == MODE_POSIX_2008 && (strchr(from, '%') || strchr(to, '%'))) {
443			evalerr(ctx,
444			        "pattern macros in substitution references are not valid in POSIX 2008",
445			        0);
446			free(base);
447			free(from);
448			free(to);
449			return xstrdup("");
450		}
451		val = substval(base, from, to);
452		free(base);
453	} else if (auto_name) {
454		char *inner;
455
456		inner = xstrndup(s, n);
457		val = cat3("$(", inner, ")");
458		free(inner);
459	} else {
460		val = xstrdup("");
461	}
462	free(from);
463	free(to);
464	return val;
465}
466
467typedef char *(*fn1_t)(const char *);
468typedef char *(*fn2_t)(const char *, const char *);
469typedef char *(*fn3_t)(const char *, const char *, const char *);
470typedef char *(*ctxfn_t)(struct EvalCtx *, const char *);
471
472enum FuncMode {
473	FNEXP1,
474	FNEXP2,
475	FNEXP3,
476	FNCTX,
477};
478
479/* posix2008 and posix2024 both have no builtin functions, gnu has all of them */
480
481struct func {
482	const char *name;
483	enum FuncMode mode;
484	union {
485		fn1_t f1;
486		fn2_t f2;
487		fn3_t f3;
488		ctxfn_t ctx;
489	} fn;
490};
491
492static const char *const allfuncs[] = {
493    "abspath",
494    "addprefix",
495    "addsuffix",
496    "and",
497    "basename",
498    "call",
499    "dir",
500    "error",
501    "eval",
502    "file",
503    "filter",
504    "filter-out",
505    "findstring",
506    "firstword",
507    "flavor",
508    "foreach",
509    "guile",
510    "if",
511    "info",
512    "intcmp",
513    "join",
514    "lastword",
515    "let",
516    "notdir",
517    "or",
518    "origin",
519    "patsubst",
520    "realpath",
521    "shell",
522    "sort",
523    "strip",
524    "subst",
525    "suffix",
526    "value",
527    "warning",
528    "wildcard",
529    "word",
530    "wordlist",
531    "words",
532    0,
533};
534
535static const struct func funcs[] = {
536    {"wildcard",   FNEXP1, {.f1 = fnwildcard}},
537    {"shell",      FNEXP1, {.f1 = fnshell}},
538    {"sort",       FNEXP1, {.f1 = fnsort}},
539    {"info",       FNCTX,  {.ctx = fninfo}},
540    {"origin",     FNCTX,  {.ctx = fnorigin}},
541    {"notdir",     FNEXP1, {.f1 = fnnotdir}},
542    {"dir",        FNEXP1, {.f1 = fndir}},
543    {"basename",   FNEXP1, {.f1 = fnbasename}},
544    {"filter-out", FNEXP2, {.f2 = fnfilterout}},
545    {"filter",     FNEXP2, {.f2 = fnfilter}},
546    {"findstring", FNEXP2, {.f2 = fnfindstring}},
547    {"addprefix",  FNEXP2, {.f2 = fnaddprefix}},
548    {"addsuffix",  FNEXP2, {.f2 = fnaddsuffix}},
549    {"join",       FNEXP2, {.f2 = fnjoin}},
550    {"strip",      FNEXP1, {.f1 = fnstrip}},
551    {"subst",      FNEXP3, {.f3 = fnsubst}},
552    {"patsubst",   FNEXP3, {.f3 = fnpatsubst}},
553    {"if",         FNEXP3, {.f3 = fnif}},
554    {"or",         FNCTX,  {.ctx = fnor}},
555    {"call",       FNCTX,  {.ctx = fncall}},
556    {"foreach",    FNCTX,  {.ctx = fnforeach}},
557    {"eval",       FNCTX,  {.ctx = fneval}},
558    {"value",      FNCTX,  {.ctx = fnvalue}},
559    {"words",      FNEXP1, {.f1 = fnwords}},
560    {"word",       FNEXP2, {.f2 = fnword}},
561    {"wordlist",   FNEXP3, {.f3 = fnwordlist}},
562    {"firstword",  FNEXP1, {.f1 = fnfirstword}},
563    {"lastword",   FNEXP1, {.f1 = fnlastword}},
564    {"realpath",   FNEXP1, {.f1 = fnrealpath}},
565    {"abspath",    FNEXP1, {.f1 = fnabspath}},
566    {0,            FNEXP1, {.f1 = 0}},
567};
568
569static char *
570funcref(struct EvalCtx *ctx, const char *s, size_t n)
571{
572	size_t i, namelen, start;
573	const struct func *f;
574	char *val;
575
576	if (ctx->mode != MODE_GNU)
577		return 0;
578	for (i = 0; funcs[i].name; i++) {
579		f = &funcs[i];
580		namelen = strlen(f->name);
581		if (n < namelen || memcmp(s, f->name, namelen) != 0)
582			continue;
583		start = namelen;
584		while (start < n && isspace((unsigned char)s[start]))
585			start++;
586		if (f->mode == FNCTX) {
587			char *args;
588
589			args = xstrndup(s + start, n - start);
590			val = f->fn.ctx(ctx, args);
591			free(args);
592		} else if (f->mode == FNEXP1) {
593			char *raw, *exp;
594
595			raw = xstrndup(s + start, n - start);
596			exp = expandstr(ctx, raw);
597			free(raw);
598			val = f->fn.f1(exp);
599			free(exp);
600		} else if (f->mode == FNEXP2) {
601			char *args, *lhs_raw, *rhs_raw, *lhs_exp, *rhs_exp, *detail;
602			ptrdiff_t comma;
603
604			args = xstrndup(s + start, n - start);
605			comma = findargcomma(args, strlen(args));
606			if (comma < 0) {
607				detail = cat3("$(", f->name, ")");
608				evalerr(ctx, "malformed function arguments", detail);
609				free(detail);
610				free(args);
611				return xstrdup("");
612			}
613			lhs_raw = xstrndup(args, (size_t)comma);
614			rhs_raw = xstrdup(args + comma + 1);
615			free(args);
616			lhs_exp = expandstr(ctx, lhs_raw);
617			rhs_exp = expandstr(ctx, rhs_raw);
618			free(lhs_raw);
619			free(rhs_raw);
620			val = f->fn.f2(lhs_exp, rhs_exp);
621			free(lhs_exp);
622			free(rhs_exp);
623		} else {
624			char *args, *a1r, *a2r, *a3r, *a1e, *a2e, *a3e, *detail;
625			ptrdiff_t c1, c2;
626			size_t rest;
627
628			args = xstrndup(s + start, n - start);
629			c1 = findargcomma(args, strlen(args));
630			if (c1 < 0) {
631				detail = cat3("$(", f->name, ")");
632				evalerr(ctx, "malformed function arguments", detail);
633				free(detail);
634				free(args);
635				return xstrdup("");
636			}
637			a1r = xstrndup(args, (size_t)c1);
638			rest = (size_t)c1 + 1;
639			c2 = findargcomma(args + rest, strlen(args + rest));
640			if (c2 < 0) {
641				a2r = xstrdup(args + rest);
642				a3r = xstrdup("");
643			} else {
644				a2r = xstrndup(args + rest, (size_t)c2);
645				a3r = xstrdup(args + rest + (size_t)c2 + 1);
646			}
647			free(args);
648			a1e = expandstr(ctx, a1r);
649			a2e = expandstr(ctx, a2r);
650			a3e = expandstr(ctx, a3r);
651			free(a1r);
652			free(a2r);
653			free(a3r);
654			val = f->fn.f3(a1e, a2e, a3e);
655			free(a1e);
656			free(a2e);
657			free(a3e);
658		}
659		return val;
660	}
661	return 0;
662}
663
664static int
665isfunc(const char *s, size_t n)
666{
667	size_t i;
668
669	for (i = 0; allfuncs[i]; i++) {
670		if (strlen(allfuncs[i]) == n && strncmp(allfuncs[i], s, n) == 0)
671			return 1;
672	}
673	return 0;
674}
675
676static char *
677expandref(struct EvalCtx *ctx, const char *s, size_t n)
678{
679	size_t colon, eq, i;
680	char *val;
681
682	if (isplainvar(s, n))
683		return expandvarref(ctx, s, n);
684	if (issubstref(s, n, &colon, &eq))
685		return expandsubstref(ctx, s, colon, eq, n);
686	val = funcref(ctx, s, n);
687	if (val)
688		return val;
689	for (i = 0; i < n && !isspace((unsigned char)s[i]); i++)
690		;
691	if (i < n && isfunc(s, i)) {
692		char *detail;
693
694		detail = xstrndup(s, n);
695		if (ctx->mode != MODE_GNU)
696			evalerr(ctx, "functions are only valid in GNU", detail);
697		else
698			evalerr(ctx, "i don't know how to handle that yet", detail);
699		free(detail);
700		return xstrdup("");
701	}
702	return expandvarref(ctx, s, n);
703}
704
705char *
706expandstr(struct EvalCtx *ctx, const char *s)
707{
708	size_t i, j, n;
709	char close;
710	char *val;
711	struct Buf out;
712
713	n = strlen(s);
714	bufinit(&out, n + 1);
715	for (i = 0; i < n; i++) {
716		if (s[i] == '$' && i + 1 < n && s[i + 1] == '$') {
717			bufappendc(&out, '$');
718			i++;
719			continue;
720		}
721		if (s[i] != '$' || i + 1 >= n) {
722			bufappendc(&out, s[i]);
723			continue;
724		}
725		if (s[i + 1] != '(' && s[i + 1] != '{') {
726			if (s[i + 1] == '@' && ctx->auto_target) {
727				bufappend(&out, ctx->auto_target);
728				i++;
729				continue;
730			}
731			if (s[i + 1] == '<' && ctx->auto_prereqs) {
732				if (ctx->auto_prereqs->n > 0)
733					bufappend(&out, ctx->auto_prereqs->v[0]);
734				i++;
735				continue;
736			}
737			if ((s[i + 1] == '^' || s[i + 1] == '+' || s[i + 1] == '?') &&
738			    ctx->auto_prereqs) {
739				char *joined;
740
741				joined = autoprereqsval(ctx->auto_prereqs, s[i + 1]);
742				bufappend(&out, joined);
743				free(joined);
744				i++;
745				continue;
746			}
747			if (s[i + 1] == '*' && ctx->auto_stem) {
748				bufappend(&out, ctx->auto_stem);
749				i++;
750				continue;
751			}
752			/* leave unresolved automatic vars for later translation */
753			if (s[i + 1] == '@' || s[i + 1] == '<' || s[i + 1] == '^' ||
754			    s[i + 1] == '+' || s[i + 1] == '?' || s[i + 1] == '*' ||
755			    s[i + 1] == '%') {
756				bufappendc(&out, s[i]);
757				continue;
758			}
759			/* some single char variable like $x */
760			val = expandvarref(ctx, s + i + 1, 1);
761			bufappend(&out, val);
762			free(val);
763			i++;
764			continue;
765		}
766		close = s[i + 1] == '(' ? ')' : '}';
767		j = findclose(s, i, n, close);
768		if (!j) {
769			bufappendn(&out, s + i, n - i);
770			break;
771		}
772		val = expandref(ctx, s + i + 2, j - i - 3);
773		bufappend(&out, val);
774		free(val);
775		i = j - 1;
776	}
777	return bufdone(&out);
778}