lexer rules relaxed a little; please, don't use tokens like abc: -- use "abc:" instead
[k8jam.git] / src / builtins.c
blob73148f1232866e94068ba37b1cf5ae4cdc35357f
1 /*
2 * Copyright 1993-2002 Christopher Seiwald and Perforce Software, Inc.
4 * This file is part of Jam - see jam.c for Copyright information.
5 */
7 /*
8 * builtins.c - builtin jam rules
10 * External routines:
12 * load_builtin() - define builtin rules
14 * Internal routines:
16 * builtin_depends() - DEPENDS/INCLUDES rule
17 * builtin_echo() - ECHO rule
18 * builtin_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
37 #include <unistd.h>
38 #include <limits.h>
40 #include "jam.h"
42 #include "lists.h"
43 #include "parse.h"
44 #include "builtins.h"
45 #include "rules.h"
46 #include "filesys.h"
47 #include "newstr.h"
48 #include "hsregexp.h"
49 #include "pathsys.h"
50 #include "hdrmacro.h"
52 #include "kstrings.h"
56 * compile_builtin() - define builtin rules
59 #define P0 (PARSE *)0
60 #define C0 (char *)0
63 int glob (const char *s, const char *c);
66 void load_builtins (void) {
67 bindrule("Always")->procedure =
68 bindrule("ALWAYS")->procedure =
69 parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_TOUCHED);
71 bindrule("Depends")->procedure =
72 bindrule("DEPENDS")->procedure =
73 parse_make(builtin_depends, P0, P0, P0, C0, C0, 0);
75 bindrule("echo")->procedure =
76 bindrule("Echo")->procedure =
77 bindrule("ECHO")->procedure =
78 parse_make(builtin_echo, P0, P0, P0, C0, C0, 0);
80 bindrule("echo-n")->procedure =
81 bindrule("Echo-n")->procedure =
82 bindrule("ECHO-N")->procedure =
83 parse_make(builtin_echon, P0, P0, P0, C0, C0, 0);
85 bindrule("o-flush")->procedure =
86 bindrule("O-flush")->procedure =
87 bindrule("O-Flush")->procedure =
88 bindrule("O-FLUSH")->procedure =
89 parse_make(builtin_oflush, P0, P0, P0, C0, C0, 0);
91 bindrule("exit")->procedure =
92 bindrule("Exit")->procedure =
93 bindrule("EXIT")->procedure =
94 parse_make(builtin_exit, P0, P0, P0, C0, C0, 0);
96 bindrule("Glob")->procedure =
97 bindrule("GLOB")->procedure =
98 parse_make(builtin_glob, P0, P0, P0, C0, C0, 0);
100 bindrule("Includes")->procedure =
101 bindrule("INCLUDES")->procedure =
102 parse_make(builtin_depends, P0, P0, P0, C0, C0, 1);
104 bindrule("Leaves")->procedure =
105 bindrule("LEAVES")->procedure =
106 parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_LEAVES);
108 bindrule("Match")->procedure =
109 bindrule("MATCH")->procedure =
110 parse_make(builtin_match, P0, P0, P0, C0, C0, 0);
112 bindrule("NoCare")->procedure =
113 bindrule("NOCARE")->procedure =
114 parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_NOCARE);
116 bindrule("NOTIME")->procedure =
117 bindrule("NotFile")->procedure =
118 bindrule("NOTFILE")->procedure =
119 parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_NOTFILE);
121 bindrule("NoUpdate")->procedure =
122 bindrule("NOUPDATE")->procedure =
123 parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_NOUPDATE);
125 bindrule("Temporary")->procedure =
126 bindrule("TEMPORARY")->procedure =
127 parse_make(builtin_flags, P0, P0, P0, C0, C0, T_FLAG_TEMP);
129 bindrule("HdrMacro")->procedure =
130 bindrule("HDRMACRO")->procedure =
131 parse_make(builtin_hdrmacro, P0, P0, P0, C0, C0, 0);
133 bindrule("PWD")->procedure =
134 bindrule("Pwd")->procedure =
135 parse_make(builtin_pwd, P0, P0, P0, C0, C0, 0);
137 bindrule("SORT")->procedure =
138 bindrule("Sort")->procedure =
139 parse_make(builtin_sort, P0, P0, P0, C0, C0, 0);
141 bindrule("COMMAND")->procedure =
142 bindrule("Command")->procedure =
143 parse_make(builtin_command, P0, P0, P0, C0, C0, 0);
145 bindrule("ForceCare")->procedure =
146 bindrule("FORCECARE")->procedure =
147 parse_make(builtin_flags_forcecare, P0, P0, P0, C0, C0, T_FLAG_FORCECARE);
149 bindrule("ExprI1")->procedure =
150 bindrule("EXPRI1")->procedure =
151 parse_make(builtin_expri1, P0, P0, P0, C0, C0, 0);
153 bindrule("Split")->procedure =
154 bindrule("SPLIT")->procedure =
155 parse_make(builtin_split, P0, P0, P0, C0, C0, 0);
157 bindrule("DependsList")->procedure =
158 bindrule("DEPENDSLIST")->procedure =
159 parse_make(builtin_dependslist, P0, P0, P0, C0, C0, 0);
164 * builtin_depends() - DEPENDS/INCLUDES rule
166 * The DEPENDS builtin rule appends each of the listed sources on the
167 * dependency list of each of the listed targets.
168 * It binds both the targets and sources as TARGETs.
170 LIST *builtin_depends (PARSE *parse, LOL *args, int *jmp) {
171 LIST *targets = lol_get(args, 0);
172 LIST *sources = lol_get(args, 1);
173 LIST *l;
175 for (l = targets; l; l = list_next(l)) {
176 TARGET *t = bindtarget(l->string);
177 /* If doing INCLUDES, switch to the TARGET's include */
178 /* TARGET, creating it if needed. The internal include */
179 /* TARGET shares the name of its parent. */
180 if (parse->num) {
181 if (!t->includes) t->includes = copytarget(t);
182 t = t->includes;
184 t->depends = targetlist(t->depends, sources);
186 return L0;
191 * builtin_echo() - ECHO rule
193 * The ECHO builtin rule echoes the targets to the user.
194 * No other actions are taken.
196 LIST *builtin_echo (PARSE *parse, LOL *args, int *jmp) {
197 list_print(lol_get(args, 0));
198 printf("\n");
199 return L0;
204 * builtin_echon() - ECHO-N rule
206 * The ECHO-N builtin rule echoes the targets to the user.
207 * No other actions are taken, no newline is written.
209 LIST *builtin_echon (PARSE *parse, LOL *args, int *jmp) {
210 list_print(lol_get(args, 0));
211 return L0;
216 * builtin_oflush() - O-FLUSH rule
218 * The O-FLUSH builtin rule flushes current output stream.
219 * Used with ECHO-N.
221 LIST *builtin_oflush (PARSE *parse, LOL *args, int *jmp) {
222 fflush(stdout);
223 return L0;
228 * builtin_exit() - EXIT rule
230 * The EXIT builtin rule echoes the targets to the user and exits
231 * the program with a failure status.
233 LIST *builtin_exit (PARSE *parse, LOL *args, int *jmp) {
234 list_print(lol_get(args, 0));
235 printf("\n");
236 exit(EXITBAD); /* yeech */
237 return L0;
242 * builtin_flags() - NOCARE, NOTFILE, TEMPORARY rule
244 * Builtin_flags() marks the target with the appropriate flag, for use by make0().
245 * It binds each target as a TARGET.
247 LIST *builtin_flags (PARSE *parse, LOL *args, int *jmp) {
248 LIST *l = lol_get(args, 0);
249 for (; l; l = list_next(l)) bindtarget(l->string)->flags |= parse->num;
250 return L0;
255 * builtin_flags_forcecare() - ForceCare rule
257 LIST *builtin_flags_forcecare (PARSE *parse, LOL *args, int *jmp) {
258 LIST *l = lol_get(args, 0);
259 for( ; l; l = list_next(l)) {
260 TARGET *t = bindtarget(l->string);
261 t->flags |= T_FLAG_FORCECARE;
262 t->flags &= ~T_FLAG_NOCARE;
264 return L0;
269 * builtin_globbing() - GLOB rule
271 struct globbing {
272 LIST *patterns;
273 LIST *results;
277 static void builtin_glob_back (void *closure, const char *file, int status, time_t time) {
278 struct globbing *globbing = (struct globbing *)closure;
279 LIST *l;
280 PATHNAME f;
281 char buf[MAXJPATH];
282 /* null out directory for matching */
283 /* we wish we had file_dirscan() pass up a PATHNAME */
284 path_parse(file, &f);
285 f.f_dir.len = 0;
286 /* For globbing, we unconditionally ignore current and parent
287 * directory items. Since those items always exist, there's no
288 * reason why caller of GLOB would want to see them.
289 * We could also change file_dirscan, but then paths with embedded
290 * "." and ".." won't work anywhere. */
291 /* k8: will this break anything? it shouldn't... */
292 if (!strcmp(f.f_base.ptr, ".") || !strcmp(f.f_base.ptr, "..")) return;
293 path_build(&f, buf, 0);
294 for (l = globbing->patterns; l; l = l->next) {
295 if (!glob(l->string, buf)) {
296 globbing->results = list_new(globbing->results, file, 0);
297 break;
303 LIST *builtin_glob (PARSE *parse, LOL *args, int *jmp) {
304 struct globbing globbing;
305 LIST *l = lol_get(args, 0);
306 LIST *r = lol_get(args, 1);
307 globbing.results = L0;
308 globbing.patterns = r;
309 for (; l; l = list_next(l)) file_dirscan(l->string, builtin_glob_back, &globbing);
310 return globbing.results;
315 * builtin_match() - MATCH rule, regexp matching
317 LIST *builtin_match (PARSE *parse, LOL *args, int *jmp) {
318 LIST *l, *r;
319 LIST *res = 0;
320 /* for each pattern */
321 for (l = lol_get(args, 0); l; l = l->next) {
322 HSRegExp *re = hsRxCompile(l->string);
323 /* for each string to match against */
324 for (r = lol_get(args, 1); r; r = r->next) {
325 if (hsRxExec(re, r->string)) {
326 int i, top;
327 /* find highest parameter */
328 for (top = NSUBEXP; top-- > 1;) if (re->startp[top]) break;
329 /* and add all parameters up to highest onto list */
330 /* must have parameters to have results! */
331 for (i = 1; i <= top; i++) {
332 char buf[MAXSYM];
333 int l = re->endp[i]-re->startp[i];
334 memcpy(buf, re->startp[i], l);
335 buf[l] = 0;
336 res = list_new(res, buf, 0);
340 hsRxFree(re);
342 return res;
346 LIST *builtin_hdrmacro (PARSE *parse, LOL *args, int *jmp) {
347 LIST *l = lol_get(args, 0);
348 for (; l; l = list_next(l)) {
349 TARGET *t = bindtarget(l->string);
350 /* scan file for header filename macro definitions */
351 if (DEBUG_HEADER) printf("scanning '%s' for header file macro definitions\n", l->string);
352 macro_headers(t);
354 return L0;
358 /* backported from boost-jam */
360 * Return the current working directory.
362 * Usage: pwd = [ PWD ] ;
364 LIST *builtin_pwd (PARSE *parse, LOL *args, int *jmp) {
365 char pwd_buffer[PATH_MAX];
366 if (!getcwd(pwd_buffer, sizeof(pwd_buffer))) {
367 perror("can not get current directory");
368 return L0;
370 return list_new(L0, pwd_buffer, 0);
374 /* backported from boost-jam */
375 LIST *builtin_sort (PARSE *parse, LOL *args, int *jmp) {
376 LIST *arg = lol_get(args, 0);
377 arg = list_sort(arg);
378 return arg;
382 /* backported from boost-jam; greatly improved */
383 /* Command shcmd [[ : options ]] */
384 LIST *builtin_command (PARSE *parse, LOL *args, int *jmp) {
385 LIST *res = 0;
386 LIST *l;
387 int ret;
388 char buffer[1024], buf1[32], *spos, *epos;
389 FILE *p = NULL;
390 int exitStatus = -1;
391 int optExitStatus = 0;
392 int optNoOutput = 0;
393 int optTrimLeft = 1;
394 int optTrimRight = 1;
395 int optStatus1st = 0;
396 int optParseOut = 0;
397 int optSpaceBreak = 1;
398 int optTabBreak = 1;
399 int optCRBreak = 1;
400 int optLFBreak = 1;
401 tKString str;
403 /* for each string in 2nd list: check for arg */
404 for (l = lol_get(args, 1); l != NULL; l = l->next) {
405 if (!strcmp("exit-status", l->string)) optExitStatus = 1;
406 else if (!strcmp("exit-code", l->string)) optExitStatus = 1;
407 else if (!strcmp("status-first", l->string)) optStatus1st = 1;
408 else if (!strcmp("code-first", l->string)) optStatus1st = 1;
409 else if (!strcmp("no-output", l->string)) optNoOutput = 1;
410 else if (!strcmp("no-trim", l->string)) optTrimLeft = optTrimRight = 0;
411 else if (!strcmp("no-trim-left", l->string)) optTrimLeft = 0;
412 else if (!strcmp("no-trim-right", l->string)) optTrimRight = 0;
413 else if (!strcmp("parse-output", l->string)) optParseOut = 1;
414 else if (!strcmp("no-space-break", l->string)) optSpaceBreak = 0;
415 else if (!strcmp("no-tab-break", l->string)) optTabBreak = 0;
416 else if (!strcmp("no-nl-break", l->string)) optLFBreak = 0;
417 else if (!strcmp("no-lf-break", l->string)) optLFBreak = 0;
418 else if (!strcmp("no-cr-break", l->string)) optCRBreak = 0;
419 else {
420 printf("jam: invalid option for COMMAND built-in: '%s'\n", l->string);
421 exit(EXITBAD); /* yeech */
424 /* build shell command */
425 kStringNew(&str);
426 /* for each arg */
427 for (l = lol_get(args, 0); l; l = l->next) {
428 if (kStringLen(&str)) kStringPushBack(&str, ' ');
429 kStringAppend(&str, l->string);
431 /* no shell command? */
432 if (kStringLen(&str) < 1) { kStringFree(&str); return L0; }
434 fflush(NULL);
435 p = popen(kStringCStr(&str), "r");
436 if (!p) { kStringFree(&str); return L0; }
438 kStringClear(&str);
439 while ((ret = fread(buffer, sizeof(char), sizeof(buffer)-1, p)) > 0) {
440 if (!optNoOutput) {
441 buffer[ret] = 0;
442 kStringAppend(&str, buffer);
445 exitStatus = pclose(p);
446 if (optExitStatus && optStatus1st) {
447 sprintf(buf1, "%d", exitStatus);
448 res = list_new(res, buf1, 0);
450 /* trim output if necessary */
451 if (!optNoOutput) {
452 if (!optParseOut) {
453 /* don't parse */
454 if (optTrimRight) {
455 // trim trailing blanks
456 int sl = kStringLen(&str);
457 spos = kStringCStr(&str);
458 while (sl > 0 && (unsigned char)spos[sl] <= ' ') --sl;
459 kStringTruncate(&str, sl);
461 spos = kStringCStr(&str);
462 if (optTrimLeft) {
463 // trim leading blanks
464 while (*spos && *((unsigned char *)spos) <= ' ') ++spos;
466 res = list_new(res, spos, 0);
467 } else {
468 tKString tmp;
469 /* parse output */
470 ret = 0; /* was anything added? list must have at least one element */
471 spos = kStringCStr(&str);
472 kStringNew(&tmp);
473 while (*spos) {
474 /* skip delimiters */
475 while (*spos) {
476 unsigned char ch = (unsigned char)(*spos);
477 if (ch == ' ') { if (!optSpaceBreak) break; }
478 else if (ch == '\t') { if (!optTabBreak) break; }
479 else if (ch == '\r') { if (!optCRBreak) break; }
480 else if (ch == '\n') { if (!optLFBreak) break; }
481 else if (ch > ' ') break;
482 ++spos;
484 if (!*spos) break;
485 epos = spos+1;
486 while (*epos) {
487 int ch = *epos;
488 if (ch == ' ') { if (optSpaceBreak) break; }
489 else if (ch == '\t') { if (optTabBreak) break; }
490 else if (ch == '\r') { if (optCRBreak) break; }
491 else if (ch == '\n') { if (optLFBreak) break; }
492 else if ((unsigned char)ch <= ' ') break;
493 ++epos;
495 kStringClear(&tmp);
496 kStringAppendRange(&tmp, spos, epos);
497 res = list_new(res, kStringCStr(&tmp), 0);
498 ret = 1;
499 spos = epos;
501 kStringFree(&tmp);
502 if (!ret) { buf1[0] = '\0'; res = list_new(res, buf1, 0); }
504 } else {
505 res = list_new(res, kStringCStr(&str), 0);
507 kStringFree(&str);
508 /* command exit result next */
509 if (optExitStatus && !optStatus1st) {
510 sprintf(buf1, "%d", exitStatus);
511 res = list_new(res, buf1, 0);
513 return res;
517 LIST *builtin_expri1 (PARSE *parse, LOL *args, int *jmp) {
518 char buffer[100];
519 int op0;
520 int op1;
521 int res;
522 LIST *el = lol_get(args, 0);
524 if (!el || !el->next || !el->next->next) return L0;
525 op0 = atoi(el->string);
526 op1 = atoi(el->next->next->string);
527 res = 0;
528 switch (el->next->string[0]) {
529 case '+': res = op0+op1; break;
530 case '-': res = op0-op1; break;
531 case '*': res = op0*op1; break;
532 case '/': res = op0/op1; break;
533 case '%': res = op0%op1; break;
534 default:
535 printf("jam: rule ExprI1: unknown operator: '%s'\n", el->next->string);
536 exit(EXITBAD);
538 sprintf(buffer, "%d", res);
539 return list_new(L0, buffer, 0);
543 /* Based on code from ftjam by David Turner */
544 LIST *builtin_split (PARSE *parse, LOL *args, int *jmp) {
545 LIST *input = lol_get(args, 0);
546 LIST *tokens = lol_get(args, 1);
547 LIST *res = L0;
548 char token[256];
549 tKString str;
551 kStringNew(&str);
552 /* build token array */
553 memset(token, 0, sizeof(token));
554 for (; tokens; tokens = tokens->next) {
555 const char *s = tokens->string;
556 for (; *s; ++s) token[(unsigned char)*s] = 1;
558 /* now parse the input and split it */
559 for (; input; input = input->next) {
560 const char *ptr = input->string;
561 const char *lastPtr = input->string;
562 while (*ptr) {
563 if (token[(unsigned char)*ptr]) {
564 size_t count = ptr-lastPtr;
565 if (count > 0) {
566 kStringClear(&str);
567 kStringAppendRange(&str, lastPtr, ptr);
568 res = list_new(res, kStringCStr(&str), 0);
570 lastPtr = ptr+1;
572 ++ptr;
574 if (ptr > lastPtr) res = list_new(res, lastPtr, 0);
576 kStringFree(&str);
577 return res;
581 LIST *builtin_dependslist (PARSE *parse, LOL *args, int *jmp) {
582 LIST *res = L0;
583 LIST *parents;
585 for (parents = lol_get(args, 0); parents; parents = parents->next) {
586 TARGET *t = bindtarget(parents->string);
587 TARGETS *child;
589 for (child = t->depends; child; child = child->next) res = list_new(res, child->target->name, 1);
591 return res;