cosmetix in filesystem operations
[k8jam.git] / src / builtins.c
blob288c8236d52b1a6b03f7e5343425c5ae5f1e083b
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 "dstrings.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 for (LIST *l = targets; l; l = list_next(l)) {
72 TARGET *t = bindtarget(l->string);
73 /* If doing INCLUDES, switch to the TARGET's include TARGET, creating it if needed.
74 * The internal include TARGET shares the name of its parent. */
75 if (parse->num) {
76 if (!t->includes) t->includes = copytarget(t);
77 t = t->includes;
79 t->depends = targetlist(t->depends, sources);
81 return L0;
86 * builtin_echo() - ECHO rule
88 * The ECHO builtin rule echoes the targets to the user.
89 * No other actions are taken.
91 static LIST *builtin_echo (PARSE *parse, LOL *args, int *jmp) {
92 int no_newline = 0, no_out = 0;
93 for (LIST *alst = lol_get(args, 1); alst; alst = list_next(alst)) {
94 if (strcmp(alst->string, "-n") == 0) no_newline = 1;
95 else if (strcmp(alst->string, "-Q") == 0) no_out = 1;
97 if (!no_out) list_print(lol_get(args, 0));
98 if (!no_newline) printf("\n"); else fflush(stdout);
99 return L0;
104 * builtin_exit() - EXIT rule
106 * The EXIT builtin rule echoes the targets to the user and exits
107 * the program with a failure status.
109 static LIST *builtin_exit (PARSE *parse, LOL *args, int *jmp) {
110 LIST *l = lol_get(args, 0);
111 int no_newline = 0, no_out = 0;
112 for (LIST *alst = lol_get(args, 1); alst; alst = list_next(alst)) {
113 if (strcmp(alst->string, "-n") == 0) no_newline = 1;
114 else if (strcmp(alst->string, "-Q") == 0) no_out = 1;
116 if (l != NULL) {
117 if (!no_out) list_print(l);
118 if (!no_newline) printf("\n"); else fflush(stdout);
119 exit(EXITBAD); /* yeech */
121 exit(EXITOK);
122 #if __GNUC__ <= 2
123 return L0;
124 #endif
129 * builtin_flags() - NOCARE, NOTFILE, TEMPORARY rule
131 * Builtin_flags() marks the target with the appropriate flag, for use by make0().
132 * It binds each target as a TARGET.
134 static LIST *builtin_flags (PARSE *parse, LOL *args, int *jmp) {
135 LIST *l = lol_get(args, 0);
136 int flag = parse->num, andflag = ~0;
137 switch (flag) {
138 case T_FLAG_NOCARE: andflag = ~T_FLAG_FORCECARE; break;
139 case T_FLAG_FORCECARE: andflag = ~T_FLAG_NOCARE; break;
140 case 666: flag = 0; andflag = ~T_FLAG_NOTFILE; break;
142 for (; l; l = list_next(l)) {
143 TARGET *t = bindtarget(l->string);
144 t->flags |= flag;
145 t->flags &= andflag;
147 return L0;
151 typedef enum {
152 GLOB_ANY,
153 GLOB_DIRS,
154 GLOB_FILES
155 } glob_mode;
159 * builtin_globbing() - GLOB rule
161 struct globbing {
162 LIST *patterns;
163 LIST *results;
164 int casesens;
165 int cmptype; // <0:glob; 0: plain; >0:# of regexps
166 glob_mode mode;
167 int namesonly;
168 re9_prog_t **re;
172 static void builtin_glob_back (void *closure, const char *file, int status, time_t time) {
173 struct globbing *globbing = (struct globbing *)closure;
174 LIST *l;
175 PATHNAME f;
176 static char buf[/*MAXJPATH*/8192];
177 /* null out directory for matching */
178 /* we wish we had file_dirscan() pass up a PATHNAME */
179 path_parse(file, &f);
180 f.f_dir.len = 0;
181 /* For globbing, we unconditionally ignore current and parent
182 * directory items. Since those items always exist, there's no
183 * reason why caller of GLOB would want to see them.
184 * We could also change file_dirscan, but then paths with embedded
185 * "." and ".." won't work anywhere. */
186 /* k8: will this break anything? it shouldn't... */
187 if (!strcmp(f.f_base.ptr, ".") || !strcmp(f.f_base.ptr, "..")) return;
188 path_build(&f, buf, 0);
190 fprintf(stderr, "buf: [%s]\n", buf);
191 int c;
192 for (c = 0; c < 6; ++c) fprintf(stderr, " %d: [%s]\n", c, f.part[c].ptr);
194 if (globbing->mode != GLOB_ANY) {
195 int ftype = file_type(file);
196 switch (globbing->mode) {
197 case GLOB_DIRS: if (ftype != 1) return; break;
198 case GLOB_FILES: if (ftype != 0) return; break;
199 default: ;
202 if (globbing->cmptype < 0) {
203 for (l = globbing->patterns; l; l = l->next) {
204 if (matchglobex(l->string, buf, globbing->casesens) == 0) {
205 globbing->results = list_new(globbing->results, (globbing->namesonly ? buf : file), 0);
206 break;
209 } else if (globbing->cmptype > 0) {
210 for (int f = 0; f < globbing->cmptype; ++f) {
211 if (re9_execute(globbing->re[f], RE9_FLAG_NONUTF8, buf, NULL, 0) > 0) {
212 globbing->results = list_new(globbing->results, (globbing->namesonly ? buf : file), 0);
213 break;
216 } else {
217 for (l = globbing->patterns; l; l = l->next) {
218 if ((globbing->casesens ? strcmp : strcasecmp)(l->string, buf) == 0) {
219 globbing->results = list_new(globbing->results, (globbing->namesonly ? buf : file), 0);
220 break;
227 static LIST *builtin_glob (PARSE *parse, LOL *args, int *jmp) {
228 LIST *l = lol_get(args, 0);
229 LIST *r = lol_get(args, 1);
230 LIST *lo;
231 struct globbing globbing;
232 if (!r) return L0;
233 globbing.results = L0;
234 globbing.patterns = r;
235 globbing.casesens = 1;
236 globbing.cmptype = -1;
237 globbing.mode = GLOB_ANY;
238 globbing.namesonly = 0;
239 for (lo = lol_get(args, 2); lo != NULL; lo = lo->next) {
240 if (!strcmp("case-sensitive", lo->string)) globbing.casesens = 1;
241 else if (!strcmp("case-insensitive", lo->string)) globbing.casesens = 0;
242 else if (!strcmp("ignore-case", lo->string)) globbing.casesens = 0;
243 else if (!strcmp("glob", lo->string)) globbing.cmptype = -1;
244 else if (!strcmp("regexp", lo->string)) globbing.cmptype = 1;
245 else if (!strcmp("plain", lo->string)) globbing.cmptype = 0;
246 else if (!strcmp("dirs-only", lo->string)) globbing.mode = GLOB_DIRS;
247 else if (!strcmp("files-only", lo->string)) globbing.mode = GLOB_FILES;
248 else if (!strcmp("any", lo->string)) globbing.mode = GLOB_ANY;
249 else if (!strcmp("names-only", lo->string)) globbing.namesonly = 1;
250 else if (!strcmp("full-path", lo->string)) globbing.namesonly = 0;
251 else {
252 printf("jam: invalid option for Glob built-in: '%s'\n", lo->string);
253 exit(EXITBAD); /* yeech */
256 if (globbing.cmptype > 0) {
257 /* compile regexps */
258 globbing.cmptype = list_length(r);
259 globbing.re = malloc(sizeof(globbing.re[0])*globbing.cmptype);
260 if (globbing.re == NULL) {
261 printf("FATAL: out of memory in Glob\n");
262 exit(42);
264 for (int f = 0; r; r = r->next, ++f) {
265 const char *errmsg;
266 if ((globbing.re[f] = re9_compile(r->string, RE9_FLAG_NONUTF8|(globbing.casesens ? 0 : RE9_FLAG_CASEINSENS), &errmsg)) == NULL) {
267 printf("FATAL: invalid regexp in Glob: %s\n", errmsg);
268 exit(42);
271 } else {
272 globbing.re = NULL;
274 for (; l; l = list_next(l)) file_dirscan(l->string, builtin_glob_back, &globbing);
275 if (globbing.re) {
276 for (int f = 0; f < globbing.cmptype; ++f) re9_free(globbing.re[f]);
277 free(globbing.re);
279 return globbing.results;
284 * builtin_match() - MATCH rule, regexp matching
286 static LIST *builtin_match (PARSE *parse, LOL *args, int *jmp) {
287 LIST *l, *lo;
288 LIST *res = 0;
289 int casesens = 1;
290 int cmptype = 1;
291 for (lo = lol_get(args, 2); lo != NULL; lo = lo->next) {
292 if (!strcmp("case-sensitive", lo->string)) casesens = 1;
293 else if (!strcmp("case-insensitive", lo->string)) casesens = 0;
294 else if (!strcmp("ignore-case", lo->string)) casesens = 0;
295 else if (!strcmp("glob", lo->string)) cmptype = -1;
296 else if (!strcmp("regexp", lo->string)) cmptype = 1;
297 else if (!strcmp("plain", lo->string)) cmptype = 0;
298 else {
299 printf("jam: invalid option for Match built-in: '%s'\n", lo->string);
300 exit(EXITBAD); /* yeech */
303 /* for each pattern */
304 for (l = lol_get(args, 0); l; l = l->next) {
305 LIST *r;
306 if (cmptype > 0) {
307 re9_prog_t *re;
308 re9_sub_t mt[RE9_SUBEXP_MAX];
309 const char *errmsg;
310 if ((re = re9_compile(l->string, RE9_FLAG_NONUTF8|(casesens ? 0 : RE9_FLAG_CASEINSENS), &errmsg)) == NULL) {
311 printf("FATAL: %s\n", errmsg);
312 exit(42);
314 /* for each string to match against */
315 for (r = lol_get(args, 1); r; r = r->next) {
316 mt[0].sp = mt[0].ep = NULL;
317 if (re9_execute(re, RE9_FLAG_NONUTF8, r->string, mt, RE9_SUBEXP_MAX) > 0) {
318 int i;
319 dstring_t buf;
320 /* add all parameters up to highest onto list */
321 /* must have parameters to have results! */
322 dstr_init(&buf);
323 for (i = 1; i < RE9_SUBEXP_MAX; ++i) {
324 int l = mt[i].ep-mt[i].sp;
325 dstr_clear(&buf);
326 if (l > 0) dstr_push_buf(&buf, mt[i].sp, l);
327 res = list_new(res, dstr_cstr(&buf), 0);
329 dstr_done(&buf);
332 re9_free(re);
333 } else if (cmptype < 0) {
334 for (r = lol_get(args, 1); r; r = r->next) {
335 if (matchglobex(l->string, r->string, casesens) == 0) {
336 res = list_new(res, r->string, 0);
339 } else {
340 for (r = lol_get(args, 1); r; r = r->next) {
341 if ((casesens ? strcmp : strcasecmp)(l->string, r->string) == 0) {
342 res = list_new(res, r->string, 0);
347 return res;
351 static LIST *builtin_hdrmacro (PARSE *parse, LOL *args, int *jmp) {
352 LIST *l = lol_get(args, 0);
353 for (; l; l = list_next(l)) {
354 TARGET *t = bindtarget(l->string);
355 /* scan file for header filename macro definitions */
356 if (DEBUG_HEADER) printf("scanning '%s' for header file macro definitions\n", l->string);
357 macro_headers(t);
359 return L0;
363 /* backported from boost-jam */
365 * Return the current working directory.
367 * Usage: pwd = [ PWD ] ;
369 static LIST *builtin_pwd (PARSE *parse, LOL *args, int *jmp) {
370 char pwd_buffer[PATH_MAX];
371 if (!getcwd(pwd_buffer, sizeof(pwd_buffer))) {
372 perror("can not get current directory");
373 return L0;
375 return list_new(L0, pwd_buffer, 0);
379 /* backported from boost-jam */
380 static LIST *builtin_sort (PARSE *parse, LOL *args, int *jmp) {
381 LIST *arg = lol_get(args, 0);
382 arg = list_sort(arg);
383 return arg;
387 /* backported from boost-jam; greatly improved */
388 /* Command shcmd [[ : options ]] */
389 static LIST *builtin_command (PARSE *parse, LOL *args, int *jmp) {
390 LIST *res = NULL;
391 LIST *l;
392 int ret;
393 char buffer[1024], buf1[32], *spos, *epos;
394 FILE *p = NULL;
395 int exitStatus = -1;
396 int optExitStatus = 0;
397 int optNoOutput = 0;
398 int optTrimLeft = 1;
399 int optTrimRight = 1;
400 int optStatus1st = 0;
401 int optParseOut = 0;
402 int optSpaceBreak = 1;
403 int optTabBreak = 1;
404 int optCRBreak = 1;
405 int optLFBreak = 1;
406 int no_options = ((l = lol_get(args, 1)) == NULL);
407 dstring_t str;
408 /* for each string in 2nd list: check for arg */
409 for (; l != NULL; l = l->next) {
410 if (!strcmp("exit-status", l->string)) optExitStatus = 1;
411 else if (!strcmp("exit-code", l->string)) optExitStatus = 1;
412 else if (!strcmp("status-first", l->string)) optStatus1st = 1;
413 else if (!strcmp("code-first", l->string)) optStatus1st = 1;
414 else if (!strcmp("no-output", l->string)) optNoOutput = 1;
415 else if (!strcmp("no-trim", l->string)) optTrimLeft = optTrimRight = 0;
416 else if (!strcmp("no-trim-left", l->string)) optTrimLeft = 0;
417 else if (!strcmp("no-trim-right", l->string)) optTrimRight = 0;
418 else if (!strcmp("parse-output", l->string)) optParseOut = 1;
419 else if (!strcmp("no-space-break", l->string)) optSpaceBreak = 0;
420 else if (!strcmp("no-tab-break", l->string)) optTabBreak = 0;
421 else if (!strcmp("no-nl-break", l->string)) optLFBreak = 0;
422 else if (!strcmp("no-lf-break", l->string)) optLFBreak = 0;
423 else if (!strcmp("no-cr-break", l->string)) optCRBreak = 0;
424 else {
425 printf("jam: invalid option for Command built-in: '%s'\n", l->string);
426 exit(EXITBAD); /* yeech */
429 if (no_options) optNoOutput = 1;
430 /* build shell command */
431 dstr_init(&str);
432 /* for each arg */
433 for (l = lol_get(args, 0); l; l = l->next) {
434 if (dstr_len(&str)) dstr_push_char(&str, ' ');
435 dstr_push_cstr(&str, l->string);
437 /* no shell command? */
438 if (dstr_len(&str) < 1) { dstr_done(&str); return L0; }
439 fflush(NULL); /* flush ALL output streams */
440 p = popen(dstr_cstr(&str), "r");
441 if (!p) { dstr_done(&str); return L0; }
442 dstr_clear(&str);
443 while ((ret = fread(buffer, sizeof(char), sizeof(buffer)-1, p)) > 0) {
444 if (!optNoOutput) {
445 buffer[ret] = 0;
446 dstr_push_cstr(&str, buffer);
449 exitStatus = pclose(p);
450 if (no_options) {
451 if (exitStatus) {
452 snprintf(buf1, sizeof(buf1), "%d", exitStatus);
453 res = list_new(L0, buffer, 0);
454 } else {
455 res = L0;
457 } else {
458 if (optExitStatus && optStatus1st) {
459 snprintf(buf1, sizeof(buf1), "%d", exitStatus);
460 res = list_new(res, buf1, 0);
462 /* trim output if necessary */
463 if (!optNoOutput) {
464 if (!optParseOut) {
465 /* don't parse */
466 if (optTrimRight) {
467 // trim trailing blanks
468 int sl = dstr_len(&str);
469 spos = dstr_cstr(&str);
470 while (sl > 0 && (unsigned char)spos[sl-1] <= ' ') --sl;
471 dstr_chop(&str, sl);
473 spos = dstr_cstr(&str);
474 if (optTrimLeft) {
475 // trim leading blanks
476 while (*spos && *((unsigned char *)spos) <= ' ') ++spos;
478 res = list_new(res, spos, 0);
479 } else {
480 dstring_t tmp;
481 /* parse output */
482 ret = 0; /* was anything added? list must have at least one element */
483 spos = dstr_cstr(&str);
484 dstr_init(&tmp);
485 while (*spos) {
486 /* skip delimiters */
487 while (*spos) {
488 unsigned char ch = (unsigned char)(*spos);
489 if (ch == ' ') { if (!optSpaceBreak) break; }
490 else if (ch == '\t') { if (!optTabBreak) break; }
491 else if (ch == '\r') { if (!optCRBreak) break; }
492 else if (ch == '\n') { if (!optLFBreak) break; }
493 else if (ch > ' ') break;
494 ++spos;
496 if (!*spos) break;
497 epos = spos+1;
498 while (*epos) {
499 int ch = *epos;
500 if (ch == ' ') { if (optSpaceBreak) break; }
501 else if (ch == '\t') { if (optTabBreak) break; }
502 else if (ch == '\r') { if (optCRBreak) break; }
503 else if (ch == '\n') { if (optLFBreak) break; }
504 else if ((unsigned char)ch <= ' ') break;
505 ++epos;
507 dstr_clear(&tmp);
508 dstr_push_memrange(&tmp, spos, epos);
509 res = list_new(res, dstr_cstr(&tmp), 0);
510 ret = 1;
511 spos = epos;
513 dstr_done(&tmp);
514 if (!ret) { buf1[0] = '\0'; res = list_new(res, buf1, 0); }
517 /* command exit result next */
518 if (optExitStatus && !optStatus1st) {
519 snprintf(buf1, sizeof(buf1), "%d", exitStatus);
520 res = list_new(res, buf1, 0);
523 dstr_done(&str);
524 return res;
528 /* ExprI1 op0 math op1 */
529 static LIST *builtin_expri1 (PARSE *parse, LOL *args, int *jmp) {
530 char buffer[100];
531 int op0, op1, res, comp = 0;
532 LIST *el = lol_get(args, 0);
533 if (!el || !el->next) return L0;
534 if (el->string[0] == '#') {
535 // string length
536 snprintf(buffer, sizeof(buffer), "%d", strlen(el->next->string));
537 return list_new(L0, buffer, 0);
539 if (!el->next->next) return L0;
540 op0 = atoi(el->string);
541 op1 = atoi(el->next->next->string);
542 res = 0;
543 switch (el->next->string[0]) {
544 case '+': res = op0+op1; break;
545 case '-': res = op0-op1; break;
546 case '*': res = op0*op1; break;
547 case '/': res = op0/op1; break;
548 case '%': res = op0%op1; break;
549 case '<':
550 comp = 1;
551 if (el->next->string[1] == '=') res = (op0 <= op1); else res = (op0 < op1);
552 break;
553 case '=': comp = 1; res = (op0 == op1); break;
554 case '!': comp = 1; res = (op0 != op1); break;
555 case '>':
556 comp = 1;
557 if (el->next->string[1] == '=') res = (op0 >= op1); else res = (op0 > op1);
558 break;
559 default:
560 printf("jam: rule ExprI1: unknown operator: '%s'\n", el->next->string);
561 exit(EXITBAD);
563 if (comp) return (res ? list_new(L0, "tan", 0) : L0);
564 snprintf(buffer, sizeof(buffer), "%d", res);
565 return list_new(L0, buffer, 0);
569 /* based on the code from ftjam by David Turner */
570 static LIST *builtin_split (PARSE *parse, LOL *args, int *jmp) {
571 LIST *input = lol_get(args, 0);
572 LIST *tokens = lol_get(args, 1);
573 LIST *res = L0;
574 char token[256];
575 dstring_t str;
576 int explode = 0;
577 dstr_init(&str);
578 /* build token array */
579 if (tokens == NULL) {
580 memset(token, 1, sizeof(token));
581 explode = 1;
582 } else {
583 memset(token, 0, sizeof(token));
584 for (; tokens; tokens = tokens->next) {
585 const char *s = tokens->string;
586 for (; *s; ++s) token[(unsigned char)*s] = 1;
588 if (memchr(token, 1, sizeof(token)) == NULL) {
589 memset(token, 1, sizeof(token));
590 explode = 1;
593 token[0] = 0;
594 /* now parse the input and split it */
595 for (; input; input = input->next) {
596 const char *ptr = input->string;
597 const char *lastPtr = input->string;
598 while (*ptr) {
599 if (token[(unsigned char)*ptr]) {
600 size_t count = ptr-lastPtr+explode;
601 if (count > 0) {
602 dstr_clear(&str);
603 dstr_push_memrange(&str, lastPtr, ptr+explode);
604 res = list_new(res, dstr_cstr(&str), 0);
606 lastPtr = ptr+1;
608 ++ptr;
610 if (ptr > lastPtr) res = list_new(res, lastPtr, 0);
612 dstr_done(&str);
613 return res;
618 * builtin_dependslist()
620 * The DependsList builtin rule returns list of dependencies for
621 * a given target.
623 static LIST *builtin_dependslist (PARSE *parse, LOL *args, int *jmp) {
624 LIST *res = L0;
625 LIST *parents;
626 for (parents = lol_get(args, 0); parents; parents = parents->next) {
627 TARGET *t = bindtarget(parents->string);
628 TARGETS *child;
629 for (child = t->depends; child; child = child->next) res = list_new(res, child->target->name, 1);
631 return res;
636 * NormPath path
638 * it will add 'pwd' if path is not absolute and will try to resolve some '.' and '..' (only leading '..' though).
639 * it can be used in SubDir replacement to automate dir building
641 static LIST *builtin_normpath (PARSE *parse, LOL *args, int *jmp) {
642 LIST *el = lol_get(args, 0);
643 char *buf;
644 int bsz;
645 if (!el || !el->string) return L0;
646 bsz = strlen(el->string)*2+1024;
647 buf = malloc(bsz);
648 if (buf == NULL) return L0;
649 if (!normalize_path(el->string, buf, bsz)) { free(buf); return L0; }
650 el = list_new(NULL, buf, 0);
651 free(buf);
652 return el;
657 * ListLength list
659 static LIST *builtin_listlength (PARSE *parse, LOL *args, int *jmp) {
660 char buffer[100];
661 LIST *el = lol_get(args, 0);
662 if (!el) return list_new(L0, "0", 0);
663 snprintf(buffer, sizeof(buffer), "%d", list_length(el));
664 return list_new(L0, buffer, 0);
669 * HaveRule and HaveActions
671 typedef struct {
672 LIST *el;
673 int wantAction;
674 int casesens;
675 } HRNGCI;
678 typedef struct {
679 const char *str;
680 int wantAction;
681 int casesens;
682 } HRNormalData;
685 static int hr_normal (const void *hdata, void *udata) {
686 const RULE *r = (const RULE *)hdata;
687 const HRNormalData *d = (const HRNormalData *)udata;
688 if (strcasecmp(r->name, d->str) == 0) {
689 if (d->wantAction && r->actions) return 1; // got it
690 if (!d->wantAction && r->procedure) return 1; // got it
692 return 0;
696 static int hr_glob (const void *hdata, void *udata) {
697 const RULE *r = (const RULE *)hdata;
698 const HRNormalData *d = (const HRNormalData *)udata;
699 if (matchglobex(d->str, r->name, d->casesens) == 0) {
700 //fprintf(stderr, ":[%s]\n", r->name);
701 if (d->wantAction && r->actions) return 1; // got it
702 if (!d->wantAction && r->procedure) return 1; // got it
704 return 0;
708 typedef struct {
709 re9_prog_t *re;
710 int wantAction;
711 } HRREData;
714 static int hr_regexp (const void *hdata, void *udata) {
715 const RULE *r = (const RULE *)hdata;
716 HRREData *d = (HRREData *)udata;
717 if (re9_execute(d->re, RE9_FLAG_NONUTF8, r->name, NULL, 0) > 0) {
718 //fprintf(stderr, ":[%s]\n", r->name);
719 if (d->wantAction && r->actions) return 1; // got it
720 if (!d->wantAction && r->procedure) return 1; // got it
722 return 0;
726 static LIST *builtin_haveruleactions (PARSE *parse, LOL *args, int *jmp) {
727 LIST *el = lol_get(args, 0), *l;
728 int wantAction = parse->num;
729 int casesens = 1;
730 int cmptype = 0; // <0:glob; >0:regexp
731 if (!el) return L0;
732 for (l = lol_get(args, 1); l != NULL; l = l->next) {
733 if (!strcmp("case-sensitive", l->string)) casesens = 1;
734 else if (!strcmp("case-insensitive", l->string)) casesens = 0;
735 else if (!strcmp("ignore-case", l->string)) casesens = 0;
736 else if (!strcmp("glob", l->string)) cmptype = -1;
737 else if (!strcmp("regexp", l->string)) cmptype = 1;
738 else if (!strcmp("plain", l->string)) cmptype = 0;
739 else {
740 printf("jam: invalid option for Have%s built-in: '%s'\n", (wantAction ? "Actions" : "Rule"), l->string);
741 exit(EXITBAD); /* yeech */
744 if (casesens == 1 && cmptype == 0) {
745 // standard mode
746 for (; el; el = el->next) {
747 RULE *r = findrule(el->string);
748 if (!r) return L0;
749 if (wantAction && !r->actions) return L0;
750 if (!wantAction && !r->procedure) return L0;
752 } else if (cmptype < 0) {
753 // glob
754 HRNormalData nfo;
755 nfo.wantAction = wantAction;
756 nfo.casesens = casesens;
757 for (; el; el = el->next) {
758 nfo.str = el->string;
759 if (!iteraterules(hr_glob, &nfo)) return L0;
761 } else if (cmptype > 0) {
762 // regexp
763 HRREData nfo;
764 nfo.wantAction = wantAction;
765 for (; el; el = el->next) {
766 const char *errmsg;
767 int err;
768 if ((nfo.re = re9_compile(el->string, RE9_FLAG_NONUTF8|(casesens ? 0 : RE9_FLAG_CASEINSENS), &errmsg)) == NULL) {
769 printf("FATAL: invalid regexp in Have%s: %s\n", (wantAction ? "Actions" : "Rule"), errmsg);
770 exit(EXITBAD);
772 err = iteraterules(hr_regexp, &nfo);
773 re9_free(nfo.re);
774 if (!err) return L0;
776 } else {
777 // normal, case-insensitive
778 HRNormalData nfo;
779 nfo.wantAction = wantAction;
780 for (; el; el = el->next) {
781 nfo.str = el->string;
782 if (!iteraterules(hr_normal, &nfo)) return L0;
785 return list_new(L0, "1", 0);
789 /* generate random name:
790 * [ RandName ] -- /tmp/XXX
791 * [ RandName "abc/" ] -- abc/XXX
792 * [ RandName "" ] -- XXX
794 static LIST *builtin_randname (PARSE *parse, LOL *args, int *jmp) {
795 static BJRandCtx rctx;
796 static int initialized = 0;
797 static const char alphabet[] = "0123456789abcdefghijklmnopqrstuvwxyz";
798 LIST *el;
799 char buffer[9], *s;
800 const char *path;
801 #ifdef OS_NT
802 static char tp[8192]
803 #endif
804 if (!initialized) {
805 initialized = 1;
806 bjprngInit(&rctx, bjprngGenRandSeed());
807 #ifdef OS_NT
808 GetTempPath(sizeof(tp), tp);
809 #endif
811 for (int f = 0; f < 8; ++f) buffer[f] = alphabet[bjprngRand(&rctx)%strlen(alphabet)];
812 buffer[8] = 0;
813 el = lol_get(args, 0);
814 path = (el != NULL && el->string != NULL ? el->string :
815 #ifdef OS_NT
817 #else
818 "/tmp/"
819 #endif
821 s = alloca(strlen(path)+strlen(buffer)+2);
822 sprintf(s, "%s%s", path, buffer);
823 return list_new(L0, s, 0);
827 /* write list to file:
828 * ListFileWrite filename : list [: terminator] [: append]
829 * default terminator is '\n'
830 * return success flag
832 static LIST *builtin_listwrite (PARSE *parse, LOL *args, int *jmp) {
833 LIST *el = lol_get(args, 0);
834 if (el != NULL && el->string != NULL && el->string[0]) {
835 LIST *l = lol_get(args, 3);
836 FILE *fo = fopen(el->string, (l != NULL && l->string[0] ? "a" : "w"));
837 if (fo != NULL) {
838 const char *term;
839 l = lol_get(args, 2);
840 term = (l != NULL ? l->string : "\n");
841 for (l = lol_get(args, 1); l != NULL; l = l->next) {
842 if (fprintf(fo, "%s%s", l->string, term) < 0) {
843 fclose(fo);
844 unlink(el->string);
845 return L0;
848 fclose(fo);
849 return list_new(L0, "1", 0);
852 return L0;
857 * compile_builtin() - define builtin rules
860 #define P0 ((PARSE *)0)
861 #define C0 ((char *)0)
864 /* ":" -- previous name in upper case; "." -- previous name in lower case */
865 static JAMFA_SENTINEL void bind_builtin (PARSE *pp, ...) {
866 va_list ap;
867 const char *name, *lastname = "BAD";
869 va_start(ap, pp);
870 while ((name = va_arg(ap, const char *))) {
871 int updown = name[0]==':'?1:(name[0]=='.'?-1:0);
873 if (updown) {
874 if (updown == 1 && !compat_old_builtin_names) continue;
875 if (!(*(++name))) {
876 char *s = alloca(strlen(lastname)+1), *p;
878 strcpy(s, lastname);
879 for (p = s; *p; ++p) {
880 if (updown > 0) {
881 if (*p >= 'a' && *p <= 'z') (*p) -= 32; // upcase
882 } else {
883 if (*p >= 'A' && *p <= 'Z') (*p) += 32; // locase
886 name = s;
888 //fprintf(stderr, "%2d: [%s]\n", updown, name);
889 } else {
890 lastname = name;
892 bindrule(name)->procedure = pp;
894 va_end(ap);
898 void load_builtins (void) {
899 bind_builtin(parse_make(builtin_depends, P0, P0, P0, C0, C0, 0),
900 "Depends",
901 ":", NULL);
902 bind_builtin(parse_make(builtin_depends, P0, P0, P0, C0, C0, 1),
903 "Includes",
904 ":", NULL);
905 bind_builtin(parse_make(builtin_dependslist, P0, P0, P0, C0, C0, 0),
906 "DependsList",
907 ":", NULL);
909 bind_builtin(parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_TOUCHED),
910 "Always",
911 ":", NULL);
912 bind_builtin(parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_LEAVES),
913 "Leaves",
914 ":", NULL);
915 bind_builtin(parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_NOCARE),
916 "NoCare",
917 ":", NULL);
918 bind_builtin(parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_NOTFILE),
919 "NotFile", ":",
920 "NoTime",
921 ":", NULL);
922 bind_builtin(parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_NOUPDATE),
923 "NoUpdate",
924 ":", NULL);
925 bind_builtin(parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_TEMP),
926 "Temporary",
927 ":", NULL);
928 bind_builtin(parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_FORCECARE),
929 "ForceCare",
930 ":", NULL);
931 bind_builtin(parse_make(builtin_flags, P0, P0, P0, C0, C0, 666),
932 "ForceFile", ":",
933 ":", NULL);
935 bind_builtin(parse_make(builtin_echo, P0, P0, P0, C0, C0, 0),
936 "Echo", ".",
937 ":", NULL);
939 bind_builtin(parse_make(builtin_exit, P0, P0, P0, C0, C0, 0),
940 "Exit", ".",
941 ":", NULL);
943 bind_builtin(parse_make(builtin_glob, P0, P0, P0, C0, C0, 0),
944 "Glob",
945 ":", NULL);
946 bind_builtin(parse_make(builtin_match, P0, P0, P0, C0, C0, 0),
947 "Match",
948 ":", NULL);
950 bind_builtin(parse_make(builtin_hdrmacro, P0, P0, P0, C0, C0, 0),
951 "HdrMacro",
952 ":", NULL);
954 bind_builtin(parse_make(builtin_pwd, P0, P0, P0, C0, C0, 0),
955 "Pwd",
956 ":", NULL);
958 bind_builtin(parse_make(builtin_sort, P0, P0, P0, C0, C0, 0),
959 "Sort",
960 ":", NULL);
962 bind_builtin(parse_make(builtin_command, P0, P0, P0, C0, C0, 0),
963 "Command",
964 ":", NULL);
966 bind_builtin(parse_make(builtin_expri1, P0, P0, P0, C0, C0, 0),
967 "ExprI1",
968 ":", NULL);
970 bind_builtin(parse_make(builtin_split, P0, P0, P0, C0, C0, 0),
971 "Split",
972 ":", NULL);
974 bind_builtin(parse_make(builtin_normpath, P0, P0, P0, C0, C0, 0),
975 "NormPath",
976 ":", NULL);
978 bind_builtin(parse_make(builtin_listlength, P0, P0, P0, C0, C0, 0),
979 "ListLength",
980 ":", NULL);
982 bind_builtin(parse_make(builtin_haveruleactions, P0, P0, P0, C0, C0, 0),
983 "HaveRule",
984 ":", NULL);
985 bind_builtin(parse_make(builtin_haveruleactions, P0, P0, P0, C0, C0, 1),
986 "HaveActions",
987 ":", NULL);
989 bind_builtin(parse_make(builtin_randname, P0, P0, P0, C0, C0, 0),
990 "RandName", NULL);
992 bind_builtin(parse_make(builtin_listwrite, P0, P0, P0, C0, C0, 0),
993 "ListWrite", NULL);