added "HOST_CPU", "HOST_32BIT", "HOST_64BIT" built-in variables
[k8jam.git] / src / builtins.c
blobcf85378cb82e580e1ce575f7179cf5dfa2508e61
1 /*
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
20 * External routines:
22 * load_builtin() - define builtin rules
24 * Internal routines:
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
34 #include <limits.h>
35 #include <stdarg.h>
36 #include <stddef.h>
37 #include <stdint.h>
38 #include <strings.h>
39 #include <unistd.h>
40 #ifdef _AIX
41 #include <alloca.h>
42 #endif
44 #include "jam.h"
46 #include "lists.h"
47 #include "parse.h"
48 #include "bjprng.h"
49 #include "builtins.h"
50 #include "rules.h"
51 #include "filesys.h"
52 #include "newstr.h"
53 #include "re9.h"
54 #include "pathsys.h"
55 #include "hdrmacro.h"
56 #include "matchglob.h"
57 #include "dstrings.h"
61 * builtin_depends() - DEPENDS/INCLUDES rule
63 * The DEPENDS builtin rule appends each of the listed sources on the
64 * dependency list of each of the listed targets.
65 * It binds both the targets and sources as TARGETs.
67 static LIST *builtin_depends (PARSE *parse, LOL *args, int *jmp) {
68 LIST *targets = lol_get(args, 0);
69 LIST *sources = lol_get(args, 1);
70 for (LIST *l = targets; l; l = list_next(l)) {
71 TARGET *t = bindtarget(l->string);
72 /* If doing INCLUDES, switch to the TARGET's include TARGET, creating it if needed.
73 * The internal include TARGET shares the name of its parent. */
74 if (parse->num) {
75 if (!t->includes) t->includes = copytarget(t);
76 t = t->includes;
78 t->depends = targetlist(t->depends, sources);
80 return L0;
84 typedef struct {
85 int no_newline;
86 int no_out;
87 int flags;
88 FILE *stream;
89 } echo_flags_t;
92 static void parse_echo_flags (echo_flags_t *flg, const LIST *l) {
93 flg->no_newline = 0;
94 flg->no_out = 0;
95 flg->flags = LPFLAG_NO_TRSPACE;
96 flg->stream = stdout;
97 for (; l; l = list_next(l)) {
98 const char *s = l->string;
99 if (*s == '-') {
100 for (++s; *s; ++s) {
101 switch (*s) {
102 case 'n': flg->no_newline = 1; break;
103 case 'Q': flg->no_out = 1; break;
104 case 'S': flg->flags |= LPFLAG_NO_SPACES; break;
105 case 's': flg->flags &= ~LPFLAG_NO_TRSPACE; break;
106 case 'w': flg->stream = stderr; break;
115 * builtin_echo() - ECHO rule
117 * The ECHO builtin rule echoes the targets to the user.
118 * No other actions are taken.
120 static LIST *builtin_echo (PARSE *parse, LOL *args, int *jmp) {
121 echo_flags_t ef;
122 parse_echo_flags(&ef, lol_get(args, 1));
123 if (!ef.no_out) list_print_ex(ef.stream, lol_get(args, 0), ef.flags);
124 if (!ef.no_newline) fputc('\n', ef.stream); else fflush(ef.stream);
125 return L0;
130 * builtin_exit() - EXIT rule
132 * The EXIT builtin rule echoes the targets to the user and exits
133 * the program with a failure status.
135 static LIST *builtin_exit (PARSE *parse, LOL *args, int *jmp) {
136 LIST *l = lol_get(args, 0);
137 echo_flags_t ef;
138 parse_echo_flags(&ef, lol_get(args, 1));
139 if (l != NULL) {
140 if (!ef.no_out) list_print_ex(ef.stream, l, ef.flags);
141 if (!ef.no_newline) fputc('\n', ef.stream); else fflush(ef.stream);
142 exit(EXITBAD); /* yeech */
144 exit(EXITOK);
145 #if __GNUC__ <= 2
146 return L0;
147 #endif
152 * builtin_flags() - NOCARE, NOTFILE, TEMPORARY rule
154 * Builtin_flags() marks the target with the appropriate flag, for use by make0().
155 * It binds each target as a TARGET.
157 static LIST *builtin_flags (PARSE *parse, LOL *args, int *jmp) {
158 LIST *l = lol_get(args, 0);
159 int flag = parse->num, andflag = ~0;
160 switch (flag) {
161 case T_FLAG_NOCARE: andflag = ~T_FLAG_FORCECARE; break;
162 case T_FLAG_FORCECARE: andflag = ~T_FLAG_NOCARE; break;
163 case 666: flag = 0; andflag = ~T_FLAG_NOTFILE; break;
165 for (; l; l = list_next(l)) {
166 TARGET *t = bindtarget(l->string);
167 t->flags |= flag;
168 t->flags &= andflag;
170 return L0;
174 typedef enum {
175 GLOB_ANY,
176 GLOB_DIRS,
177 GLOB_FILES
178 } glob_mode;
182 * builtin_globbing() - GLOB rule
184 struct globbing {
185 LIST *patterns;
186 LIST *results;
187 int casesens;
188 int cmptype; // <0:glob; 0: plain; >0:# of regexps
189 glob_mode mode;
190 int namesonly;
191 regexp_t **re;
195 static void builtin_glob_back (void *closure, const char *file, int status, time_t time) {
196 struct globbing *globbing = (struct globbing *)closure;
197 LIST *l;
198 PATHNAME f;
199 static char buf[MAXJPATH];
200 /* null out directory for matching */
201 /* we wish we had file_dirscan() pass up a PATHNAME */
202 path_parse(file, &f);
203 f.f_dir.len = 0;
204 /* For globbing, we unconditionally ignore current and parent
205 * directory items. Since those items always exist, there's no
206 * reason why caller of GLOB would want to see them.
207 * We could also change file_dirscan, but then paths with embedded
208 * "." and ".." won't work anywhere. */
209 /* k8: will this break anything? it shouldn't... */
210 if (!strcmp(f.f_base.ptr, ".") || !strcmp(f.f_base.ptr, "..")) return;
211 path_build(buf, &f);
213 fprintf(stderr, "buf: [%s]\n", buf);
214 int c;
215 for (c = 0; c < 6; ++c) fprintf(stderr, " %d: [%s]\n", c, f.part[c].ptr);
217 if (globbing->mode != GLOB_ANY) {
218 int ftype = file_type(file);
219 switch (globbing->mode) {
220 case GLOB_DIRS: if (ftype != 1) return; break;
221 case GLOB_FILES: if (ftype != 0) return; break;
222 default: ;
225 if (globbing->cmptype < 0) {
226 for (l = globbing->patterns; l; l = l->next) {
227 if (matchglobex(l->string, buf, globbing->casesens) == 0) {
228 globbing->results = list_new(globbing->results, (globbing->namesonly ? buf : file), 0);
229 break;
232 } else if (globbing->cmptype > 0) {
233 for (int xxf = 0; xxf < globbing->cmptype; ++xxf) {
234 if (regexp_execute(globbing->re[xxf], buf, NULL, 0) > 0) {
235 globbing->results = list_new(globbing->results, (globbing->namesonly ? buf : file), 0);
236 break;
239 } else {
240 for (l = globbing->patterns; l; l = l->next) {
241 if ((globbing->casesens ? strcmp : strcasecmp)(l->string, buf) == 0) {
242 globbing->results = list_new(globbing->results, (globbing->namesonly ? buf : file), 0);
243 break;
250 static LIST *builtin_glob (PARSE *parse, LOL *args, int *jmp) {
251 LIST *l = lol_get(args, 0);
252 LIST *r = lol_get(args, 1);
253 LIST *lo;
254 struct globbing globbing;
255 if (!r) return L0;
256 globbing.results = L0;
257 globbing.patterns = r;
258 globbing.casesens = 1;
259 globbing.cmptype = -1;
260 globbing.mode = GLOB_ANY;
261 globbing.namesonly = 0;
262 for (lo = lol_get(args, 2); lo != NULL; lo = lo->next) {
263 if (!strcmp("case-sensitive", lo->string)) globbing.casesens = 1;
264 else if (!strcmp("case-insensitive", lo->string)) globbing.casesens = 0;
265 else if (!strcmp("ignore-case", lo->string)) globbing.casesens = 0;
266 else if (!strcmp("glob", lo->string)) globbing.cmptype = -1;
267 else if (!strcmp("regexp", lo->string)) globbing.cmptype = 1;
268 else if (!strcmp("plain", lo->string)) globbing.cmptype = 0;
269 else if (!strcmp("dirs-only", lo->string)) globbing.mode = GLOB_DIRS;
270 else if (!strcmp("files-only", lo->string)) globbing.mode = GLOB_FILES;
271 else if (!strcmp("any", lo->string)) globbing.mode = GLOB_ANY;
272 else if (!strcmp("names-only", lo->string)) globbing.namesonly = 1;
273 else if (!strcmp("full-path", lo->string)) globbing.namesonly = 0;
274 else {
275 printf("jam: invalid option for Glob built-in: '%s'\n", lo->string);
276 exit(EXITBAD); /* yeech */
279 if (globbing.cmptype > 0) {
280 /* compile regexps */
281 globbing.cmptype = list_length(r);
282 globbing.re = malloc(sizeof(globbing.re[0])*globbing.cmptype);
283 if (globbing.re == NULL) { printf("FATAL: out of memory in Glob\n"); exit(42); }
284 for (int f = 0; r; r = r->next, ++f) globbing.re[f] = regexp_compile(r->string, (globbing.casesens ? 0 : RE9_FLAG_CASEINSENS));
285 } else {
286 globbing.re = NULL;
288 for (; l; l = list_next(l)) file_dirscan(l->string, builtin_glob_back, &globbing);
289 if (globbing.re != NULL) {
290 for (int f = 0; f < globbing.cmptype; ++f) regexp_free(globbing.re[f]);
291 free(globbing.re);
293 return globbing.results;
298 * builtin_match() - MATCH rule, regexp matching
300 static LIST *builtin_match (PARSE *parse, LOL *args, int *jmp) {
301 LIST *l, *lo;
302 LIST *res = L0;
303 int casesens = 1;
304 int cmptype = 1;
305 for (lo = lol_get(args, 2); lo != NULL; lo = lo->next) {
306 if (!strcmp("case-sensitive", lo->string)) casesens = 1;
307 else if (!strcmp("case-insensitive", lo->string)) casesens = 0;
308 else if (!strcmp("ignore-case", lo->string)) casesens = 0;
309 else if (!strcmp("glob", lo->string)) cmptype = -1;
310 else if (!strcmp("regexp", lo->string)) cmptype = 1;
311 else if (!strcmp("plain", lo->string)) cmptype = 0;
312 else {
313 printf("jam: invalid option for Match built-in: '%s'\n", lo->string);
314 exit(EXITBAD); /* yeech */
317 /* for each pattern */
318 for (l = lol_get(args, 0); l; l = l->next) {
319 LIST *r;
320 if (cmptype > 0) {
321 regexp_t *re;
322 re9_sub_t mt[RE9_SUBEXP_MAX];
323 re = regexp_compile(l->string, (casesens ? 0: RE9_FLAG_CASEINSENS));
324 /* for each string to match against */
325 for (r = lol_get(args, 1); r; r = r->next) {
326 mt[0].sp = mt[0].ep = NULL;
327 if (regexp_execute(re, r->string, mt, RE9_SUBEXP_MAX) > 0) {
328 dstring_t buf;
329 /* add all parameters up to highest onto list */
330 /* must have parameters to have results! */
331 //fprintf(stderr, "re: <%s>: nsub=%d\n", re->restr, re9_nsub(re->re));
332 dstr_init(&buf);
333 for (int i = 1; i < re9_nsub(re->re); ++i) {
334 int xxl = mt[i].ep-mt[i].sp;
335 dstr_clear(&buf);
336 if (xxl > 0) dstr_push_buf(&buf, mt[i].sp, xxl);
337 res = list_new(res, dstr_cstr(&buf), 0);
339 /* add full match as last item */
341 int xxl = mt[0].ep-mt[0].sp;
342 dstr_clear(&buf);
343 if (xxl > 0) dstr_push_buf(&buf, mt[0].sp, xxl); else dstr_push_cstr(&buf, "1");
344 res = list_new(res, dstr_cstr(&buf), 0);
346 dstr_done(&buf);
349 regexp_free(re);
350 } else if (cmptype < 0) {
351 for (r = lol_get(args, 1); r; r = r->next) {
352 if (matchglobex(l->string, r->string, casesens) == 0) {
353 res = list_new(res, r->string, 0);
356 } else {
357 for (r = lol_get(args, 1); r; r = r->next) {
358 if ((casesens ? strcmp : strcasecmp)(l->string, r->string) == 0) {
359 res = list_new(res, r->string, 0);
364 return res;
368 static LIST *builtin_hdrmacro (PARSE *parse, LOL *args, int *jmp) {
369 LIST *l = lol_get(args, 0);
370 for (; l; l = list_next(l)) {
371 TARGET *t = bindtarget(l->string);
372 /* scan file for header filename macro definitions */
373 if (DEBUG_HEADER) printf("scanning '%s' for header file macro definitions\n", l->string);
374 macro_headers(t);
376 return L0;
380 /* backported from boost-jam */
382 * Return the current working directory.
384 * Usage: pwd = [ PWD ] ;
386 static LIST *builtin_pwd (PARSE *parse, LOL *args, int *jmp) {
387 char pwd_buffer[PATH_MAX];
388 if (!getcwd(pwd_buffer, sizeof(pwd_buffer))) {
389 perror("can not get current directory");
390 return L0;
392 return list_new(L0, pwd_buffer, 0);
396 /* backported from boost-jam */
397 static LIST *builtin_sort (PARSE *parse, LOL *args, int *jmp) {
398 LIST *arg = lol_get(args, 0);
399 arg = list_sort(arg);
400 return arg;
404 /* backported from boost-jam; greatly improved */
405 /* Command shcmd [[ : options ]] */
406 static LIST *builtin_command (PARSE *parse, LOL *args, int *jmp) {
407 LIST *res = L0;
408 LIST *l;
409 int ret;
410 char buffer[1024], buf1[32], *spos, *epos;
411 FILE *p = NULL;
412 int exitStatus = -1;
413 int optExitStatus = 0;
414 int optNoOutput = 0;
415 int optTrimLeft = 1;
416 int optTrimRight = 1;
417 int optStatus1st = 0;
418 int optParseOut = 0;
419 int optSpaceBreak = 1;
420 int optTabBreak = 1;
421 int optCRBreak = 1;
422 int optLFBreak = 1;
423 int no_options = ((l = lol_get(args, 1)) == NULL);
424 dstring_t str;
425 /* for each string in 2nd list: check for arg */
426 for (; l != NULL; l = l->next) {
427 if (!strcmp("exit-status", l->string)) optExitStatus = 1;
428 else if (!strcmp("exit-code", l->string)) optExitStatus = 1;
429 else if (!strcmp("status-first", l->string)) optStatus1st = 1;
430 else if (!strcmp("code-first", l->string)) optStatus1st = 1;
431 else if (!strcmp("no-output", l->string)) optNoOutput = 1;
432 else if (!strcmp("no-trim", l->string)) optTrimLeft = optTrimRight = 0;
433 else if (!strcmp("no-trim-left", l->string)) optTrimLeft = 0;
434 else if (!strcmp("no-trim-right", l->string)) optTrimRight = 0;
435 else if (!strcmp("parse-output", l->string)) optParseOut = 1;
436 else if (!strcmp("no-space-break", l->string)) optSpaceBreak = 0;
437 else if (!strcmp("no-tab-break", l->string)) optTabBreak = 0;
438 else if (!strcmp("no-nl-break", l->string)) optLFBreak = 0;
439 else if (!strcmp("no-lf-break", l->string)) optLFBreak = 0;
440 else if (!strcmp("no-cr-break", l->string)) optCRBreak = 0;
441 else if (!strcmp("dummy", l->string) || !strcmp("xyzzy", l->string)) {}
442 else {
443 printf("jam: invalid option for Command built-in: '%s'\n", l->string);
444 exit(EXITBAD); /* yeech */
447 if (no_options) optNoOutput = 1;
448 /* build shell command */
449 dstr_init(&str);
450 /* for each arg */
451 for (l = lol_get(args, 0); l; l = l->next) {
452 if (dstr_len(&str)) dstr_push_char(&str, ' ');
453 dstr_push_cstr(&str, l->string);
455 /* no shell command? */
456 if (dstr_len(&str) < 1) { dstr_done(&str); return L0; }
457 fflush(NULL); /* flush ALL output streams */
458 p = popen(dstr_cstr(&str), "r");
459 if (!p) { dstr_done(&str); return L0; }
460 dstr_clear(&str);
461 while ((ret = fread(buffer, sizeof(char), sizeof(buffer)-1, p)) > 0) {
462 if (!optNoOutput) {
463 buffer[ret] = 0;
464 dstr_push_cstr(&str, buffer);
467 exitStatus = pclose(p);
468 if (no_options) {
469 if (exitStatus) {
470 snprintf(buf1, sizeof(buf1), "%d", exitStatus);
471 res = list_new(L0, buf1, 0);
472 } else {
473 res = L0;
475 } else {
476 if (optExitStatus && optStatus1st) {
477 snprintf(buf1, sizeof(buf1), "%d", exitStatus);
478 res = list_new(res, buf1, 0);
480 /* trim output if necessary */
481 if (!optNoOutput) {
482 if (!optParseOut) {
483 /* don't parse */
484 if (optTrimRight) {
485 // trim trailing blanks
486 int sl = dstr_len(&str);
487 spos = dstr_cstr(&str);
488 while (sl > 0 && (unsigned char)spos[sl-1] <= ' ') --sl;
489 dstr_chop(&str, sl);
491 spos = dstr_cstr(&str);
492 if (optTrimLeft) {
493 // trim leading blanks
494 while (*spos && *((unsigned char *)spos) <= ' ') ++spos;
496 res = list_new(res, spos, 0);
497 } else {
498 dstring_t tmp;
499 /* parse output */
500 ret = 0; /* was anything added? list must have at least one element */
501 spos = dstr_cstr(&str);
502 dstr_init(&tmp);
503 while (*spos) {
504 /* skip delimiters */
505 while (*spos) {
506 unsigned char ch = (unsigned char)(*spos);
507 if (ch == ' ') { if (!optSpaceBreak) break; }
508 else if (ch == '\t') { if (!optTabBreak) break; }
509 else if (ch == '\r') { if (!optCRBreak) break; }
510 else if (ch == '\n') { if (!optLFBreak) break; }
511 else if (ch > ' ') break;
512 ++spos;
514 if (!*spos) break;
515 epos = spos+1;
516 while (*epos) {
517 int ch = *epos;
518 if (ch == ' ') { if (optSpaceBreak) break; }
519 else if (ch == '\t') { if (optTabBreak) break; }
520 else if (ch == '\r') { if (optCRBreak) break; }
521 else if (ch == '\n') { if (optLFBreak) break; }
522 else if ((unsigned char)ch <= ' ') break;
523 ++epos;
525 dstr_clear(&tmp);
526 dstr_push_memrange(&tmp, spos, epos);
527 res = list_new(res, dstr_cstr(&tmp), 0);
528 ret = 1;
529 spos = epos;
531 dstr_done(&tmp);
532 if (!ret) { buf1[0] = '\0'; res = list_new(res, buf1, 0); }
535 /* command exit result next */
536 if (optExitStatus && !optStatus1st) {
537 snprintf(buf1, sizeof(buf1), "%d", exitStatus);
538 res = list_new(res, buf1, 0);
541 dstr_done(&str);
542 return res;
546 /* ExprI1 op0 math op1 */
547 static LIST *builtin_expri1 (PARSE *parse, LOL *args, int *jmp) {
548 char buffer[100];
549 int op0, op1, res, comp = 0;
550 LIST *el = lol_get(args, 0);
551 if (!el || !el->next) return L0;
552 if (el->string[0] == '#') {
553 // string length
554 snprintf(buffer, sizeof(buffer), "%u", (unsigned int)(strlen(el->next->string)));
555 return list_new(L0, buffer, 0);
557 if (!el->next->next) return L0;
558 op0 = atoi(el->string);
559 op1 = atoi(el->next->next->string);
560 res = 0;
561 switch (el->next->string[0]) {
562 case '+': res = op0+op1; break;
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 '<':
568 comp = 1;
569 if (el->next->string[1] == '=') res = (op0 <= op1); else res = (op0 < op1);
570 break;
571 case '=': comp = 1; res = (op0 == op1); break;
572 case '!': comp = 1; res = (op0 != op1); break;
573 case '>':
574 comp = 1;
575 if (el->next->string[1] == '=') res = (op0 >= op1); else res = (op0 > op1);
576 break;
577 default:
578 printf("jam: rule ExprI1: unknown operator: '%s'\n", el->next->string);
579 exit(EXITBAD);
581 if (comp) return (res ? list_new(L0, "tan", 0) : L0);
582 snprintf(buffer, sizeof(buffer), "%d", res);
583 return list_new(L0, buffer, 0);
587 /* based on the code from ftjam by David Turner */
588 static LIST *builtin_split (PARSE *parse, LOL *args, int *jmp) {
589 LIST *input = lol_get(args, 0);
590 LIST *tokens = lol_get(args, 1);
591 LIST *res = L0;
592 char token[256];
593 dstring_t str;
594 int explode = 0;
595 dstr_init(&str);
596 /* build token array */
597 if (tokens == NULL) {
598 memset(token, 1, sizeof(token));
599 explode = 1;
600 } else {
601 memset(token, 0, sizeof(token));
602 for (; tokens; tokens = tokens->next) {
603 const char *s = tokens->string;
604 for (; *s; ++s) token[(unsigned char)*s] = 1;
606 if (memchr(token, 1, sizeof(token)) == NULL) {
607 memset(token, 1, sizeof(token));
608 explode = 1;
611 token[0] = 0;
612 /* now parse the input and split it */
613 for (; input; input = input->next) {
614 const char *ptr = input->string;
615 const char *lastPtr = input->string;
616 while (*ptr) {
617 if (token[(unsigned char)*ptr]) {
618 size_t count = ptr-lastPtr+explode;
619 if (count > 0) {
620 dstr_clear(&str);
621 dstr_push_memrange(&str, lastPtr, ptr+explode);
622 res = list_new(res, dstr_cstr(&str), 0);
624 lastPtr = ptr+1;
626 ++ptr;
628 if (ptr > lastPtr) res = list_new(res, lastPtr, 0);
630 dstr_done(&str);
631 return res;
636 * builtin_dependslist()
638 * The DependsList builtin rule returns list of dependencies for
639 * a given target.
641 static LIST *builtin_dependslist (PARSE *parse, LOL *args, int *jmp) {
642 LIST *res = L0;
643 LIST *parents;
644 for (parents = lol_get(args, 0); parents; parents = parents->next) {
645 TARGET *t = bindtarget(parents->string);
646 TARGETS *child;
647 for (child = t->depends; child; child = child->next) res = list_new(res, child->target->name, 1);
649 return res;
654 * NormalizePath path [: pwd]
656 * it will add 'pwd' if path is not absolute and will try to resolve some '.' and '..' (only leading '..' though).
657 * it can be used in SubDir replacement to automate dir building
658 * if there is no $(2), use current directory as 'pwd'
660 static LIST *builtin_normpath (PARSE *parse, LOL *args, int *jmp) {
661 LIST *el = lol_get(args, 0), *pl = lol_get(args, 1);
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, (pl != NULL ? pl->string : NULL))) { 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[64];
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 * ListReverse list
690 static LIST *builtin_listreverse (PARSE *parse, LOL *args, int *jmp) {
691 return list_reverse(lol_get(args, 0));
696 * HaveRule and HaveActions
698 typedef struct {
699 LIST *el;
700 int wantAction;
701 int casesens;
702 } HRNGCI;
705 typedef struct {
706 const char *str;
707 int wantAction;
708 int casesens;
709 } HRNormalData;
712 static int hr_normal (const void *hdata, void *udata) {
713 const RULE *r = (const RULE *)hdata;
714 const HRNormalData *d = (const HRNormalData *)udata;
715 if (strcasecmp(r->name, d->str) == 0) {
716 if (d->wantAction && r->actions) return 1; // got it
717 if (!d->wantAction && r->procedure) return 1; // got it
719 return 0;
723 static int hr_glob (const void *hdata, void *udata) {
724 const RULE *r = (const RULE *)hdata;
725 const HRNormalData *d = (const HRNormalData *)udata;
726 if (matchglobex(d->str, r->name, d->casesens) == 0) {
727 //fprintf(stderr, ":[%s]\n", r->name);
728 if (d->wantAction && r->actions) return 1; // got it
729 if (!d->wantAction && r->procedure) return 1; // got it
731 return 0;
735 typedef struct {
736 regexp_t *re;
737 int reflags;
738 int wantAction;
739 } HRREData;
742 static int hr_regexp (const void *hdata, void *udata) {
743 const RULE *r = (const RULE *)hdata;
744 HRREData *d = (HRREData *)udata;
745 if (regexp_execute(d->re, r->name, NULL, 0) > 0) {
746 //fprintf(stderr, ":[%s]\n", r->name);
747 if (d->wantAction && r->actions) return 1; // got it
748 if (!d->wantAction && r->procedure) return 1; // got it
750 return 0;
754 static LIST *builtin_haveruleactions (PARSE *parse, LOL *args, int *jmp) {
755 LIST *el = lol_get(args, 0), *l;
756 int wantAction = parse->num;
757 int casesens = 1;
758 int cmptype = 0; // <0:glob; >0:regexp
759 if (!el) return L0;
760 for (l = lol_get(args, 1); l != NULL; l = l->next) {
761 if (!strcmp("case-sensitive", l->string)) casesens = 1;
762 else if (!strcmp("case-insensitive", l->string)) casesens = 0;
763 else if (!strcmp("ignore-case", l->string)) casesens = 0;
764 else if (!strcmp("glob", l->string)) cmptype = -1;
765 else if (!strcmp("regexp", l->string)) cmptype = 1;
766 else if (!strcmp("plain", l->string)) cmptype = 0;
767 else {
768 printf("jam: invalid option for Have%s built-in: '%s'\n", (wantAction ? "Actions" : "Rule"), l->string);
769 exit(EXITBAD); /* yeech */
772 if (casesens == 1 && cmptype == 0) {
773 // standard mode
774 for (; el; el = el->next) {
775 RULE *r = findrule(el->string);
776 if (!r) return L0;
777 if (wantAction && !r->actions) return L0;
778 if (!wantAction && !r->procedure) return L0;
780 } else if (cmptype < 0) {
781 // glob
782 HRNormalData nfo;
783 nfo.wantAction = wantAction;
784 nfo.casesens = casesens;
785 for (; el; el = el->next) {
786 nfo.str = el->string;
787 if (!iteraterules(hr_glob, &nfo)) return L0;
789 } else if (cmptype > 0) {
790 // regexp
791 HRREData nfo;
792 nfo.wantAction = wantAction;
793 for (; el; el = el->next) {
794 int err;
795 nfo.re = regexp_compile(el->string, (casesens ? 0 : RE9_FLAG_CASEINSENS));
796 /*printf("FATAL: invalid regexp in Have%s: %s\n", (wantAction ? "Actions" : "Rule"), errmsg);*/
797 err = iteraterules(hr_regexp, &nfo);
798 regexp_free(nfo.re);
799 if (!err) return L0;
801 } else {
802 // normal, case-insensitive
803 HRNormalData nfo;
804 nfo.wantAction = wantAction;
805 for (; el; el = el->next) {
806 nfo.str = el->string;
807 if (!iteraterules(hr_normal, &nfo)) return L0;
810 return list_new(L0, "1", 0);
814 /* generate random name:
815 * [ RandName ] -- /tmp/XXX
816 * [ RandName "abc/" ] -- abc/XXX
817 * [ RandName "" ] -- XXX
819 static LIST *builtin_randname (PARSE *parse, LOL *args, int *jmp) {
820 static BJRandCtx rctx;
821 static int initialized = 0;
822 static const char alphabet[] = "0123456789abcdefghijklmnopqrstuvwxyz";
823 LIST *el;
824 char buffer[9], *s;
825 const char *path;
826 #ifdef OS_NT
827 static char tp[8192]
828 #endif
829 if (!initialized) {
830 initialized = 1;
831 bjprngRandomize(&rctx);
832 #ifdef OS_NT
833 GetTempPath(sizeof(tp), tp);
834 #endif
836 for (int f = 0; f < 8; ++f) buffer[f] = alphabet[bjprngRand(&rctx)%strlen(alphabet)];
837 buffer[8] = 0;
838 el = lol_get(args, 0);
839 path = (el != NULL && el->string != NULL ? el->string :
840 #ifdef OS_NT
842 #else
843 "/tmp/"
844 #endif
846 s = alloca(strlen(path)+strlen(buffer)+2);
847 sprintf(s, "%s%s", path, buffer);
848 return list_new(L0, s, 0);
852 /* write list to file:
853 * ListFileWrite filename : list [: terminator] [: append]
854 * default terminator is '\n'
855 * return success flag
857 static LIST *builtin_listwrite (PARSE *parse, LOL *args, int *jmp) {
858 LIST *el = lol_get(args, 0);
859 if (el != NULL && el->string != NULL && el->string[0]) {
860 LIST *l = lol_get(args, 3);
861 FILE *fo = fopen(el->string, (l != NULL && l->string[0] ? "a" : "w"));
862 if (fo != NULL) {
863 const char *term;
864 l = lol_get(args, 2);
865 term = (l != NULL ? l->string : "\n");
866 for (l = lol_get(args, 1); l != NULL; l = l->next) {
867 if (fprintf(fo, "%s%s", l->string, term) < 0) {
868 fclose(fo);
869 unlink(el->string);
870 return L0;
873 fclose(fo);
874 return list_new(L0, "1", 0);
877 return L0;
881 /* remove duplicates from list */
882 static LIST *builtin_listremdups (PARSE *parse, LOL *args, int *jmp) {
883 LIST *el = lol_get(args, 0);
884 if (el != NULL) {
885 LIST *l, *res = list_new(L0, el->string, 1);
886 for (l = el->next; l != NULL; l = l->next) {
887 int found = 0;
888 for (LIST *t = el; t != l && !found; t = t->next) if (strcmp(t->string, l->string) == 0) found = 1;
889 if (!found) res = list_new(res, l->string, 1);
891 return res;
893 return el; /* no need to change anything */
898 * compile_builtin() - define builtin rules
901 #define P0 ((PARSE *)0)
902 #define C0 ((char *)0)
905 /* ":" -- previous name in upper case; "." -- previous name in lower case */
906 static inline void bind_builtin (PARSE *pp, const char *name) {
907 bindrule(name)->procedure = pp;
911 static inline void bind_builtin2 (PARSE *pp, const char *name, const char *name1) {
912 bindrule(name)->procedure = pp;
913 bindrule(name1)->procedure = pp;
917 void load_builtins (void) {
918 bind_builtin(parse_make(builtin_depends, P0, P0, P0, C0, C0, 0),
919 "Depends");
920 bind_builtin(parse_make(builtin_depends, P0, P0, P0, C0, C0, 1),
921 "Includes");
922 bind_builtin(parse_make(builtin_dependslist, P0, P0, P0, C0, C0, 0),
923 "DependsList");
925 bind_builtin(parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_TOUCHED),
926 "Always");
927 bind_builtin(parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_LEAVES),
928 "Leaves");
929 bind_builtin(parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_NOCARE),
930 "NoCare");
931 bind_builtin2(parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_NOTFILE),
932 "NotFile", "NoTime");
933 bind_builtin(parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_NOUPDATE),
934 "NoUpdate");
935 bind_builtin(parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_TEMP),
936 "Temporary");
937 bind_builtin(parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_FORCECARE),
938 "ForceCare");
939 bind_builtin(parse_make(builtin_flags, P0, P0, P0, C0, C0, 666),
940 "ForceFile");
942 bind_builtin(parse_make(builtin_echo, P0, P0, P0, C0, C0, 0),
943 "Echo");
945 bind_builtin(parse_make(builtin_exit, P0, P0, P0, C0, C0, 0),
946 "Exit");
948 bind_builtin(parse_make(builtin_glob, P0, P0, P0, C0, C0, 0),
949 "Glob");
950 bind_builtin(parse_make(builtin_match, P0, P0, P0, C0, C0, 0),
951 "Match");
953 bind_builtin(parse_make(builtin_hdrmacro, P0, P0, P0, C0, C0, 0),
954 "HdrMacro");
956 bind_builtin(parse_make(builtin_pwd, P0, P0, P0, C0, C0, 0),
957 "Pwd");
959 bind_builtin(parse_make(builtin_sort, P0, P0, P0, C0, C0, 0),
960 "Sort");
962 bind_builtin(parse_make(builtin_command, P0, P0, P0, C0, C0, 0),
963 "Command");
965 bind_builtin(parse_make(builtin_expri1, P0, P0, P0, C0, C0, 0),
966 "ExprI1");
968 bind_builtin(parse_make(builtin_split, P0, P0, P0, C0, C0, 0),
969 "Split");
971 bind_builtin(parse_make(builtin_normpath, P0, P0, P0, C0, C0, 0),
972 "NormalizePath");
974 bind_builtin(parse_make(builtin_listlength, P0, P0, P0, C0, C0, 0),
975 "ListLength");
976 bind_builtin(parse_make(builtin_listwrite, P0, P0, P0, C0, C0, 0),
977 "ListWrite");
978 bind_builtin(parse_make(builtin_listremdups, P0, P0, P0, C0, C0, 0),
979 "ListRemoveDuplicates");
980 bind_builtin(parse_make(builtin_listreverse, P0, P0, P0, C0, C0, 0),
981 "ListReverse");
983 bind_builtin(parse_make(builtin_haveruleactions, P0, P0, P0, C0, C0, 0),
984 "HaveRule");
985 bind_builtin(parse_make(builtin_haveruleactions, P0, P0, P0, C0, C0, 1),
986 "HaveActions");
987 bind_builtin(parse_make(builtin_haveruleactions, P0, P0, P0, C0, C0, 0),
988 "HasRule");
989 bind_builtin(parse_make(builtin_haveruleactions, P0, P0, P0, C0, C0, 1),
990 "HasActions");
992 bind_builtin(parse_make(builtin_randname, P0, P0, P0, C0, C0, 0),
993 "RandName");