cosmetix
[k8jam.git] / src / builtins.c
blob734c3833991c15c1bf1faf77faaa6cb4294367d3
1 /*
2 * Copyright 1993-2002 Christopher Seiwald and Perforce Software, Inc.
3 * This file is part of Jam - see jam.c for Copyright information.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
19 * builtins.c - builtin jam rules
21 * External routines:
23 * load_builtin() - define builtin rules
25 * Internal routines:
27 * builtin_depends() - DEPENDS/INCLUDES rule
28 * builtin_echo() - ECHO rule
29 * builtin_exit() - EXIT rule
30 * builtin_flags() - NOCARE, NOTFILE, TEMPORARY rule
31 * builtin_glob() - GLOB rule
32 * builtin_match() - MATCH rule
33 * builtin_hdrmacro() - HDRMACRO rule
35 #include <limits.h>
36 #include <stdarg.h>
37 #include <stdint.h>
38 #include <strings.h>
39 #include <unistd.h>
41 #include "jam.h"
43 #include "lists.h"
44 #include "parse.h"
45 #include "bjprng.h"
46 #include "builtins.h"
47 #include "rules.h"
48 #include "filesys.h"
49 #include "newstr.h"
50 #include "re9.h"
51 #include "pathsys.h"
52 #include "hdrmacro.h"
53 #include "matchglob.h"
54 #include "dstrings.h"
58 * builtin_depends() - DEPENDS/INCLUDES rule
60 * The DEPENDS builtin rule appends each of the listed sources on the
61 * dependency list of each of the listed targets.
62 * It binds both the targets and sources as TARGETs.
64 static LIST *builtin_depends (PARSE *parse, LOL *args, int *jmp) {
65 LIST *targets = lol_get(args, 0);
66 LIST *sources = lol_get(args, 1);
67 for (LIST *l = targets; l; l = list_next(l)) {
68 TARGET *t = bindtarget(l->string);
69 /* If doing INCLUDES, switch to the TARGET's include TARGET, creating it if needed.
70 * The internal include TARGET shares the name of its parent. */
71 if (parse->num) {
72 if (!t->includes) t->includes = copytarget(t);
73 t = t->includes;
75 t->depends = targetlist(t->depends, sources);
77 return L0;
81 typedef struct {
82 int no_newline;
83 int no_out;
84 int flags;
85 FILE *stream;
86 } echo_flags_t;
89 static void parse_echo_flags (echo_flags_t *flg, const LIST *l) {
90 flg->no_newline = 0;
91 flg->no_out = 0;
92 flg->flags = LPFLAG_NO_TRSPACE;
93 flg->stream = stdout;
94 for (; l; l = list_next(l)) {
95 const char *s = l->string;
96 if (*s == '-') {
97 for (++s; *s; ++s) {
98 switch (*s) {
99 case 'n': flg->no_newline = 1; break;
100 case 'Q': flg->no_out = 1; break;
101 case 'S': flg->flags |= LPFLAG_NO_SPACES; break;
102 case 's': flg->flags &= ~LPFLAG_NO_TRSPACE; break;
103 case 'w': flg->stream = stderr; break;
112 * builtin_echo() - ECHO rule
114 * The ECHO builtin rule echoes the targets to the user.
115 * No other actions are taken.
117 static LIST *builtin_echo (PARSE *parse, LOL *args, int *jmp) {
118 echo_flags_t ef;
119 parse_echo_flags(&ef, lol_get(args, 1));
120 if (!ef.no_out) list_print_ex(ef.stream, lol_get(args, 0), ef.flags);
121 if (!ef.no_newline) fputc('\n', ef.stream); else fflush(ef.stream);
122 return L0;
127 * builtin_exit() - EXIT rule
129 * The EXIT builtin rule echoes the targets to the user and exits
130 * the program with a failure status.
132 static LIST *builtin_exit (PARSE *parse, LOL *args, int *jmp) {
133 LIST *l = lol_get(args, 0);
134 echo_flags_t ef;
135 parse_echo_flags(&ef, lol_get(args, 1));
136 if (l != NULL) {
137 if (!ef.no_out) list_print_ex(ef.stream, l, ef.flags);
138 if (!ef.no_newline) fputc('\n', ef.stream); else fflush(ef.stream);
139 exit(EXITBAD); /* yeech */
141 exit(EXITOK);
142 #if __GNUC__ <= 2
143 return L0;
144 #endif
149 * builtin_flags() - NOCARE, NOTFILE, TEMPORARY rule
151 * Builtin_flags() marks the target with the appropriate flag, for use by make0().
152 * It binds each target as a TARGET.
154 static LIST *builtin_flags (PARSE *parse, LOL *args, int *jmp) {
155 LIST *l = lol_get(args, 0);
156 int flag = parse->num, andflag = ~0;
157 switch (flag) {
158 case T_FLAG_NOCARE: andflag = ~T_FLAG_FORCECARE; break;
159 case T_FLAG_FORCECARE: andflag = ~T_FLAG_NOCARE; break;
160 case 666: flag = 0; andflag = ~T_FLAG_NOTFILE; break;
162 for (; l; l = list_next(l)) {
163 TARGET *t = bindtarget(l->string);
164 t->flags |= flag;
165 t->flags &= andflag;
167 return L0;
171 typedef enum {
172 GLOB_ANY,
173 GLOB_DIRS,
174 GLOB_FILES
175 } glob_mode;
179 * builtin_globbing() - GLOB rule
181 struct globbing {
182 LIST *patterns;
183 LIST *results;
184 int casesens;
185 int cmptype; // <0:glob; 0: plain; >0:# of regexps
186 glob_mode mode;
187 int namesonly;
188 regexp_t **re;
192 static void builtin_glob_back (void *closure, const char *file, int status, time_t time) {
193 struct globbing *globbing = (struct globbing *)closure;
194 LIST *l;
195 PATHNAME f;
196 static char buf[MAXJPATH];
197 /* null out directory for matching */
198 /* we wish we had file_dirscan() pass up a PATHNAME */
199 path_parse(file, &f);
200 f.f_dir.len = 0;
201 /* For globbing, we unconditionally ignore current and parent
202 * directory items. Since those items always exist, there's no
203 * reason why caller of GLOB would want to see them.
204 * We could also change file_dirscan, but then paths with embedded
205 * "." and ".." won't work anywhere. */
206 /* k8: will this break anything? it shouldn't... */
207 if (!strcmp(f.f_base.ptr, ".") || !strcmp(f.f_base.ptr, "..")) return;
208 path_build(buf, &f);
210 fprintf(stderr, "buf: [%s]\n", buf);
211 int c;
212 for (c = 0; c < 6; ++c) fprintf(stderr, " %d: [%s]\n", c, f.part[c].ptr);
214 if (globbing->mode != GLOB_ANY) {
215 int ftype = file_type(file);
216 switch (globbing->mode) {
217 case GLOB_DIRS: if (ftype != 1) return; break;
218 case GLOB_FILES: if (ftype != 0) return; break;
219 default: ;
222 if (globbing->cmptype < 0) {
223 for (l = globbing->patterns; l; l = l->next) {
224 if (matchglobex(l->string, buf, globbing->casesens) == 0) {
225 globbing->results = list_new(globbing->results, (globbing->namesonly ? buf : file), 0);
226 break;
229 } else if (globbing->cmptype > 0) {
230 for (int f = 0; f < globbing->cmptype; ++f) {
231 if (regexp_execute(globbing->re[f], buf, NULL, 0) > 0) {
232 globbing->results = list_new(globbing->results, (globbing->namesonly ? buf : file), 0);
233 break;
236 } else {
237 for (l = globbing->patterns; l; l = l->next) {
238 if ((globbing->casesens ? strcmp : strcasecmp)(l->string, buf) == 0) {
239 globbing->results = list_new(globbing->results, (globbing->namesonly ? buf : file), 0);
240 break;
247 static LIST *builtin_glob (PARSE *parse, LOL *args, int *jmp) {
248 LIST *l = lol_get(args, 0);
249 LIST *r = lol_get(args, 1);
250 LIST *lo;
251 struct globbing globbing;
252 if (!r) return L0;
253 globbing.results = L0;
254 globbing.patterns = r;
255 globbing.casesens = 1;
256 globbing.cmptype = -1;
257 globbing.mode = GLOB_ANY;
258 globbing.namesonly = 0;
259 for (lo = lol_get(args, 2); lo != NULL; lo = lo->next) {
260 if (!strcmp("case-sensitive", lo->string)) globbing.casesens = 1;
261 else if (!strcmp("case-insensitive", lo->string)) globbing.casesens = 0;
262 else if (!strcmp("ignore-case", lo->string)) globbing.casesens = 0;
263 else if (!strcmp("glob", lo->string)) globbing.cmptype = -1;
264 else if (!strcmp("regexp", lo->string)) globbing.cmptype = 1;
265 else if (!strcmp("plain", lo->string)) globbing.cmptype = 0;
266 else if (!strcmp("dirs-only", lo->string)) globbing.mode = GLOB_DIRS;
267 else if (!strcmp("files-only", lo->string)) globbing.mode = GLOB_FILES;
268 else if (!strcmp("any", lo->string)) globbing.mode = GLOB_ANY;
269 else if (!strcmp("names-only", lo->string)) globbing.namesonly = 1;
270 else if (!strcmp("full-path", lo->string)) globbing.namesonly = 0;
271 else {
272 printf("jam: invalid option for Glob built-in: '%s'\n", lo->string);
273 exit(EXITBAD); /* yeech */
276 if (globbing.cmptype > 0) {
277 /* compile regexps */
278 globbing.cmptype = list_length(r);
279 globbing.re = malloc(sizeof(globbing.re[0])*globbing.cmptype);
280 if (globbing.re == NULL) { printf("FATAL: out of memory in Glob\n"); exit(42); }
281 for (int f = 0; r; r = r->next, ++f) globbing.re[f] = regexp_compile(r->string, (globbing.casesens ? 0 : RE9_FLAG_CASEINSENS));
282 } else {
283 globbing.re = NULL;
285 for (; l; l = list_next(l)) file_dirscan(l->string, builtin_glob_back, &globbing);
286 if (globbing.re != NULL) {
287 for (int f = 0; f < globbing.cmptype; ++f) regexp_free(globbing.re[f]);
288 free(globbing.re);
290 return globbing.results;
295 * builtin_match() - MATCH rule, regexp matching
297 static LIST *builtin_match (PARSE *parse, LOL *args, int *jmp) {
298 LIST *l, *lo;
299 LIST *res = L0;
300 int casesens = 1;
301 int cmptype = 1;
302 for (lo = lol_get(args, 2); lo != NULL; lo = lo->next) {
303 if (!strcmp("case-sensitive", lo->string)) casesens = 1;
304 else if (!strcmp("case-insensitive", lo->string)) casesens = 0;
305 else if (!strcmp("ignore-case", lo->string)) casesens = 0;
306 else if (!strcmp("glob", lo->string)) cmptype = -1;
307 else if (!strcmp("regexp", lo->string)) cmptype = 1;
308 else if (!strcmp("plain", lo->string)) cmptype = 0;
309 else {
310 printf("jam: invalid option for Match built-in: '%s'\n", lo->string);
311 exit(EXITBAD); /* yeech */
314 /* for each pattern */
315 for (l = lol_get(args, 0); l; l = l->next) {
316 LIST *r;
317 if (cmptype > 0) {
318 regexp_t *re;
319 re9_sub_t mt[RE9_SUBEXP_MAX];
320 re = regexp_compile(l->string, (casesens ? 0: RE9_FLAG_CASEINSENS));
321 /* for each string to match against */
322 for (r = lol_get(args, 1); r; r = r->next) {
323 mt[0].sp = mt[0].ep = NULL;
324 if (regexp_execute(re, r->string, mt, RE9_SUBEXP_MAX) > 0) {
325 dstring_t buf;
326 /* add all parameters up to highest onto list */
327 /* must have parameters to have results! */
328 //fprintf(stderr, "re: <%s>: nsub=%d\n", re->restr, re9_nsub(re->re));
329 dstr_init(&buf);
330 for (int i = 1; i < re9_nsub(re->re); ++i) {
331 int l = mt[i].ep-mt[i].sp;
332 dstr_clear(&buf);
333 if (l > 0) dstr_push_buf(&buf, mt[i].sp, l);
334 res = list_new(res, dstr_cstr(&buf), 0);
336 /* add full match as last item */
338 int l = mt[0].ep-mt[0].sp;
339 dstr_clear(&buf);
340 if (l > 0) dstr_push_buf(&buf, mt[0].sp, l); else dstr_push_cstr(&buf, "1");
341 res = list_new(res, dstr_cstr(&buf), 0);
343 dstr_done(&buf);
346 regexp_free(re);
347 } else if (cmptype < 0) {
348 for (r = lol_get(args, 1); r; r = r->next) {
349 if (matchglobex(l->string, r->string, casesens) == 0) {
350 res = list_new(res, r->string, 0);
353 } else {
354 for (r = lol_get(args, 1); r; r = r->next) {
355 if ((casesens ? strcmp : strcasecmp)(l->string, r->string) == 0) {
356 res = list_new(res, r->string, 0);
361 return res;
365 static LIST *builtin_hdrmacro (PARSE *parse, LOL *args, int *jmp) {
366 LIST *l = lol_get(args, 0);
367 for (; l; l = list_next(l)) {
368 TARGET *t = bindtarget(l->string);
369 /* scan file for header filename macro definitions */
370 if (DEBUG_HEADER) printf("scanning '%s' for header file macro definitions\n", l->string);
371 macro_headers(t);
373 return L0;
377 /* backported from boost-jam */
379 * Return the current working directory.
381 * Usage: pwd = [ PWD ] ;
383 static LIST *builtin_pwd (PARSE *parse, LOL *args, int *jmp) {
384 char pwd_buffer[PATH_MAX];
385 if (!getcwd(pwd_buffer, sizeof(pwd_buffer))) {
386 perror("can not get current directory");
387 return L0;
389 return list_new(L0, pwd_buffer, 0);
393 /* backported from boost-jam */
394 static LIST *builtin_sort (PARSE *parse, LOL *args, int *jmp) {
395 LIST *arg = lol_get(args, 0);
396 arg = list_sort(arg);
397 return arg;
401 /* backported from boost-jam; greatly improved */
402 /* Command shcmd [[ : options ]] */
403 static LIST *builtin_command (PARSE *parse, LOL *args, int *jmp) {
404 LIST *res = L0;
405 LIST *l;
406 int ret;
407 char buffer[1024], buf1[32], *spos, *epos;
408 FILE *p = NULL;
409 int exitStatus = -1;
410 int optExitStatus = 0;
411 int optNoOutput = 0;
412 int optTrimLeft = 1;
413 int optTrimRight = 1;
414 int optStatus1st = 0;
415 int optParseOut = 0;
416 int optSpaceBreak = 1;
417 int optTabBreak = 1;
418 int optCRBreak = 1;
419 int optLFBreak = 1;
420 int no_options = ((l = lol_get(args, 1)) == NULL);
421 dstring_t str;
422 /* for each string in 2nd list: check for arg */
423 for (; l != NULL; l = l->next) {
424 if (!strcmp("exit-status", l->string)) optExitStatus = 1;
425 else if (!strcmp("exit-code", l->string)) optExitStatus = 1;
426 else if (!strcmp("status-first", l->string)) optStatus1st = 1;
427 else if (!strcmp("code-first", l->string)) optStatus1st = 1;
428 else if (!strcmp("no-output", l->string)) optNoOutput = 1;
429 else if (!strcmp("no-trim", l->string)) optTrimLeft = optTrimRight = 0;
430 else if (!strcmp("no-trim-left", l->string)) optTrimLeft = 0;
431 else if (!strcmp("no-trim-right", l->string)) optTrimRight = 0;
432 else if (!strcmp("parse-output", l->string)) optParseOut = 1;
433 else if (!strcmp("no-space-break", l->string)) optSpaceBreak = 0;
434 else if (!strcmp("no-tab-break", l->string)) optTabBreak = 0;
435 else if (!strcmp("no-nl-break", l->string)) optLFBreak = 0;
436 else if (!strcmp("no-lf-break", l->string)) optLFBreak = 0;
437 else if (!strcmp("no-cr-break", l->string)) optCRBreak = 0;
438 else if (!strcmp("dummy", l->string) || !strcmp("xyzzy", l->string)) {}
439 else {
440 printf("jam: invalid option for Command built-in: '%s'\n", l->string);
441 exit(EXITBAD); /* yeech */
444 if (no_options) optNoOutput = 1;
445 /* build shell command */
446 dstr_init(&str);
447 /* for each arg */
448 for (l = lol_get(args, 0); l; l = l->next) {
449 if (dstr_len(&str)) dstr_push_char(&str, ' ');
450 dstr_push_cstr(&str, l->string);
452 /* no shell command? */
453 if (dstr_len(&str) < 1) { dstr_done(&str); return L0; }
454 fflush(NULL); /* flush ALL output streams */
455 p = popen(dstr_cstr(&str), "r");
456 if (!p) { dstr_done(&str); return L0; }
457 dstr_clear(&str);
458 while ((ret = fread(buffer, sizeof(char), sizeof(buffer)-1, p)) > 0) {
459 if (!optNoOutput) {
460 buffer[ret] = 0;
461 dstr_push_cstr(&str, buffer);
464 exitStatus = pclose(p);
465 if (no_options) {
466 if (exitStatus) {
467 snprintf(buf1, sizeof(buf1), "%d", exitStatus);
468 res = list_new(L0, buf1, 0);
469 } else {
470 res = L0;
472 } else {
473 if (optExitStatus && optStatus1st) {
474 snprintf(buf1, sizeof(buf1), "%d", exitStatus);
475 res = list_new(res, buf1, 0);
477 /* trim output if necessary */
478 if (!optNoOutput) {
479 if (!optParseOut) {
480 /* don't parse */
481 if (optTrimRight) {
482 // trim trailing blanks
483 int sl = dstr_len(&str);
484 spos = dstr_cstr(&str);
485 while (sl > 0 && (unsigned char)spos[sl-1] <= ' ') --sl;
486 dstr_chop(&str, sl);
488 spos = dstr_cstr(&str);
489 if (optTrimLeft) {
490 // trim leading blanks
491 while (*spos && *((unsigned char *)spos) <= ' ') ++spos;
493 res = list_new(res, spos, 0);
494 } else {
495 dstring_t tmp;
496 /* parse output */
497 ret = 0; /* was anything added? list must have at least one element */
498 spos = dstr_cstr(&str);
499 dstr_init(&tmp);
500 while (*spos) {
501 /* skip delimiters */
502 while (*spos) {
503 unsigned char ch = (unsigned char)(*spos);
504 if (ch == ' ') { if (!optSpaceBreak) break; }
505 else if (ch == '\t') { if (!optTabBreak) break; }
506 else if (ch == '\r') { if (!optCRBreak) break; }
507 else if (ch == '\n') { if (!optLFBreak) break; }
508 else if (ch > ' ') break;
509 ++spos;
511 if (!*spos) break;
512 epos = spos+1;
513 while (*epos) {
514 int ch = *epos;
515 if (ch == ' ') { if (optSpaceBreak) break; }
516 else if (ch == '\t') { if (optTabBreak) break; }
517 else if (ch == '\r') { if (optCRBreak) break; }
518 else if (ch == '\n') { if (optLFBreak) break; }
519 else if ((unsigned char)ch <= ' ') break;
520 ++epos;
522 dstr_clear(&tmp);
523 dstr_push_memrange(&tmp, spos, epos);
524 res = list_new(res, dstr_cstr(&tmp), 0);
525 ret = 1;
526 spos = epos;
528 dstr_done(&tmp);
529 if (!ret) { buf1[0] = '\0'; res = list_new(res, buf1, 0); }
532 /* command exit result next */
533 if (optExitStatus && !optStatus1st) {
534 snprintf(buf1, sizeof(buf1), "%d", exitStatus);
535 res = list_new(res, buf1, 0);
538 dstr_done(&str);
539 return res;
543 /* ExprI1 op0 math op1 */
544 static LIST *builtin_expri1 (PARSE *parse, LOL *args, int *jmp) {
545 char buffer[100];
546 int op0, op1, res, comp = 0;
547 LIST *el = lol_get(args, 0);
548 if (!el || !el->next) return L0;
549 if (el->string[0] == '#') {
550 // string length
551 snprintf(buffer, sizeof(buffer), "%u", (unsigned int)(strlen(el->next->string)));
552 return list_new(L0, buffer, 0);
554 if (!el->next->next) return L0;
555 op0 = atoi(el->string);
556 op1 = atoi(el->next->next->string);
557 res = 0;
558 switch (el->next->string[0]) {
559 case '+': res = op0+op1; break;
560 case '-': res = op0-op1; break;
561 case '*': res = op0*op1; break;
562 case '/': res = op0/op1; break;
563 case '%': res = op0%op1; break;
564 case '<':
565 comp = 1;
566 if (el->next->string[1] == '=') res = (op0 <= op1); else res = (op0 < op1);
567 break;
568 case '=': comp = 1; res = (op0 == op1); break;
569 case '!': comp = 1; res = (op0 != op1); break;
570 case '>':
571 comp = 1;
572 if (el->next->string[1] == '=') res = (op0 >= op1); else res = (op0 > op1);
573 break;
574 default:
575 printf("jam: rule ExprI1: unknown operator: '%s'\n", el->next->string);
576 exit(EXITBAD);
578 if (comp) return (res ? list_new(L0, "tan", 0) : L0);
579 snprintf(buffer, sizeof(buffer), "%d", res);
580 return list_new(L0, buffer, 0);
584 /* based on the code from ftjam by David Turner */
585 static LIST *builtin_split (PARSE *parse, LOL *args, int *jmp) {
586 LIST *input = lol_get(args, 0);
587 LIST *tokens = lol_get(args, 1);
588 LIST *res = L0;
589 char token[256];
590 dstring_t str;
591 int explode = 0;
592 dstr_init(&str);
593 /* build token array */
594 if (tokens == NULL) {
595 memset(token, 1, sizeof(token));
596 explode = 1;
597 } else {
598 memset(token, 0, sizeof(token));
599 for (; tokens; tokens = tokens->next) {
600 const char *s = tokens->string;
601 for (; *s; ++s) token[(unsigned char)*s] = 1;
603 if (memchr(token, 1, sizeof(token)) == NULL) {
604 memset(token, 1, sizeof(token));
605 explode = 1;
608 token[0] = 0;
609 /* now parse the input and split it */
610 for (; input; input = input->next) {
611 const char *ptr = input->string;
612 const char *lastPtr = input->string;
613 while (*ptr) {
614 if (token[(unsigned char)*ptr]) {
615 size_t count = ptr-lastPtr+explode;
616 if (count > 0) {
617 dstr_clear(&str);
618 dstr_push_memrange(&str, lastPtr, ptr+explode);
619 res = list_new(res, dstr_cstr(&str), 0);
621 lastPtr = ptr+1;
623 ++ptr;
625 if (ptr > lastPtr) res = list_new(res, lastPtr, 0);
627 dstr_done(&str);
628 return res;
633 * builtin_dependslist()
635 * The DependsList builtin rule returns list of dependencies for
636 * a given target.
638 static LIST *builtin_dependslist (PARSE *parse, LOL *args, int *jmp) {
639 LIST *res = L0;
640 LIST *parents;
641 for (parents = lol_get(args, 0); parents; parents = parents->next) {
642 TARGET *t = bindtarget(parents->string);
643 TARGETS *child;
644 for (child = t->depends; child; child = child->next) res = list_new(res, child->target->name, 1);
646 return res;
651 * NormalizePath path [: pwd]
653 * it will add 'pwd' if path is not absolute and will try to resolve some '.' and '..' (only leading '..' though).
654 * it can be used in SubDir replacement to automate dir building
655 * if there is no $(2), use current directory as 'pwd'
657 static LIST *builtin_normpath (PARSE *parse, LOL *args, int *jmp) {
658 LIST *el = lol_get(args, 0), *pl = lol_get(args, 1);
659 char *buf;
660 int bsz;
661 if (!el || !el->string) return L0;
662 bsz = strlen(el->string)*2+1024;
663 buf = malloc(bsz);
664 if (buf == NULL) return L0;
665 if (!normalize_path(el->string, buf, bsz, (pl != NULL ? pl->string : NULL))) { free(buf); return L0; }
666 el = list_new(NULL, buf, 0);
667 free(buf);
668 return el;
673 * ListLength list
675 static LIST *builtin_listlength (PARSE *parse, LOL *args, int *jmp) {
676 char buffer[100];
677 LIST *el = lol_get(args, 0);
678 if (!el) return list_new(L0, "0", 0);
679 snprintf(buffer, sizeof(buffer), "%d", list_length(el));
680 return list_new(L0, buffer, 0);
685 * HaveRule and HaveActions
687 typedef struct {
688 LIST *el;
689 int wantAction;
690 int casesens;
691 } HRNGCI;
694 typedef struct {
695 const char *str;
696 int wantAction;
697 int casesens;
698 } HRNormalData;
701 static int hr_normal (const void *hdata, void *udata) {
702 const RULE *r = (const RULE *)hdata;
703 const HRNormalData *d = (const HRNormalData *)udata;
704 if (strcasecmp(r->name, d->str) == 0) {
705 if (d->wantAction && r->actions) return 1; // got it
706 if (!d->wantAction && r->procedure) return 1; // got it
708 return 0;
712 static int hr_glob (const void *hdata, void *udata) {
713 const RULE *r = (const RULE *)hdata;
714 const HRNormalData *d = (const HRNormalData *)udata;
715 if (matchglobex(d->str, r->name, d->casesens) == 0) {
716 //fprintf(stderr, ":[%s]\n", r->name);
717 if (d->wantAction && r->actions) return 1; // got it
718 if (!d->wantAction && r->procedure) return 1; // got it
720 return 0;
724 typedef struct {
725 regexp_t *re;
726 int reflags;
727 int wantAction;
728 } HRREData;
731 static int hr_regexp (const void *hdata, void *udata) {
732 const RULE *r = (const RULE *)hdata;
733 HRREData *d = (HRREData *)udata;
734 if (regexp_execute(d->re, r->name, NULL, 0) > 0) {
735 //fprintf(stderr, ":[%s]\n", r->name);
736 if (d->wantAction && r->actions) return 1; // got it
737 if (!d->wantAction && r->procedure) return 1; // got it
739 return 0;
743 static LIST *builtin_haveruleactions (PARSE *parse, LOL *args, int *jmp) {
744 LIST *el = lol_get(args, 0), *l;
745 int wantAction = parse->num;
746 int casesens = 1;
747 int cmptype = 0; // <0:glob; >0:regexp
748 if (!el) return L0;
749 for (l = lol_get(args, 1); l != NULL; l = l->next) {
750 if (!strcmp("case-sensitive", l->string)) casesens = 1;
751 else if (!strcmp("case-insensitive", l->string)) casesens = 0;
752 else if (!strcmp("ignore-case", l->string)) casesens = 0;
753 else if (!strcmp("glob", l->string)) cmptype = -1;
754 else if (!strcmp("regexp", l->string)) cmptype = 1;
755 else if (!strcmp("plain", l->string)) cmptype = 0;
756 else {
757 printf("jam: invalid option for Have%s built-in: '%s'\n", (wantAction ? "Actions" : "Rule"), l->string);
758 exit(EXITBAD); /* yeech */
761 if (casesens == 1 && cmptype == 0) {
762 // standard mode
763 for (; el; el = el->next) {
764 RULE *r = findrule(el->string);
765 if (!r) return L0;
766 if (wantAction && !r->actions) return L0;
767 if (!wantAction && !r->procedure) return L0;
769 } else if (cmptype < 0) {
770 // glob
771 HRNormalData nfo;
772 nfo.wantAction = wantAction;
773 nfo.casesens = casesens;
774 for (; el; el = el->next) {
775 nfo.str = el->string;
776 if (!iteraterules(hr_glob, &nfo)) return L0;
778 } else if (cmptype > 0) {
779 // regexp
780 HRREData nfo;
781 nfo.wantAction = wantAction;
782 for (; el; el = el->next) {
783 int err;
784 nfo.re = regexp_compile(el->string, (casesens ? 0 : RE9_FLAG_CASEINSENS));
785 /*printf("FATAL: invalid regexp in Have%s: %s\n", (wantAction ? "Actions" : "Rule"), errmsg);*/
786 err = iteraterules(hr_regexp, &nfo);
787 regexp_free(nfo.re);
788 if (!err) return L0;
790 } else {
791 // normal, case-insensitive
792 HRNormalData nfo;
793 nfo.wantAction = wantAction;
794 for (; el; el = el->next) {
795 nfo.str = el->string;
796 if (!iteraterules(hr_normal, &nfo)) return L0;
799 return list_new(L0, "1", 0);
803 /* generate random name:
804 * [ RandName ] -- /tmp/XXX
805 * [ RandName "abc/" ] -- abc/XXX
806 * [ RandName "" ] -- XXX
808 static LIST *builtin_randname (PARSE *parse, LOL *args, int *jmp) {
809 static BJRandCtx rctx;
810 static int initialized = 0;
811 static const char alphabet[] = "0123456789abcdefghijklmnopqrstuvwxyz";
812 LIST *el;
813 char buffer[9], *s;
814 const char *path;
815 #ifdef OS_NT
816 static char tp[8192]
817 #endif
818 if (!initialized) {
819 initialized = 1;
820 bjprngInit(&rctx, bjprngGenRandSeed());
821 #ifdef OS_NT
822 GetTempPath(sizeof(tp), tp);
823 #endif
825 for (int f = 0; f < 8; ++f) buffer[f] = alphabet[bjprngRand(&rctx)%strlen(alphabet)];
826 buffer[8] = 0;
827 el = lol_get(args, 0);
828 path = (el != NULL && el->string != NULL ? el->string :
829 #ifdef OS_NT
831 #else
832 "/tmp/"
833 #endif
835 s = alloca(strlen(path)+strlen(buffer)+2);
836 sprintf(s, "%s%s", path, buffer);
837 return list_new(L0, s, 0);
841 /* write list to file:
842 * ListFileWrite filename : list [: terminator] [: append]
843 * default terminator is '\n'
844 * return success flag
846 static LIST *builtin_listwrite (PARSE *parse, LOL *args, int *jmp) {
847 LIST *el = lol_get(args, 0);
848 if (el != NULL && el->string != NULL && el->string[0]) {
849 LIST *l = lol_get(args, 3);
850 FILE *fo = fopen(el->string, (l != NULL && l->string[0] ? "a" : "w"));
851 if (fo != NULL) {
852 const char *term;
853 l = lol_get(args, 2);
854 term = (l != NULL ? l->string : "\n");
855 for (l = lol_get(args, 1); l != NULL; l = l->next) {
856 if (fprintf(fo, "%s%s", l->string, term) < 0) {
857 fclose(fo);
858 unlink(el->string);
859 return L0;
862 fclose(fo);
863 return list_new(L0, "1", 0);
866 return L0;
870 /* remove duplicates from list */
871 static LIST *builtin_listremdups (PARSE *parse, LOL *args, int *jmp) {
872 LIST *el = lol_get(args, 0);
873 if (el != NULL) {
874 LIST *l, *res = list_new(L0, el->string, 1);
875 for (l = el->next; l != NULL; l = l->next) {
876 int found = 0;
877 for (LIST *t = el; t != l && !found; t = t->next) if (strcmp(t->string, l->string) == 0) found = 1;
878 if (!found) res = list_new(res, l->string, 1);
880 return res;
882 return el; /* no need to change anything */
887 * compile_builtin() - define builtin rules
890 #define P0 ((PARSE *)0)
891 #define C0 ((char *)0)
894 /* ":" -- previous name in upper case; "." -- previous name in lower case */
895 static inline void bind_builtin (PARSE *pp, const char *name) {
896 bindrule(name)->procedure = pp;
900 static inline void bind_builtin2 (PARSE *pp, const char *name, const char *name1) {
901 bindrule(name)->procedure = pp;
902 bindrule(name1)->procedure = pp;
906 void load_builtins (void) {
907 bind_builtin(parse_make(builtin_depends, P0, P0, P0, C0, C0, 0),
908 "Depends");
909 bind_builtin(parse_make(builtin_depends, P0, P0, P0, C0, C0, 1),
910 "Includes");
911 bind_builtin(parse_make(builtin_dependslist, P0, P0, P0, C0, C0, 0),
912 "DependsList");
914 bind_builtin(parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_TOUCHED),
915 "Always");
916 bind_builtin(parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_LEAVES),
917 "Leaves");
918 bind_builtin(parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_NOCARE),
919 "NoCare");
920 bind_builtin2(parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_NOTFILE),
921 "NotFile", "NoTime");
922 bind_builtin(parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_NOUPDATE),
923 "NoUpdate");
924 bind_builtin(parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_TEMP),
925 "Temporary");
926 bind_builtin(parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_FORCECARE),
927 "ForceCare");
928 bind_builtin(parse_make(builtin_flags, P0, P0, P0, C0, C0, 666),
929 "ForceFile");
931 bind_builtin(parse_make(builtin_echo, P0, P0, P0, C0, C0, 0),
932 "Echo");
934 bind_builtin(parse_make(builtin_exit, P0, P0, P0, C0, C0, 0),
935 "Exit");
937 bind_builtin(parse_make(builtin_glob, P0, P0, P0, C0, C0, 0),
938 "Glob");
939 bind_builtin(parse_make(builtin_match, P0, P0, P0, C0, C0, 0),
940 "Match");
942 bind_builtin(parse_make(builtin_hdrmacro, P0, P0, P0, C0, C0, 0),
943 "HdrMacro");
945 bind_builtin(parse_make(builtin_pwd, P0, P0, P0, C0, C0, 0),
946 "Pwd");
948 bind_builtin(parse_make(builtin_sort, P0, P0, P0, C0, C0, 0),
949 "Sort");
951 bind_builtin(parse_make(builtin_command, P0, P0, P0, C0, C0, 0),
952 "Command");
954 bind_builtin(parse_make(builtin_expri1, P0, P0, P0, C0, C0, 0),
955 "ExprI1");
957 bind_builtin(parse_make(builtin_split, P0, P0, P0, C0, C0, 0),
958 "Split");
960 bind_builtin(parse_make(builtin_normpath, P0, P0, P0, C0, C0, 0),
961 "NormalizePath");
963 bind_builtin(parse_make(builtin_listlength, P0, P0, P0, C0, C0, 0),
964 "ListLength");
965 bind_builtin(parse_make(builtin_listwrite, P0, P0, P0, C0, C0, 0),
966 "ListWrite");
967 bind_builtin(parse_make(builtin_listremdups, P0, P0, P0, C0, C0, 0),
968 "ListRemoveDuplicates");
970 bind_builtin(parse_make(builtin_haveruleactions, P0, P0, P0, C0, C0, 0),
971 "HaveRule");
972 bind_builtin(parse_make(builtin_haveruleactions, P0, P0, P0, C0, C0, 1),
973 "HaveActions");
975 bind_builtin(parse_make(builtin_randname, P0, P0, P0, C0, C0, 0),
976 "RandName");