2 * Copyright 1993-2002 Christopher Seiwald and Perforce Software, Inc.
3 * This file is part of Jam - see jam.c for Copyright information.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3 of the License ONLY.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 * builtins.c - builtin jam rules
22 * load_builtin() - define builtin rules
26 * builtin_depends() - DEPENDS/INCLUDES rule
27 * builtin_echo() - ECHO rule
28 * builtin_exit() - EXIT rule
29 * builtin_flags() - NOCARE, NOTFILE, TEMPORARY rule
30 * builtin_glob() - GLOB rule
31 * builtin_match() - MATCH rule
32 * builtin_hdrmacro() - HDRMACRO rule
55 #include "matchglob.h"
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. */
74 if (!t
->includes
) t
->includes
= copytarget(t
);
77 t
->depends
= targetlist(t
->depends
, sources
);
91 static void parse_echo_flags (echo_flags_t
*flg
, const LIST
*l
) {
94 flg
->flags
= LPFLAG_NO_TRSPACE
;
96 for (; l
; l
= list_next(l
)) {
97 const char *s
= l
->string
;
101 case 'n': flg
->no_newline
= 1; break;
102 case 'Q': flg
->no_out
= 1; break;
103 case 'S': flg
->flags
|= LPFLAG_NO_SPACES
; break;
104 case 's': flg
->flags
&= ~LPFLAG_NO_TRSPACE
; break;
105 case 'w': flg
->stream
= stderr
; break;
114 * builtin_echo() - ECHO rule
116 * The ECHO builtin rule echoes the targets to the user.
117 * No other actions are taken.
119 static LIST
*builtin_echo (PARSE
*parse
, LOL
*args
, int *jmp
) {
121 parse_echo_flags(&ef
, lol_get(args
, 1));
122 if (!ef
.no_out
) list_print_ex(ef
.stream
, lol_get(args
, 0), ef
.flags
);
123 if (!ef
.no_newline
) fputc('\n', ef
.stream
); else fflush(ef
.stream
);
129 * builtin_exit() - EXIT rule
131 * The EXIT builtin rule echoes the targets to the user and exits
132 * the program with a failure status.
134 static LIST
*builtin_exit (PARSE
*parse
, LOL
*args
, int *jmp
) {
135 LIST
*l
= lol_get(args
, 0);
137 parse_echo_flags(&ef
, lol_get(args
, 1));
139 if (!ef
.no_out
) list_print_ex(ef
.stream
, l
, ef
.flags
);
140 if (!ef
.no_newline
) fputc('\n', ef
.stream
); else fflush(ef
.stream
);
141 exit(EXITBAD
); /* yeech */
151 * builtin_flags() - NOCARE, NOTFILE, TEMPORARY rule
153 * Builtin_flags() marks the target with the appropriate flag, for use by make0().
154 * It binds each target as a TARGET.
156 static LIST
*builtin_flags (PARSE
*parse
, LOL
*args
, int *jmp
) {
157 LIST
*l
= lol_get(args
, 0);
158 int flag
= parse
->num
, andflag
= ~0;
160 case T_FLAG_NOCARE
: andflag
= ~T_FLAG_FORCECARE
; break;
161 case T_FLAG_FORCECARE
: andflag
= ~T_FLAG_NOCARE
; break;
162 case 666: flag
= 0; andflag
= ~T_FLAG_NOTFILE
; break;
164 for (; l
; l
= list_next(l
)) {
165 TARGET
*t
= bindtarget(l
->string
);
181 * builtin_globbing() - GLOB rule
187 int cmptype
; // <0:glob; 0: plain; >0:# of regexps
194 static void builtin_glob_back (void *closure
, const char *file
, int status
, time_t time
) {
195 struct globbing
*globbing
= (struct globbing
*)closure
;
198 static char buf
[MAXJPATH
];
199 /* null out directory for matching */
200 /* we wish we had file_dirscan() pass up a PATHNAME */
201 path_parse(file
, &f
);
203 /* For globbing, we unconditionally ignore current and parent
204 * directory items. Since those items always exist, there's no
205 * reason why caller of GLOB would want to see them.
206 * We could also change file_dirscan, but then paths with embedded
207 * "." and ".." won't work anywhere. */
208 /* k8: will this break anything? it shouldn't... */
209 if (!strcmp(f
.f_base
.ptr
, ".") || !strcmp(f
.f_base
.ptr
, "..")) return;
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
= file_type(file
);
218 switch (globbing
->mode
) {
219 case GLOB_DIRS
: if (ftype
!= 1) return; break;
220 case GLOB_FILES
: if (ftype
!= 0) return; break;
224 if (globbing
->cmptype
< 0) {
225 for (l
= globbing
->patterns
; l
; l
= l
->next
) {
226 if (matchglobex(l
->string
, buf
, globbing
->casesens
) == 0) {
227 globbing
->results
= list_new(globbing
->results
, (globbing
->namesonly
? buf
: file
), 0);
231 } else if (globbing
->cmptype
> 0) {
232 for (int xxf
= 0; xxf
< globbing
->cmptype
; ++xxf
) {
233 if (regexp_execute(globbing
->re
[xxf
], buf
, NULL
, 0) > 0) {
234 globbing
->results
= list_new(globbing
->results
, (globbing
->namesonly
? buf
: file
), 0);
239 for (l
= globbing
->patterns
; l
; l
= l
->next
) {
240 if ((globbing
->casesens
? strcmp
: strcasecmp
)(l
->string
, buf
) == 0) {
241 globbing
->results
= list_new(globbing
->results
, (globbing
->namesonly
? buf
: file
), 0);
249 static LIST
*builtin_glob (PARSE
*parse
, LOL
*args
, int *jmp
) {
250 LIST
*l
= lol_get(args
, 0);
251 LIST
*r
= lol_get(args
, 1);
253 struct globbing globbing
;
255 globbing
.results
= L0
;
256 globbing
.patterns
= r
;
257 globbing
.casesens
= 1;
258 globbing
.cmptype
= -1;
259 globbing
.mode
= GLOB_ANY
;
260 globbing
.namesonly
= 0;
261 for (lo
= lol_get(args
, 2); lo
!= NULL
; lo
= lo
->next
) {
262 if (!strcmp("case-sensitive", lo
->string
)) globbing
.casesens
= 1;
263 else if (!strcmp("case-insensitive", lo
->string
)) globbing
.casesens
= 0;
264 else if (!strcmp("ignore-case", lo
->string
)) globbing
.casesens
= 0;
265 else if (!strcmp("glob", lo
->string
)) globbing
.cmptype
= -1;
266 else if (!strcmp("regexp", lo
->string
)) globbing
.cmptype
= 1;
267 else if (!strcmp("plain", lo
->string
)) globbing
.cmptype
= 0;
268 else if (!strcmp("dirs-only", lo
->string
)) globbing
.mode
= GLOB_DIRS
;
269 else if (!strcmp("files-only", lo
->string
)) globbing
.mode
= GLOB_FILES
;
270 else if (!strcmp("any", lo
->string
)) globbing
.mode
= GLOB_ANY
;
271 else if (!strcmp("names-only", lo
->string
)) globbing
.namesonly
= 1;
272 else if (!strcmp("full-path", lo
->string
)) globbing
.namesonly
= 0;
274 printf("jam: invalid option for Glob built-in: '%s'\n", lo
->string
);
275 exit(EXITBAD
); /* yeech */
278 if (globbing
.cmptype
> 0) {
279 /* compile regexps */
280 globbing
.cmptype
= list_length(r
);
281 globbing
.re
= malloc(sizeof(globbing
.re
[0])*globbing
.cmptype
);
282 if (globbing
.re
== NULL
) { printf("FATAL: out of memory in Glob\n"); exit(42); }
283 for (int f
= 0; r
; r
= r
->next
, ++f
) globbing
.re
[f
] = regexp_compile(r
->string
, (globbing
.casesens
? 0 : RE9_FLAG_CASEINSENS
));
287 for (; l
; l
= list_next(l
)) file_dirscan(l
->string
, builtin_glob_back
, &globbing
);
288 if (globbing
.re
!= NULL
) {
289 for (int f
= 0; f
< globbing
.cmptype
; ++f
) regexp_free(globbing
.re
[f
]);
292 return globbing
.results
;
297 * builtin_match() - MATCH rule, regexp matching
299 static LIST
*builtin_match (PARSE
*parse
, LOL
*args
, int *jmp
) {
304 for (lo
= lol_get(args
, 2); lo
!= NULL
; lo
= lo
->next
) {
305 if (!strcmp("case-sensitive", lo
->string
)) casesens
= 1;
306 else if (!strcmp("case-insensitive", lo
->string
)) casesens
= 0;
307 else if (!strcmp("ignore-case", lo
->string
)) casesens
= 0;
308 else if (!strcmp("glob", lo
->string
)) cmptype
= -1;
309 else if (!strcmp("regexp", lo
->string
)) cmptype
= 1;
310 else if (!strcmp("plain", lo
->string
)) cmptype
= 0;
312 printf("jam: invalid option for Match built-in: '%s'\n", lo
->string
);
313 exit(EXITBAD
); /* yeech */
316 /* for each pattern */
317 for (l
= lol_get(args
, 0); l
; l
= l
->next
) {
321 re9_sub_t mt
[RE9_SUBEXP_MAX
];
322 re
= regexp_compile(l
->string
, (casesens
? 0: RE9_FLAG_CASEINSENS
));
323 /* for each string to match against */
324 for (r
= lol_get(args
, 1); r
; r
= r
->next
) {
325 mt
[0].sp
= mt
[0].ep
= NULL
;
326 if (regexp_execute(re
, r
->string
, mt
, RE9_SUBEXP_MAX
) > 0) {
328 /* add all parameters up to highest onto list */
329 /* must have parameters to have results! */
330 //fprintf(stderr, "re: <%s>: nsub=%d\n", re->restr, re9_nsub(re->re));
332 for (int i
= 1; i
< re9_nsub(re
->re
); ++i
) {
333 int xxl
= mt
[i
].ep
-mt
[i
].sp
;
335 if (xxl
> 0) dstr_push_buf(&buf
, mt
[i
].sp
, xxl
);
336 res
= list_new(res
, dstr_cstr(&buf
), 0);
338 /* add full match as last item */
340 int xxl
= mt
[0].ep
-mt
[0].sp
;
342 if (xxl
> 0) dstr_push_buf(&buf
, mt
[0].sp
, xxl
); else dstr_push_cstr(&buf
, "1");
343 res
= list_new(res
, dstr_cstr(&buf
), 0);
349 } else if (cmptype
< 0) {
350 for (r
= lol_get(args
, 1); r
; r
= r
->next
) {
351 if (matchglobex(l
->string
, r
->string
, casesens
) == 0) {
352 res
= list_new(res
, r
->string
, 0);
356 for (r
= lol_get(args
, 1); r
; r
= r
->next
) {
357 if ((casesens
? strcmp
: strcasecmp
)(l
->string
, r
->string
) == 0) {
358 res
= list_new(res
, r
->string
, 0);
367 static LIST
*builtin_hdrmacro (PARSE
*parse
, LOL
*args
, int *jmp
) {
368 LIST
*l
= lol_get(args
, 0);
369 for (; l
; l
= list_next(l
)) {
370 TARGET
*t
= bindtarget(l
->string
);
371 /* scan file for header filename macro definitions */
372 if (DEBUG_HEADER
) printf("scanning '%s' for header file macro definitions\n", l
->string
);
379 /* backported from boost-jam */
381 * Return the current working directory.
383 * Usage: pwd = [ PWD ] ;
385 static LIST
*builtin_pwd (PARSE
*parse
, LOL
*args
, int *jmp
) {
386 char pwd_buffer
[PATH_MAX
];
387 if (!getcwd(pwd_buffer
, sizeof(pwd_buffer
))) {
388 perror("can not get current directory");
391 return list_new(L0
, pwd_buffer
, 0);
395 /* backported from boost-jam */
396 static LIST
*builtin_sort (PARSE
*parse
, LOL
*args
, int *jmp
) {
397 LIST
*arg
= lol_get(args
, 0);
398 arg
= list_sort(arg
);
403 /* backported from boost-jam; greatly improved */
404 /* Command shcmd [[ : options ]] */
405 static LIST
*builtin_command (PARSE
*parse
, LOL
*args
, int *jmp
) {
409 char buffer
[1024], buf1
[32], *spos
, *epos
;
412 int optExitStatus
= 0;
415 int optTrimRight
= 1;
416 int optStatus1st
= 0;
418 int optSpaceBreak
= 1;
422 int no_options
= ((l
= lol_get(args
, 1)) == NULL
);
424 /* for each string in 2nd list: check for arg */
425 for (; l
!= NULL
; l
= l
->next
) {
426 if (!strcmp("exit-status", l
->string
)) optExitStatus
= 1;
427 else if (!strcmp("exit-code", l
->string
)) optExitStatus
= 1;
428 else if (!strcmp("status-first", l
->string
)) optStatus1st
= 1;
429 else if (!strcmp("code-first", l
->string
)) optStatus1st
= 1;
430 else if (!strcmp("no-output", l
->string
)) optNoOutput
= 1;
431 else if (!strcmp("no-trim", l
->string
)) optTrimLeft
= optTrimRight
= 0;
432 else if (!strcmp("no-trim-left", l
->string
)) optTrimLeft
= 0;
433 else if (!strcmp("no-trim-right", l
->string
)) optTrimRight
= 0;
434 else if (!strcmp("parse-output", l
->string
)) optParseOut
= 1;
435 else if (!strcmp("no-space-break", l
->string
)) optSpaceBreak
= 0;
436 else if (!strcmp("no-tab-break", l
->string
)) optTabBreak
= 0;
437 else if (!strcmp("no-nl-break", l
->string
)) optLFBreak
= 0;
438 else if (!strcmp("no-lf-break", l
->string
)) optLFBreak
= 0;
439 else if (!strcmp("no-cr-break", l
->string
)) optCRBreak
= 0;
440 else if (!strcmp("dummy", l
->string
) || !strcmp("xyzzy", l
->string
)) {}
442 printf("jam: invalid option for Command built-in: '%s'\n", l
->string
);
443 exit(EXITBAD
); /* yeech */
446 if (no_options
) optNoOutput
= 1;
447 /* build shell command */
450 for (l
= lol_get(args
, 0); l
; l
= l
->next
) {
451 if (dstr_len(&str
)) dstr_push_char(&str
, ' ');
452 dstr_push_cstr(&str
, l
->string
);
454 /* no shell command? */
455 if (dstr_len(&str
) < 1) { dstr_done(&str
); return L0
; }
456 fflush(NULL
); /* flush ALL output streams */
457 p
= popen(dstr_cstr(&str
), "r");
458 if (!p
) { dstr_done(&str
); return L0
; }
460 while ((ret
= fread(buffer
, sizeof(char), sizeof(buffer
)-1, p
)) > 0) {
463 dstr_push_cstr(&str
, buffer
);
466 exitStatus
= pclose(p
);
469 snprintf(buf1
, sizeof(buf1
), "%d", exitStatus
);
470 res
= list_new(L0
, buf1
, 0);
475 if (optExitStatus
&& optStatus1st
) {
476 snprintf(buf1
, sizeof(buf1
), "%d", exitStatus
);
477 res
= list_new(res
, buf1
, 0);
479 /* trim output if necessary */
484 // trim trailing blanks
485 int sl
= dstr_len(&str
);
486 spos
= dstr_cstr(&str
);
487 while (sl
> 0 && (unsigned char)spos
[sl
-1] <= ' ') --sl
;
490 spos
= dstr_cstr(&str
);
492 // trim leading blanks
493 while (*spos
&& *((unsigned char *)spos
) <= ' ') ++spos
;
495 res
= list_new(res
, spos
, 0);
499 ret
= 0; /* was anything added? list must have at least one element */
500 spos
= dstr_cstr(&str
);
503 /* skip delimiters */
505 unsigned char ch
= (unsigned char)(*spos
);
506 if (ch
== ' ') { if (!optSpaceBreak
) break; }
507 else if (ch
== '\t') { if (!optTabBreak
) break; }
508 else if (ch
== '\r') { if (!optCRBreak
) break; }
509 else if (ch
== '\n') { if (!optLFBreak
) break; }
510 else if (ch
> ' ') break;
517 if (ch
== ' ') { if (optSpaceBreak
) break; }
518 else if (ch
== '\t') { if (optTabBreak
) break; }
519 else if (ch
== '\r') { if (optCRBreak
) break; }
520 else if (ch
== '\n') { if (optLFBreak
) break; }
521 else if ((unsigned char)ch
<= ' ') break;
525 dstr_push_memrange(&tmp
, spos
, epos
);
526 res
= list_new(res
, dstr_cstr(&tmp
), 0);
531 if (!ret
) { buf1
[0] = '\0'; res
= list_new(res
, buf1
, 0); }
534 /* command exit result next */
535 if (optExitStatus
&& !optStatus1st
) {
536 snprintf(buf1
, sizeof(buf1
), "%d", exitStatus
);
537 res
= list_new(res
, buf1
, 0);
545 /* ExprI1 op0 math op1 */
546 static LIST
*builtin_expri1 (PARSE
*parse
, LOL
*args
, int *jmp
) {
548 int op0
, op1
, res
, comp
= 0;
549 LIST
*el
= lol_get(args
, 0);
550 if (!el
|| !el
->next
) return L0
;
551 if (el
->string
[0] == '#') {
553 snprintf(buffer
, sizeof(buffer
), "%u", (unsigned int)(strlen(el
->next
->string
)));
554 return list_new(L0
, buffer
, 0);
556 if (!el
->next
->next
) return L0
;
557 op0
= atoi(el
->string
);
558 op1
= atoi(el
->next
->next
->string
);
560 switch (el
->next
->string
[0]) {
561 case '+': res
= op0
+op1
; break;
562 case '-': res
= op0
-op1
; break;
563 case '*': res
= op0
*op1
; break;
564 case '/': res
= op0
/op1
; break;
565 case '%': res
= op0
%op1
; break;
568 if (el
->next
->string
[1] == '=') res
= (op0
<= op1
); else res
= (op0
< op1
);
570 case '=': comp
= 1; res
= (op0
== op1
); break;
571 case '!': comp
= 1; res
= (op0
!= op1
); break;
574 if (el
->next
->string
[1] == '=') res
= (op0
>= op1
); else res
= (op0
> op1
);
577 printf("jam: rule ExprI1: unknown operator: '%s'\n", el
->next
->string
);
580 if (comp
) return (res
? list_new(L0
, "tan", 0) : L0
);
581 snprintf(buffer
, sizeof(buffer
), "%d", res
);
582 return list_new(L0
, buffer
, 0);
586 /* based on the code from ftjam by David Turner */
587 static LIST
*builtin_split (PARSE
*parse
, LOL
*args
, int *jmp
) {
588 LIST
*input
= lol_get(args
, 0);
589 LIST
*tokens
= lol_get(args
, 1);
595 /* build token array */
596 if (tokens
== NULL
) {
597 memset(token
, 1, sizeof(token
));
600 memset(token
, 0, sizeof(token
));
601 for (; tokens
; tokens
= tokens
->next
) {
602 const char *s
= tokens
->string
;
603 for (; *s
; ++s
) token
[(unsigned char)*s
] = 1;
605 if (memchr(token
, 1, sizeof(token
)) == NULL
) {
606 memset(token
, 1, sizeof(token
));
611 /* now parse the input and split it */
612 for (; input
; input
= input
->next
) {
613 const char *ptr
= input
->string
;
614 const char *lastPtr
= input
->string
;
616 if (token
[(unsigned char)*ptr
]) {
617 size_t count
= ptr
-lastPtr
+explode
;
620 dstr_push_memrange(&str
, lastPtr
, ptr
+explode
);
621 res
= list_new(res
, dstr_cstr(&str
), 0);
627 if (ptr
> lastPtr
) res
= list_new(res
, lastPtr
, 0);
635 * builtin_dependslist()
637 * The DependsList builtin rule returns list of dependencies for
640 static LIST
*builtin_dependslist (PARSE
*parse
, LOL
*args
, int *jmp
) {
643 for (parents
= lol_get(args
, 0); parents
; parents
= parents
->next
) {
644 TARGET
*t
= bindtarget(parents
->string
);
646 for (child
= t
->depends
; child
; child
= child
->next
) res
= list_new(res
, child
->target
->name
, 1);
653 * NormalizePath path [: pwd]
655 * it will add 'pwd' if path is not absolute and will try to resolve some '.' and '..' (only leading '..' though).
656 * it can be used in SubDir replacement to automate dir building
657 * if there is no $(2), use current directory as 'pwd'
659 static LIST
*builtin_normpath (PARSE
*parse
, LOL
*args
, int *jmp
) {
660 LIST
*el
= lol_get(args
, 0), *pl
= lol_get(args
, 1);
663 if (!el
|| !el
->string
) return L0
;
664 bsz
= strlen(el
->string
)*2+1024;
666 if (buf
== NULL
) return L0
;
667 if (!normalize_path(el
->string
, buf
, bsz
, (pl
!= NULL
? pl
->string
: NULL
))) { free(buf
); return L0
; }
668 el
= list_new(NULL
, buf
, 0);
677 static LIST
*builtin_listlength (PARSE
*parse
, LOL
*args
, int *jmp
) {
679 LIST
*el
= lol_get(args
, 0);
680 if (!el
) return list_new(L0
, "0", 0);
681 snprintf(buffer
, sizeof(buffer
), "%d", list_length(el
));
682 return list_new(L0
, buffer
, 0);
689 static LIST
*builtin_listreverse (PARSE
*parse
, LOL
*args
, int *jmp
) {
690 return list_reverse(lol_get(args
, 0));
695 * HaveRule and HaveActions
711 static int hr_normal (const void *hdata
, void *udata
) {
712 const RULE
*r
= (const RULE
*)hdata
;
713 const HRNormalData
*d
= (const HRNormalData
*)udata
;
714 if (strcasecmp(r
->name
, d
->str
) == 0) {
715 if (d
->wantAction
&& r
->actions
) return 1; // got it
716 if (!d
->wantAction
&& r
->procedure
) return 1; // got it
722 static int hr_glob (const void *hdata
, void *udata
) {
723 const RULE
*r
= (const RULE
*)hdata
;
724 const HRNormalData
*d
= (const HRNormalData
*)udata
;
725 if (matchglobex(d
->str
, r
->name
, d
->casesens
) == 0) {
726 //fprintf(stderr, ":[%s]\n", r->name);
727 if (d
->wantAction
&& r
->actions
) return 1; // got it
728 if (!d
->wantAction
&& r
->procedure
) return 1; // got it
741 static int hr_regexp (const void *hdata
, void *udata
) {
742 const RULE
*r
= (const RULE
*)hdata
;
743 HRREData
*d
= (HRREData
*)udata
;
744 if (regexp_execute(d
->re
, r
->name
, NULL
, 0) > 0) {
745 //fprintf(stderr, ":[%s]\n", r->name);
746 if (d
->wantAction
&& r
->actions
) return 1; // got it
747 if (!d
->wantAction
&& r
->procedure
) return 1; // got it
753 static LIST
*builtin_haveruleactions (PARSE
*parse
, LOL
*args
, int *jmp
) {
754 LIST
*el
= lol_get(args
, 0), *l
;
755 int wantAction
= parse
->num
;
757 int cmptype
= 0; // <0:glob; >0:regexp
759 for (l
= lol_get(args
, 1); l
!= NULL
; l
= l
->next
) {
760 if (!strcmp("case-sensitive", l
->string
)) casesens
= 1;
761 else if (!strcmp("case-insensitive", l
->string
)) casesens
= 0;
762 else if (!strcmp("ignore-case", l
->string
)) casesens
= 0;
763 else if (!strcmp("glob", l
->string
)) cmptype
= -1;
764 else if (!strcmp("regexp", l
->string
)) cmptype
= 1;
765 else if (!strcmp("plain", l
->string
)) cmptype
= 0;
767 printf("jam: invalid option for Have%s built-in: '%s'\n", (wantAction
? "Actions" : "Rule"), l
->string
);
768 exit(EXITBAD
); /* yeech */
771 if (casesens
== 1 && cmptype
== 0) {
773 for (; el
; el
= el
->next
) {
774 RULE
*r
= findrule(el
->string
);
776 if (wantAction
&& !r
->actions
) return L0
;
777 if (!wantAction
&& !r
->procedure
) return L0
;
779 } else if (cmptype
< 0) {
782 nfo
.wantAction
= wantAction
;
783 nfo
.casesens
= casesens
;
784 for (; el
; el
= el
->next
) {
785 nfo
.str
= el
->string
;
786 if (!iteraterules(hr_glob
, &nfo
)) return L0
;
788 } else if (cmptype
> 0) {
791 nfo
.wantAction
= wantAction
;
792 for (; el
; el
= el
->next
) {
794 nfo
.re
= regexp_compile(el
->string
, (casesens
? 0 : RE9_FLAG_CASEINSENS
));
795 /*printf("FATAL: invalid regexp in Have%s: %s\n", (wantAction ? "Actions" : "Rule"), errmsg);*/
796 err
= iteraterules(hr_regexp
, &nfo
);
801 // normal, case-insensitive
803 nfo
.wantAction
= wantAction
;
804 for (; el
; el
= el
->next
) {
805 nfo
.str
= el
->string
;
806 if (!iteraterules(hr_normal
, &nfo
)) return L0
;
809 return list_new(L0
, "1", 0);
813 /* generate random name:
814 * [ RandName ] -- /tmp/XXX
815 * [ RandName "abc/" ] -- abc/XXX
816 * [ RandName "" ] -- XXX
818 static LIST
*builtin_randname (PARSE
*parse
, LOL
*args
, int *jmp
) {
819 static BJRandCtx rctx
;
820 static int initialized
= 0;
821 static const char alphabet
[] = "0123456789abcdefghijklmnopqrstuvwxyz";
830 bjprngRandomize(&rctx
);
832 GetTempPath(sizeof(tp
), tp
);
835 for (int f
= 0; f
< 8; ++f
) buffer
[f
] = alphabet
[bjprngRand(&rctx
)%strlen(alphabet
)];
837 el
= lol_get(args
, 0);
838 path
= (el
!= NULL
&& el
->string
!= NULL
? el
->string
:
845 s
= alloca(strlen(path
)+strlen(buffer
)+2);
846 sprintf(s
, "%s%s", path
, buffer
);
847 return list_new(L0
, s
, 0);
851 /* write list to file:
852 * ListFileWrite filename : list [: terminator] [: append]
853 * default terminator is '\n'
854 * return success flag
856 static LIST
*builtin_listwrite (PARSE
*parse
, LOL
*args
, int *jmp
) {
857 LIST
*el
= lol_get(args
, 0);
858 if (el
!= NULL
&& el
->string
!= NULL
&& el
->string
[0]) {
859 LIST
*l
= lol_get(args
, 3);
860 FILE *fo
= fopen(el
->string
, (l
!= NULL
&& l
->string
[0] ? "a" : "w"));
863 l
= lol_get(args
, 2);
864 term
= (l
!= NULL
? l
->string
: "\n");
865 for (l
= lol_get(args
, 1); l
!= NULL
; l
= l
->next
) {
866 if (fprintf(fo
, "%s%s", l
->string
, term
) < 0) {
873 return list_new(L0
, "1", 0);
880 /* remove duplicates from list */
881 static LIST
*builtin_listremdups (PARSE
*parse
, LOL
*args
, int *jmp
) {
882 LIST
*el
= lol_get(args
, 0);
884 LIST
*l
, *res
= list_new(L0
, el
->string
, 1);
885 for (l
= el
->next
; l
!= NULL
; l
= l
->next
) {
887 for (LIST
*t
= el
; t
!= l
&& !found
; t
= t
->next
) if (strcmp(t
->string
, l
->string
) == 0) found
= 1;
888 if (!found
) res
= list_new(res
, l
->string
, 1);
892 return el
; /* no need to change anything */
897 * compile_builtin() - define builtin rules
900 #define P0 ((PARSE *)0)
901 #define C0 ((char *)0)
904 /* ":" -- previous name in upper case; "." -- previous name in lower case */
905 static inline void bind_builtin (PARSE
*pp
, const char *name
) {
906 bindrule(name
)->procedure
= pp
;
910 static inline void bind_builtin2 (PARSE
*pp
, const char *name
, const char *name1
) {
911 bindrule(name
)->procedure
= pp
;
912 bindrule(name1
)->procedure
= pp
;
916 void load_builtins (void) {
917 bind_builtin(parse_make(builtin_depends
, P0
, P0
, P0
, C0
, C0
, 0),
919 bind_builtin(parse_make(builtin_depends
, P0
, P0
, P0
, C0
, C0
, 1),
921 bind_builtin(parse_make(builtin_dependslist
, P0
, P0
, P0
, C0
, C0
, 0),
924 bind_builtin(parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, T_FLAG_TOUCHED
),
926 bind_builtin(parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, T_FLAG_LEAVES
),
928 bind_builtin(parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, T_FLAG_NOCARE
),
930 bind_builtin2(parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, T_FLAG_NOTFILE
),
931 "NotFile", "NoTime");
932 bind_builtin(parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, T_FLAG_NOUPDATE
),
934 bind_builtin(parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, T_FLAG_TEMP
),
936 bind_builtin(parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, T_FLAG_FORCECARE
),
938 bind_builtin(parse_make(builtin_flags
, P0
, P0
, P0
, C0
, C0
, 666),
941 bind_builtin(parse_make(builtin_echo
, P0
, P0
, P0
, C0
, C0
, 0),
944 bind_builtin(parse_make(builtin_exit
, P0
, P0
, P0
, C0
, C0
, 0),
947 bind_builtin(parse_make(builtin_glob
, P0
, P0
, P0
, C0
, C0
, 0),
949 bind_builtin(parse_make(builtin_match
, P0
, P0
, P0
, C0
, C0
, 0),
952 bind_builtin(parse_make(builtin_hdrmacro
, P0
, P0
, P0
, C0
, C0
, 0),
955 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),
961 bind_builtin(parse_make(builtin_command
, P0
, P0
, P0
, C0
, C0
, 0),
964 bind_builtin(parse_make(builtin_expri1
, P0
, P0
, P0
, C0
, C0
, 0),
967 bind_builtin(parse_make(builtin_split
, P0
, P0
, P0
, C0
, C0
, 0),
970 bind_builtin(parse_make(builtin_normpath
, P0
, P0
, P0
, C0
, C0
, 0),
973 bind_builtin(parse_make(builtin_listlength
, P0
, P0
, P0
, C0
, C0
, 0),
975 bind_builtin(parse_make(builtin_listwrite
, P0
, P0
, P0
, C0
, C0
, 0),
977 bind_builtin(parse_make(builtin_listremdups
, P0
, P0
, P0
, C0
, C0
, 0),
978 "ListRemoveDuplicates");
979 bind_builtin(parse_make(builtin_listreverse
, P0
, P0
, P0
, C0
, C0
, 0),
982 bind_builtin(parse_make(builtin_haveruleactions
, P0
, P0
, P0
, C0
, C0
, 0),
984 bind_builtin(parse_make(builtin_haveruleactions
, P0
, P0
, P0
, C0
, C0
, 1),
986 bind_builtin(parse_make(builtin_haveruleactions
, P0
, P0
, P0
, C0
, C0
, 0),
988 bind_builtin(parse_make(builtin_haveruleactions
, P0
, P0
, P0
, C0
, C0
, 1),
991 bind_builtin(parse_make(builtin_randname
, P0
, P0
, P0
, C0
, C0
, 0),