fixed idiotic bug in Command built-in
[k8jam.git] / src / builtins.c
blob389803b703a53ea2f4d518262dc5fb434e06c21d
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_exit() - EXIT rule
19 * builtin_flags() - NOCARE, NOTFILE, TEMPORARY rule
20 * builtin_glob() - GLOB rule
21 * builtin_match() - MATCH rule
22 * builtin_hdrmacro() - HDRMACRO rule
24 * 01/10/01 (seiwald) - split from compile.c
25 * 01/08/01 (seiwald) - new 'Glob' (file expansion) builtin
26 * 03/02/02 (seiwald) - new 'Match' (regexp match) builtin
27 * 04/03/02 (seiwald) - Glob matches only filename, not directory
28 * 10/22/02 (seiwald) - list_new() now does its own newstr()/copystr()
29 * 10/22/02 (seiwald) - working return/break/continue statements
30 * 11/04/02 (seiwald) - const-ing for string literals
31 * 12/03/02 (seiwald) - fix odd includes support by grafting them onto depends
32 * 01/14/03 (seiwald) - fix includes fix with new internal includes TARGET
34 #include <limits.h>
35 #include <stdarg.h>
36 #include <stdint.h>
37 #include <strings.h>
38 #include <unistd.h>
40 #include "jam.h"
42 #include "lists.h"
43 #include "parse.h"
44 #include "bjprng.h"
45 #include "builtins.h"
46 #include "rules.h"
47 #include "filesys.h"
48 #include "newstr.h"
49 #include "re9.h"
50 #include "pathsys.h"
51 #include "hdrmacro.h"
52 #include "matchglob.h"
53 #include "dstrings.h"
56 int compat_old_builtin_names = 0;
60 * builtin_depends() - DEPENDS/INCLUDES rule
62 * The DEPENDS builtin rule appends each of the listed sources on the
63 * dependency list of each of the listed targets.
64 * It binds both the targets and sources as TARGETs.
66 static LIST *builtin_depends (PARSE *parse, LOL *args, int *jmp) {
67 LIST *targets = lol_get(args, 0);
68 LIST *sources = lol_get(args, 1);
69 for (LIST *l = targets; l; l = list_next(l)) {
70 TARGET *t = bindtarget(l->string);
71 /* If doing INCLUDES, switch to the TARGET's include TARGET, creating it if needed.
72 * The internal include TARGET shares the name of its parent. */
73 if (parse->num) {
74 if (!t->includes) t->includes = copytarget(t);
75 t = t->includes;
77 t->depends = targetlist(t->depends, sources);
79 return L0;
83 typedef struct {
84 int no_newline;
85 int no_out;
86 int flags;
87 } echo_flags_t;
90 static void parse_echo_flags (echo_flags_t *flg, const LIST *l) {
91 flg->no_newline = 0;
92 flg->no_out = 0;
93 flg->flags = LPFLAG_NO_TRSPACE;
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;
111 * builtin_echo() - ECHO rule
113 * The ECHO builtin rule echoes the targets to the user.
114 * No other actions are taken.
116 static LIST *builtin_echo (PARSE *parse, LOL *args, int *jmp) {
117 echo_flags_t ef;
118 parse_echo_flags(&ef, lol_get(args, 1));
119 if (!ef.no_out) list_print_ex(lol_get(args, 0), ef.flags);
120 if (!ef.no_newline) printf("\n"); else fflush(stdout);
121 return L0;
126 * builtin_exit() - EXIT rule
128 * The EXIT builtin rule echoes the targets to the user and exits
129 * the program with a failure status.
131 static LIST *builtin_exit (PARSE *parse, LOL *args, int *jmp) {
132 LIST *l = lol_get(args, 0);
133 echo_flags_t ef;
134 parse_echo_flags(&ef, lol_get(args, 1));
135 if (l != NULL) {
136 if (!ef.no_out) list_print_ex(l, ef.flags);
137 if (!ef.no_newline) printf("\n"); else fflush(stdout);
138 exit(EXITBAD); /* yeech */
140 exit(EXITOK);
141 #if __GNUC__ <= 2
142 return L0;
143 #endif
148 * builtin_flags() - NOCARE, NOTFILE, TEMPORARY rule
150 * Builtin_flags() marks the target with the appropriate flag, for use by make0().
151 * It binds each target as a TARGET.
153 static LIST *builtin_flags (PARSE *parse, LOL *args, int *jmp) {
154 LIST *l = lol_get(args, 0);
155 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;
161 for (; l; l = list_next(l)) {
162 TARGET *t = bindtarget(l->string);
163 t->flags |= flag;
164 t->flags &= andflag;
166 return L0;
170 typedef enum {
171 GLOB_ANY,
172 GLOB_DIRS,
173 GLOB_FILES
174 } glob_mode;
178 * builtin_globbing() - GLOB rule
180 struct globbing {
181 LIST *patterns;
182 LIST *results;
183 int casesens;
184 int cmptype; // <0:glob; 0: plain; >0:# of regexps
185 glob_mode mode;
186 int namesonly;
187 re9_prog_t **re;
191 static void builtin_glob_back (void *closure, const char *file, int status, time_t time) {
192 struct globbing *globbing = (struct globbing *)closure;
193 LIST *l;
194 PATHNAME f;
195 static char buf[/*MAXJPATH*/8192];
196 /* null out directory for matching */
197 /* we wish we had file_dirscan() pass up a PATHNAME */
198 path_parse(file, &f);
199 f.f_dir.len = 0;
200 /* For globbing, we unconditionally ignore current and parent
201 * directory items. Since those items always exist, there's no
202 * reason why caller of GLOB would want to see them.
203 * We could also change file_dirscan, but then paths with embedded
204 * "." and ".." won't work anywhere. */
205 /* k8: will this break anything? it shouldn't... */
206 if (!strcmp(f.f_base.ptr, ".") || !strcmp(f.f_base.ptr, "..")) return;
207 path_build(&f, buf, 0);
209 fprintf(stderr, "buf: [%s]\n", buf);
210 int c;
211 for (c = 0; c < 6; ++c) fprintf(stderr, " %d: [%s]\n", c, f.part[c].ptr);
213 if (globbing->mode != GLOB_ANY) {
214 int ftype = file_type(file);
215 switch (globbing->mode) {
216 case GLOB_DIRS: if (ftype != 1) return; break;
217 case GLOB_FILES: if (ftype != 0) return; break;
218 default: ;
221 if (globbing->cmptype < 0) {
222 for (l = globbing->patterns; l; l = l->next) {
223 if (matchglobex(l->string, buf, globbing->casesens) == 0) {
224 globbing->results = list_new(globbing->results, (globbing->namesonly ? buf : file), 0);
225 break;
228 } else if (globbing->cmptype > 0) {
229 for (int f = 0; f < globbing->cmptype; ++f) {
230 if (re9_execute(globbing->re[f], RE9_FLAG_NONUTF8, buf, NULL, 0) > 0) {
231 globbing->results = list_new(globbing->results, (globbing->namesonly ? buf : file), 0);
232 break;
235 } else {
236 for (l = globbing->patterns; l; l = l->next) {
237 if ((globbing->casesens ? strcmp : strcasecmp)(l->string, buf) == 0) {
238 globbing->results = list_new(globbing->results, (globbing->namesonly ? buf : file), 0);
239 break;
246 static LIST *builtin_glob (PARSE *parse, LOL *args, int *jmp) {
247 LIST *l = lol_get(args, 0);
248 LIST *r = lol_get(args, 1);
249 LIST *lo;
250 struct globbing globbing;
251 if (!r) return L0;
252 globbing.results = L0;
253 globbing.patterns = r;
254 globbing.casesens = 1;
255 globbing.cmptype = -1;
256 globbing.mode = GLOB_ANY;
257 globbing.namesonly = 0;
258 for (lo = lol_get(args, 2); lo != NULL; lo = lo->next) {
259 if (!strcmp("case-sensitive", lo->string)) globbing.casesens = 1;
260 else if (!strcmp("case-insensitive", lo->string)) globbing.casesens = 0;
261 else if (!strcmp("ignore-case", lo->string)) globbing.casesens = 0;
262 else if (!strcmp("glob", lo->string)) globbing.cmptype = -1;
263 else if (!strcmp("regexp", lo->string)) globbing.cmptype = 1;
264 else if (!strcmp("plain", lo->string)) globbing.cmptype = 0;
265 else if (!strcmp("dirs-only", lo->string)) globbing.mode = GLOB_DIRS;
266 else if (!strcmp("files-only", lo->string)) globbing.mode = GLOB_FILES;
267 else if (!strcmp("any", lo->string)) globbing.mode = GLOB_ANY;
268 else if (!strcmp("names-only", lo->string)) globbing.namesonly = 1;
269 else if (!strcmp("full-path", lo->string)) globbing.namesonly = 0;
270 else {
271 printf("jam: invalid option for Glob built-in: '%s'\n", lo->string);
272 exit(EXITBAD); /* yeech */
275 if (globbing.cmptype > 0) {
276 /* compile regexps */
277 globbing.cmptype = list_length(r);
278 globbing.re = malloc(sizeof(globbing.re[0])*globbing.cmptype);
279 if (globbing.re == NULL) {
280 printf("FATAL: out of memory in Glob\n");
281 exit(42);
283 for (int f = 0; r; r = r->next, ++f) {
284 const char *errmsg;
285 if ((globbing.re[f] = re9_compile(r->string, RE9_FLAG_NONUTF8|(globbing.casesens ? 0 : RE9_FLAG_CASEINSENS), &errmsg)) == NULL) {
286 printf("FATAL: invalid regexp in Glob: %s\n", errmsg);
287 exit(42);
290 } else {
291 globbing.re = NULL;
293 for (; l; l = list_next(l)) file_dirscan(l->string, builtin_glob_back, &globbing);
294 if (globbing.re) {
295 for (int f = 0; f < globbing.cmptype; ++f) re9_free(globbing.re[f]);
296 free(globbing.re);
298 return globbing.results;
303 * builtin_match() - MATCH rule, regexp matching
305 static LIST *builtin_match (PARSE *parse, LOL *args, int *jmp) {
306 LIST *l, *lo;
307 LIST *res = 0;
308 int casesens = 1;
309 int cmptype = 1;
310 for (lo = lol_get(args, 2); lo != NULL; lo = lo->next) {
311 if (!strcmp("case-sensitive", lo->string)) casesens = 1;
312 else if (!strcmp("case-insensitive", lo->string)) casesens = 0;
313 else if (!strcmp("ignore-case", lo->string)) casesens = 0;
314 else if (!strcmp("glob", lo->string)) cmptype = -1;
315 else if (!strcmp("regexp", lo->string)) cmptype = 1;
316 else if (!strcmp("plain", lo->string)) cmptype = 0;
317 else {
318 printf("jam: invalid option for Match built-in: '%s'\n", lo->string);
319 exit(EXITBAD); /* yeech */
322 /* for each pattern */
323 for (l = lol_get(args, 0); l; l = l->next) {
324 LIST *r;
325 if (cmptype > 0) {
326 re9_prog_t *re;
327 re9_sub_t mt[RE9_SUBEXP_MAX];
328 const char *errmsg;
329 if ((re = re9_compile(l->string, RE9_FLAG_NONUTF8|(casesens ? 0 : RE9_FLAG_CASEINSENS), &errmsg)) == NULL) {
330 printf("FATAL: %s\n", errmsg);
331 exit(42);
333 /* for each string to match against */
334 for (r = lol_get(args, 1); r; r = r->next) {
335 mt[0].sp = mt[0].ep = NULL;
336 if (re9_execute(re, RE9_FLAG_NONUTF8, r->string, mt, RE9_SUBEXP_MAX) > 0) {
337 int i;
338 dstring_t buf;
339 /* add all parameters up to highest onto list */
340 /* must have parameters to have results! */
341 dstr_init(&buf);
342 for (i = 1; i < RE9_SUBEXP_MAX; ++i) {
343 int l = mt[i].ep-mt[i].sp;
344 dstr_clear(&buf);
345 if (l > 0) dstr_push_buf(&buf, mt[i].sp, l);
346 res = list_new(res, dstr_cstr(&buf), 0);
348 dstr_done(&buf);
351 re9_free(re);
352 } else if (cmptype < 0) {
353 for (r = lol_get(args, 1); r; r = r->next) {
354 if (matchglobex(l->string, r->string, casesens) == 0) {
355 res = list_new(res, r->string, 0);
358 } else {
359 for (r = lol_get(args, 1); r; r = r->next) {
360 if ((casesens ? strcmp : strcasecmp)(l->string, r->string) == 0) {
361 res = list_new(res, r->string, 0);
366 return res;
370 static LIST *builtin_hdrmacro (PARSE *parse, LOL *args, int *jmp) {
371 LIST *l = lol_get(args, 0);
372 for (; l; l = list_next(l)) {
373 TARGET *t = bindtarget(l->string);
374 /* scan file for header filename macro definitions */
375 if (DEBUG_HEADER) printf("scanning '%s' for header file macro definitions\n", l->string);
376 macro_headers(t);
378 return L0;
382 /* backported from boost-jam */
384 * Return the current working directory.
386 * Usage: pwd = [ PWD ] ;
388 static LIST *builtin_pwd (PARSE *parse, LOL *args, int *jmp) {
389 char pwd_buffer[PATH_MAX];
390 if (!getcwd(pwd_buffer, sizeof(pwd_buffer))) {
391 perror("can not get current directory");
392 return L0;
394 return list_new(L0, pwd_buffer, 0);
398 /* backported from boost-jam */
399 static LIST *builtin_sort (PARSE *parse, LOL *args, int *jmp) {
400 LIST *arg = lol_get(args, 0);
401 arg = list_sort(arg);
402 return arg;
406 /* backported from boost-jam; greatly improved */
407 /* Command shcmd [[ : options ]] */
408 static LIST *builtin_command (PARSE *parse, LOL *args, int *jmp) {
409 LIST *res = NULL;
410 LIST *l;
411 int ret;
412 char buffer[1024], buf1[32], *spos, *epos;
413 FILE *p = NULL;
414 int exitStatus = -1;
415 int optExitStatus = 0;
416 int optNoOutput = 0;
417 int optTrimLeft = 1;
418 int optTrimRight = 1;
419 int optStatus1st = 0;
420 int optParseOut = 0;
421 int optSpaceBreak = 1;
422 int optTabBreak = 1;
423 int optCRBreak = 1;
424 int optLFBreak = 1;
425 int no_options = ((l = lol_get(args, 1)) == NULL);
426 dstring_t str;
427 /* for each string in 2nd list: check for arg */
428 for (; l != NULL; l = l->next) {
429 if (!strcmp("exit-status", l->string)) optExitStatus = 1;
430 else if (!strcmp("exit-code", l->string)) optExitStatus = 1;
431 else if (!strcmp("status-first", l->string)) optStatus1st = 1;
432 else if (!strcmp("code-first", l->string)) optStatus1st = 1;
433 else if (!strcmp("no-output", l->string)) optNoOutput = 1;
434 else if (!strcmp("no-trim", l->string)) optTrimLeft = optTrimRight = 0;
435 else if (!strcmp("no-trim-left", l->string)) optTrimLeft = 0;
436 else if (!strcmp("no-trim-right", l->string)) optTrimRight = 0;
437 else if (!strcmp("parse-output", l->string)) optParseOut = 1;
438 else if (!strcmp("no-space-break", l->string)) optSpaceBreak = 0;
439 else if (!strcmp("no-tab-break", l->string)) optTabBreak = 0;
440 else if (!strcmp("no-nl-break", l->string)) optLFBreak = 0;
441 else if (!strcmp("no-lf-break", l->string)) optLFBreak = 0;
442 else if (!strcmp("no-cr-break", l->string)) optCRBreak = 0;
443 else {
444 printf("jam: invalid option for Command built-in: '%s'\n", l->string);
445 exit(EXITBAD); /* yeech */
448 if (no_options) optNoOutput = 1;
449 /* build shell command */
450 dstr_init(&str);
451 /* for each arg */
452 for (l = lol_get(args, 0); l; l = l->next) {
453 if (dstr_len(&str)) dstr_push_char(&str, ' ');
454 dstr_push_cstr(&str, l->string);
456 /* no shell command? */
457 if (dstr_len(&str) < 1) { dstr_done(&str); return L0; }
458 fflush(NULL); /* flush ALL output streams */
459 p = popen(dstr_cstr(&str), "r");
460 if (!p) { dstr_done(&str); return L0; }
461 dstr_clear(&str);
462 while ((ret = fread(buffer, sizeof(char), sizeof(buffer)-1, p)) > 0) {
463 if (!optNoOutput) {
464 buffer[ret] = 0;
465 dstr_push_cstr(&str, buffer);
468 exitStatus = pclose(p);
469 if (no_options) {
470 if (exitStatus) {
471 snprintf(buf1, sizeof(buf1), "%d", exitStatus);
472 res = list_new(L0, buf1, 0);
473 } else {
474 res = L0;
476 } else {
477 if (optExitStatus && optStatus1st) {
478 snprintf(buf1, sizeof(buf1), "%d", exitStatus);
479 res = list_new(res, buf1, 0);
481 /* trim output if necessary */
482 if (!optNoOutput) {
483 if (!optParseOut) {
484 /* don't parse */
485 if (optTrimRight) {
486 // trim trailing blanks
487 int sl = dstr_len(&str);
488 spos = dstr_cstr(&str);
489 while (sl > 0 && (unsigned char)spos[sl-1] <= ' ') --sl;
490 dstr_chop(&str, sl);
492 spos = dstr_cstr(&str);
493 if (optTrimLeft) {
494 // trim leading blanks
495 while (*spos && *((unsigned char *)spos) <= ' ') ++spos;
497 res = list_new(res, spos, 0);
498 } else {
499 dstring_t tmp;
500 /* parse output */
501 ret = 0; /* was anything added? list must have at least one element */
502 spos = dstr_cstr(&str);
503 dstr_init(&tmp);
504 while (*spos) {
505 /* skip delimiters */
506 while (*spos) {
507 unsigned char ch = (unsigned char)(*spos);
508 if (ch == ' ') { if (!optSpaceBreak) break; }
509 else if (ch == '\t') { if (!optTabBreak) break; }
510 else if (ch == '\r') { if (!optCRBreak) break; }
511 else if (ch == '\n') { if (!optLFBreak) break; }
512 else if (ch > ' ') break;
513 ++spos;
515 if (!*spos) break;
516 epos = spos+1;
517 while (*epos) {
518 int ch = *epos;
519 if (ch == ' ') { if (optSpaceBreak) break; }
520 else if (ch == '\t') { if (optTabBreak) break; }
521 else if (ch == '\r') { if (optCRBreak) break; }
522 else if (ch == '\n') { if (optLFBreak) break; }
523 else if ((unsigned char)ch <= ' ') break;
524 ++epos;
526 dstr_clear(&tmp);
527 dstr_push_memrange(&tmp, spos, epos);
528 res = list_new(res, dstr_cstr(&tmp), 0);
529 ret = 1;
530 spos = epos;
532 dstr_done(&tmp);
533 if (!ret) { buf1[0] = '\0'; res = list_new(res, buf1, 0); }
536 /* command exit result next */
537 if (optExitStatus && !optStatus1st) {
538 snprintf(buf1, sizeof(buf1), "%d", exitStatus);
539 res = list_new(res, buf1, 0);
542 dstr_done(&str);
543 return res;
547 /* ExprI1 op0 math op1 */
548 static LIST *builtin_expri1 (PARSE *parse, LOL *args, int *jmp) {
549 char buffer[100];
550 int op0, op1, res, comp = 0;
551 LIST *el = lol_get(args, 0);
552 if (!el || !el->next) return L0;
553 if (el->string[0] == '#') {
554 // string length
555 snprintf(buffer, sizeof(buffer), "%u", (unsigned int)(strlen(el->next->string)));
556 return list_new(L0, buffer, 0);
558 if (!el->next->next) return L0;
559 op0 = atoi(el->string);
560 op1 = atoi(el->next->next->string);
561 res = 0;
562 switch (el->next->string[0]) {
563 case '+': res = op0+op1; break;
564 case '-': res = op0-op1; break;
565 case '*': res = op0*op1; break;
566 case '/': res = op0/op1; break;
567 case '%': res = op0%op1; break;
568 case '<':
569 comp = 1;
570 if (el->next->string[1] == '=') res = (op0 <= op1); else res = (op0 < op1);
571 break;
572 case '=': comp = 1; res = (op0 == op1); break;
573 case '!': comp = 1; res = (op0 != op1); break;
574 case '>':
575 comp = 1;
576 if (el->next->string[1] == '=') res = (op0 >= op1); else res = (op0 > op1);
577 break;
578 default:
579 printf("jam: rule ExprI1: unknown operator: '%s'\n", el->next->string);
580 exit(EXITBAD);
582 if (comp) return (res ? list_new(L0, "tan", 0) : L0);
583 snprintf(buffer, sizeof(buffer), "%d", res);
584 return list_new(L0, buffer, 0);
588 /* based on the code from ftjam by David Turner */
589 static LIST *builtin_split (PARSE *parse, LOL *args, int *jmp) {
590 LIST *input = lol_get(args, 0);
591 LIST *tokens = lol_get(args, 1);
592 LIST *res = L0;
593 char token[256];
594 dstring_t str;
595 int explode = 0;
596 dstr_init(&str);
597 /* build token array */
598 if (tokens == NULL) {
599 memset(token, 1, sizeof(token));
600 explode = 1;
601 } else {
602 memset(token, 0, sizeof(token));
603 for (; tokens; tokens = tokens->next) {
604 const char *s = tokens->string;
605 for (; *s; ++s) token[(unsigned char)*s] = 1;
607 if (memchr(token, 1, sizeof(token)) == NULL) {
608 memset(token, 1, sizeof(token));
609 explode = 1;
612 token[0] = 0;
613 /* now parse the input and split it */
614 for (; input; input = input->next) {
615 const char *ptr = input->string;
616 const char *lastPtr = input->string;
617 while (*ptr) {
618 if (token[(unsigned char)*ptr]) {
619 size_t count = ptr-lastPtr+explode;
620 if (count > 0) {
621 dstr_clear(&str);
622 dstr_push_memrange(&str, lastPtr, ptr+explode);
623 res = list_new(res, dstr_cstr(&str), 0);
625 lastPtr = ptr+1;
627 ++ptr;
629 if (ptr > lastPtr) res = list_new(res, lastPtr, 0);
631 dstr_done(&str);
632 return res;
637 * builtin_dependslist()
639 * The DependsList builtin rule returns list of dependencies for
640 * a given target.
642 static LIST *builtin_dependslist (PARSE *parse, LOL *args, int *jmp) {
643 LIST *res = L0;
644 LIST *parents;
645 for (parents = lol_get(args, 0); parents; parents = parents->next) {
646 TARGET *t = bindtarget(parents->string);
647 TARGETS *child;
648 for (child = t->depends; child; child = child->next) res = list_new(res, child->target->name, 1);
650 return res;
655 * NormPath path
657 * it will add 'pwd' if path is not absolute and will try to resolve some '.' and '..' (only leading '..' though).
658 * it can be used in SubDir replacement to automate dir building
660 static LIST *builtin_normpath (PARSE *parse, LOL *args, int *jmp) {
661 LIST *el = lol_get(args, 0);
662 char *buf;
663 int bsz;
664 if (!el || !el->string) return L0;
665 bsz = strlen(el->string)*2+1024;
666 buf = malloc(bsz);
667 if (buf == NULL) return L0;
668 if (!normalize_path(el->string, buf, bsz)) { free(buf); return L0; }
669 el = list_new(NULL, buf, 0);
670 free(buf);
671 return el;
676 * ListLength list
678 static LIST *builtin_listlength (PARSE *parse, LOL *args, int *jmp) {
679 char buffer[100];
680 LIST *el = lol_get(args, 0);
681 if (!el) return list_new(L0, "0", 0);
682 snprintf(buffer, sizeof(buffer), "%d", list_length(el));
683 return list_new(L0, buffer, 0);
688 * HaveRule and HaveActions
690 typedef struct {
691 LIST *el;
692 int wantAction;
693 int casesens;
694 } HRNGCI;
697 typedef struct {
698 const char *str;
699 int wantAction;
700 int casesens;
701 } HRNormalData;
704 static int hr_normal (const void *hdata, void *udata) {
705 const RULE *r = (const RULE *)hdata;
706 const HRNormalData *d = (const HRNormalData *)udata;
707 if (strcasecmp(r->name, d->str) == 0) {
708 if (d->wantAction && r->actions) return 1; // got it
709 if (!d->wantAction && r->procedure) return 1; // got it
711 return 0;
715 static int hr_glob (const void *hdata, void *udata) {
716 const RULE *r = (const RULE *)hdata;
717 const HRNormalData *d = (const HRNormalData *)udata;
718 if (matchglobex(d->str, r->name, d->casesens) == 0) {
719 //fprintf(stderr, ":[%s]\n", r->name);
720 if (d->wantAction && r->actions) return 1; // got it
721 if (!d->wantAction && r->procedure) return 1; // got it
723 return 0;
727 typedef struct {
728 re9_prog_t *re;
729 int wantAction;
730 } HRREData;
733 static int hr_regexp (const void *hdata, void *udata) {
734 const RULE *r = (const RULE *)hdata;
735 HRREData *d = (HRREData *)udata;
736 if (re9_execute(d->re, RE9_FLAG_NONUTF8, r->name, NULL, 0) > 0) {
737 //fprintf(stderr, ":[%s]\n", r->name);
738 if (d->wantAction && r->actions) return 1; // got it
739 if (!d->wantAction && r->procedure) return 1; // got it
741 return 0;
745 static LIST *builtin_haveruleactions (PARSE *parse, LOL *args, int *jmp) {
746 LIST *el = lol_get(args, 0), *l;
747 int wantAction = parse->num;
748 int casesens = 1;
749 int cmptype = 0; // <0:glob; >0:regexp
750 if (!el) return L0;
751 for (l = lol_get(args, 1); l != NULL; l = l->next) {
752 if (!strcmp("case-sensitive", l->string)) casesens = 1;
753 else if (!strcmp("case-insensitive", l->string)) casesens = 0;
754 else if (!strcmp("ignore-case", l->string)) casesens = 0;
755 else if (!strcmp("glob", l->string)) cmptype = -1;
756 else if (!strcmp("regexp", l->string)) cmptype = 1;
757 else if (!strcmp("plain", l->string)) cmptype = 0;
758 else {
759 printf("jam: invalid option for Have%s built-in: '%s'\n", (wantAction ? "Actions" : "Rule"), l->string);
760 exit(EXITBAD); /* yeech */
763 if (casesens == 1 && cmptype == 0) {
764 // standard mode
765 for (; el; el = el->next) {
766 RULE *r = findrule(el->string);
767 if (!r) return L0;
768 if (wantAction && !r->actions) return L0;
769 if (!wantAction && !r->procedure) return L0;
771 } else if (cmptype < 0) {
772 // glob
773 HRNormalData nfo;
774 nfo.wantAction = wantAction;
775 nfo.casesens = casesens;
776 for (; el; el = el->next) {
777 nfo.str = el->string;
778 if (!iteraterules(hr_glob, &nfo)) return L0;
780 } else if (cmptype > 0) {
781 // regexp
782 HRREData nfo;
783 nfo.wantAction = wantAction;
784 for (; el; el = el->next) {
785 const char *errmsg;
786 int err;
787 if ((nfo.re = re9_compile(el->string, RE9_FLAG_NONUTF8|(casesens ? 0 : RE9_FLAG_CASEINSENS), &errmsg)) == NULL) {
788 printf("FATAL: invalid regexp in Have%s: %s\n", (wantAction ? "Actions" : "Rule"), errmsg);
789 exit(EXITBAD);
791 err = iteraterules(hr_regexp, &nfo);
792 re9_free(nfo.re);
793 if (!err) return L0;
795 } else {
796 // normal, case-insensitive
797 HRNormalData nfo;
798 nfo.wantAction = wantAction;
799 for (; el; el = el->next) {
800 nfo.str = el->string;
801 if (!iteraterules(hr_normal, &nfo)) return L0;
804 return list_new(L0, "1", 0);
808 /* generate random name:
809 * [ RandName ] -- /tmp/XXX
810 * [ RandName "abc/" ] -- abc/XXX
811 * [ RandName "" ] -- XXX
813 static LIST *builtin_randname (PARSE *parse, LOL *args, int *jmp) {
814 static BJRandCtx rctx;
815 static int initialized = 0;
816 static const char alphabet[] = "0123456789abcdefghijklmnopqrstuvwxyz";
817 LIST *el;
818 char buffer[9], *s;
819 const char *path;
820 #ifdef OS_NT
821 static char tp[8192]
822 #endif
823 if (!initialized) {
824 initialized = 1;
825 bjprngInit(&rctx, bjprngGenRandSeed());
826 #ifdef OS_NT
827 GetTempPath(sizeof(tp), tp);
828 #endif
830 for (int f = 0; f < 8; ++f) buffer[f] = alphabet[bjprngRand(&rctx)%strlen(alphabet)];
831 buffer[8] = 0;
832 el = lol_get(args, 0);
833 path = (el != NULL && el->string != NULL ? el->string :
834 #ifdef OS_NT
836 #else
837 "/tmp/"
838 #endif
840 s = alloca(strlen(path)+strlen(buffer)+2);
841 sprintf(s, "%s%s", path, buffer);
842 return list_new(L0, s, 0);
846 /* write list to file:
847 * ListFileWrite filename : list [: terminator] [: append]
848 * default terminator is '\n'
849 * return success flag
851 static LIST *builtin_listwrite (PARSE *parse, LOL *args, int *jmp) {
852 LIST *el = lol_get(args, 0);
853 if (el != NULL && el->string != NULL && el->string[0]) {
854 LIST *l = lol_get(args, 3);
855 FILE *fo = fopen(el->string, (l != NULL && l->string[0] ? "a" : "w"));
856 if (fo != NULL) {
857 const char *term;
858 l = lol_get(args, 2);
859 term = (l != NULL ? l->string : "\n");
860 for (l = lol_get(args, 1); l != NULL; l = l->next) {
861 if (fprintf(fo, "%s%s", l->string, term) < 0) {
862 fclose(fo);
863 unlink(el->string);
864 return L0;
867 fclose(fo);
868 return list_new(L0, "1", 0);
871 return L0;
876 * compile_builtin() - define builtin rules
879 #define P0 ((PARSE *)0)
880 #define C0 ((char *)0)
883 /* ":" -- previous name in upper case; "." -- previous name in lower case */
884 static JAMFA_SENTINEL void bind_builtin (PARSE *pp, ...) {
885 va_list ap;
886 const char *name, *lastname = "BAD";
888 va_start(ap, pp);
889 while ((name = va_arg(ap, const char *))) {
890 int updown = name[0]==':'?1:(name[0]=='.'?-1:0);
892 if (updown) {
893 if (updown == 1 && !compat_old_builtin_names) continue;
894 if (!(*(++name))) {
895 char *s = alloca(strlen(lastname)+1), *p;
897 strcpy(s, lastname);
898 for (p = s; *p; ++p) {
899 if (updown > 0) {
900 if (*p >= 'a' && *p <= 'z') (*p) -= 32; // upcase
901 } else {
902 if (*p >= 'A' && *p <= 'Z') (*p) += 32; // locase
905 name = s;
907 //fprintf(stderr, "%2d: [%s]\n", updown, name);
908 } else {
909 lastname = name;
911 bindrule(name)->procedure = pp;
913 va_end(ap);
917 void load_builtins (void) {
918 bind_builtin(parse_make(builtin_depends, P0, P0, P0, C0, C0, 0),
919 "Depends",
920 ":", NULL);
921 bind_builtin(parse_make(builtin_depends, P0, P0, P0, C0, C0, 1),
922 "Includes",
923 ":", NULL);
924 bind_builtin(parse_make(builtin_dependslist, P0, P0, P0, C0, C0, 0),
925 "DependsList",
926 ":", NULL);
928 bind_builtin(parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_TOUCHED),
929 "Always",
930 ":", NULL);
931 bind_builtin(parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_LEAVES),
932 "Leaves",
933 ":", NULL);
934 bind_builtin(parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_NOCARE),
935 "NoCare",
936 ":", NULL);
937 bind_builtin(parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_NOTFILE),
938 "NotFile", ":",
939 "NoTime",
940 ":", NULL);
941 bind_builtin(parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_NOUPDATE),
942 "NoUpdate",
943 ":", NULL);
944 bind_builtin(parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_TEMP),
945 "Temporary",
946 ":", NULL);
947 bind_builtin(parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_FORCECARE),
948 "ForceCare",
949 ":", NULL);
950 bind_builtin(parse_make(builtin_flags, P0, P0, P0, C0, C0, 666),
951 "ForceFile", ":",
952 ":", NULL);
954 bind_builtin(parse_make(builtin_echo, P0, P0, P0, C0, C0, 0),
955 "Echo", ".",
956 ":", NULL);
958 bind_builtin(parse_make(builtin_exit, P0, P0, P0, C0, C0, 0),
959 "Exit", ".",
960 ":", NULL);
962 bind_builtin(parse_make(builtin_glob, P0, P0, P0, C0, C0, 0),
963 "Glob",
964 ":", NULL);
965 bind_builtin(parse_make(builtin_match, P0, P0, P0, C0, C0, 0),
966 "Match",
967 ":", NULL);
969 bind_builtin(parse_make(builtin_hdrmacro, P0, P0, P0, C0, C0, 0),
970 "HdrMacro",
971 ":", NULL);
973 bind_builtin(parse_make(builtin_pwd, P0, P0, P0, C0, C0, 0),
974 "Pwd",
975 ":", NULL);
977 bind_builtin(parse_make(builtin_sort, P0, P0, P0, C0, C0, 0),
978 "Sort",
979 ":", NULL);
981 bind_builtin(parse_make(builtin_command, P0, P0, P0, C0, C0, 0),
982 "Command",
983 ":", NULL);
985 bind_builtin(parse_make(builtin_expri1, P0, P0, P0, C0, C0, 0),
986 "ExprI1",
987 ":", NULL);
989 bind_builtin(parse_make(builtin_split, P0, P0, P0, C0, C0, 0),
990 "Split",
991 ":", NULL);
993 bind_builtin(parse_make(builtin_normpath, P0, P0, P0, C0, C0, 0),
994 "NormPath",
995 ":", NULL);
997 bind_builtin(parse_make(builtin_listlength, P0, P0, P0, C0, C0, 0),
998 "ListLength",
999 ":", NULL);
1001 bind_builtin(parse_make(builtin_haveruleactions, P0, P0, P0, C0, C0, 0),
1002 "HaveRule",
1003 ":", NULL);
1004 bind_builtin(parse_make(builtin_haveruleactions, P0, P0, P0, C0, C0, 1),
1005 "HaveActions",
1006 ":", NULL);
1008 bind_builtin(parse_make(builtin_randname, P0, P0, P0, C0, C0, 0),
1009 "RandName", NULL);
1011 bind_builtin(parse_make(builtin_listwrite, P0, P0, P0, C0, C0, 0),
1012 "ListWrite", NULL);