main shrub/shinobi / src / gnu / functions.c
   1#include "internal.h"
   2
   3#include <ctype.h>
   4#include <glob.h>
   5#include <stddef.h>
   6#include <stdio.h>
   7#include <stdlib.h>
   8#include <string.h>
   9
  10/* implementations of gnu make builtin functions*/
  11
  12static char *trimspacesdup(const char *s);
  13static char *shellquote(const char *s);
  14
  15static int
  16matchword(const char *patterns, const char *word, size_t nword)
  17{
  18	size_t p0, p1;
  19
  20	for (p0 = 0; patterns[p0];) {
  21		const char *pat;
  22		char *cooked;
  23		size_t npat, i, n, pct, pre, suf;
  24		int haspct;
  25
  26		while (patterns[p0] && isspace((unsigned char)patterns[p0]))
  27			p0++;
  28		if (!patterns[p0])
  29			break;
  30		p1 = p0;
  31		while (patterns[p1] && !isspace((unsigned char)patterns[p1]))
  32			p1++;
  33		pat = patterns + p0;
  34		npat = p1 - p0;
  35		cooked = xmalloc(npat + 1);
  36		n = 0;
  37		pct = 0;
  38		haspct = 0;
  39		for (i = 0; i < npat; i++) {
  40			if (pat[i] == '\\' && i + 1 < npat && (pat[i + 1] == '\\' || pat[i + 1] == '%')) {
  41				cooked[n++] = pat[++i];
  42				continue;
  43			}
  44			if (pat[i] == '%' && !haspct) {
  45				pct = n;
  46				haspct = 1;
  47				continue;
  48			}
  49			cooked[n++] = pat[i];
  50		}
  51		cooked[n] = 0;
  52		if (!haspct) {
  53			if (n == nword && memcmp(cooked, word, n) == 0) {
  54				free(cooked);
  55				return 1;
  56			}
  57			free(cooked);
  58			p0 = p1;
  59			continue;
  60		}
  61		pre = pct;
  62		suf = n - pre;
  63		if (nword >= pre + suf &&
  64		    memcmp(cooked, word, pre) == 0 &&
  65		    memcmp(cooked + pre, word + nword - suf, suf) == 0) {
  66			free(cooked);
  67			return 1;
  68		}
  69		free(cooked);
  70		p0 = p1;
  71	}
  72	return 0;
  73}
  74
  75static char *
  76shellquote(const char *s)
  77{
  78	size_t i, len, cap;
  79	char *out;
  80
  81	cap = strlen(s) * 4 + 3;
  82	out = xmalloc(cap);
  83	len = 0;
  84	out[len++] = '\'';
  85	for (i = 0; s[i]; i++) {
  86		if (s[i] == '\'') {
  87			memcpy(out + len, "'\\''", 4);
  88			len += 4;
  89		} else {
  90			out[len++] = s[i];
  91		}
  92	}
  93	out[len++] = '\'';
  94	out[len] = 0;
  95	return out;
  96}
  97
  98char *
  99fnwildcard(const char *patterns)
 100{
 101	char *out;
 102	size_t len, cap;
 103	const char *p;
 104
 105	cap = 64;
 106	len = 0;
 107	out = xmalloc(cap);
 108	out[0] = 0;
 109
 110	p = patterns;
 111	while (*p) {
 112		glob_t g;
 113		size_t i;
 114		const char *start;
 115		char *pat;
 116		int rc;
 117
 118		while (*p && isspace((unsigned char)*p))
 119			p++;
 120		if (!*p)
 121			break;
 122		start = p;
 123		while (*p && !isspace((unsigned char)*p))
 124			p++;
 125		pat = xstrndup(start, (size_t)(p - start));
 126
 127		memset(&g, 0, sizeof(g));
 128		rc = glob(pat, 0, 0, &g);
 129		free(pat);
 130		if (rc == 0) {
 131			for (i = 0; i < g.gl_pathc; i++) {
 132				size_t plen, need;
 133
 134				plen = strlen(g.gl_pathv[i]);
 135				need = len + plen + 2;
 136				if (need > cap) {
 137					while (cap < need)
 138						cap *= 2;
 139					out = xrealloc(out, cap);
 140				}
 141				if (len)
 142					out[len++] = ' ';
 143				memcpy(out + len, g.gl_pathv[i], plen);
 144				len += plen;
 145				out[len] = 0;
 146			}
 147		}
 148		globfree(&g);
 149	}
 150	return out;
 151}
 152
 153char *
 154fnshell(const char *cmd)
 155{
 156	FILE *fp;
 157	char buf[4096];
 158	char *out;
 159	size_t len, cap, nread, i, j;
 160
 161	fp = popen(cmd, "r");
 162	if (!fp)
 163		return xstrdup("");
 164
 165	cap = 64;
 166	len = 0;
 167	out = xmalloc(cap);
 168	out[0] = 0;
 169
 170	while ((nread = fread(buf, 1, sizeof(buf), fp)) > 0) {
 171		if (len + nread + 1 > cap) {
 172			while (cap < len + nread + 1)
 173				cap *= 2;
 174			out = xrealloc(out, cap);
 175		}
 176		memcpy(out + len, buf, nread);
 177		len += nread;
 178	}
 179	out[len] = 0;
 180	pclose(fp);
 181
 182	while (len > 0 && (out[len - 1] == '\n' || out[len - 1] == '\r'))
 183		len--;
 184	for (i = 0, j = 0; i < len; i++) {
 185		unsigned char ch;
 186
 187		ch = (unsigned char)out[i];
 188		if (ch == '\n' || ch == '\r') {
 189			out[j++] = ' ';
 190			continue;
 191		}
 192		out[j++] = out[i];
 193	}
 194	out[j] = 0;
 195	return out;
 196}
 197
 198char *
 199fnfilter(const char *patterns, const char *text)
 200{
 201	char *out;
 202	size_t cap, len, i, j;
 203
 204	cap = strlen(text) + 1;
 205	if (cap < 64)
 206		cap = 64;
 207	len = 0;
 208	out = xmalloc(cap);
 209	out[0] = 0;
 210
 211	for (i = 0; text[i];) {
 212		int keep;
 213
 214		while (text[i] && isspace((unsigned char)text[i]))
 215			i++;
 216		if (!text[i])
 217			break;
 218		j = i;
 219		while (text[j] && !isspace((unsigned char)text[j]))
 220			j++;
 221
 222		keep = matchword(patterns, text + i, j - i);
 223
 224		if (keep) {
 225			size_t wn, need;
 226
 227			wn = j - i;
 228			need = len + wn + 2;
 229			if (need > cap) {
 230				while (cap < need)
 231					cap *= 2;
 232				out = xrealloc(out, cap);
 233			}
 234			if (len)
 235				out[len++] = ' ';
 236			memcpy(out + len, text + i, wn);
 237			len += wn;
 238			out[len] = 0;
 239		}
 240		i = j;
 241	}
 242
 243	return out;
 244}
 245
 246char *
 247fnfilterout(const char *patterns, const char *text)
 248{
 249	char *out;
 250	size_t cap, len, i, j;
 251
 252	cap = strlen(text) + 1;
 253	if (cap < 64)
 254		cap = 64;
 255	len = 0;
 256	out = xmalloc(cap);
 257	out[0] = 0;
 258
 259	for (i = 0; text[i];) {
 260		size_t wn, need;
 261
 262		while (text[i] && isspace((unsigned char)text[i]))
 263			i++;
 264		if (!text[i])
 265			break;
 266		j = i;
 267		while (text[j] && !isspace((unsigned char)text[j]))
 268			j++;
 269		if (matchword(patterns, text + i, j - i)) {
 270			i = j;
 271			continue;
 272		}
 273		wn = j - i;
 274		need = len + wn + 2;
 275		if (need > cap) {
 276			while (cap < need)
 277				cap *= 2;
 278			out = xrealloc(out, cap);
 279		}
 280		if (len)
 281			out[len++] = ' ';
 282		memcpy(out + len, text + i, wn);
 283		len += wn;
 284		out[len] = 0;
 285		i = j;
 286	}
 287
 288	return out;
 289}
 290
 291char *
 292fnfindstring(const char *find, const char *in)
 293{
 294	return strstr(in, find) ? xstrdup(find) : xstrdup("");
 295}
 296
 297char *
 298fnaddprefix(const char *prefix, const char *names)
 299{
 300	char *out;
 301	size_t cap, len, i, j, nprefix;
 302
 303	nprefix = strlen(prefix);
 304	cap = strlen(names) + nprefix + 1;
 305	if (cap < 64)
 306		cap = 64;
 307	len = 0;
 308	out = xmalloc(cap);
 309	out[0] = 0;
 310
 311	for (i = 0; names[i];) {
 312		size_t wn, need;
 313
 314		while (names[i] && isspace((unsigned char)names[i]))
 315			i++;
 316		if (!names[i])
 317			break;
 318		j = i;
 319		while (names[j] && !isspace((unsigned char)names[j]))
 320			j++;
 321		wn = j - i;
 322		need = len + nprefix + wn + 2;
 323		if (need > cap) {
 324			while (cap < need)
 325				cap *= 2;
 326			out = xrealloc(out, cap);
 327		}
 328		if (len)
 329			out[len++] = ' ';
 330		memcpy(out + len, prefix, nprefix);
 331		len += nprefix;
 332		memcpy(out + len, names + i, wn);
 333		len += wn;
 334		out[len] = 0;
 335		i = j;
 336	}
 337
 338	return out;
 339}
 340
 341char *
 342fnaddsuffix(const char *suffix, const char *names)
 343{
 344	char *out;
 345	size_t cap, len, i, j, nsuffix;
 346
 347	nsuffix = strlen(suffix);
 348	cap = strlen(names) + nsuffix + 1;
 349	if (cap < 64)
 350		cap = 64;
 351	len = 0;
 352	out = xmalloc(cap);
 353	out[0] = 0;
 354
 355	for (i = 0; names[i];) {
 356		size_t wn, need;
 357
 358		while (names[i] && isspace((unsigned char)names[i]))
 359			i++;
 360		if (!names[i])
 361			break;
 362		j = i;
 363		while (names[j] && !isspace((unsigned char)names[j]))
 364			j++;
 365		wn = j - i;
 366		need = len + wn + nsuffix + 2;
 367		if (need > cap) {
 368			while (cap < need)
 369				cap *= 2;
 370			out = xrealloc(out, cap);
 371		}
 372		if (len)
 373			out[len++] = ' ';
 374		memcpy(out + len, names + i, wn);
 375		len += wn;
 376		memcpy(out + len, suffix, nsuffix);
 377		len += nsuffix;
 378		out[len] = 0;
 379		i = j;
 380	}
 381
 382	return out;
 383}
 384
 385char *
 386fnjoin(const char *list1, const char *list2)
 387{
 388	char *out;
 389	size_t i1, j1, i2, j2, len, cap;
 390
 391	cap = strlen(list1) + strlen(list2) + 1;
 392	if (cap < 64)
 393		cap = 64;
 394	len = 0;
 395	out = xmalloc(cap);
 396	out[0] = 0;
 397
 398	i1 = 0;
 399	i2 = 0;
 400	for (;;) {
 401		size_t w1, w2, need;
 402
 403		while (list1[i1] && isspace((unsigned char)list1[i1]))
 404			i1++;
 405		while (list2[i2] && isspace((unsigned char)list2[i2]))
 406			i2++;
 407		if (!list1[i1] && !list2[i2])
 408			break;
 409
 410		j1 = i1;
 411		while (list1[j1] && !isspace((unsigned char)list1[j1]))
 412			j1++;
 413		j2 = i2;
 414		while (list2[j2] && !isspace((unsigned char)list2[j2]))
 415			j2++;
 416		w1 = j1 - i1;
 417		w2 = j2 - i2;
 418
 419		need = len + w1 + w2 + 2;
 420		if (need > cap) {
 421			while (cap < need)
 422				cap *= 2;
 423			out = xrealloc(out, cap);
 424		}
 425		if (len)
 426			out[len++] = ' ';
 427		if (w1) {
 428			memcpy(out + len, list1 + i1, w1);
 429			len += w1;
 430		}
 431		if (w2) {
 432			memcpy(out + len, list2 + i2, w2);
 433			len += w2;
 434		}
 435		out[len] = 0;
 436
 437		i1 = j1;
 438		i2 = j2;
 439	}
 440
 441	return out;
 442}
 443
 444char *
 445fnstrip(const char *text)
 446{
 447	char *out;
 448	size_t i, j, len, cap;
 449	int need_space;
 450
 451	cap = strlen(text) + 1;
 452	if (cap < 16)
 453		cap = 16;
 454	out = xmalloc(cap);
 455	len = 0;
 456	need_space = 0;
 457
 458	for (i = 0; text[i];) {
 459		while (text[i] && isspace((unsigned char)text[i]))
 460			i++;
 461		if (!text[i])
 462			break;
 463		if (need_space)
 464			out[len++] = ' ';
 465		j = i;
 466		while (text[j] && !isspace((unsigned char)text[j]))
 467			j++;
 468		if (len + (j - i) + 1 > cap) {
 469			while (cap < len + (j - i) + 1)
 470				cap *= 2;
 471			out = xrealloc(out, cap);
 472		}
 473		memcpy(out + len, text + i, j - i);
 474		len += j - i;
 475		need_space = 1;
 476		i = j;
 477	}
 478	out[len] = 0;
 479	return out;
 480}
 481
 482static int
 483cmpstr(const void *a, const void *b)
 484{
 485	const char *const *sa;
 486	const char *const *sb;
 487
 488	sa = a;
 489	sb = b;
 490	return strcmp(*sa, *sb);
 491}
 492
 493char *
 494fnsort(const char *text)
 495{
 496	char **words;
 497	char *out;
 498	size_t i, j, n, cap, len;
 499
 500	words = 0;
 501	n = 0;
 502	for (i = 0; text[i];) {
 503		size_t start;
 504
 505		while (text[i] && isspace((unsigned char)text[i]))
 506			i++;
 507		if (!text[i])
 508			break;
 509		start = i;
 510		while (text[i] && !isspace((unsigned char)text[i]))
 511			i++;
 512		words = xrealloc(words, (n + 1) * sizeof(words[0]));
 513		words[n++] = xstrndup(text + start, i - start);
 514	}
 515
 516	if (n == 0)
 517		return xstrdup("");
 518
 519	qsort(words, n, sizeof(words[0]), cmpstr);
 520
 521	cap = strlen(text) + 1;
 522	if (cap < 64)
 523		cap = 64;
 524	len = 0;
 525	out = xmalloc(cap);
 526	out[0] = 0;
 527	for (i = 0; i < n; i++) {
 528		size_t wn, need;
 529
 530		if (i > 0 && strcmp(words[i - 1], words[i]) == 0)
 531			continue;
 532		wn = strlen(words[i]);
 533		need = len + wn + 2;
 534		if (need > cap) {
 535			while (cap < need)
 536				cap *= 2;
 537			out = xrealloc(out, cap);
 538		}
 539		if (len)
 540			out[len++] = ' ';
 541		memcpy(out + len, words[i], wn);
 542		len += wn;
 543		out[len] = 0;
 544	}
 545
 546	for (j = 0; j < n; j++)
 547		free(words[j]);
 548	free(words);
 549	return out;
 550}
 551
 552char *
 553fninfo(struct EvalCtx *ctx, const char *args)
 554{
 555	char *text;
 556
 557	text = expandstr(ctx, args);
 558	if (ctx->avoid_io && ctx->side_effects) {
 559		char *cmd, *quoted;
 560
 561		/* this is kinda a hack, but it gets more tests to pass:
 562		 * we tag delayed $(info) output so the test wrapper can distinguish
 563		   synthetic info side effects from real normal echo-having recipes. */
 564		quoted = shellquote(text);
 565		cmd = xmalloc(strlen("printf '%s\\n'  # __shin_info__") + strlen(quoted) + 1);
 566		sprintf(cmd, "printf '%%s\\n' %s # __shin_info__", quoted);
 567		free(quoted);
 568		addstr(ctx->side_effects, cmd);
 569		free(cmd);
 570	} else {
 571		fputs(text, stdout);
 572		fputc('\n', stdout);
 573	}
 574	free(text);
 575	return xstrdup("");
 576}
 577
 578char *
 579fnorigin(struct EvalCtx *ctx, const char *args)
 580{
 581	char *name, *trimmed;
 582	struct Var *v;
 583	const char *origin;
 584
 585	name = expandstr(ctx, args);
 586	trimmed = trimspacesdup(name);
 587	free(name);
 588
 589	if (trimmed[0] == '@' && trimmed[1] == 0) {
 590		free(trimmed);
 591		return xstrdup("automatic");
 592	}
 593	if (trimmed[0] == '<' && trimmed[1] == 0) {
 594		free(trimmed);
 595		return xstrdup("automatic");
 596	}
 597	if (trimmed[0] == '^' && trimmed[1] == 0) {
 598		free(trimmed);
 599		return xstrdup("automatic");
 600	}
 601	if (trimmed[0] == '+' && trimmed[1] == 0) {
 602		free(trimmed);
 603		return xstrdup("automatic");
 604	}
 605	if (trimmed[0] == '?' && trimmed[1] == 0) {
 606		free(trimmed);
 607		return xstrdup("automatic");
 608	}
 609	if (trimmed[0] == '*' && trimmed[1] == 0) {
 610		free(trimmed);
 611		return xstrdup("automatic");
 612	}
 613	if (trimmed[0] == '%' && trimmed[1] == 0) {
 614		free(trimmed);
 615		return xstrdup("automatic");
 616	}
 617
 618	v = findvar(ctx->env, trimmed);
 619	if (!v) {
 620		free(trimmed);
 621		return xstrdup("undefined");
 622	}
 623
 624	switch (v->origin) {
 625	case ORIGIN_DEFAULT:
 626		origin = "default";
 627		break;
 628	case ORIGIN_ENV:
 629		origin = "environment";
 630		break;
 631	case ORIGIN_FILE:
 632		origin = "file";
 633		break;
 634	case ORIGIN_ENV_OVERRIDE:
 635		origin = "environment override";
 636		break;
 637	case ORIGIN_COMMAND:
 638		origin = "command line";
 639		break;
 640	case ORIGIN_OVERRIDE:
 641		origin = "override";
 642		break;
 643	default:
 644		origin = "undefined";
 645		break;
 646	}
 647	free(trimmed);
 648	return xstrdup(origin);
 649}
 650
 651char *
 652fnvalue(struct EvalCtx *ctx, const char *args)
 653{
 654	char *name, *trimmed;
 655	struct Var *v;
 656
 657	name = expandstr(ctx, args);
 658	trimmed = trimspacesdup(name);
 659	free(name);
 660	v = findvar(ctx->env, trimmed);
 661	free(trimmed);
 662	if (!v)
 663		return xstrdup("");
 664	return xstrdup(v->val);
 665}
 666
 667char *
 668fnnotdir(const char *names)
 669{
 670	size_t i, j, k, len, cap, need, wn;
 671	const char *slash;
 672	char *out;
 673
 674	cap = strlen(names) + 1;
 675	if (cap < 16)
 676		cap = 16;
 677	len = 0;
 678	out = xmalloc(cap);
 679	out[0] = 0;
 680	for (i = 0; names[i];) {
 681		while (names[i] && isspace((unsigned char)names[i]))
 682			i++;
 683		if (!names[i])
 684			break;
 685		j = i;
 686		while (names[j] && !isspace((unsigned char)names[j]))
 687			j++;
 688		slash = 0;
 689		for (k = i; k < j; k++) {
 690			if (names[k] == '/')
 691				slash = names + k;
 692		}
 693		wn = slash ? (size_t)(names + j - slash - 1) : j - i;
 694		need = len + wn + 2;
 695		if (need > cap) {
 696			while (cap < need)
 697				cap *= 2;
 698			out = xrealloc(out, cap);
 699		}
 700		if (len)
 701			out[len++] = ' ';
 702		memcpy(out + len, slash ? slash + 1 : names + i, wn);
 703		len += wn;
 704		out[len] = 0;
 705		i = j;
 706	}
 707	return out;
 708}
 709
 710char *
 711fndir(const char *names)
 712{
 713	size_t i, j, k, len, cap, need, dlen;
 714	const char *slash;
 715	char *out;
 716
 717	cap = strlen(names) + 1;
 718	if (cap < 16)
 719		cap = 16;
 720	len = 0;
 721	out = xmalloc(cap);
 722	out[0] = 0;
 723	for (i = 0; names[i];) {
 724		while (names[i] && isspace((unsigned char)names[i]))
 725			i++;
 726		if (!names[i])
 727			break;
 728		j = i;
 729		while (names[j] && !isspace((unsigned char)names[j]))
 730			j++;
 731		slash = 0;
 732		for (k = i; k < j; k++) {
 733			if (names[k] == '/')
 734				slash = names + k;
 735		}
 736		dlen = slash ? (size_t)(slash - (names + i)) + 1 : 2;
 737		need = len + dlen + 2;
 738		if (need > cap) {
 739			while (cap < need)
 740				cap *= 2;
 741			out = xrealloc(out, cap);
 742		}
 743		if (len)
 744			out[len++] = ' ';
 745		if (slash) {
 746			memcpy(out + len, names + i, dlen);
 747		} else {
 748			memcpy(out + len, "./", 2);
 749		}
 750		len += dlen;
 751		out[len] = 0;
 752		i = j;
 753	}
 754	return out;
 755}
 756
 757char *
 758fnbasename(const char *names)
 759{
 760	size_t i, j, k, len, cap, need, wn;
 761	const char *fnstart, *dot;
 762	char *out;
 763
 764	cap = strlen(names) + 1;
 765	if (cap < 16)
 766		cap = 16;
 767	len = 0;
 768	out = xmalloc(cap);
 769	out[0] = 0;
 770	for (i = 0; names[i];) {
 771		while (names[i] && isspace((unsigned char)names[i]))
 772			i++;
 773		if (!names[i])
 774			break;
 775		j = i;
 776		while (names[j] && !isspace((unsigned char)names[j]))
 777			j++;
 778		wn = j - i;
 779		fnstart = names + i;
 780		dot = 0;
 781		for (k = i; k < j; k++) {
 782			if (names[k] == '/') {
 783				fnstart = names + k + 1;
 784				dot = 0;
 785			} else if (names[k] == '.' && names + k > fnstart)
 786				dot = names + k;
 787		}
 788		if (dot)
 789			wn = (size_t)(dot - (names + i));
 790		need = len + wn + 2;
 791		if (need > cap) {
 792			while (cap < need)
 793				cap *= 2;
 794			out = xrealloc(out, cap);
 795		}
 796		if (len)
 797			out[len++] = ' ';
 798		memcpy(out + len, names + i, wn);
 799		len += wn;
 800		out[len] = 0;
 801		i = j;
 802	}
 803	return out;
 804}
 805
 806char *
 807fnsubst(const char *from, const char *to, const char *text)
 808{
 809	size_t nfrom, nto, i, len, cap, need;
 810	char *out;
 811
 812	nfrom = strlen(from);
 813	nto = strlen(to);
 814	if (!nfrom)
 815		return xstrdup(text);
 816	cap = strlen(text) + 1;
 817	if (cap < 16)
 818		cap = 16;
 819	len = 0;
 820	out = xmalloc(cap);
 821	out[0] = 0;
 822	for (i = 0; text[i];) {
 823		if (strncmp(text + i, from, nfrom) == 0) {
 824			need = len + nto + 1;
 825			if (need > cap) {
 826				while (cap < need)
 827					cap *= 2;
 828				out = xrealloc(out, cap);
 829			}
 830			memcpy(out + len, to, nto);
 831			len += nto;
 832			i += nfrom;
 833		} else {
 834			need = len + 2;
 835			if (need > cap) {
 836				cap *= 2;
 837				out = xrealloc(out, cap);
 838			}
 839			out[len++] = text[i++];
 840		}
 841	}
 842	out[len] = 0;
 843	return out;
 844}
 845
 846static char *
 847patsubstword(const char *word, size_t wn, const char *pattern, const char *replacement)
 848{
 849	size_t npat, nrep, pre, suf, stem, rpre, rsuf;
 850	const char *pct, *rpct;
 851	char *out;
 852
 853	npat = strlen(pattern);
 854	nrep = strlen(replacement);
 855	pct = strchr(pattern, '%');
 856	if (!pct) {
 857		if (wn == npat && memcmp(word, pattern, wn) == 0)
 858			return xstrdup(replacement);
 859		return xstrndup(word, wn);
 860	}
 861	pre = (size_t)(pct - pattern);
 862	suf = npat - pre - 1;
 863	if (wn < pre + suf)
 864		return xstrndup(word, wn);
 865	if (memcmp(word, pattern, pre) != 0 || memcmp(word + wn - suf, pct + 1, suf) != 0)
 866		return xstrndup(word, wn);
 867	stem = wn - pre - suf;
 868	rpct = strchr(replacement, '%');
 869	if (!rpct)
 870		return xstrdup(replacement);
 871	rpre = (size_t)(rpct - replacement);
 872	rsuf = nrep - rpre - 1;
 873	out = xmalloc(rpre + stem + rsuf + 1);
 874	memcpy(out, replacement, rpre);
 875	memcpy(out + rpre, word + pre, stem);
 876	memcpy(out + rpre + stem, rpct + 1, rsuf);
 877	out[rpre + stem + rsuf] = 0;
 878	return out;
 879}
 880
 881char *
 882fnpatsubst(const char *pattern, const char *replacement, const char *text)
 883{
 884	size_t i, j, wlen, len, cap, need;
 885	char *w, *out;
 886
 887	cap = strlen(text) + 1;
 888	if (cap < 16)
 889		cap = 16;
 890	len = 0;
 891	out = xmalloc(cap);
 892	out[0] = 0;
 893	for (i = 0; text[i];) {
 894		while (text[i] && isspace((unsigned char)text[i]))
 895			i++;
 896		if (!text[i])
 897			break;
 898		j = i;
 899		while (text[j] && !isspace((unsigned char)text[j]))
 900			j++;
 901		w = patsubstword(text + i, j - i, pattern, replacement);
 902		wlen = strlen(w);
 903		need = len + wlen + 2;
 904		if (need > cap) {
 905			while (cap < need)
 906				cap *= 2;
 907			out = xrealloc(out, cap);
 908		}
 909		if (len)
 910			out[len++] = ' ';
 911		memcpy(out + len, w, wlen);
 912		len += wlen;
 913		out[len] = 0;
 914		free(w);
 915		i = j;
 916	}
 917	return out;
 918}
 919
 920char *
 921fnif(const char *cond, const char *then, const char *otherwise)
 922{
 923	const char *p;
 924
 925	for (p = cond; *p && isspace((unsigned char)*p); p++)
 926		;
 927	return xstrdup(*p ? then : otherwise);
 928}
 929
 930char *
 931fnwords(const char *text)
 932{
 933	size_t n;
 934	char buf[32];
 935
 936	n = 0;
 937	while (*text) {
 938		while (*text && isspace((unsigned char)*text))
 939			text++;
 940		if (!*text)
 941			break;
 942		n++;
 943		while (*text && !isspace((unsigned char)*text))
 944			text++;
 945	}
 946	snprintf(buf, sizeof(buf), "%zu", n);
 947	return xstrdup(buf);
 948}
 949
 950char *
 951fnword(const char *n, const char *list)
 952{
 953	size_t idx, i, wstart;
 954	char *end;
 955
 956	while (isspace((unsigned char)*n))
 957		n++;
 958	if (!*n)
 959		return xstrdup("");
 960	idx = (size_t)strtoul(n, &end, 10);
 961	if (end == n || idx == 0)
 962		return xstrdup("");
 963
 964	i = 0;
 965	while (list[i]) {
 966		while (list[i] && isspace((unsigned char)list[i]))
 967			i++;
 968		if (!list[i])
 969			break;
 970		wstart = i;
 971		while (list[i] && !isspace((unsigned char)list[i]))
 972			i++;
 973		if (--idx == 0)
 974			return xstrndup(list + wstart, i - wstart);
 975	}
 976	return xstrdup("");
 977}
 978
 979char *
 980fnwordlist(const char *s, const char *e, const char *list)
 981{
 982	size_t start, end, n, i, wstart, wlen, len, cap, need;
 983	char *out, *ep;
 984
 985	while (isspace((unsigned char)*s))
 986		s++;
 987	while (isspace((unsigned char)*e))
 988		e++;
 989	if (!*s || !*e)
 990		return xstrdup("");
 991	start = (size_t)strtoul(s, &ep, 10);
 992	if (ep == s || start == 0)
 993		return xstrdup("");
 994	end = (size_t)strtoul(e, &ep, 10);
 995	if (ep == e)
 996		return xstrdup("");
 997	if (start > end)
 998		return xstrdup("");
 999
1000	cap = strlen(list) + 1;
1001	if (cap < 16)
1002		cap = 16;
1003	len = 0;
1004	out = xmalloc(cap);
1005	out[0] = 0;
1006
1007	n = 0;
1008	i = 0;
1009	while (list[i]) {
1010		while (list[i] && isspace((unsigned char)list[i]))
1011			i++;
1012		if (!list[i])
1013			break;
1014		wstart = i;
1015		while (list[i] && !isspace((unsigned char)list[i]))
1016			i++;
1017		wlen = i - wstart;
1018		n++;
1019		if (n < start)
1020			continue;
1021		if (n > end)
1022			break;
1023		need = len + wlen + 2;
1024		if (need > cap) {
1025			while (cap < need)
1026				cap *= 2;
1027			out = xrealloc(out, cap);
1028		}
1029		if (len)
1030			out[len++] = ' ';
1031		memcpy(out + len, list + wstart, wlen);
1032		len += wlen;
1033		out[len] = 0;
1034	}
1035	return out;
1036}
1037
1038char *
1039fnfirstword(const char *list)
1040{
1041	size_t i, j;
1042
1043	for (i = 0; list[i] && isspace((unsigned char)list[i]); i++)
1044		;
1045	if (!list[i])
1046		return xstrdup("");
1047	for (j = i; list[j] && !isspace((unsigned char)list[j]); j++)
1048		;
1049	return xstrndup(list + i, j - i);
1050}
1051
1052char *
1053fnlastword(const char *list)
1054{
1055	const char *last;
1056	size_t lastlen;
1057	const char *p;
1058
1059	last = 0;
1060	lastlen = 0;
1061	p = list;
1062	while (*p) {
1063		while (*p && isspace((unsigned char)*p))
1064			p++;
1065		if (!*p)
1066			break;
1067		last = p;
1068		while (*p && !isspace((unsigned char)*p))
1069			p++;
1070		lastlen = (size_t)(p - last);
1071	}
1072	return last ? xstrndup(last, lastlen) : xstrdup("");
1073}
1074
1075static char **
1076splitargsraw(const char *s, size_t *out_n)
1077{
1078	size_t i, start, depth, argc, n;
1079	char **argv;
1080
1081	while (*s && isspace((unsigned char)*s))
1082		s++;
1083	n = strlen(s);
1084	argv = 0;
1085	argc = 0;
1086	start = 0;
1087	depth = 0;
1088	for (i = 0; i < n; i++) {
1089		if (s[i] == '$' && i + 1 < n && (s[i + 1] == '(' || s[i + 1] == '{')) {
1090			depth++;
1091			i++;
1092			continue;
1093		}
1094		if ((s[i] == ')' || s[i] == '}') && depth > 0) {
1095			depth--;
1096			continue;
1097		}
1098		if (s[i] != ',' || depth != 0)
1099			continue;
1100		argv = xrealloc(argv, (argc + 1) * sizeof(argv[0]));
1101		argv[argc++] = xstrndup(s + start, i - start);
1102		start = i + 1;
1103	}
1104	argv = xrealloc(argv, (argc + 1) * sizeof(argv[0]));
1105	argv[argc++] = xstrndup(s + start, n - start);
1106	*out_n = argc;
1107	return argv;
1108}
1109
1110static void
1111freeargsraw(char **argv, size_t argc)
1112{
1113	size_t i;
1114
1115	for (i = 0; i < argc; i++)
1116		free(argv[i]);
1117	free(argv);
1118}
1119
1120static char *
1121trimspacesdup(const char *s)
1122{
1123	size_t i, j;
1124
1125	for (i = 0; s[i] && isspace((unsigned char)s[i]); i++)
1126		;
1127	for (j = strlen(s); j > i && isspace((unsigned char)s[j - 1]); j--)
1128		;
1129	return xstrndup(s + i, j - i);
1130}
1131
1132char *
1133fnrealpath(const char *names)
1134{
1135	size_t i, j, cap, len, need, rplen;
1136	char *out, *rp, *w;
1137
1138	cap = strlen(names) + 1;
1139	if (cap < 16)
1140		cap = 16;
1141	len = 0;
1142	out = xmalloc(cap);
1143	out[0] = 0;
1144	for (i = 0; names[i];) {
1145		while (names[i] && isspace((unsigned char)names[i]))
1146			i++;
1147		if (!names[i])
1148			break;
1149		j = i;
1150		while (names[j] && !isspace((unsigned char)names[j]))
1151			j++;
1152		w = xstrndup(names + i, j - i);
1153		rp = realpath(w, NULL);
1154		free(w);
1155		rplen = rp ? strlen(rp) : 0;
1156		need = len + rplen + 2;
1157		if (need > cap) {
1158			while (cap < need)
1159				cap *= 2;
1160			out = xrealloc(out, cap);
1161		}
1162		if (rp) {
1163			if (len)
1164				out[len++] = ' ';
1165			memcpy(out + len, rp, rplen);
1166			free(rp);
1167			len += rplen;
1168			out[len] = 0;
1169		}
1170		i = j;
1171	}
1172	return out;
1173}
1174
1175static char *
1176normabspath(const char *path)
1177{
1178	char *cwd, *jp, *np;
1179
1180	if (*path == '/') {
1181		return normpath(path);
1182	}
1183	cwd = getcwddup();
1184	jp = joinpath(cwd, path);
1185	np = normpath(jp);
1186	free(cwd);
1187	free(jp);
1188	return np;
1189}
1190
1191char *
1192fnabspath(const char *names)
1193{
1194	size_t i, j, cap, len, need, nplen;
1195	char *out, *np, *w;
1196
1197	cap = strlen(names) + 1;
1198	if (cap < 16)
1199		cap = 16;
1200	len = 0;
1201	out = xmalloc(cap);
1202	out[0] = 0;
1203	for (i = 0; names[i];) {
1204		while (names[i] && isspace((unsigned char)names[i]))
1205			i++;
1206		if (!names[i])
1207			break;
1208		j = i;
1209		while (names[j] && !isspace((unsigned char)names[j]))
1210			j++;
1211		w = xstrndup(names + i, j - i);
1212		np = normabspath(w);
1213		free(w);
1214		nplen = strlen(np);
1215		need = len + nplen + 2;
1216		if (need > cap) {
1217			while (cap < need)
1218				cap *= 2;
1219			out = xrealloc(out, cap);
1220		}
1221		if (len)
1222			out[len++] = ' ';
1223		memcpy(out + len, np, nplen);
1224		free(np);
1225		len += nplen;
1226		out[len] = 0;
1227		i = j;
1228	}
1229	return out;
1230}
1231
1232char *
1233fnor(struct EvalCtx *ctx, const char *args)
1234{
1235	char **raw;
1236	char *val;
1237	size_t argc, i;
1238
1239	raw = splitargsraw(args, &argc);
1240	for (i = 0; i < argc; i++) {
1241		val = expandstr(ctx, raw[i]);
1242		if (val[0]) {
1243			freeargsraw(raw, argc);
1244			return val;
1245		}
1246		free(val);
1247	}
1248	freeargsraw(raw, argc);
1249	return xstrdup("");
1250}
1251
1252char *
1253fncall(struct EvalCtx *ctx, const char *args)
1254{
1255	char **raw, **exp;
1256	char *name, *val;
1257	size_t argc, i;
1258	struct Var *v;
1259	struct CallFrame frame;
1260
1261	raw = splitargsraw(args, &argc);
1262	if (argc == 0) {
1263		freeargsraw(raw, argc);
1264		return xstrdup("");
1265	}
1266
1267	name = expandstr(ctx, raw[0]);
1268	exp = xrealloc(0, argc * sizeof(exp[0]));
1269	exp[0] = xstrdup(name);
1270	for (i = 1; i < argc; i++)
1271		exp[i] = expandstr(ctx, raw[i]);
1272	freeargsraw(raw, argc);
1273
1274	memset(&frame, 0, sizeof(frame));
1275	frame.args = exp;
1276	frame.nargs = argc;
1277	frame.next = ctx->call;
1278	ctx->call = &frame;
1279
1280	v = findvar(ctx->env, name);
1281	if (v) {
1282		char *body;
1283
1284		/* The function body may use $(eval ...) to replace its own variable,
1285		 * so we make a private copy before expanding */
1286		body = xstrdup(v->val);
1287		val = expandstr(ctx, body);
1288		free(body);
1289	} else {
1290		size_t need, pos;
1291		char *inv;
1292
1293		need = 2 + strlen(name) + 2;
1294		for (i = 1; i < argc; i++)
1295			need += strlen(exp[i]) + 1;
1296		inv = xmalloc(need);
1297		pos = 0;
1298		inv[pos++] = '$';
1299		inv[pos++] = '(';
1300		memcpy(inv + pos, name, strlen(name));
1301		pos += strlen(name);
1302		if (argc > 1)
1303			inv[pos++] = ' ';
1304		for (i = 1; i < argc; i++) {
1305			size_t len = strlen(exp[i]);
1306
1307			memcpy(inv + pos, exp[i], len);
1308			pos += len;
1309			if (i + 1 < argc)
1310				inv[pos++] = ',';
1311		}
1312		inv[pos++] = ')';
1313		inv[pos] = 0;
1314		val = expandstr(ctx, inv);
1315		free(inv);
1316	}
1317	ctx->call = frame.next;
1318
1319	for (i = 0; i < argc; i++)
1320		free(exp[i]);
1321	free(exp);
1322	free(name);
1323	return val;
1324}
1325
1326char *
1327fnforeach(struct EvalCtx *ctx, const char *args)
1328{
1329	char **raw;
1330	char *name_raw, *name, *list, *out;
1331	char *saved_val;
1332	const char *iname;
1333	struct Var *saved;
1334	size_t argc, i, j, len, cap;
1335	int saved_simple;
1336	int saved_exported;
1337	enum Origin saved_origin;
1338	int had_saved;
1339
1340	raw = splitargsraw(args, &argc);
1341	if (argc < 3) {
1342		freeargsraw(raw, argc);
1343		return xstrdup("");
1344	}
1345
1346	name_raw = expandstr(ctx, raw[0]);
1347	name = trimspacesdup(name_raw);
1348	free(name_raw);
1349	list = expandstr(ctx, raw[1]);
1350	iname = intern(name);
1351
1352	saved = findvar(ctx->env, name);
1353	had_saved = saved != 0;
1354	if (had_saved) {
1355		saved_val = xstrdup(saved->val);
1356		saved_simple = saved->simple;
1357		saved_origin = saved->origin;
1358		saved_exported = saved->exported;
1359	} else {
1360		saved_val = 0;
1361		saved_simple = 0;
1362		saved_origin = ORIGIN_FILE;
1363		saved_exported = 0;
1364	}
1365
1366	cap = strlen(list) + 1;
1367	if (cap < 16)
1368		cap = 16;
1369	len = 0;
1370	out = xmalloc(cap);
1371	out[0] = 0;
1372
1373	for (i = 0; list[i];) {
1374		char *word, *exp;
1375		size_t wn, need;
1376
1377		while (list[i] && isspace((unsigned char)list[i]))
1378			i++;
1379		if (!list[i])
1380			break;
1381		j = i;
1382		while (list[j] && !isspace((unsigned char)list[j]))
1383			j++;
1384		word = xstrndup(list + i, j - i);
1385		envsetvar(ctx->env, name, word, 1, ORIGIN_OVERRIDE, 0);
1386		exp = expandstr(ctx, raw[2]);
1387		wn = strlen(exp);
1388		need = len + wn + 2;
1389		if (need > cap) {
1390			while (cap < need)
1391				cap *= 2;
1392			out = xrealloc(out, cap);
1393		}
1394		if (len)
1395			out[len++] = ' ';
1396		memcpy(out + len, exp, wn);
1397		len += wn;
1398		out[len] = 0;
1399		free(exp);
1400		i = j;
1401	}
1402
1403	if (had_saved) {
1404		free(saved->val);
1405		saved->val = saved_val;
1406		saved->simple = saved_simple;
1407		saved->origin = saved_origin;
1408		saved->exported = saved_exported;
1409	} else {
1410		envdelvar(ctx->env, iname);
1411	}
1412
1413	free(list);
1414	free(name);
1415	freeargsraw(raw, argc);
1416	return out;
1417}
1418
1419char *
1420fneval(struct EvalCtx *ctx, const char *args)
1421{
1422	char *exp;
1423
1424	while (*args && isspace((unsigned char)*args))
1425		args++;
1426	exp = expandstr(ctx, args);
1427	if (evalsnippet(ctx, "*eval*", exp) < 0)
1428		ctx->errors++;
1429	free(exp);
1430	return xstrdup("");
1431}