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