2 * Copyright 1993-2002 Christopher Seiwald and Perforce Software, Inc.
4 * This file is part of Jam - see jam.c for Copyright information.
8 * builtins.c - builtin jam rules
12 * load_builtin() - define builtin rules
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
54 #include "matchglob.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. */
76 if (!t
->includes
) t
->includes
= copytarget(t
);
79 t
->depends
= targetlist(t
->depends
, sources
);
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
);
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;
117 if (!no_out
) list_print(l
);
118 if (!no_newline
) printf("\n"); else fflush(stdout
);
119 exit(EXITBAD
); /* yeech */
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;
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
);
159 * builtin_globbing() - GLOB rule
165 int cmptype
; // <0:glob; 0: plain; >0:# of regexps
172 static void builtin_glob_back (void *closure
, const char *file
, int status
, time_t time
) {
173 struct globbing
*globbing
= (struct globbing
*)closure
;
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
);
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);
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;
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);
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);
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);
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);
231 struct globbing globbing
;
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;
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");
264 for (int f
= 0; r
; r
= r
->next
, ++f
) {
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
);
274 for (; l
; l
= list_next(l
)) file_dirscan(l
->string
, builtin_glob_back
, &globbing
);
276 for (int f
= 0; f
< globbing
.cmptype
; ++f
) re9_free(globbing
.re
[f
]);
279 return globbing
.results
;
284 * builtin_match() - MATCH rule, regexp matching
286 static LIST
*builtin_match (PARSE
*parse
, LOL
*args
, int *jmp
) {
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;
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
) {
308 re9_sub_t mt
[RE9_SUBEXP_MAX
];
310 if ((re
= re9_compile(l
->string
, RE9_FLAG_NONUTF8
|(casesens
? 0 : RE9_FLAG_CASEINSENS
), &errmsg
)) == NULL
) {
311 printf("FATAL: %s\n", errmsg
);
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) {
320 /* add all parameters up to highest onto list */
321 /* must have parameters to have results! */
323 for (i
= 1; i
< RE9_SUBEXP_MAX
; ++i
) {
324 int l
= mt
[i
].ep
-mt
[i
].sp
;
326 if (l
> 0) dstr_push_buf(&buf
, mt
[i
].sp
, l
);
327 res
= list_new(res
, dstr_cstr(&buf
), 0);
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);
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);
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
);
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");
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
);
387 /* backported from boost-jam; greatly improved */
388 /* Command shcmd [[ : options ]] */
389 static LIST
*builtin_command (PARSE
*parse
, LOL
*args
, int *jmp
) {
393 char buffer
[1024], buf1
[32], *spos
, *epos
;
396 int optExitStatus
= 0;
399 int optTrimRight
= 1;
400 int optStatus1st
= 0;
402 int optSpaceBreak
= 1;
406 int no_options
= ((l
= lol_get(args
, 1)) == NULL
);
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;
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 */
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
; }
443 while ((ret
= fread(buffer
, sizeof(char), sizeof(buffer
)-1, p
)) > 0) {
446 dstr_push_cstr(&str
, buffer
);
449 exitStatus
= pclose(p
);
452 snprintf(buf1
, sizeof(buf1
), "%d", exitStatus
);
453 res
= list_new(L0
, buffer
, 0);
458 if (optExitStatus
&& optStatus1st
) {
459 snprintf(buf1
, sizeof(buf1
), "%d", exitStatus
);
460 res
= list_new(res
, buf1
, 0);
462 /* trim output if necessary */
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
;
473 spos
= dstr_cstr(&str
);
475 // trim leading blanks
476 while (*spos
&& *((unsigned char *)spos
) <= ' ') ++spos
;
478 res
= list_new(res
, spos
, 0);
482 ret
= 0; /* was anything added? list must have at least one element */
483 spos
= dstr_cstr(&str
);
486 /* skip delimiters */
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;
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;
508 dstr_push_memrange(&tmp
, spos
, epos
);
509 res
= list_new(res
, dstr_cstr(&tmp
), 0);
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);
528 /* ExprI1 op0 math op1 */
529 static LIST
*builtin_expri1 (PARSE
*parse
, LOL
*args
, int *jmp
) {
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] == '#') {
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
);
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;
551 if (el
->next
->string
[1] == '=') res
= (op0
<= op1
); else res
= (op0
< op1
);
553 case '=': comp
= 1; res
= (op0
== op1
); break;
554 case '!': comp
= 1; res
= (op0
!= op1
); break;
557 if (el
->next
->string
[1] == '=') res
= (op0
>= op1
); else res
= (op0
> op1
);
560 printf("jam: rule ExprI1: unknown operator: '%s'\n", el
->next
->string
);
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);
578 /* build token array */
579 if (tokens
== NULL
) {
580 memset(token
, 1, sizeof(token
));
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
));
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
;
599 if (token
[(unsigned char)*ptr
]) {
600 size_t count
= ptr
-lastPtr
+explode
;
603 dstr_push_memrange(&str
, lastPtr
, ptr
+explode
);
604 res
= list_new(res
, dstr_cstr(&str
), 0);
610 if (ptr
> lastPtr
) res
= list_new(res
, lastPtr
, 0);
618 * builtin_dependslist()
620 * The DependsList builtin rule returns list of dependencies for
623 static LIST
*builtin_dependslist (PARSE
*parse
, LOL
*args
, int *jmp
) {
626 for (parents
= lol_get(args
, 0); parents
; parents
= parents
->next
) {
627 TARGET
*t
= bindtarget(parents
->string
);
629 for (child
= t
->depends
; child
; child
= child
->next
) res
= list_new(res
, child
->target
->name
, 1);
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);
645 if (!el
|| !el
->string
) return L0
;
646 bsz
= strlen(el
->string
)*2+1024;
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);
659 static LIST
*builtin_listlength (PARSE
*parse
, LOL
*args
, int *jmp
) {
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
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
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
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
726 static LIST
*builtin_haveruleactions (PARSE
*parse
, LOL
*args
, int *jmp
) {
727 LIST
*el
= lol_get(args
, 0), *l
;
728 int wantAction
= parse
->num
;
730 int cmptype
= 0; // <0:glob; >0:regexp
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;
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) {
746 for (; el
; el
= el
->next
) {
747 RULE
*r
= findrule(el
->string
);
749 if (wantAction
&& !r
->actions
) return L0
;
750 if (!wantAction
&& !r
->procedure
) return L0
;
752 } else if (cmptype
< 0) {
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) {
764 nfo
.wantAction
= wantAction
;
765 for (; el
; el
= el
->next
) {
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
);
772 err
= iteraterules(hr_regexp
, &nfo
);
777 // normal, case-insensitive
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";
806 bjprngInit(&rctx
, bjprngGenRandSeed());
808 GetTempPath(sizeof(tp
), tp
);
811 for (int f
= 0; f
< 8; ++f
) buffer
[f
] = alphabet
[bjprngRand(&rctx
)%strlen(alphabet
)];
813 el
= lol_get(args
, 0);
814 path
= (el
!= NULL
&& el
->string
!= NULL
? el
->string
:
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"));
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) {
849 return list_new(L0
, "1", 0);
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
, ...) {
867 const char *name
, *lastname
= "BAD";
870 while ((name
= va_arg(ap
, const char *))) {
871 int updown
= name
[0]==':'?1:(name
[0]=='.'?-1:0);
874 if (updown
== 1 && !compat_old_builtin_names
) continue;
876 char *s
= alloca(strlen(lastname
)+1), *p
;
879 for (p
= s
; *p
; ++p
) {
881 if (*p
>= 'a' && *p
<= 'z') (*p
) -= 32; // upcase
883 if (*p
>= 'A' && *p
<= 'Z') (*p
) += 32; // locase
888 //fprintf(stderr, "%2d: [%s]\n", updown, name);
892 bindrule(name
)->procedure
= pp
;
898 void load_builtins (void) {
899 bind_builtin(parse_make(builtin_depends
, P0
, P0
, P0
, C0
, C0
, 0),
902 bind_builtin(parse_make(builtin_depends
, P0
, P0
, P0
, C0
, C0
, 1),
905 bind_builtin(parse_make(builtin_dependslist
, P0
, P0
, P0
, C0
, C0
, 0),
909 bind_builtin(parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, T_FLAG_TOUCHED
),
912 bind_builtin(parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, T_FLAG_LEAVES
),
915 bind_builtin(parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, T_FLAG_NOCARE
),
918 bind_builtin(parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, T_FLAG_NOTFILE
),
922 bind_builtin(parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, T_FLAG_NOUPDATE
),
925 bind_builtin(parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, T_FLAG_TEMP
),
928 bind_builtin(parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, T_FLAG_FORCECARE
),
931 bind_builtin(parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, 666),
935 bind_builtin(parse_make(builtin_echo
, P0
, P0
, P0
, C0
, C0
, 0),
939 bind_builtin(parse_make(builtin_exit
, P0
, P0
, P0
, C0
, C0
, 0),
943 bind_builtin(parse_make(builtin_glob
, P0
, P0
, P0
, C0
, C0
, 0),
946 bind_builtin(parse_make(builtin_match
, P0
, P0
, P0
, C0
, C0
, 0),
950 bind_builtin(parse_make(builtin_hdrmacro
, P0
, P0
, P0
, C0
, C0
, 0),
954 bind_builtin(parse_make(builtin_pwd
, P0
, P0
, P0
, C0
, C0
, 0),
958 bind_builtin(parse_make(builtin_sort
, P0
, P0
, P0
, C0
, C0
, 0),
962 bind_builtin(parse_make(builtin_command
, P0
, P0
, P0
, C0
, C0
, 0),
966 bind_builtin(parse_make(builtin_expri1
, P0
, P0
, P0
, C0
, C0
, 0),
970 bind_builtin(parse_make(builtin_split
, P0
, P0
, P0
, C0
, C0
, 0),
974 bind_builtin(parse_make(builtin_normpath
, P0
, P0
, P0
, C0
, C0
, 0),
978 bind_builtin(parse_make(builtin_listlength
, P0
, P0
, P0
, C0
, C0
, 0),
982 bind_builtin(parse_make(builtin_haveruleactions
, P0
, P0
, P0
, C0
, C0
, 0),
985 bind_builtin(parse_make(builtin_haveruleactions
, P0
, P0
, P0
, C0
, C0
, 1),
989 bind_builtin(parse_make(builtin_randname
, P0
, P0
, P0
, C0
, C0
, 0),
992 bind_builtin(parse_make(builtin_listwrite
, P0
, P0
, P0
, C0
, C0
, 0),