1 /* acd 1.10 - A compiler driver Author: Kees J. Bot
3 * Needs about 25kw heap + stack.
5 char version
[] = "1.9";
8 #define _POSIX_SOURCE 1
24 #define LIB "/usr/lib" /* Default library directory. */
27 #define arraysize(a) (sizeof(a) / sizeof((a)[0]))
28 #define arraylimit(a) ((a) + arraysize(a))
30 char *program
; /* Call name. */
32 int verbose
= 0; /* -v0: Silent.
33 * -v1: Show abbreviated pass names.
34 * -v2: Show executed UNIX commands.
35 * -v3: Show executed ACD commands.
36 * -v4: Show descr file as it is read.
39 int action
= 2; /* 0: An error occured, don't do anything anymore.
40 * 1: (-vn) Do not execute, play-act.
41 * 2: Execute UNIX commands.
44 void report(char *label
)
46 if (label
== nil
|| label
[0] == 0) {
47 fprintf(stderr
, "%s: %s\n", program
, strerror(errno
));
49 fprintf(stderr
, "%s: %s: %s\n",
50 program
, label
, strerror(errno
));
55 void quit(int exit_code
);
57 void fatal(char *label
)
63 size_t heap_chunks
= 0;
65 void *allocate(void *mem
, size_t size
)
66 /* Safe malloc/realloc. (I have heard that one can call realloc with a
67 * null first argument with the effect below, but that is of course to
68 * ridiculous to believe.)
74 mem
= realloc(mem
, size
);
79 if (mem
== nil
) fatal(nil
);
83 void deallocate(void *mem
)
91 char *copystr(const char *s
)
94 c
= allocate(nil
, (strlen(s
)+1) * sizeof(*c
));
99 /* Every object, list, letter, or variable, is made with cells. */
100 typedef struct cell
{
101 unsigned short refc
; /* Reference count. */
102 char type
; /* Type of object. */
103 unsigned char letter
; /* Simply a letter. */
104 char *name
; /* Name of a word. */
105 struct cell
*hash
; /* Hash chain. */
106 struct cell
*car
, *cdr
; /* To form lists. */
109 # define value car /* Value of a variable. */
110 # define base cdr /* Base-name in transformations. */
111 # define suffix cdr /* Suffix in a treat-as. */
112 # define flags letter /* Special flags. */
114 /* A substitution: */
120 CELL
, /* A list cell. */
121 STRING
, /* To make a list of characters and substs. */
122 SUBST
, /* Variable to substitute. */
123 /* Unique objects. */
124 LETTER
, /* A letter. */
125 WORD
, /* A string collapses to a word. */
126 EQUALS
, /* = operator, etc. */
138 N_TYPES
/* number of different types */
141 #define is_unique(type) ((type) >= LETTER)
143 /* Flags on a word. */
144 #define W_SET 0x01 /* Not undefined, e.g. assigned to. */
145 #define W_RDONLY 0x02 /* Read only. */
146 #define W_LOCAL 0x04 /* Local variable, immediate substitution. */
147 #define W_TEMP 0x08 /* Name of a temporary file, delete on quit. */
148 #define W_SUFF 0x10 /* Has a suffix set on it. */
151 /* Print a character, escaped if important to the shell *within* quotes. */
153 if (strchr("\\'\"<>();~$^&*|{}[]?", c
) != nil
) fputc('\\', stdout
);
157 void prinstr(char *s
)
158 /* Print a string, in quotes if the shell might not like it. */
164 if (strchr("~`$^&*()=\\|[]{};'\"<>?", *s2
++) != nil
) q
= 1;
166 if (q
) fputc('"', stdout
);
167 while (*s
!= 0) princhar(*s
++);
168 if (q
) fputc('"', stdout
);
171 void prin2(cell_t
*p
);
173 void prin1(cell_t
*p
)
174 /* Print a cell structure for debugging purposes. */
177 printf("(\b(\b()\b)\b)");
188 printf("\"\b\"\b\"");
190 printf("\"\b\"\b\"");
193 printf("$\b$\b${%s}", p
->subst
->name
);
214 printf(verbose
>= 3 ? "<\b<\b<" : "<");
217 printf(verbose
>= 3 ? ">\b>\b>" : ">");
224 void prin2(cell_t
*p
)
225 /* Print a list for debugging purposes. */
227 while (p
!= nil
&& p
->type
<= STRING
) {
230 if (p
->type
== CELL
&& p
->cdr
!= nil
) fputc(' ', stdout
);
234 if (p
!= nil
) prin1(p
); /* Dotted pair? */
237 void prin1n(cell_t
*p
) { prin1(p
); fputc('\n', stdout
); }
239 void prin2n(cell_t
*p
) { prin2(p
); fputc('\n', stdout
); }
241 /* A program is consists of a series of lists at a certain indentation level. */
242 typedef struct program
{
243 struct program
*next
;
244 cell_t
*file
; /* Associated description file. */
245 unsigned indent
; /* Line indentation level. */
246 unsigned lineno
; /* Line number where this is found. */
247 cell_t
*line
; /* One line of tokens. */
250 program_t
*pc
; /* Program Counter (what else?) */
251 program_t
*nextpc
; /* Next line to execute. */
253 cell_t
*oldcells
; /* Keep a list of old cells, don't deallocate. */
255 cell_t
*newcell(void)
256 /* Make a new empty cell. */
260 if (oldcells
!= nil
) {
265 p
= allocate(nil
, sizeof(*p
));
277 #define N_CHARS (1 + (unsigned char) -1)
278 #define HASHDENSE 0x400
280 cell_t
*oblist
[HASHDENSE
+ N_CHARS
+ N_TYPES
];
282 unsigned hashfun(cell_t
*p
)
283 /* Use a blender on a cell. */
292 while (*name
!= 0) h
= (h
* 0x1111) + *name
++;
293 return h
% HASHDENSE
;
295 return HASHDENSE
+ p
->letter
;
297 return HASHDENSE
+ N_CHARS
+ p
->type
;
301 cell_t
*search(cell_t
*p
, cell_t
***hook
)
302 /* Search for *p, return the one found. *hook may be used to insert or
308 sp
= *(*hook
= &oblist
[hashfun(p
)]);
310 if (p
->type
== WORD
) {
311 /* More than one name per hash slot. */
314 while (sp
!= nil
&& (cmp
= strcmp(p
->name
, sp
->name
)) > 0)
315 sp
= *(*hook
= &sp
->hash
);
317 if (cmp
!= 0) sp
= nil
;
323 /* Decrease the number of references to p, if zero delete and recurse. */
325 if (p
== nil
|| --p
->refc
> 0) return;
327 if (is_unique(p
->type
)) {
328 /* Remove p from the oblist. */
334 /* It's there, remove it. */
339 if (p
->type
== WORD
&& (p
->flags
& W_TEMP
)) {
340 /* A filename to remove. */
346 if (unlink(p
->name
) < 0 && errno
!= ENOENT
)
358 cell_t
*inc(cell_t
*p
)
359 /* Increase the number of references to p. */
363 if (p
== nil
) return nil
;
365 if (++p
->refc
> 1 || !is_unique(p
->type
)) return p
;
367 /* First appearance, put p on the oblist. */
371 /* Not there yet, add it. */
375 /* There is another object already there with the same info. */
383 cell_t
*go(cell_t
*p
, cell_t
*field
)
384 /* Often happening: You've got p, you want p->field. */
391 cell_t
*cons(type_t type
, cell_t
*p
)
392 /* P is to be added to a list (or a string). */
394 cell_t
*l
= newcell();
401 cell_t
*append(type_t type
, cell_t
*p
)
402 /* P is to be appended to a list (or a string). */
404 return p
== nil
|| p
->type
== type
? p
: cons(type
, p
);
407 cell_t
*findnword(char *name
, size_t n
)
408 /* Find the word with the given name of length n. */
410 cell_t
*w
= newcell();
412 w
->name
= allocate(nil
, (n
+1) * sizeof(*w
->name
));
413 memcpy(w
->name
, name
, n
);
418 cell_t
*findword(char *name
)
419 /* Find the word with the given null-terminated name. */
421 return findnword(name
, strlen(name
));
424 void quit(int exstat
)
425 /* Remove all temporary names, then exit. */
427 cell_t
**op
, *p
, *v
, *b
;
430 /* Remove cycles, like X = X. */
431 for (op
= oblist
; op
< oblist
+ HASHDENSE
; op
++) {
434 if (p
->value
!= nil
|| p
->base
!= nil
) {
449 /* Something may remain on an early quit: tempfiles. */
450 for (op
= oblist
; op
< oblist
+ HASHDENSE
; op
++) {
452 while (*op
!= nil
) { (*op
)->refc
= 1; dec(*op
); }
455 if (exstat
!= -1 && chunks
> 0) {
457 "%s: internal fault: %d chunks still on the heap\n",
463 void interrupt(int sig
)
465 signal(sig
, interrupt
);
466 if (verbose
>= 2) write(1, "# interrupt\n", 12);
471 /* Uppercase, lowercase, digit, underscore or anything non-American. */
473 return isalnum(c
) || c
== '_' || c
>= 0200;
476 char *descr
; /* Name of current description file. */
477 FILE *dfp
; /* Open description file. */
478 int dch
; /* Input character. */
479 unsigned lineno
; /* Line number in file. */
480 unsigned indent
; /* Indentation level. */
484 if (dch
== EOF
) return;
486 if (dch
== '\n') { lineno
++; indent
= 0; }
488 if ((dch
= getc(dfp
)) == EOF
&& ferror(dfp
)) fatal(descr
);
491 fprintf(stderr
, "%s: %s is a binary file.\n", program
, descr
);
496 #define E_BASH 0x01 /* Escaped by backslash. */
497 #define E_QUOTE 0x02 /* Escaped by double quote. */
498 #define E_SIMPLE 0x04 /* More simple characters? */
500 cell_t
*get_token(void)
501 /* Read one token from the description file. */
504 static int escape
= 0;
509 if (escape
& E_SIMPLE
) {
510 /* More simple characters? (Note: performance hack.) */
521 /* Gather whitespace. */
523 if (dch
== '\\' && whitetype
== 0) {
526 /* \ whitespace: remove. */
529 if (dch
== '#' && !(escape
& E_QUOTE
)) {
536 } while (isspace(dch
));
539 escape
|= E_BASH
; /* Escaped character. */
542 if (escape
!= 0) break;
544 if (dch
== '#' && (indent
== 0 || whitetype
!= 0)) {
546 do getdesc(); while (dch
!= '\n' && dch
!= EOF
);
551 if (!isspace(dch
) || dch
== '\n' || dch
== EOF
) break;
556 if (dch
== '\t') indent
= (indent
+ 7) & ~7;
561 if (dch
== EOF
) return nil
;
566 if (whitetype
!= 0) {
567 tok
->type
= whitetype
;
571 if (!(escape
& E_BASH
) && dch
== '"') {
573 if (!(escape
& E_QUOTE
)) {
574 /* Start of a string, signal this with a string cell. */
579 /* End of a string, back to normal mode. */
587 || strchr(escape
& E_QUOTE
? "$" : "$=()+-*<>;\n", dch
) == nil
591 "\"%s\", line %u: missing closing quote\n",
596 if (escape
& E_BASH
&& dch
== 'n') dch
= '\n';
599 /* A simple character. */
608 /* Single character token. */
610 case '=': tok
->type
= EQUALS
; break;
611 case '(': tok
->type
= OPEN
; break;
612 case ')': tok
->type
= CLOSE
; break;
613 case '+': tok
->type
= PLUS
; break;
614 case '-': tok
->type
= MINUS
; break;
615 case '*': tok
->type
= STAR
; break;
616 case '<': tok
->type
= INPUT
; break;
617 case '>': tok
->type
= OUTPUT
; break;
618 case ';': tok
->type
= SEMI
; break;
619 case '\n': tok
->type
= EOLN
; break;
627 if (dch
== EOF
|| isspace(dch
)) {
628 fprintf(stderr
, "\"%s\", line %u: Word expected after '$'\n",
635 name
= allocate(nil
, (n
= 16) * sizeof(*name
));
638 if (dch
== '{' || dch
== '(' /* )} */ ) {
640 int lpar
= dch
; /* ( */
641 int rpar
= lpar
== '{' ? '}' : ')';
645 if (dch
== rpar
) { getdesc(); break; }
646 if (isspace(dch
) || dch
== EOF
) {
648 "\"%s\", line %u: $%c unmatched, no '%c'\n",
649 descr
, lineno
, lpar
, rpar
);
655 name
= allocate(name
, (n
*= 2) * sizeof(char));
663 name
= allocate(name
, (n
*= 2) * sizeof(char));
665 } while (extalnum(dch
));
672 name
= allocate(name
, i
* sizeof(char));
674 tok
->subst
= newcell();
675 tok
->subst
->type
= WORD
;
676 tok
->subst
->name
= name
;
677 tok
->subst
= inc(tok
->subst
);
681 typedef enum how
{ SUPERFICIAL
, PARTIAL
, FULL
, EXPLODE
, IMPLODE
} how_t
;
683 cell_t
*explode(cell_t
*p
, how_t how
);
685 cell_t
*get_string(cell_t
**pp
)
686 /* Get a string: A series of letters and substs. Special tokens '=', '+', '-'
687 * and '*' are also recognized if on their own. A finished string is "exploded"
688 * to a word if it consists of letters only.
691 cell_t
*p
= *pp
, *s
= nil
, **ps
= &s
;
706 *ps
= cons(STRING
, p
);
717 /* A single special token must be folded up. */
718 if (!quoted
&& s
!= nil
&& s
->cdr
== nil
) {
719 switch (s
->car
->type
) {
725 return go(s
, s
->car
);
729 /* Go over the string changing '=', '+', '-', '*' to letters. */
730 for (p
= s
; p
!= nil
; p
= p
->cdr
) {
733 switch (p
->car
->type
) {
746 p
->car
->type
= LETTER
;
751 return explode(s
, SUPERFICIAL
);
754 cell_t
*get_list(cell_t
**pp
, type_t stop
)
755 /* Read a series of tokens upto a token of type "stop". */
757 cell_t
*p
= *pp
, *l
= nil
, **pl
= &l
;
759 while (p
!= nil
&& p
->type
!= stop
760 && !(stop
== EOLN
&& p
->type
== SEMI
)) {
773 *pl
= cons(CELL
, get_list(&p
, CLOSE
));
779 /* Unexpected closing parenthesis. (*/
780 fprintf(stderr
, "\"%s\", line %u: unmatched ')'\n",
799 *pl
= cons(CELL
, get_string(&p
));
807 if (p
== nil
&& stop
== CLOSE
) {
808 /* Couldn't get the closing parenthesis. */
809 fprintf(stderr
, "\"%s\", lines %u-%u: unmatched '('\n", /*)*/
810 descr
, pc
->lineno
, lineno
);
817 program_t
*get_line(cell_t
*file
)
821 static keep_indent
= 0;
822 static unsigned old_indent
= 0;
824 /* Skip leading whitespace to determine the indentation level. */
826 while ((p
= get_token()) != nil
&& p
->type
== WHITE
) dec(p
);
828 if (p
== nil
) return nil
; /* EOF */
830 if (p
->type
== EOLN
) indent
= old_indent
; /* Empty line. */
832 /* Make a program line. */
833 pc
= l
= allocate(nil
, sizeof(*l
));
837 l
->indent
= keep_indent
? old_indent
: indent
;
840 l
->line
= get_list(&p
, EOLN
);
842 /* If the line ended in a semicolon then keep the indentation level. */
843 keep_indent
= (p
!= nil
&& p
->type
== SEMI
);
844 old_indent
= l
->indent
;
852 printf("%*s", (int) l
->indent
, "");
859 program_t
*get_prog(void)
860 /* Read the description file into core. */
863 program_t
*prog
, **ppg
= &prog
;
865 descr
= copystr(descr
);
867 if (descr
[0] == '-' && descr
[1] == 0) {
868 /* -descr -: Read from standard input. */
870 descr
= copystr("stdin");
875 if (*d
== '.' && *++d
== '.') d
++;
877 /* -descr name: Read /usr/lib/<name>/descr. */
879 d
= allocate(nil
, sizeof(LIB
) +
880 (strlen(descr
) + 7) * sizeof(*d
));
881 sprintf(d
, "%s/%s/descr", LIB
, descr
);
885 if ((dfp
= fopen(descr
, "r")) == nil
) fatal(descr
);
887 file
= findword(descr
);
891 /* Preread the first character. */
897 while ((*ppg
= get_line(file
)) != nil
) ppg
= &(*ppg
)->next
;
899 if (dfp
!= stdin
) (void) fclose(dfp
);
905 void makenames(cell_t
***ppr
, cell_t
*s
, char **name
, size_t i
, size_t *n
)
906 /* Turn a string of letters and lists into words. A list denotes a choice
907 * between several paths, like a search on $PATH.
913 /* Simply add letters, skip empty lists. */
914 while (s
!= nil
&& (s
->car
== nil
|| s
->car
->type
== LETTER
)) {
916 if (i
== *n
) *name
= allocate(*name
,
917 (*n
*= 2) * sizeof(**name
));
918 (*name
)[i
++]= s
->car
->letter
;
923 /* If the end is reached then make a word out of the result. */
925 **ppr
= cons(CELL
, findnword(*name
, i
));
930 /* Elements of a list must be tried one by one. */
935 if (p
->type
== WORD
) {
938 assert(p
->type
== CELL
);
939 q
= p
->car
; p
= p
->cdr
;
941 assert(q
->type
== WORD
);
943 len
= strlen(q
->name
);
944 if (i
+ len
> *n
) *name
= allocate(*name
,
945 (*n
+= i
+ len
) * sizeof(**name
));
946 memcpy(*name
+ i
, q
->name
, len
);
948 makenames(ppr
, s
, name
, i
+len
, n
);
952 int constant(cell_t
*p
)
953 /* See if a string has been partially evaluated to a constant so that it
954 * can be imploded to a word.
961 if (!constant(p
->car
)) return 0;
973 cell_t
*evaluate(cell_t
*p
, how_t how
);
975 cell_t
*explode(cell_t
*s
, how_t how
)
976 /* Explode a string with several choices to just one list of choices. */
978 cell_t
*t
, *r
= nil
, **pr
= &r
;
983 if (how
>= PARTIAL
) {
984 /* Evaluate the string, expanding substitutions. */
986 assert(s
->type
== STRING
);
990 t
= evaluate(t
, how
== IMPLODE
? EXPLODE
: how
);
992 /* A list of one element becomes that element. */
993 if (t
!= nil
&& t
->type
== CELL
&& t
->cdr
== nil
)
996 /* Append the result, trying to flatten it. */
999 /* Find the end of what has just been added. */
1000 while ((*pr
) != nil
) {
1001 *pr
= append(STRING
, *pr
);
1008 /* Is the result a simple string of constants? */
1009 if (how
<= PARTIAL
&& !constant(s
)) return s
;
1011 /* Explode the string to all possible choices, by now the string is
1012 * a series of characters, words and lists of words.
1015 name
= allocate(nil
, (n
= 16) * sizeof(char));
1018 makenames(&pr
, s
, &name
, i
, &n
);
1024 /* "How" may specify that a choice must be made. */
1025 if (how
== IMPLODE
) {
1026 if (s
->cdr
!= nil
) {
1027 /* More than one choice, find the file. */
1029 assert(s
->car
->type
== WORD
);
1030 if (stat(s
->car
->name
, &st
) >= 0)
1031 return go(r
, s
->car
); /* Found. */
1032 } while ((s
= s
->cdr
) != nil
);
1034 /* The first name is the default if nothing is found. */
1035 return go(r
, r
->car
);
1038 /* If the result is a list of one word then return that word, otherwise
1039 * turn it into a string again unless this explode has been called
1040 * by another explode. (Exploding a string inside a string, the joys
1043 if (s
->cdr
== nil
) return go(s
, s
->car
);
1045 return how
>= EXPLODE
? s
: cons(STRING
, s
);
1048 void modify(cell_t
**pp
, cell_t
*p
, type_t mode
)
1049 /* Add or remove the element p from the list *pp. */
1051 while (*pp
!= nil
) {
1052 *pp
= append(CELL
, *pp
);
1054 if ((*pp
)->car
== p
) {
1055 /* Found it, if adding then exit, else remove. */
1056 if (mode
== PLUS
) break;
1057 *pp
= go(*pp
, (*pp
)->cdr
);
1062 if (*pp
== nil
&& mode
== PLUS
) {
1063 /* Not found, add it. */
1069 int tainted(cell_t
*p
)
1070 /* A variable is tainted (must be substituted) if either it is marked as a
1071 * local variable, or some subst in its value is.
1074 if (p
== nil
) return 0;
1079 return tainted(p
->car
) || tainted(p
->cdr
);
1081 return p
->subst
->flags
& W_LOCAL
|| tainted(p
->subst
->value
);
1087 cell_t
*evaluate(cell_t
*p
, how_t how
)
1088 /* Evaluate an expression, usually the right hand side of an assignment. */
1090 cell_t
*q
, *t
, *r
= nil
, **pr
= &r
;
1093 if (p
== nil
) return nil
;
1097 break; /* see below */
1099 return explode(p
, how
);
1101 if (how
>= FULL
|| tainted(p
))
1102 p
= evaluate(go(p
, p
->subst
->value
), how
);
1106 "\"%s\", line %u: Can't do nested assignments\n",
1122 /* It's a list, see if there is a '*' there forcing a full expansion,
1123 * or a '+' or '-' forcing an implosive expansion. (Yeah, right.)
1124 * Otherwise evaluate each element.
1128 if ((t
= p
->car
) != nil
) {
1129 if (t
->type
== STAR
) {
1130 if (how
< FULL
) how
= FULL
;
1132 *pr
= evaluate(go(p
, p
->cdr
), how
);
1135 if (how
>=FULL
&& (t
->type
== PLUS
|| t
->type
== MINUS
))
1139 t
= evaluate(inc(t
), how
);
1140 assert(p
->type
== CELL
);
1144 /* Flatten the list. */
1147 /* Keep the nested list structure. */
1151 /* Find the end of what has just been added. */
1152 while ((*pr
) != nil
) {
1153 *pr
= append(CELL
, *pr
);
1159 /* No PLUS or MINUS: done. */
1164 /* A PLUS or MINUS, reevaluate the original list implosively. */
1165 if (how
< IMPLODE
) {
1168 return evaluate(q
, IMPLODE
);
1172 /* Execute the PLUSes and MINUSes. */
1177 if (t
!= nil
&& (t
->type
== PLUS
|| t
->type
== MINUS
)) {
1178 /* Change the add/subtract mode. */
1184 t
= evaluate(t
, IMPLODE
);
1186 /* Add or remove all elements of t to/from r. */
1188 if (t
->type
== CELL
) {
1189 modify(&r
, inc(t
->car
), mode
);
1191 modify(&r
, t
, mode
);
1200 /* An ACD program can be in three phases: Initialization (the first run
1201 * of the program), argument scanning, and compilation.
1203 typedef enum phase
{ INIT
, SCAN
, COMPILE
} phase_t
;
1207 typedef struct rule
{ /* Transformation rule. */
1209 char type
; /* arg, transform, combine */
1211 unsigned short npaths
; /* Number of paths running through. */
1212 # define match from /* Arg matching strings. */
1213 cell_t
*from
; /* Transformation source suffixe(s) */
1214 cell_t
*to
; /* Destination suffix. */
1215 cell_t
*wait
; /* Files waiting to be transformed. */
1216 program_t
*prog
; /* Program to execute. */
1217 struct rule
*path
; /* Transformation path. */
1220 typedef enum ruletype
{ ARG
, PREFER
, TRANSFORM
, COMBINE
} ruletype_t
;
1222 #define R_PREFER 0x01 /* A preferred transformation. */
1226 void newrule(ruletype_t type
, cell_t
*from
, cell_t
*to
)
1227 /* Make a new rule cell. */
1229 rule_t
*r
= nil
, **pr
= &rules
;
1231 /* See if there is a rule with the same suffixes, probably a matching
1232 * transform and prefer, or a re-execution of the same arg command.
1234 while ((r
= *pr
) != nil
) {
1235 if (r
->from
== from
&& r
->to
== to
) break;
1240 /* Add a new rule. */
1241 *pr
= r
= allocate(nil
, sizeof(*r
));
1246 r
->from
= r
->to
= r
->wait
= nil
;
1249 if (type
== TRANSFORM
) r
->type
= TRANSFORM
;
1250 if (type
== PREFER
) r
->flags
|= R_PREFER
;
1251 if (type
!= PREFER
) r
->prog
= pc
;
1252 dec(r
->from
); r
->from
= from
;
1253 dec(r
->to
); r
->to
= to
;
1257 /* True if verbose and if so indent what is to come. */
1259 if (verbose
< 3) return 0;
1260 printf("%*s", (int) pc
->indent
, "");
1264 void unix_exec(cell_t
*c
)
1265 /* Execute the list of words p as a UNIX command. */
1274 if (action
== 0) return; /* Error mode. */
1276 if (talk() || verbose
>= 2) prin2n(c
);
1280 argv
= allocate(nil
, (n
= 16) * sizeof(*argv
));
1283 /* Gather argv[] and scan for I/O redirection. */
1284 for (v
= c
; v
!= nil
; v
= v
->cdr
) {
1287 if (a
->type
== INPUT
) pf
= &fd
[0];
1288 if (a
->type
== OUTPUT
) pf
= &fd
[1];
1293 if (i
==n
) argv
= allocate(argv
, (n
*= 2) * sizeof(*argv
));
1296 /* I/O redirection. */
1297 if ((v
= v
->cdr
) == nil
|| (a
= v
->car
)->type
!= WORD
) {
1299 "\"%s\", line %u: I/O redirection without a file\n",
1302 if (v
== nil
) break;
1304 if (*pf
>= 0) close(*pf
);
1307 && (*pf
= open(a
->name
, pf
== &fd
[0] ? O_RDONLY
1308 : O_WRONLY
| O_CREAT
| O_TRUNC
, 0666)) < 0
1316 if (i
>= 0 && action
> 0 && verbose
== 1) {
1317 char *name
= strrchr(argv
[0], '/');
1319 if (name
== nil
) name
= argv
[0]; else name
++;
1321 printf("%s\n", name
);
1323 if (i
>= 0 && action
>= 2) {
1324 /* Really execute the command. */
1326 switch (pid
= fork()) {
1330 if (fd
[0] >= 0) { dup2(fd
[0], 0); close(fd
[0]); }
1331 if (fd
[1] >= 0) { dup2(fd
[1], 1); close(fd
[1]); }
1332 execvp(argv
[0], argv
);
1337 if (fd
[0] >= 0) close(fd
[0]);
1338 if (fd
[1] >= 0) close(fd
[1]);
1340 if (i
>= 0 && action
>= 2) {
1341 /* Wait for the command to terminate. */
1342 while ((r
= wait(&status
)) != pid
&& (r
>= 0 || errno
== EINTR
));
1345 int sig
= WTERMSIG(status
);
1347 if (!WIFEXITED(status
)
1348 && sig
!= SIGINT
&& sig
!= SIGPIPE
) {
1349 fprintf(stderr
, "%s: %s: Signal %d%s\n",
1350 program
, argv
[0], sig
,
1351 status
& 0x80 ? " - core dumped" : "");
1359 /* Special read-only variables ($*) and lists. */
1360 cell_t
*V_star
, **pV_star
;
1361 cell_t
*L_files
, **pL_files
= &L_files
;
1362 cell_t
*V_in
, *V_out
, *V_stop
, *L_args
, *L_predef
;
1364 typedef enum exec
{ DOIT
, DONT
} exec_t
;
1366 void execute(exec_t how
, unsigned indent
);
1368 int equal(cell_t
*p
, cell_t
*q
)
1369 /* Two lists are equal if they contain each others elements. */
1371 cell_t
*t
, *m1
, *m2
;
1374 t
->cdr
= inc(newcell());
1375 t
->cdr
->cdr
= inc(newcell());
1376 t
->cdr
->car
= newcell();
1377 t
->cdr
->car
->type
= MINUS
;
1378 t
->cdr
->car
= inc(t
->cdr
->car
);
1380 /* Compute p - q. */
1382 t
->cdr
->cdr
->car
= inc(q
);
1383 m1
= evaluate(inc(t
), IMPLODE
);
1386 /* Compute q - p. */
1388 t
->cdr
->cdr
->car
= p
;
1389 m2
= evaluate(t
, IMPLODE
);
1392 /* Both results must be empty. */
1393 return m1
== nil
&& m2
== nil
;
1396 int wordlist(cell_t
**pw
, int atom
)
1397 /* Check if p is a list of words, typically an imploded list. Return
1398 * the number of words seen, -1 if they are not words (INPUT/OUTPUT?).
1399 * If atom is true than a list of one word is turned into a word.
1403 cell_t
*p
, **pp
= pw
;
1405 while (*pp
!= nil
) {
1406 *pp
= append(CELL
, *pp
);
1408 n
= n
>= 0 && p
!= nil
&& p
->type
== WORD
? n
+1 : -1;
1411 if (atom
&& n
== 1) *pw
= go(*pw
, (*pw
)->car
);
1415 char *template; /* Current name of a temporary file. */
1416 static char *tp
; /* Current place withing the tempfile. */
1418 char *maketemp(void)
1419 /* Return a name that can be used as a temporary filename. */
1424 size_t len
= strlen(template);
1426 template= allocate(template, (len
+20) * sizeof(*template));
1427 sprintf(template+len
, "/acd%d", getpid());
1428 tp
= template + strlen(template);
1434 tp
[i
+1]= 0; return template;
1435 case 'z': tp
[i
++]= 'a'; break;
1436 default: tp
[i
]++; return template;
1441 void inittemp(char *tmpdir
)
1442 /* Initialize the temporary filename generator. */
1444 template= allocate(nil
, (strlen(tmpdir
)+20) * sizeof(*template));
1445 sprintf(template, "%s/acd%d", tmpdir
, getpid());
1446 tp
= template + strlen(template);
1448 /* Create a directory within tempdir that we can safely play in. */
1449 while (action
!= 1 && mkdir(template, 0700) < 0) {
1450 if (errno
== EEXIST
) {
1457 if (verbose
>= 2) printf("mkdir %s\n", template);
1458 while (*tp
!= 0) tp
++;
1464 /* Remove our temporary temporaries directory. */
1466 while (*--tp
!= '/') {}
1468 if (rmdir(template) < 0 && errno
!= ENOENT
) report(template);
1469 if (verbose
>= 2) printf("rmdir %s\n", template);
1470 deallocate(template);
1473 cell_t
*splitenv(char *env
)
1474 /* Split a string from the environment into several words at whitespace
1475 * and colons. Two colons (::) become a dot.
1478 cell_t
*r
= nil
, **pr
= &r
;
1482 while (*env
!= 0 && isspace(*env
)) env
++;
1484 if (*env
== 0) break;
1487 while (*p
!= 0 && !isspace(*p
) && *p
!= ':') p
++;
1490 p
== env
? findword(".") : findnword(env
, p
-env
));
1493 } while (*env
++ != 0);
1497 void key_usage(char *how
)
1499 fprintf(stderr
, "\"%s\", line %u: Usage: %s %s\n",
1500 descr
, pc
->lineno
, pc
->line
->car
->name
, how
);
1504 void inappropriate(void)
1506 fprintf(stderr
, "\"%s\", line %u: wrong execution phase for '%s'\n",
1507 descr
, pc
->lineno
, pc
->line
->car
->name
);
1511 int readonly(cell_t
*v
)
1513 if (v
->flags
& W_RDONLY
) {
1514 fprintf(stderr
, "\"%s\", line %u: %s is read-only\n",
1515 descr
, pc
->lineno
, v
->name
);
1522 void complain(cell_t
*err
)
1527 fprintf(stderr
, "%s:", program
);
1529 while (err
!= nil
) {
1530 if (err
->type
== CELL
) {
1531 w
= err
->car
; err
= err
->cdr
;
1535 fprintf(stderr
, " %s", w
->name
);
1540 int keyword(char *name
)
1541 /* True if the current line is headed by the given keyword. */
1545 return (t
= pc
->line
) != nil
&& t
->type
== CELL
1546 && (t
= t
->car
) != nil
&& t
->type
== WORD
1547 && strcmp(t
->name
, name
) == 0;
1550 cell_t
*getvar(cell_t
*v
)
1551 /* Return a word or the word referenced by a subst. */
1553 if (v
== nil
) return nil
;
1554 if (v
->type
== WORD
) return v
;
1555 if (v
->type
== SUBST
) return v
->subst
;
1559 void argscan(void), compile(void);
1560 void transform(rule_t
*);
1563 /* Execute one line of the program. */
1565 cell_t
*v
, *p
, *q
, *r
, *t
;
1567 static int last_if
= 1;
1569 /* Description file this line came from. */
1570 descr
= pc
->file
->name
;
1572 for (p
= pc
->line
; p
!= nil
; p
= p
->cdr
) n
++;
1574 if (n
== 0) return; /* Null statement. */
1578 r
= q
== nil
? nil
: q
->cdr
;
1580 /* Try one by one all the different commands. */
1582 if (n
>= 2 && q
->car
!= nil
&& q
->car
->type
== EQUALS
) {
1583 /* An assignment. */
1586 if ((v
= getvar(p
->car
)) == nil
) {
1588 "\"%s\", line %u: Usage: <var> = expr ...\n",
1594 if (readonly(v
)) return;
1597 v
->flags
|= W_LOCAL
|W_RDONLY
;
1598 t
= evaluate(inc(r
), PARTIAL
);
1601 v
->flags
= flags
| W_SET
;
1603 printf("%s =\b=\b= ", v
->name
);
1607 if (keyword("unset")) {
1608 /* Set a variable to "undefined". */
1610 if (n
!= 2 || (v
= getvar(q
->car
)) == nil
) {
1614 if (readonly(v
)) return;
1616 if (talk()) prin2n(p
);
1622 if (keyword("import")) {
1623 /* Import a variable from the UNIX environment. */
1626 if (n
!= 2 || (v
= getvar(q
->car
)) == nil
) {
1630 if (readonly(v
)) return;
1632 if ((env
= getenv(v
->name
)) == nil
) return;
1634 if (talk()) printf("import %s=%s\n", v
->name
, env
);
1641 if (keyword("mktemp")) {
1642 /* Assign a variable the name of a temporary file. */
1645 r
= evaluate(inc(r
), IMPLODE
);
1646 if (n
== 3 && wordlist(&r
, 1) != 1) n
= 0;
1648 if ((n
!= 2 && n
!= 3) || (v
= getvar(q
->car
)) == nil
) {
1650 key_usage("<var> [<suffix>]");
1653 if (readonly(v
)) { dec(r
); return; }
1656 suff
= r
== nil
? "" : r
->name
;
1660 t
->name
= allocate(nil
,
1661 (strlen(tmp
) + strlen(suff
) + 1) * sizeof(*t
->name
));
1662 strcpy(t
->name
, tmp
);
1663 strcat(t
->name
, suff
);
1670 if (talk()) printf("mktemp %s=%s\n", v
->name
, t
->name
);
1672 if (keyword("temporary")) {
1673 /* Mark a word as a temporary file. */
1676 tmp
= evaluate(inc(q
), IMPLODE
);
1678 if (wordlist(&tmp
, 1) < 0) {
1680 key_usage("<word>");
1683 if (talk()) printf("temporary %s\n", tmp
->name
);
1685 tmp
->flags
|= W_TEMP
;
1688 if (keyword("stop")) {
1689 /* Set the suffix to stop the transformation on. */
1692 if (phase
> SCAN
) { inappropriate(); return; }
1694 suff
= evaluate(inc(q
), IMPLODE
);
1696 if (wordlist(&suff
, 1) != 1) {
1698 key_usage("<suffix>");
1703 if (talk()) printf("stop %s\n", suff
->name
);
1705 if (keyword("numeric")) {
1706 /* Check if a string denotes a number, like $n in -O$n. */
1710 num
= evaluate(inc(q
), IMPLODE
);
1712 if (wordlist(&num
, 1) != 1) {
1717 if (talk()) printf("numeric %s\n", num
->name
);
1719 (void) strtoul(num
->name
, &pn
, 10);
1721 complain(phase
== SCAN
? V_star
->value
: nil
);
1722 if (phase
== SCAN
) fputc(':', stderr
);
1723 fprintf(stderr
, " '%s' is not a number\n", num
->name
);
1727 if (keyword("error")) {
1728 /* Signal an error. */
1731 err
= evaluate(inc(q
), IMPLODE
);
1733 if (wordlist(&err
, 0) < 1) {
1735 key_usage("expr ...");
1739 if (talk()) { printf("error "); prin2n(err
); }
1742 fputc('\n', stderr
);
1745 if (keyword("if")) {
1746 /* if (list) = (list) using set comparison. */
1749 if (n
!= 4 || r
->car
== nil
|| r
->car
->type
!= EQUALS
) {
1750 key_usage("<expr> = <expr>");
1751 execute(DONT
, pc
->indent
+1);
1759 prin1(t
= evaluate(inc(q
), IMPLODE
));
1762 prin1n(t
= evaluate(inc(r
), IMPLODE
));
1766 execute(eq
? DOIT
: DONT
, pc
->indent
+1);
1769 if (keyword("ifdef") || keyword("ifndef")) {
1770 /* Is a variable defined or undefined? */
1773 if (n
!= 2 || (v
= getvar(q
->car
)) == nil
) {
1775 execute(DONT
, pc
->indent
+1);
1779 if (talk()) prin2n(p
);
1781 doit
= ((v
->flags
& W_SET
) != 0) ^ (p
->car
->name
[2] == 'n');
1782 execute(doit
? DOIT
: DONT
, pc
->indent
+1);
1785 if (keyword("iftemp") || keyword("ifhash")) {
1786 /* Is a file a temporary file? */
1787 /* Does a file need preprocessing? */
1791 file
= evaluate(inc(q
), IMPLODE
);
1793 if (wordlist(&file
, 1) != 1) {
1798 if (talk()) printf("%s %s\n", p
->car
->name
, file
->name
);
1800 if (p
->car
->name
[2] == 't') {
1802 if (file
->flags
& W_TEMP
) doit
= 1;
1808 if ((fd
= open(file
->name
, O_RDONLY
)) >= 0) {
1809 if (read(fd
, &hash
, 1) == 1 && hash
== '#')
1816 execute(doit
? DOIT
: DONT
, pc
->indent
+1);
1819 if (keyword("else")) {
1820 /* Else clause for an if, ifdef, or ifndef. */
1823 execute(DONT
, pc
->indent
+1);
1826 if (talk()) prin2n(p
);
1828 execute(!last_if
? DOIT
: DONT
, pc
->indent
+1);
1830 if (keyword("treat")) {
1831 /* Treat a file as having a certain suffix. */
1833 if (phase
> SCAN
) { inappropriate(); return; }
1836 q
= evaluate(inc(q
->car
), IMPLODE
);
1837 r
= evaluate(inc(r
->car
), IMPLODE
);
1839 if (n
!= 3 || wordlist(&q
, 1) != 1 || wordlist(&r
, 1) != 1) {
1840 if (n
== 3) { dec(q
); dec(r
); }
1841 key_usage("<file> <suffix>");
1844 if (talk()) printf("treat %s %s\n", q
->name
, r
->name
);
1851 if (keyword("apply")) {
1852 /* Apply a transformation rule to the current input file. */
1853 rule_t
*rule
, *sav_path
;
1854 cell_t
*sav_wait
, *sav_in
, *sav_out
;
1855 program_t
*sav_next
;
1857 if (phase
!= COMPILE
) { inappropriate(); return; }
1859 if (V_star
->value
->cdr
!= nil
) {
1860 fprintf(stderr
, "\"%s\", line %u: $* is not one file\n",
1866 q
= evaluate(inc(q
->car
), IMPLODE
);
1867 r
= evaluate(inc(r
->car
), IMPLODE
);
1869 if (n
!= 3 || wordlist(&q
, 1) != 1 || wordlist(&r
, 1) != 1) {
1870 if (n
== 3) { dec(q
); dec(r
); }
1871 key_usage("<file> <suffix>");
1874 if (talk()) printf("apply %s %s\n", q
->name
, r
->name
);
1877 for (rule
= rules
; rule
!= nil
; rule
= rule
->next
) {
1878 if (rule
->type
== TRANSFORM
1879 && rule
->from
== q
&& rule
->to
== r
) break;
1883 "\"%s\", line %u: no %s %s transformation\n",
1884 descr
, pc
->lineno
, q
->name
, r
->name
);
1889 if (rule
== nil
) return;
1891 /* Save the world. */
1892 sav_path
= rule
->path
;
1893 sav_wait
= rule
->wait
;
1894 sav_in
= V_in
->value
;
1895 sav_out
= V_out
->value
;
1898 /* Isolate the rule and give it new input. */
1900 rule
->wait
= V_star
->value
;
1907 /* Retrieve the new $* and repair. */
1908 V_star
->value
= rule
->wait
;
1909 rule
->path
= sav_path
;
1910 rule
->wait
= sav_wait
;
1911 V_in
->value
= sav_in
;
1912 V_out
->value
= sav_out
;
1913 V_out
->flags
= W_SET
|W_LOCAL
;
1916 if (keyword("include")) {
1917 /* Include another description file into this program. */
1919 program_t
*incl
, *prog
, **ppg
= &prog
;
1921 file
= evaluate(inc(q
), IMPLODE
);
1923 if (wordlist(&file
, 1) != 1) {
1925 key_usage("<file>");
1928 if (talk()) printf("include %s\n", file
->name
);
1934 /* Raise the program to the include's indent level. */
1935 while (*ppg
!= nil
) {
1936 (*ppg
)->indent
+= incl
->indent
;
1940 /* Kill the include and splice the included program in. */
1948 if (keyword("arg")) {
1949 /* An argument scanning rule. */
1951 if (phase
> SCAN
) { inappropriate(); return; }
1954 key_usage("<string> ...");
1955 execute(DONT
, pc
->indent
+1);
1958 if (talk()) prin2n(p
);
1960 newrule(ARG
, inc(q
), nil
);
1962 /* Always skip the body, it comes later. */
1963 execute(DONT
, pc
->indent
+1);
1965 if (keyword("transform")) {
1966 /* A file transformation rule. */
1968 if (phase
> SCAN
) { inappropriate(); return; }
1971 q
= evaluate(inc(q
->car
), IMPLODE
);
1972 r
= evaluate(inc(r
->car
), IMPLODE
);
1974 if (n
!= 3 || wordlist(&q
, 1) != 1 || wordlist(&r
, 1) != 1) {
1975 if (n
== 3) { dec(q
); dec(r
); }
1976 key_usage("<suffix1> <suffix2>");
1977 execute(DONT
, pc
->indent
+1);
1980 if (talk()) printf("transform %s %s\n", q
->name
, r
->name
);
1982 newrule(TRANSFORM
, q
, r
);
1984 /* Body comes later. */
1985 execute(DONT
, pc
->indent
+1);
1987 if (keyword("prefer")) {
1988 /* Prefer a transformation over others. */
1990 if (phase
> SCAN
) { inappropriate(); return; }
1993 q
= evaluate(inc(q
->car
), IMPLODE
);
1994 r
= evaluate(inc(r
->car
), IMPLODE
);
1996 if (n
!= 3 || wordlist(&q
, 1) != 1 || wordlist(&r
, 1) != 1) {
1997 if (n
== 3) { dec(q
); dec(r
); }
1998 key_usage("<suffix1> <suffix2>");
2001 if (talk()) printf("prefer %s %s\n", q
->name
, r
->name
);
2003 newrule(PREFER
, q
, r
);
2005 if (keyword("combine")) {
2006 /* A file combination (loader) rule. */
2008 if (phase
> SCAN
) { inappropriate(); return; }
2011 q
= evaluate(inc(q
->car
), IMPLODE
);
2012 r
= evaluate(inc(r
->car
), IMPLODE
);
2014 if (n
!= 3 || wordlist(&q
, 0) < 1 || wordlist(&r
, 1) != 1) {
2015 if (n
== 3) { dec(q
); dec(r
); }
2016 key_usage("<suffix-list> <suffix>");
2017 execute(DONT
, pc
->indent
+1);
2023 printf(" %s\n", r
->name
);
2026 newrule(COMBINE
, q
, r
);
2028 /* Body comes later. */
2029 execute(DONT
, pc
->indent
+1);
2031 if (keyword("scan") || keyword("compile")) {
2032 program_t
*next
= nextpc
;
2034 if (n
!= 1) { key_usage(""); return; }
2035 if (phase
!= INIT
) { inappropriate(); return; }
2037 if (talk()) prin2n(p
);
2040 if (p
->car
->name
[0] == 'c') compile();
2043 /* A UNIX command. */
2044 t
= evaluate(inc(pc
->line
), IMPLODE
);
2050 void execute(exec_t how
, unsigned indent
)
2051 /* Execute (or skip) all lines with at least the given indent. */
2053 int work
= 0; /* Need to execute at least one line. */
2055 unsigned nice_indent
= 0; /* 0 = Don't know what's nice yet. */
2057 if (pc
== nil
) return; /* End of program. */
2059 firstline
= pc
->lineno
;
2062 /* Skipping a body, but is there another guard? */
2064 if (pc
!= nil
&& pc
->indent
< indent
&& pc
->line
!= nil
) {
2065 /* There is one! Bail out, then it get's executed. */
2069 /* Skip lines with a lesser indentation, they are guards for
2070 * the same substatements. Don't go past empty lines.
2072 while (pc
!= nil
&& pc
->indent
< indent
&& pc
->line
!= nil
)
2076 /* Execute all lines with an indentation of at least "indent". */
2077 while (pc
!= nil
&& pc
->indent
>= indent
) {
2078 if (pc
->indent
!= nice_indent
&& how
== DOIT
) {
2079 if (nice_indent
!= 0) {
2081 "\"%s\", line %u: (warning) sudden indentation shift\n",
2084 nice_indent
= pc
->indent
;
2087 if (how
== DOIT
) exec_one();
2092 if (indent
> 0 && !work
) {
2093 fprintf(stderr
, "\"%s\", line %u: empty body, no statements\n",
2099 int argmatch(int shift
, cell_t
*match
, cell_t
*match1
, char *arg1
)
2100 /* Try to match an arg rule to the input file list L_args. Execute the arg
2101 * body (pc is set to it) on success.
2110 /* An argument has been accepted and may be shifted to $*. */
2111 cell_t
**oldpstar
= pV_star
;
2113 L_args
= *(pV_star
= &L_args
->cdr
);
2116 if (argmatch(0, match
->cdr
, nil
, nil
)) return 1;
2118 /* Undo the damage. */
2120 L_args
= *(pV_star
= oldpstar
);
2126 /* A full match, execute the arg body. */
2129 V_out
->flags
= W_SET
|W_LOCAL
;
2133 printf(" =\b=\b= ");
2134 prin2n(V_star
->value
);
2136 execute(DOIT
, pc
->indent
+1);
2138 /* Append $> to the file list. */
2139 if (V_out
->value
!= nil
) {
2140 *pL_files
= cons(CELL
, V_out
->value
);
2141 pL_files
= &(*pL_files
)->cdr
;
2146 V_out
->flags
= W_SET
|W_LOCAL
|W_RDONLY
;
2151 if (L_args
== nil
) return 0; /* Out of arguments to match. */
2153 /* Match is a list of words, substs and strings containing letters and
2154 * substs. Match1 is the current element of the first element of match.
2155 * Arg1 is the current character of the first element of L_args.
2157 if (match1
== nil
) {
2158 /* match1 is at the end of a string, then arg1 must also. */
2160 if (*arg1
!= 0) return 0;
2161 return argmatch(1, match
, nil
, nil
);
2163 /* If both are nil: Initialize. */
2165 arg1
= L_args
->car
->name
;
2167 /* A subst may not match a leading '-'. */
2168 if (arg1
[0] == '-') minus
= 1;
2171 if (match1
->type
== WORD
&& strcmp(match1
->name
, arg1
) == 0) {
2172 /* A simple match of an argument. */
2174 return argmatch(1, match
, nil
, nil
);
2177 if (match1
->type
== SUBST
&& !minus
) {
2178 /* A simple match of a subst. */
2180 /* The variable gets the first of the arguments as its value. */
2182 if (v
->flags
& W_RDONLY
) return 0; /* ouch */
2184 v
->flags
= W_SET
|W_LOCAL
|W_RDONLY
;
2186 v
->value
= inc(L_args
->car
);
2188 m
= argmatch(1, match
, nil
, nil
);
2190 /* Recover the value of the variable. */
2196 if (match1
->type
!= STRING
) return 0;
2198 /* Match the first item in the string. */
2199 if (match1
->car
== nil
) return 0;
2201 if (match1
->car
->type
== LETTER
2202 && match1
->car
->letter
== (unsigned char) *arg1
) {
2203 /* A letter matches, try the rest of the string. */
2205 return argmatch(0, match
, match1
->cdr
, arg1
+1);
2208 /* It can only be a subst in a string now. */
2210 if (match1
->car
->type
!= SUBST
|| minus
|| len
== 0) return 0;
2212 /* The variable can match from 1 character to all of the argument.
2213 * Matching as few characters as possible happens to be the Right Thing.
2215 v
= match1
->car
->subst
;
2216 if (v
->flags
& W_RDONLY
) return 0; /* ouch */
2218 v
->flags
= W_SET
|W_LOCAL
|W_RDONLY
;
2222 for (i
= match1
->cdr
== nil
? len
: 1; !m
&& i
<= len
; i
++) {
2223 v
->value
= findnword(arg1
, i
);
2225 m
= argmatch(0, match
, match1
->cdr
, arg1
+i
);
2229 /* Recover the value of the variable. */
2236 /* Match all the arguments to the arg rules, those that don't match are
2237 * used as files for transformation.
2245 /* Process all the arguments. */
2246 while (L_args
!= nil
) {
2247 pV_star
= &V_star
->value
;
2249 /* Try all the arg rules. */
2251 for (rule
= rules
; !m
&& rule
!= nil
; rule
= rule
->next
) {
2252 if (rule
->type
!= ARG
) continue;
2256 m
= argmatch(0, rule
->match
, nil
, nil
);
2261 /* On failure, add the first argument to the list of files. */
2264 L_args
= *(pL_files
= &L_args
->cdr
);
2271 int member(cell_t
*p
, cell_t
*l
)
2272 /* True if p is a member of list l. */
2274 while (l
!= nil
&& l
->type
== CELL
) {
2275 if (p
== l
->car
) return 1;
2281 long basefind(cell_t
*f
, cell_t
*l
)
2282 /* See if f has a suffix in list l + set the base name of f.
2283 * -1 if not found, preference number for a short basename otherwise. */
2289 /* Determine base name of f, with suffix. */
2290 if ((base
= strrchr(f
->name
, '/')) == nil
) base
= f
->name
; else base
++;
2295 if (l
->type
== CELL
) {
2296 suff
= l
->car
; l
= l
->cdr
;
2300 if (f
->flags
& W_SUFF
) {
2301 /* F has a suffix imposed on it. */
2302 if (f
->suffix
== suff
) return 0;
2305 slen
= strlen(suff
->name
);
2306 if (slen
< blen
&& strcmp(base
+blen
-slen
, suff
->name
) == 0) {
2309 f
->base
= findnword(base
, blen
-slen
);
2310 return 10000L * (blen
- slen
);
2316 #define NO_PATH 2000000000 /* No path found yet. */
2318 long shortest
; /* Length of the shortest path as yet. */
2320 rule_t
*findpath(long depth
, int seek
, cell_t
*file
, rule_t
*start
)
2321 /* Find the path of the shortest transformation to the stop suffix. */
2325 if (action
== 0) return nil
;
2328 /* No starting point defined, find one using "file". */
2330 for (rule
= rules
; rule
!= nil
; rule
= rule
->next
) {
2331 if (rule
->type
< TRANSFORM
) continue;
2333 if ((depth
= basefind(file
, rule
->from
)) >= 0) {
2334 if (findpath(depth
, seek
, nil
, rule
) != nil
)
2342 if (start
->path
!= nil
) {
2343 /* We can't have cycles through combines. */
2344 if (start
->type
== COMBINE
) {
2346 "\"%s\": contains a combine-combine cycle\n",
2353 /* Preferred transformations are cheap. */
2354 if (start
->flags
& R_PREFER
) depth
-= 100;
2356 /* Try to go from start closer to the stop suffix. */
2357 for (rule
= rules
; rule
!= nil
; rule
= rule
->next
) {
2358 if (rule
->type
< TRANSFORM
) continue;
2360 if (member(start
->to
, rule
->from
)) {
2363 if (findpath(depth
+1, seek
, nil
, rule
) != nil
)
2370 if (V_stop
== nil
) {
2371 fprintf(stderr
, "\"%s\": no stop suffix has been defined\n",
2377 /* End of the line? */
2378 if (start
->to
== V_stop
) {
2381 /* Second hunt, do we find the shortest? */
2382 if (depth
== shortest
) return start
;
2384 /* Is this path shorter than the last one? */
2385 if (depth
< shortest
) shortest
= depth
;
2388 return nil
; /* Fail. */
2391 void transform(rule_t
*rule
)
2392 /* Transform the file(s) connected to the rule according to the rule. */
2394 cell_t
*file
, *in
, *out
;
2397 /* Let $* be the list of input files. */
2398 while (rule
->wait
!= nil
) {
2400 rule
->wait
= file
->cdr
;
2401 file
->cdr
= V_star
->value
;
2402 V_star
->value
= file
;
2405 /* Set $< to the basename of the first input file. */
2407 V_in
->value
= in
= inc(file
->flags
& W_SUFF
? file
: file
->base
);
2408 file
->flags
&= ~W_SUFF
;
2410 /* Set $> to the output file name of the transformation. */
2413 base
= rule
->path
== nil
? in
->name
: maketemp();
2414 out
->name
= allocate(nil
,
2415 (strlen(base
)+strlen(rule
->to
->name
)+1) * sizeof(*out
->name
));
2416 strcpy(out
->name
, base
);
2417 if (rule
->path
== nil
|| strchr(rule
->to
->name
, '/') == nil
)
2418 strcat(out
->name
, rule
->to
->name
);
2420 if (rule
->path
!= nil
) out
->flags
|= W_TEMP
;
2423 V_out
->flags
= W_SET
|W_LOCAL
;
2425 /* Do a transformation. (Finally) */
2427 printf("%s ", rule
->type
==TRANSFORM
? "transform" : "combine");
2428 prin2(V_star
->value
);
2429 printf(" %s\n", out
->name
);
2432 execute(DOIT
, pc
->indent
+1);
2434 /* Hand $> over to the next rule, it must be a single word. */
2435 out
= evaluate(V_out
->value
, IMPLODE
);
2436 if (wordlist(&out
, 1) != 1) {
2438 "\"%s\", line %u: $> should be returned as a single word\n",
2439 descr
, rule
->prog
->lineno
);
2443 if ((rule
= rule
->path
) != nil
) {
2444 /* There is a next rule. */
2446 out
->base
= in
; /* Basename of input file. */
2447 file
= inc(newcell());
2449 file
->cdr
= rule
->wait
;
2456 /* Undo the damage to $*, $<, and $>. */
2461 V_out
->flags
= W_SET
|W_LOCAL
|W_RDONLY
;
2471 /* Implode the files list. */
2472 L_files
= evaluate(L_files
, IMPLODE
);
2473 if (wordlist(&L_files
, 0) < 0) {
2474 fprintf(stderr
, "\"%s\": An assignment to $> contained junk\n",
2479 while (action
!= 0 && L_files
!= nil
) {
2484 for (rule
= rules
; rule
!= nil
; rule
= rule
->next
)
2487 /* Try all possible transformation paths. */
2488 (void) findpath(0L, 0, file
, nil
);
2490 if (shortest
== NO_PATH
) { /* Can't match the file. */
2492 "%s: %s: can't compile, no transformation applies\n",
2493 program
, file
->name
);
2498 /* Find the first short path. */
2499 if ((rule
= findpath(0L, 1, file
, nil
)) == nil
) return;
2501 /* Transform the file until you hit a combine. */
2504 L_files
= go(L_files
, L_files
->cdr
);
2507 while (action
!= 0 && rule
!= nil
&& rule
->type
!= COMBINE
) {
2513 /* All input files have been transformed to combine rule(s). Now
2514 * we need to find the combine rule with the least number of paths
2515 * running through it (this combine may be followed by another) and
2516 * transform from there.
2518 while (action
!= 0) {
2522 for (rule
= rules
; rule
!= nil
; rule
= rule
->next
) {
2525 if (rule
->type
!= COMBINE
|| rule
->wait
== nil
)
2528 if (comb
== nil
|| rule
->npaths
< least
) {
2529 least
= rule
->npaths
;
2534 /* No combine? Then we're done. */
2535 if (comb
== nil
) break;
2540 /* Try all possible transformation paths. */
2541 (void) findpath(0L, 0, nil
, comb
);
2543 if (shortest
== NO_PATH
) break;
2545 /* Find the first short path. */
2546 if ((rule
= findpath(0L, 1, nil
, comb
)) == nil
) return;
2548 /* Transform until you hit another combine. */
2552 } while (action
!= 0 && rule
!= nil
&& rule
->type
!= COMBINE
);
2557 cell_t
*predef(char *var
, char *val
)
2558 /* A predefined variable var with value val, or a special variable. */
2563 if (val
!= nil
) { /* Predefined. */
2570 printf(" =\b=\b= ");
2573 } else { /* Special: $* and such. */
2574 p
->flags
= W_SET
|W_LOCAL
|W_RDONLY
;
2586 "Usage: %s -v<n> -vn<n> -name <name> -descr <descr> -T <dir> ...\n",
2591 int main(int argc
, char **argv
)
2598 /* Call name of the program, decides which description to use. */
2599 if ((program
= strrchr(argv
[0], '/')) == nil
)
2604 /* Directory for temporary files. */
2605 if ((tmpdir
= getenv("TMPDIR")) == nil
|| *tmpdir
== 0)
2608 /* Transform arguments to a list, processing the few ACD options. */
2610 for (i
= 1; i
< argc
; i
++) {
2611 if (argv
[i
][0] == '-' && argv
[i
][1] == 'v') {
2614 if (*a
== 'n') { a
++; action
= 1; }
2618 verbose
= strtoul(a
, &a
, 10);
2619 if (*a
!= 0) usage();
2622 if (strcmp(argv
[i
], "-name") == 0) {
2623 if (++i
== argc
) usage();
2626 if (strcmp(argv
[i
], "-descr") == 0) {
2627 if (++i
== argc
) usage();
2630 if (argv
[i
][0] == '-' && argv
[i
][1] == 'T') {
2631 if (argv
[i
][2] == 0) {
2632 if (++i
== argc
) usage();
2637 /* Any other argument must be processed. */
2638 *pa
= cons(CELL
, findword(argv
[i
]));
2643 /* Default description file is based on the program name. */
2644 if (descr
== nil
) descr
= program
;
2646 /* Default description file is predefined. */
2647 if (descr
== nil
) descr
= DESCR
;
2652 /* Catch user signals. */
2653 if (signal(SIGHUP
, SIG_IGN
) != SIG_IGN
) signal(SIGHUP
, interrupt
);
2654 if (signal(SIGINT
, SIG_IGN
) != SIG_IGN
) signal(SIGINT
, interrupt
);
2655 if (signal(SIGTERM
, SIG_IGN
) != SIG_IGN
) signal(SIGTERM
, interrupt
);
2657 /* Predefined or special variables. */
2658 predef("PROGRAM", program
);
2659 predef("VERSION", version
);
2661 predef("ARCH", ARCH
); /* Cross-compilers like this. */
2663 V_star
= predef("*", nil
);
2664 V_in
= predef("<", nil
);
2665 V_out
= predef(">", nil
);
2667 /* Read the description file. */
2668 if (verbose
>= 3) printf("include %s\n", descr
);
2678 /* Delete all allocated data to test inc/dec balance. */
2679 while (prog
!= nil
) {
2680 program_t
*junk
= prog
;
2686 while (rules
!= nil
) {
2687 rule_t
*junk
= rules
;
2700 quit(action
== 0 ? 1 : 0);