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);
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. */
79 if (!t
->includes
) t
->includes
= copytarget(t
);
82 t
->depends
= targetlist(t
->depends
, sources
);
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));
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));
115 * builtin_oflush() - O-FLUSH rule
117 * The O-FLUSH builtin rule flushes current output stream.
120 static LIST
*builtin_oflush (PARSE
*parse
, LOL
*args
, int *jmp
) {
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);
137 exit(EXITBAD
); /* yeech */
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;
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
);
180 * builtin_globbing() - GLOB rule
186 int cmptype
; // <0:glob; 0: plain; >0:# of regexps
187 int mode
; // GLOB_xxx
193 static void builtin_glob_back (void *closure
, const char *file
, int status
, time_t time
) {
194 struct globbing
*globbing
= (struct globbing
*)closure
;
198 /* null out directory for matching */
199 /* we wish we had file_dirscan() pass up a PATHNAME */
200 path_parse(file
, &f
);
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);
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;
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);
233 } else if (globbing
->cmptype
> 0) {
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);
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);
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);
257 struct globbing globbing
;
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;
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 */
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");
295 for (f
= 0; r
; r
= r
->next
, ++f
) {
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
);
306 for (; l
; l
= list_next(l
)) file_dirscan(l
->string
, builtin_glob_back
, &globbing
);
311 for (f
= 0; f
< globbing
.cmptype
; ++f
) re9_free(globbing
.re
[f
]);
315 return globbing
.results
;
320 * builtin_match() - MATCH rule, regexp matching
322 static LIST
*builtin_match (PARSE
*parse
, LOL
*args
, int *jmp
) {
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;
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
) {
346 re9_sub_t mt
[RE9_SUBEXP_MAX
];
349 if ((re
= re9_compile(l
->string
, RE9_FLAG_NONUTF8
, &errmsg
)) == NULL
) {
350 printf("FATAL: %s\n", errmsg
);
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) {
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
) {
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
);
367 res
= list_new(res
, buf
, 0);
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);
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);
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
);
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");
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
);
426 /* backported from boost-jam; greatly improved */
427 /* Command shcmd [[ : options ]] */
428 static LIST
*builtin_command (PARSE
*parse
, LOL
*args
, int *jmp
) {
432 char buffer
[1024], buf1
[32], *spos
, *epos
;
435 int optExitStatus
= 0;
438 int optTrimRight
= 1;
439 int optStatus1st
= 0;
441 int optSpaceBreak
= 1;
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;
464 printf("jam: invalid option for Command built-in: '%s'\n", l
->string
);
465 exit(EXITBAD
); /* yeech */
468 /* build shell command */
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
; }
479 p
= popen(kStringCStr(&str
), "r");
480 if (!p
) { kStringFree(&str
); return L0
; }
483 while ((ret
= fread(buffer
, sizeof(char), sizeof(buffer
)-1, p
)) > 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 */
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
);
507 // trim leading blanks
508 while (*spos
&& *((unsigned char *)spos
) <= ' ') ++spos
;
510 res
= list_new(res
, spos
, 0);
514 ret
= 0; /* was anything added? list must have at least one element */
515 spos
= kStringCStr(&str
);
518 /* skip delimiters */
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;
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;
540 kStringAppendRange(&tmp
, spos
, epos
);
541 res
= list_new(res
, kStringCStr(&tmp
), 0);
546 if (!ret
) { buf1
[0] = '\0'; res
= list_new(res
, buf1
, 0); }
550 /* command exit result next */
551 if (optExitStatus
&& !optStatus1st
) {
552 snprintf(buf1
, sizeof(buf1
), "%d", exitStatus
);
553 res
= list_new(res
, buf1
, 0);
559 static LIST
*builtin_expri1 (PARSE
*parse
, LOL
*args
, int *jmp
) {
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] == '#') {
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
);
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;
582 if (el
->next
->string
[1] == '=') res
= (op0
<= op1
); else res
= (op0
< op1
);
584 case '=': comp
= 1; res
= (op0
== op1
); break;
585 case '!': comp
= 1; res
= (op0
!= op1
); break;
588 if (el
->next
->string
[1] == '=') res
= (op0
>= op1
); else res
= (op0
> op1
);
591 printf("jam: rule ExprI1: unknown operator: '%s'\n", el
->next
->string
);
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);
610 /* build token array */
611 if (tokens
== NULL
) {
612 memset(token
, 1, sizeof(token
));
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
));
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
;
631 if (token
[(unsigned char)*ptr
]) {
632 size_t count
= ptr
-lastPtr
+explode
;
635 kStringAppendRange(&str
, lastPtr
, ptr
+explode
);
636 res
= list_new(res
, kStringCStr(&str
), 0);
642 if (ptr
> lastPtr
) res
= list_new(res
, lastPtr
, 0);
650 * builtin_dependslist()
652 * The DependsList builtin rule returns list of dependencies for
655 static LIST
*builtin_dependslist (PARSE
*parse
, LOL
*args
, int *jmp
) {
659 for (parents
= lol_get(args
, 0); parents
; parents
= parents
->next
) {
660 TARGET
*t
= bindtarget(parents
->string
);
663 for (child
= t
->depends
; child
; child
= child
->next
) res
= list_new(res
, child
->target
->name
, 1);
669 static LIST
*builtin_normpath (PARSE
*parse
, LOL
*args
, int *jmp
) {
670 LIST
*el
= lol_get(args
, 0);
674 if (!el
|| !el
->string
) return L0
;
675 bsz
= strlen(el
->string
)*2+1024;
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);
685 static LIST
*builtin_listlength (PARSE
*parse
, LOL
*args
, int *jmp
) {
687 LIST
*el
= lol_get(args
, 0);
690 snprintf(buffer
, sizeof(buffer
), "%d", list_length(el
));
691 return list_new(L0
, buffer
, 0);
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
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
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
750 static LIST
*builtin_haveruleactions (PARSE
*parse
, LOL
*args
, int *jmp
) {
751 LIST
*el
= lol_get(args
, 0), *l
;
752 int wantAction
= parse
->num
;
754 int cmptype
= 0; // <0:glob; >0:regexp
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;
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) {
773 for (; el
; el
= el
->next
) {
774 RULE
*r
= findrule(el
->string
);
777 if (wantAction
&& !r
->actions
) return L0
;
778 if (!wantAction
&& !r
->procedure
) return L0
;
780 } else if (cmptype
< 0) {
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) {
796 nfo
.wantAction
= wantAction
;
798 for (; el
; el
= el
->next
) {
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
);
805 err
= iteraterules(hr_regexp
, &nfo
);
810 // normal, case-insensitive
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";
839 bjprngInit(&rctx
, bjprngGenRandSeed());
841 GetTempPath(sizeof(tp
), tp
);
844 for (int f
= 0; f
< 8; ++f
) buffer
[f
] = alphabet
[bjprngRand(&rctx
)%strlen(alphabet
)];
846 el
= lol_get(args
, 0);
847 path
= (el
!= NULL
&& el
->string
!= NULL
? el
->string
:
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"));
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) {
882 return list_new(L0
, "1", 0);
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
, ...) {
900 const char *name
, *lastname
= "BAD";
903 while ((name
= va_arg(ap
, const char *))) {
904 int updown
= name
[0]==':'?1:(name
[0]=='.'?-1:0);
907 if (updown
== 1 && !compat_old_builtin_names
) continue;
909 char *s
= alloca(strlen(lastname
)+1), *p
;
912 for (p
= s
; *p
; ++p
) {
914 if (*p
>= 'a' && *p
<= 'z') (*p
) -= 32; // upcase
916 if (*p
>= 'A' && *p
<= 'Z') (*p
) += 32; // locase
921 //fprintf(stderr, "%2d: [%s]\n", updown, name);
925 bindrule(name
)->procedure
= pp
;
931 void load_builtins (void) {
932 bind_builtin(parse_make(builtin_depends
, P0
, P0
, P0
, C0
, C0
, 0),
935 bind_builtin(parse_make(builtin_depends
, P0
, P0
, P0
, C0
, C0
, 1),
938 bind_builtin(parse_make(builtin_dependslist
, P0
, P0
, P0
, C0
, C0
, 0),
942 bind_builtin(parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, T_FLAG_TOUCHED
),
945 bind_builtin(parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, T_FLAG_LEAVES
),
948 bind_builtin(parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, T_FLAG_NOCARE
),
951 bind_builtin(parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, T_FLAG_NOTFILE
),
955 bind_builtin(parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, T_FLAG_NOUPDATE
),
958 bind_builtin(parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, T_FLAG_TEMP
),
961 bind_builtin(parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, T_FLAG_FORCECARE
),
964 bind_builtin(parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, 666),
968 bind_builtin(parse_make(builtin_echo
, P0
, P0
, P0
, C0
, C0
, 0),
971 bind_builtin(parse_make(builtin_echon
, P0
, P0
, P0
, C0
, C0
, 0),
974 bind_builtin(parse_make(builtin_oflush
, P0
, P0
, P0
, C0
, C0
, 0),
975 "O-Flush", "O-flush", ".",
978 bind_builtin(parse_make(builtin_exit
, P0
, P0
, P0
, C0
, C0
, 0),
982 bind_builtin(parse_make(builtin_glob
, P0
, P0
, P0
, C0
, C0
, 0),
985 bind_builtin(parse_make(builtin_match
, P0
, P0
, P0
, C0
, C0
, 0),
989 bind_builtin(parse_make(builtin_hdrmacro
, P0
, P0
, P0
, C0
, C0
, 0),
993 bind_builtin(parse_make(builtin_pwd
, P0
, P0
, P0
, C0
, C0
, 0),
997 bind_builtin(parse_make(builtin_sort
, P0
, P0
, P0
, C0
, C0
, 0),
1001 bind_builtin(parse_make(builtin_command
, P0
, P0
, P0
, C0
, C0
, 0),
1005 bind_builtin(parse_make(builtin_expri1
, P0
, P0
, P0
, C0
, C0
, 0),
1009 bind_builtin(parse_make(builtin_split
, P0
, P0
, P0
, C0
, C0
, 0),
1013 bind_builtin(parse_make(builtin_normpath
, P0
, P0
, P0
, C0
, C0
, 0),
1017 bind_builtin(parse_make(builtin_listlength
, P0
, P0
, P0
, C0
, C0
, 0),
1021 bind_builtin(parse_make(builtin_haveruleactions
, P0
, P0
, P0
, C0
, C0
, 0),
1024 bind_builtin(parse_make(builtin_haveruleactions
, P0
, P0
, P0
, C0
, C0
, 1),
1028 bind_builtin(parse_make(builtin_randname
, P0
, P0
, P0
, C0
, C0
, 0),
1031 bind_builtin(parse_make(builtin_listwrite
, P0
, P0
, P0
, C0
, C0
, 0),