cosmetix (added some __attribute__()s)
[k8jam.git] / src / builtins.c
bloba1855f2db211c35749cc988f9fd1ec1e0e05b73f
1 /*
2 * Copyright 1993-2002 Christopher Seiwald and Perforce Software, Inc.
4 * This file is part of Jam - see jam.c for Copyright information.
5 */
7 /*
8 * builtins.c - builtin jam rules
10 * External routines:
12 * load_builtin() - define builtin rules
14 * Internal routines:
16 * builtin_depends() - DEPENDS/INCLUDES rule
17 * builtin_echo() - ECHO rule
18 * builtin_echon() - ECHO-N rule
19 * builtin_oflush() - O-FLUSH rule
20 * builtin_exit() - EXIT rule
21 * builtin_flags() - NOCARE, NOTFILE, TEMPORARY rule
22 * builtin_glob() - GLOB rule
23 * builtin_match() - MATCH rule
24 * builtin_hdrmacro() - HDRMACRO rule
26 * 01/10/01 (seiwald) - split from compile.c
27 * 01/08/01 (seiwald) - new 'Glob' (file expansion) builtin
28 * 03/02/02 (seiwald) - new 'Match' (regexp match) builtin
29 * 04/03/02 (seiwald) - Glob matches only filename, not directory
30 * 10/22/02 (seiwald) - list_new() now does its own newstr()/copystr()
31 * 10/22/02 (seiwald) - working return/break/continue statements
32 * 11/04/02 (seiwald) - const-ing for string literals
33 * 12/03/02 (seiwald) - fix odd includes support by grafting them onto depends
34 * 01/14/03 (seiwald) - fix includes fix with new internal includes TARGET
36 #include <limits.h>
37 #include <stdarg.h>
38 #include <stdint.h>
39 #include <strings.h>
40 #include <unistd.h>
42 #include "jam.h"
44 #include "lists.h"
45 #include "parse.h"
46 #include "bjprng.h"
47 #include "builtins.h"
48 #include "rules.h"
49 #include "filesys.h"
50 #include "newstr.h"
51 #include "re9.h"
52 #include "pathsys.h"
53 #include "hdrmacro.h"
54 #include "matchglob.h"
55 #include "kstrings.h"
58 int compat_old_builtin_names = 0;
62 * builtin_depends() - DEPENDS/INCLUDES rule
64 * The DEPENDS builtin rule appends each of the listed sources on the
65 * dependency list of each of the listed targets.
66 * It binds both the targets and sources as TARGETs.
68 static LIST *builtin_depends (PARSE *parse, LOL *args, int *jmp) {
69 LIST *targets = lol_get(args, 0);
70 LIST *sources = lol_get(args, 1);
71 LIST *l;
73 for (l = targets; l; l = list_next(l)) {
74 TARGET *t = bindtarget(l->string);
75 /* If doing INCLUDES, switch to the TARGET's include */
76 /* TARGET, creating it if needed. The internal include */
77 /* TARGET shares the name of its parent. */
78 if (parse->num) {
79 if (!t->includes) t->includes = copytarget(t);
80 t = t->includes;
82 t->depends = targetlist(t->depends, sources);
84 return L0;
89 * builtin_echo() - ECHO rule
91 * The ECHO builtin rule echoes the targets to the user.
92 * No other actions are taken.
94 static LIST *builtin_echo (PARSE *parse, LOL *args, int *jmp) {
95 list_print(lol_get(args, 0));
96 printf("\n");
97 return L0;
102 * builtin_echon() - ECHO-N rule
104 * The ECHO-N builtin rule echoes the targets to the user.
105 * No other actions are taken, no newline is written.
107 static LIST *builtin_echon (PARSE *parse, LOL *args, int *jmp) {
108 list_print(lol_get(args, 0));
109 fflush(stdout);
110 return L0;
115 * builtin_oflush() - O-FLUSH rule
117 * The O-FLUSH builtin rule flushes current output stream.
118 * Used with ECHO-N.
120 static LIST *builtin_oflush (PARSE *parse, LOL *args, int *jmp) {
121 fflush(stdout);
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 if (l != NULL) {
135 list_print(l);
136 printf("\n");
137 exit(EXITBAD); /* yeech */
139 exit(EXITOK);
140 #if __GNUC__ <= 2
141 return L0;
142 #endif
147 * builtin_flags() - NOCARE, NOTFILE, TEMPORARY rule
149 * Builtin_flags() marks the target with the appropriate flag, for use by make0().
150 * It binds each target as a TARGET.
152 static LIST *builtin_flags (PARSE *parse, LOL *args, int *jmp) {
153 LIST *l = lol_get(args, 0);
154 int flag = parse->num, andflag = ~0;
156 switch (flag) {
157 case T_FLAG_NOCARE: andflag = ~T_FLAG_FORCECARE; break;
158 case T_FLAG_FORCECARE: andflag = ~T_FLAG_NOCARE; break;
159 case 666: flag = 0; andflag = ~T_FLAG_NOTFILE; break;
162 for (; l; l = list_next(l)) {
163 TARGET *t = bindtarget(l->string);
165 t->flags |= flag;
166 t->flags &= andflag;
168 return L0;
172 enum {
173 GLOB_ANY,
174 GLOB_DIRS,
175 GLOB_FILES
180 * builtin_globbing() - GLOB rule
182 struct globbing {
183 LIST *patterns;
184 LIST *results;
185 int casesens;
186 int cmptype; // <0:glob; 0: plain; >0:# of regexps
187 int mode; // GLOB_xxx
188 int namesonly;
189 re9_prog_t **re;
193 static void builtin_glob_back (void *closure, const char *file, int status, time_t time) {
194 struct globbing *globbing = (struct globbing *)closure;
195 LIST *l;
196 PATHNAME f;
197 char buf[MAXJPATH];
198 /* null out directory for matching */
199 /* we wish we had file_dirscan() pass up a PATHNAME */
200 path_parse(file, &f);
201 f.f_dir.len = 0;
202 /* For globbing, we unconditionally ignore current and parent
203 * directory items. Since those items always exist, there's no
204 * reason why caller of GLOB would want to see them.
205 * We could also change file_dirscan, but then paths with embedded
206 * "." and ".." won't work anywhere. */
207 /* k8: will this break anything? it shouldn't... */
208 if (!strcmp(f.f_base.ptr, ".") || !strcmp(f.f_base.ptr, "..")) return;
209 path_build(&f, buf, 0);
212 fprintf(stderr, "buf: [%s]\n", buf);
213 int c;
214 for (c = 0; c < 6; ++c) fprintf(stderr, " %d: [%s]\n", c, f.part[c].ptr);
216 if (globbing->mode != GLOB_ANY) {
217 int ftype = getFileType(file);
219 switch (globbing->mode) {
220 case GLOB_DIRS: if (ftype != 1) return; break;
221 case GLOB_FILES: if (ftype != 0) return; break;
222 default: ;
226 if (globbing->cmptype < 0) {
227 for (l = globbing->patterns; l; l = l->next) {
228 if (matchglobex(l->string, buf, globbing->casesens) == 0) {
229 globbing->results = list_new(globbing->results, globbing->namesonly?buf:file, 0);
230 break;
233 } else if (globbing->cmptype > 0) {
234 int f;
236 for (f = 0; f < globbing->cmptype; ++f) {
237 if (re9_execute(globbing->re[f], RE9_FLAG_NONUTF8, buf, NULL, 0) > 0) {
238 globbing->results = list_new(globbing->results, globbing->namesonly?buf:file, 0);
239 break;
242 } else {
243 for (l = globbing->patterns; l; l = l->next) {
244 if ((globbing->casesens?strcmp:strcasecmp)(l->string, buf) == 0) {
245 globbing->results = list_new(globbing->results, globbing->namesonly?buf:file, 0);
246 break;
253 static LIST *builtin_glob (PARSE *parse, LOL *args, int *jmp) {
254 LIST *l = lol_get(args, 0);
255 LIST *r = lol_get(args, 1);
256 LIST *lo;
257 struct globbing globbing;
259 if (!r) return L0;
260 globbing.results = L0;
261 globbing.patterns = r;
262 globbing.casesens = 1;
263 globbing.cmptype = -1;
264 globbing.mode = GLOB_ANY;
265 globbing.namesonly = 0;
267 for (lo = lol_get(args, 2); lo != NULL; lo = lo->next) {
268 if (!strcmp("case-sensitive", lo->string)) globbing.casesens = 1;
269 else if (!strcmp("case-insensitive", lo->string)) globbing.casesens = 0;
270 else if (!strcmp("ignore-case", lo->string)) globbing.casesens = 0;
271 else if (!strcmp("glob", lo->string)) globbing.cmptype = -1;
272 else if (!strcmp("regexp", lo->string)) globbing.cmptype = 1;
273 else if (!strcmp("plain", lo->string)) globbing.cmptype = 0;
274 else if (!strcmp("dirs-only", lo->string)) globbing.mode = GLOB_DIRS;
275 else if (!strcmp("files-only", lo->string)) globbing.mode = GLOB_FILES;
276 else if (!strcmp("any", lo->string)) globbing.mode = GLOB_ANY;
277 else if (!strcmp("names-only", lo->string)) globbing.namesonly = 1;
278 else if (!strcmp("full-path", lo->string)) globbing.namesonly = 0;
279 else {
280 printf("jam: invalid option for Glob built-in: '%s'\n", lo->string);
281 exit(EXITBAD); /* yeech */
285 if (globbing.cmptype > 0) {
286 /* compile regexps */
287 int f;
289 globbing.cmptype = list_length(r);
290 globbing.re = malloc(sizeof(globbing.re[0])*globbing.cmptype);
291 if (globbing.re == NULL) {
292 printf("FATAL: out of memory in Glob\n");
293 exit(42);
295 for (f = 0; r; r = r->next, ++f) {
296 const char *errmsg;
297 if ((globbing.re[f] = re9_compile(r->string, RE9_FLAG_NONUTF8|(globbing.casesens ? 0 : RE9_FLAG_CASEINSENS), &errmsg)) == NULL) {
298 printf("FATAL: invalid regexp in Glob: %s\n", errmsg);
299 exit(42);
302 } else {
303 globbing.re = NULL;
306 for (; l; l = list_next(l)) file_dirscan(l->string, builtin_glob_back, &globbing);
308 if (globbing.re) {
309 int f;
311 for (f = 0; f < globbing.cmptype; ++f) re9_free(globbing.re[f]);
312 free(globbing.re);
315 return globbing.results;
320 * builtin_match() - MATCH rule, regexp matching
322 static LIST *builtin_match (PARSE *parse, LOL *args, int *jmp) {
323 LIST *l, *lo;
324 LIST *res = 0;
325 int casesens = 1;
326 int cmptype = 1;
328 for (lo = lol_get(args, 2); lo != NULL; lo = lo->next) {
329 if (!strcmp("case-sensitive", lo->string)) casesens = 1;
330 else if (!strcmp("case-insensitive", lo->string)) casesens = 0;
331 else if (!strcmp("ignore-case", lo->string)) casesens = 0;
332 else if (!strcmp("glob", lo->string)) cmptype = -1;
333 else if (!strcmp("regexp", lo->string)) cmptype = 1;
334 else if (!strcmp("plain", lo->string)) cmptype = 0;
335 else {
336 printf("jam: invalid option for Match built-in: '%s'\n", lo->string);
337 exit(EXITBAD); /* yeech */
340 /* for each pattern */
341 for (l = lol_get(args, 0); l; l = l->next) {
342 LIST *r;
344 if (cmptype > 0) {
345 re9_prog_t *re;
346 re9_sub_t mt[RE9_SUBEXP_MAX];
347 const char *errmsg;
349 if ((re = re9_compile(l->string, RE9_FLAG_NONUTF8, &errmsg)) == NULL) {
350 printf("FATAL: %s\n", errmsg);
351 exit(42);
353 /* for each string to match against */
354 for (r = lol_get(args, 1); r; r = r->next) {
355 mt[0].sp = mt[0].ep = NULL;
356 if (re9_execute(re, RE9_FLAG_NONUTF8, r->string, mt, RE9_SUBEXP_MAX) > 0) {
357 int i;
358 /* add all parameters up to highest onto list */
359 /* must have parameters to have results! */
360 for (i = 1; i < RE9_SUBEXP_MAX; ++i) {
361 char buf[MAXSYM];
362 //FIXME
363 int l = mt[i].ep-mt[i].sp;
364 if (l > sizeof(buf)-1) l = sizeof(buf)-1;
365 if (l > 0) memcpy(buf, mt[i].sp, l);
366 buf[l] = 0;
367 res = list_new(res, buf, 0);
371 re9_free(re);
372 } else if (cmptype < 0) {
373 for (r = lol_get(args, 1); r; r = r->next) {
374 if (matchglobex(l->string, r->string, casesens) == 0) {
375 res = list_new(res, r->string, 0);
378 } else {
379 for (r = lol_get(args, 1); r; r = r->next) {
380 if ((casesens?strcmp:strcasecmp)(l->string, r->string) == 0) {
381 res = list_new(res, r->string, 0);
386 return res;
390 static LIST *builtin_hdrmacro (PARSE *parse, LOL *args, int *jmp) {
391 LIST *l = lol_get(args, 0);
392 for (; l; l = list_next(l)) {
393 TARGET *t = bindtarget(l->string);
394 /* scan file for header filename macro definitions */
395 if (DEBUG_HEADER) printf("scanning '%s' for header file macro definitions\n", l->string);
396 macro_headers(t);
398 return L0;
402 /* backported from boost-jam */
404 * Return the current working directory.
406 * Usage: pwd = [ PWD ] ;
408 static LIST *builtin_pwd (PARSE *parse, LOL *args, int *jmp) {
409 char pwd_buffer[PATH_MAX];
410 if (!getcwd(pwd_buffer, sizeof(pwd_buffer))) {
411 perror("can not get current directory");
412 return L0;
414 return list_new(L0, pwd_buffer, 0);
418 /* backported from boost-jam */
419 static LIST *builtin_sort (PARSE *parse, LOL *args, int *jmp) {
420 LIST *arg = lol_get(args, 0);
421 arg = list_sort(arg);
422 return arg;
426 /* backported from boost-jam; greatly improved */
427 /* Command shcmd [[ : options ]] */
428 static LIST *builtin_command (PARSE *parse, LOL *args, int *jmp) {
429 LIST *res = NULL;
430 LIST *l;
431 int ret;
432 char buffer[1024], buf1[32], *spos, *epos;
433 FILE *p = NULL;
434 int exitStatus = -1;
435 int optExitStatus = 0;
436 int optNoOutput = 0;
437 int optTrimLeft = 1;
438 int optTrimRight = 1;
439 int optStatus1st = 0;
440 int optParseOut = 0;
441 int optSpaceBreak = 1;
442 int optTabBreak = 1;
443 int optCRBreak = 1;
444 int optLFBreak = 1;
445 tKString str;
447 /* for each string in 2nd list: check for arg */
448 for (l = lol_get(args, 1); l != NULL; l = l->next) {
449 if (!strcmp("exit-status", l->string)) optExitStatus = 1;
450 else if (!strcmp("exit-code", l->string)) optExitStatus = 1;
451 else if (!strcmp("status-first", l->string)) optStatus1st = 1;
452 else if (!strcmp("code-first", l->string)) optStatus1st = 1;
453 else if (!strcmp("no-output", l->string)) optNoOutput = 1;
454 else if (!strcmp("no-trim", l->string)) optTrimLeft = optTrimRight = 0;
455 else if (!strcmp("no-trim-left", l->string)) optTrimLeft = 0;
456 else if (!strcmp("no-trim-right", l->string)) optTrimRight = 0;
457 else if (!strcmp("parse-output", l->string)) optParseOut = 1;
458 else if (!strcmp("no-space-break", l->string)) optSpaceBreak = 0;
459 else if (!strcmp("no-tab-break", l->string)) optTabBreak = 0;
460 else if (!strcmp("no-nl-break", l->string)) optLFBreak = 0;
461 else if (!strcmp("no-lf-break", l->string)) optLFBreak = 0;
462 else if (!strcmp("no-cr-break", l->string)) optCRBreak = 0;
463 else {
464 printf("jam: invalid option for Command built-in: '%s'\n", l->string);
465 exit(EXITBAD); /* yeech */
468 /* build shell command */
469 kStringNew(&str);
470 /* for each arg */
471 for (l = lol_get(args, 0); l; l = l->next) {
472 if (kStringLen(&str)) kStringPushBack(&str, ' ');
473 kStringAppendCStr(&str, l->string);
475 /* no shell command? */
476 if (kStringLen(&str) < 1) { kStringFree(&str); return L0; }
478 fflush(NULL);
479 p = popen(kStringCStr(&str), "r");
480 if (!p) { kStringFree(&str); return L0; }
482 kStringClear(&str);
483 while ((ret = fread(buffer, sizeof(char), sizeof(buffer)-1, p)) > 0) {
484 if (!optNoOutput) {
485 buffer[ret] = 0;
486 kStringAppendCStr(&str, buffer);
489 exitStatus = pclose(p);
490 if (optExitStatus && optStatus1st) {
491 snprintf(buf1, sizeof(buf1), "%d", exitStatus);
492 res = list_new(res, buf1, 0);
494 /* trim output if necessary */
495 if (!optNoOutput) {
496 if (!optParseOut) {
497 /* don't parse */
498 if (optTrimRight) {
499 // trim trailing blanks
500 int sl = kStringLen(&str);
501 spos = kStringCStr(&str);
502 while (sl > 0 && (unsigned char)spos[sl-1] <= ' ') --sl;
503 kStringTruncate(&str, sl);
505 spos = kStringCStr(&str);
506 if (optTrimLeft) {
507 // trim leading blanks
508 while (*spos && *((unsigned char *)spos) <= ' ') ++spos;
510 res = list_new(res, spos, 0);
511 } else {
512 tKString tmp;
513 /* parse output */
514 ret = 0; /* was anything added? list must have at least one element */
515 spos = kStringCStr(&str);
516 kStringNew(&tmp);
517 while (*spos) {
518 /* skip delimiters */
519 while (*spos) {
520 unsigned char ch = (unsigned char)(*spos);
521 if (ch == ' ') { if (!optSpaceBreak) break; }
522 else if (ch == '\t') { if (!optTabBreak) break; }
523 else if (ch == '\r') { if (!optCRBreak) break; }
524 else if (ch == '\n') { if (!optLFBreak) break; }
525 else if (ch > ' ') break;
526 ++spos;
528 if (!*spos) break;
529 epos = spos+1;
530 while (*epos) {
531 int ch = *epos;
532 if (ch == ' ') { if (optSpaceBreak) break; }
533 else if (ch == '\t') { if (optTabBreak) break; }
534 else if (ch == '\r') { if (optCRBreak) break; }
535 else if (ch == '\n') { if (optLFBreak) break; }
536 else if ((unsigned char)ch <= ' ') break;
537 ++epos;
539 kStringClear(&tmp);
540 kStringAppendRange(&tmp, spos, epos);
541 res = list_new(res, kStringCStr(&tmp), 0);
542 ret = 1;
543 spos = epos;
545 kStringFree(&tmp);
546 if (!ret) { buf1[0] = '\0'; res = list_new(res, buf1, 0); }
549 kStringFree(&str);
550 /* command exit result next */
551 if (optExitStatus && !optStatus1st) {
552 snprintf(buf1, sizeof(buf1), "%d", exitStatus);
553 res = list_new(res, buf1, 0);
555 return res;
559 static LIST *builtin_expri1 (PARSE *parse, LOL *args, int *jmp) {
560 char buffer[100];
561 int op0, op1, res, comp = 0;
562 LIST *el = lol_get(args, 0);
564 if (!el || !el->next) return L0;
565 if (el->string[0] == '#') {
566 // string length
567 snprintf(buffer, sizeof(buffer), "%d", strlen(el->next->string));
568 return list_new(L0, buffer, 0);
570 if (!el->next->next) return L0;
571 op0 = atoi(el->string);
572 op1 = atoi(el->next->next->string);
573 res = 0;
574 switch (el->next->string[0]) {
575 case '+': res = op0+op1; break;
576 case '-': res = op0-op1; break;
577 case '*': res = op0*op1; break;
578 case '/': res = op0/op1; break;
579 case '%': res = op0%op1; break;
580 case '<':
581 comp = 1;
582 if (el->next->string[1] == '=') res = (op0 <= op1); else res = (op0 < op1);
583 break;
584 case '=': comp = 1; res = (op0 == op1); break;
585 case '!': comp = 1; res = (op0 != op1); break;
586 case '>':
587 comp = 1;
588 if (el->next->string[1] == '=') res = (op0 >= op1); else res = (op0 > op1);
589 break;
590 default:
591 printf("jam: rule ExprI1: unknown operator: '%s'\n", el->next->string);
592 exit(EXITBAD);
594 if (comp) return (res ? list_new(L0, "tan", 0) : L0);
595 snprintf(buffer, sizeof(buffer), "%d", res);
596 return list_new(L0, buffer, 0);
600 /* Based on code from ftjam by David Turner */
601 static LIST *builtin_split (PARSE *parse, LOL *args, int *jmp) {
602 LIST *input = lol_get(args, 0);
603 LIST *tokens = lol_get(args, 1);
604 LIST *res = L0;
605 char token[256];
606 tKString str;
607 int explode = 0;
609 kStringNew(&str);
610 /* build token array */
611 if (tokens == NULL) {
612 memset(token, 1, sizeof(token));
613 explode = 1;
614 } else {
615 memset(token, 0, sizeof(token));
616 for (; tokens; tokens = tokens->next) {
617 const char *s = tokens->string;
618 for (; *s; ++s) token[(unsigned char)*s] = 1;
620 if (memchr(token, 1, sizeof(token)) == NULL) {
621 memset(token, 1, sizeof(token));
622 explode = 1;
625 token[0] = 0;
626 /* now parse the input and split it */
627 for (; input; input = input->next) {
628 const char *ptr = input->string;
629 const char *lastPtr = input->string;
630 while (*ptr) {
631 if (token[(unsigned char)*ptr]) {
632 size_t count = ptr-lastPtr+explode;
633 if (count > 0) {
634 kStringClear(&str);
635 kStringAppendRange(&str, lastPtr, ptr+explode);
636 res = list_new(res, kStringCStr(&str), 0);
638 lastPtr = ptr+1;
640 ++ptr;
642 if (ptr > lastPtr) res = list_new(res, lastPtr, 0);
644 kStringFree(&str);
645 return res;
650 * builtin_dependslist()
652 * The DependsList builtin rule returns list of dependencies for
653 * a given target.
655 static LIST *builtin_dependslist (PARSE *parse, LOL *args, int *jmp) {
656 LIST *res = L0;
657 LIST *parents;
659 for (parents = lol_get(args, 0); parents; parents = parents->next) {
660 TARGET *t = bindtarget(parents->string);
661 TARGETS *child;
663 for (child = t->depends; child; child = child->next) res = list_new(res, child->target->name, 1);
665 return res;
669 static LIST *builtin_normpath (PARSE *parse, LOL *args, int *jmp) {
670 LIST *el = lol_get(args, 0);
671 char *buf;
672 int bsz;
674 if (!el || !el->string) return L0;
675 bsz = strlen(el->string)*2+1024;
676 buf = malloc(bsz);
677 if (buf == NULL) return L0;
678 if (!normalize_path(el->string, buf, bsz)) { free(buf); return L0; }
679 el = list_new(NULL, buf, 0);
680 free(buf);
681 return el;
685 static LIST *builtin_listlength (PARSE *parse, LOL *args, int *jmp) {
686 char buffer[100];
687 LIST *el = lol_get(args, 0);
689 if (!el) return L0;
690 snprintf(buffer, sizeof(buffer), "%d", list_length(el));
691 return list_new(L0, buffer, 0);
695 typedef struct {
696 LIST *el;
697 int wantAction;
698 int casesens;
699 } HRNGCI;
702 typedef struct {
703 const char *str;
704 int wantAction;
705 int casesens;
706 } HRNormalData;
708 static int hr_normal (const void *hdata, void *udata) {
709 const RULE *r = (const RULE *)hdata;
710 const HRNormalData *d = (const HRNormalData *)udata;
712 if (strcasecmp(r->name, d->str) == 0) {
713 if (d->wantAction && r->actions) return 1; // got it
714 if (!d->wantAction && r->procedure) return 1; // got it
716 return 0;
719 static int hr_glob (const void *hdata, void *udata) {
720 const RULE *r = (const RULE *)hdata;
721 const HRNormalData *d = (const HRNormalData *)udata;
723 if (matchglobex(d->str, r->name, d->casesens) == 0) {
724 //fprintf(stderr, ":[%s]\n", r->name);
725 if (d->wantAction && r->actions) return 1; // got it
726 if (!d->wantAction && r->procedure) return 1; // got it
728 return 0;
732 typedef struct {
733 re9_prog_t *re;
734 int wantAction;
735 } HRREData;
737 static int hr_regexp (const void *hdata, void *udata) {
738 const RULE *r = (const RULE *)hdata;
739 HRREData *d = (HRREData *)udata;
741 if (re9_execute(d->re, RE9_FLAG_NONUTF8, r->name, NULL, 0) > 0) {
742 //fprintf(stderr, ":[%s]\n", r->name);
743 if (d->wantAction && r->actions) return 1; // got it
744 if (!d->wantAction && r->procedure) return 1; // got it
746 return 0;
750 static LIST *builtin_haveruleactions (PARSE *parse, LOL *args, int *jmp) {
751 LIST *el = lol_get(args, 0), *l;
752 int wantAction = parse->num;
753 int casesens = 1;
754 int cmptype = 0; // <0:glob; >0:regexp
756 if (!el) return L0;
758 for (l = lol_get(args, 1); l != NULL; l = l->next) {
759 if (!strcmp("case-sensitive", l->string)) casesens = 1;
760 else if (!strcmp("case-insensitive", l->string)) casesens = 0;
761 else if (!strcmp("ignore-case", l->string)) casesens = 0;
762 else if (!strcmp("glob", l->string)) cmptype = -1;
763 else if (!strcmp("regexp", l->string)) cmptype = 1;
764 else if (!strcmp("plain", l->string)) cmptype = 0;
765 else {
766 printf("jam: invalid option for Have%s built-in: '%s'\n", wantAction?"Actions":"Rule", l->string);
767 exit(EXITBAD); /* yeech */
771 if (casesens == 1 && cmptype == 0) {
772 // standard mode
773 for (; el; el = el->next) {
774 RULE *r = findrule(el->string);
776 if (!r) return L0;
777 if (wantAction && !r->actions) return L0;
778 if (!wantAction && !r->procedure) return L0;
780 } else if (cmptype < 0) {
781 // glob
782 HRNormalData nfo;
784 nfo.wantAction = wantAction;
785 nfo.casesens = casesens;
787 for (; el; el = el->next) {
789 nfo.str = el->string;
790 if (!iteraterules(hr_glob, &nfo)) return L0;
792 } else if (cmptype > 0) {
793 // regexp
794 HRREData nfo;
796 nfo.wantAction = wantAction;
798 for (; el; el = el->next) {
799 const char *errmsg;
800 int err;
801 if ((nfo.re = re9_compile(el->string, RE9_FLAG_NONUTF8|(casesens ? 0 : RE9_FLAG_CASEINSENS), &errmsg)) == NULL) {
802 printf("FATAL: invalid regexp in Have%s: %s\n", (wantAction ? "Actions" : "Rule"), errmsg);
803 exit(42);
805 err = iteraterules(hr_regexp, &nfo);
806 re9_free(nfo.re);
807 if (!err) return L0;
809 } else {
810 // normal, case-insensitive
811 HRNormalData nfo;
812 nfo.wantAction = wantAction;
813 for (; el; el = el->next) {
814 nfo.str = el->string;
815 if (!iteraterules(hr_normal, &nfo)) return L0;
818 return list_new(L0, "1", 0);
822 /* generate random name:
823 * [ RandName ] -- /tmp/XXX
824 * [ RandName "abc/" ] -- abc/XXX
825 * [ RandName "" ] -- XXX
827 static LIST *builtin_randname (PARSE *parse, LOL *args, int *jmp) {
828 static BJRandCtx rctx;
829 static int initialized = 0;
830 static const char alphabet[] = "0123456789abcdefghijklmnopqrstuvwxyz";
831 LIST *el;
832 char buffer[9], *s;
833 const char *path;
834 #ifdef OS_NT
835 static char tp[8192]
836 #endif
837 if (!initialized) {
838 initialized = 1;
839 bjprngInit(&rctx, bjprngGenRandSeed());
840 #ifdef OS_NT
841 GetTempPath(sizeof(tp), tp);
842 #endif
844 for (int f = 0; f < 8; ++f) buffer[f] = alphabet[bjprngRand(&rctx)%strlen(alphabet)];
845 buffer[8] = 0;
846 el = lol_get(args, 0);
847 path = (el != NULL && el->string != NULL ? el->string :
848 #ifdef OS_NT
850 #else
851 "/tmp/"
852 #endif
854 s = alloca(strlen(path)+strlen(buffer)+2);
855 sprintf(s, "%s%s", path, buffer);
856 return list_new(L0, s, 0);
860 /* write list to file:
861 * ListFileWrite filename : list [: terminator] [: append]
862 * default terminator is '\n'
863 * return success flag
865 static LIST *builtin_listwrite (PARSE *parse, LOL *args, int *jmp) {
866 LIST *el = lol_get(args, 0);
867 if (el != NULL && el->string != NULL && el->string[0]) {
868 LIST *l = lol_get(args, 3);
869 FILE *fo = fopen(el->string, (l != NULL && l->string[0] ? "a" : "w"));
870 if (fo != NULL) {
871 const char *term;
872 l = lol_get(args, 2);
873 term = (l != NULL ? l->string : "\n");
874 for (l = lol_get(args, 1); l != NULL; l = l->next) {
875 if (fprintf(fo, "%s%s", l->string, term) < 0) {
876 fclose(fo);
877 unlink(el->string);
878 return L0;
881 fclose(fo);
882 return list_new(L0, "1", 0);
885 return L0;
890 * compile_builtin() - define builtin rules
893 #define P0 ((PARSE *)0)
894 #define C0 ((char *)0)
897 /* ":" -- previous name in upper case; "." -- previous name in lower case */
898 static JAMFA_SENTINEL void bind_builtin (PARSE *pp, ...) {
899 va_list ap;
900 const char *name, *lastname = "BAD";
902 va_start(ap, pp);
903 while ((name = va_arg(ap, const char *))) {
904 int updown = name[0]==':'?1:(name[0]=='.'?-1:0);
906 if (updown) {
907 if (updown == 1 && !compat_old_builtin_names) continue;
908 if (!(*(++name))) {
909 char *s = alloca(strlen(lastname)+1), *p;
911 strcpy(s, lastname);
912 for (p = s; *p; ++p) {
913 if (updown > 0) {
914 if (*p >= 'a' && *p <= 'z') (*p) -= 32; // upcase
915 } else {
916 if (*p >= 'A' && *p <= 'Z') (*p) += 32; // locase
919 name = s;
921 //fprintf(stderr, "%2d: [%s]\n", updown, name);
922 } else {
923 lastname = name;
925 bindrule(name)->procedure = pp;
927 va_end(ap);
931 void load_builtins (void) {
932 bind_builtin(parse_make(builtin_depends, P0, P0, P0, C0, C0, 0),
933 "Depends",
934 ":", NULL);
935 bind_builtin(parse_make(builtin_depends, P0, P0, P0, C0, C0, 1),
936 "Includes",
937 ":", NULL);
938 bind_builtin(parse_make(builtin_dependslist, P0, P0, P0, C0, C0, 0),
939 "DependsList",
940 ":", NULL);
942 bind_builtin(parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_TOUCHED),
943 "Always",
944 ":", NULL);
945 bind_builtin(parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_LEAVES),
946 "Leaves",
947 ":", NULL);
948 bind_builtin(parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_NOCARE),
949 "NoCare",
950 ":", NULL);
951 bind_builtin(parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_NOTFILE),
952 "NotFile", ":",
953 "NoTime",
954 ":", NULL);
955 bind_builtin(parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_NOUPDATE),
956 "NoUpdate",
957 ":", NULL);
958 bind_builtin(parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_TEMP),
959 "Temporary",
960 ":", NULL);
961 bind_builtin(parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_FORCECARE),
962 "ForceCare",
963 ":", NULL);
964 bind_builtin(parse_make(builtin_flags, P0, P0, P0, C0, C0, 666),
965 "ForceFile", ":",
966 ":", NULL);
968 bind_builtin(parse_make(builtin_echo, P0, P0, P0, C0, C0, 0),
969 "Echo", ".",
970 ":", NULL);
971 bind_builtin(parse_make(builtin_echon, P0, P0, P0, C0, C0, 0),
972 "Echo-n", ".",
973 ":", NULL);
974 bind_builtin(parse_make(builtin_oflush, P0, P0, P0, C0, C0, 0),
975 "O-Flush", "O-flush", ".",
976 ":", NULL);
978 bind_builtin(parse_make(builtin_exit, P0, P0, P0, C0, C0, 0),
979 "Exit", ".",
980 ":", NULL);
982 bind_builtin(parse_make(builtin_glob, P0, P0, P0, C0, C0, 0),
983 "Glob",
984 ":", NULL);
985 bind_builtin(parse_make(builtin_match, P0, P0, P0, C0, C0, 0),
986 "Match",
987 ":", NULL);
989 bind_builtin(parse_make(builtin_hdrmacro, P0, P0, P0, C0, C0, 0),
990 "HdrMacro",
991 ":", NULL);
993 bind_builtin(parse_make(builtin_pwd, P0, P0, P0, C0, C0, 0),
994 "Pwd",
995 ":", NULL);
997 bind_builtin(parse_make(builtin_sort, P0, P0, P0, C0, C0, 0),
998 "Sort",
999 ":", NULL);
1001 bind_builtin(parse_make(builtin_command, P0, P0, P0, C0, C0, 0),
1002 "Command",
1003 ":", NULL);
1005 bind_builtin(parse_make(builtin_expri1, P0, P0, P0, C0, C0, 0),
1006 "ExprI1",
1007 ":", NULL);
1009 bind_builtin(parse_make(builtin_split, P0, P0, P0, C0, C0, 0),
1010 "Split",
1011 ":", NULL);
1013 bind_builtin(parse_make(builtin_normpath, P0, P0, P0, C0, C0, 0),
1014 "NormPath",
1015 ":", NULL);
1017 bind_builtin(parse_make(builtin_listlength, P0, P0, P0, C0, C0, 0),
1018 "ListLength",
1019 ":", NULL);
1021 bind_builtin(parse_make(builtin_haveruleactions, P0, P0, P0, C0, C0, 0),
1022 "HaveRule",
1023 ":", NULL);
1024 bind_builtin(parse_make(builtin_haveruleactions, P0, P0, P0, C0, C0, 1),
1025 "HaveActions",
1026 ":", NULL);
1028 bind_builtin(parse_make(builtin_randname, P0, P0, P0, C0, C0, 0),
1029 "RandName", NULL);
1031 bind_builtin(parse_make(builtin_listwrite, P0, P0, P0, C0, C0, 0),
1032 "ListWrite", NULL);