/* acd 1.10 - A compiler driver Author: Kees J. Bot * 7 Jan 1993 * Needs about 25kw heap + stack. */ char version[] = "1.9"; #define nil 0 #define _POSIX_SOURCE 1 #include #include #include #include #include #include #include #include #include #include #include #include #include #ifndef LIB #define LIB "/usr/lib" /* Default library directory. */ #endif #define arraysize(a) (sizeof(a) / sizeof((a)[0])) #define arraylimit(a) ((a) + arraysize(a)) char *program; /* Call name. */ int verbose= 0; /* -v0: Silent. * -v1: Show abbreviated pass names. * -v2: Show executed UNIX commands. * -v3: Show executed ACD commands. * -v4: Show descr file as it is read. */ int action= 2; /* 0: An error occured, don't do anything anymore. * 1: (-vn) Do not execute, play-act. * 2: Execute UNIX commands. */ void report(char *label) { if (label == nil || label[0] == 0) { fprintf(stderr, "%s: %s\n", program, strerror(errno)); } else { fprintf(stderr, "%s: %s: %s\n", program, label, strerror(errno)); } action= 0; } void quit(int exit_code); void fatal(char *label) { report(label); quit(-1); } size_t heap_chunks= 0; void *allocate(void *mem, size_t size) /* Safe malloc/realloc. (I have heard that one can call realloc with a * null first argument with the effect below, but that is of course to * ridiculous to believe.) */ { assert(size > 0); if (mem != nil) { mem= realloc(mem, size); } else { mem= malloc(size); heap_chunks++; } if (mem == nil) fatal(nil); return mem; } void deallocate(void *mem) { if (mem != nil) { free(mem); heap_chunks--; } } char *copystr(const char *s) { char *c; c= allocate(nil, (strlen(s)+1) * sizeof(*c)); strcpy(c, s); return c; } /* Every object, list, letter, or variable, is made with cells. */ typedef struct cell { unsigned short refc; /* Reference count. */ char type; /* Type of object. */ unsigned char letter; /* Simply a letter. */ char *name; /* Name of a word. */ struct cell *hash; /* Hash chain. */ struct cell *car, *cdr; /* To form lists. */ /* For a word: */ # define value car /* Value of a variable. */ # define base cdr /* Base-name in transformations. */ # define suffix cdr /* Suffix in a treat-as. */ # define flags letter /* Special flags. */ /* A substitution: */ # define subst car } cell_t; typedef enum type { CELL, /* A list cell. */ STRING, /* To make a list of characters and substs. */ SUBST, /* Variable to substitute. */ /* Unique objects. */ LETTER, /* A letter. */ WORD, /* A string collapses to a word. */ EQUALS, /* = operator, etc. */ OPEN, CLOSE, PLUS, MINUS, STAR, INPUT, OUTPUT, WHITE, COMMENT, SEMI, EOLN, N_TYPES /* number of different types */ } type_t; #define is_unique(type) ((type) >= LETTER) /* Flags on a word. */ #define W_SET 0x01 /* Not undefined, e.g. assigned to. */ #define W_RDONLY 0x02 /* Read only. */ #define W_LOCAL 0x04 /* Local variable, immediate substitution. */ #define W_TEMP 0x08 /* Name of a temporary file, delete on quit. */ #define W_SUFF 0x10 /* Has a suffix set on it. */ void princhar(int c) /* Print a character, escaped if important to the shell *within* quotes. */ { if (strchr("\\'\"<>();~$^&*|{}[]?", c) != nil) fputc('\\', stdout); putchar(c); } void prinstr(char *s) /* Print a string, in quotes if the shell might not like it. */ { int q= 0; char *s2= s; while (*s2 != 0) if (strchr("~`$^&*()=\\|[]{};'\"<>?", *s2++) != nil) q= 1; if (q) fputc('"', stdout); while (*s != 0) princhar(*s++); if (q) fputc('"', stdout); } void prin2(cell_t *p); void prin1(cell_t *p) /* Print a cell structure for debugging purposes. */ { if (p == nil) { printf("(\b(\b()\b)\b)"); return; } switch (p->type) { case CELL: printf("(\b(\b("); prin2(p); printf(")\b)\b)"); break; case STRING: printf("\"\b\"\b\""); prin2(p); printf("\"\b\"\b\""); break; case SUBST: printf("$\b$\b${%s}", p->subst->name); break; case LETTER: princhar(p->letter); break; case WORD: prinstr(p->name); break; case EQUALS: printf("=\b=\b="); break; case PLUS: printf("+\b+\b+"); break; case MINUS: printf("-\b-\b-"); break; case STAR: printf("*\b*\b*"); break; case INPUT: printf(verbose >= 3 ? "<\b<\b<" : "<"); break; case OUTPUT: printf(verbose >= 3 ? ">\b>\b>" : ">"); break; default: assert(0); } } void prin2(cell_t *p) /* Print a list for debugging purposes. */ { while (p != nil && p->type <= STRING) { prin1(p->car); if (p->type == CELL && p->cdr != nil) fputc(' ', stdout); p= p->cdr; } if (p != nil) prin1(p); /* Dotted pair? */ } void prin1n(cell_t *p) { prin1(p); fputc('\n', stdout); } void prin2n(cell_t *p) { prin2(p); fputc('\n', stdout); } /* A program is consists of a series of lists at a certain indentation level. */ typedef struct program { struct program *next; cell_t *file; /* Associated description file. */ unsigned indent; /* Line indentation level. */ unsigned lineno; /* Line number where this is found. */ cell_t *line; /* One line of tokens. */ } program_t; program_t *pc; /* Program Counter (what else?) */ program_t *nextpc; /* Next line to execute. */ cell_t *oldcells; /* Keep a list of old cells, don't deallocate. */ cell_t *newcell(void) /* Make a new empty cell. */ { cell_t *p; if (oldcells != nil) { p= oldcells; oldcells= p->cdr; heap_chunks++; } else { p= allocate(nil, sizeof(*p)); } p->refc= 0; p->type= CELL; p->letter= 0; p->name= nil; p->car= nil; p->cdr= nil; return p; } #define N_CHARS (1 + (unsigned char) -1) #define HASHDENSE 0x400 cell_t *oblist[HASHDENSE + N_CHARS + N_TYPES]; unsigned hashfun(cell_t *p) /* Use a blender on a cell. */ { unsigned h; char *name; switch (p->type) { case WORD: h= 0; name= p->name; while (*name != 0) h= (h * 0x1111) + *name++; return h % HASHDENSE; case LETTER: return HASHDENSE + p->letter; default: return HASHDENSE + N_CHARS + p->type; } } cell_t *search(cell_t *p, cell_t ***hook) /* Search for *p, return the one found. *hook may be used to insert or * delete. */ { cell_t *sp; sp= *(*hook= &oblist[hashfun(p)]); if (p->type == WORD) { /* More than one name per hash slot. */ int cmp= 0; while (sp != nil && (cmp= strcmp(p->name, sp->name)) > 0) sp= *(*hook= &sp->hash); if (cmp != 0) sp= nil; } return sp; } void dec(cell_t *p) /* Decrease the number of references to p, if zero delete and recurse. */ { if (p == nil || --p->refc > 0) return; if (is_unique(p->type)) { /* Remove p from the oblist. */ cell_t *o, **hook; o= search(p, &hook); if (o == p) { /* It's there, remove it. */ *hook= p->hash; p->hash= nil; } if (p->type == WORD && (p->flags & W_TEMP)) { /* A filename to remove. */ if (verbose >= 2) { printf("rm -f "); prinstr(p->name); fputc('\n', stdout); } if (unlink(p->name) < 0 && errno != ENOENT) report(p->name); } } deallocate(p->name); dec(p->car); dec(p->cdr); p->cdr= oldcells; oldcells= p; heap_chunks--; } cell_t *inc(cell_t *p) /* Increase the number of references to p. */ { cell_t *o, **hook; if (p == nil) return nil; if (++p->refc > 1 || !is_unique(p->type)) return p; /* First appearance, put p on the oblist. */ o= search(p, &hook); if (o == nil) { /* Not there yet, add it. */ p->hash= *hook; *hook= p; } else { /* There is another object already there with the same info. */ o->refc++; dec(p); p= o; } return p; } cell_t *go(cell_t *p, cell_t *field) /* Often happening: You've got p, you want p->field. */ { field= inc(field); dec(p); return field; } cell_t *cons(type_t type, cell_t *p) /* P is to be added to a list (or a string). */ { cell_t *l= newcell(); l->type= type; l->refc++; l->car= p; return l; } cell_t *append(type_t type, cell_t *p) /* P is to be appended to a list (or a string). */ { return p == nil || p->type == type ? p : cons(type, p); } cell_t *findnword(char *name, size_t n) /* Find the word with the given name of length n. */ { cell_t *w= newcell(); w->type= WORD; w->name= allocate(nil, (n+1) * sizeof(*w->name)); memcpy(w->name, name, n); w->name[n]= 0; return inc(w); } cell_t *findword(char *name) /* Find the word with the given null-terminated name. */ { return findnword(name, strlen(name)); } void quit(int exstat) /* Remove all temporary names, then exit. */ { cell_t **op, *p, *v, *b; size_t chunks; /* Remove cycles, like X = X. */ for (op= oblist; op < oblist + HASHDENSE; op++) { p= *op; while (p != nil) { if (p->value != nil || p->base != nil) { v= p->value; b= p->base; p->value= nil; p->base= nil; p= *op; dec(v); dec(b); } else { p= p->hash; } } } chunks= heap_chunks; /* Something may remain on an early quit: tempfiles. */ for (op= oblist; op < oblist + HASHDENSE; op++) { while (*op != nil) { (*op)->refc= 1; dec(*op); } } if (exstat != -1 && chunks > 0) { fprintf(stderr, "%s: internal fault: %d chunks still on the heap\n", program, chunks); } exit(exstat); } void interrupt(int sig) { signal(sig, interrupt); if (verbose >= 2) write(1, "# interrupt\n", 12); action= 0; } int extalnum(int c) /* Uppercase, lowercase, digit, underscore or anything non-American. */ { return isalnum(c) || c == '_' || c >= 0200; } char *descr; /* Name of current description file. */ FILE *dfp; /* Open description file. */ int dch; /* Input character. */ unsigned lineno; /* Line number in file. */ unsigned indent; /* Indentation level. */ void getdesc(void) { if (dch == EOF) return; if (dch == '\n') { lineno++; indent= 0; } if ((dch = getc(dfp)) == EOF && ferror(dfp)) fatal(descr); if (dch == 0) { fprintf(stderr, "%s: %s is a binary file.\n", program, descr); quit(-1); } } #define E_BASH 0x01 /* Escaped by backslash. */ #define E_QUOTE 0x02 /* Escaped by double quote. */ #define E_SIMPLE 0x04 /* More simple characters? */ cell_t *get_token(void) /* Read one token from the description file. */ { int whitetype= 0; static int escape= 0; cell_t *tok; char *name; int n, i; if (escape & E_SIMPLE) { /* More simple characters? (Note: performance hack.) */ if (isalnum(dch)) { tok= newcell(); tok->type= LETTER; tok->letter= dch; getdesc(); return inc(tok); } escape&= ~E_SIMPLE; } /* Gather whitespace. */ for (;;) { if (dch == '\\' && whitetype == 0) { getdesc(); if (isspace(dch)) { /* \ whitespace: remove. */ do { getdesc(); if (dch == '#' && !(escape & E_QUOTE)) { /* \ # comment */ do getdesc(); while (dch != '\n' && dch != EOF); } } while (isspace(dch)); continue; } escape|= E_BASH; /* Escaped character. */ } if (escape != 0) break; if (dch == '#' && (indent == 0 || whitetype != 0)) { /* # Comment. */ do getdesc(); while (dch != '\n' && dch != EOF); whitetype= COMMENT; break; } if (!isspace(dch) || dch == '\n' || dch == EOF) break; whitetype= WHITE; indent++; if (dch == '\t') indent= (indent + 7) & ~7; getdesc(); } if (dch == EOF) return nil; /* Make a token. */ tok= newcell(); if (whitetype != 0) { tok->type= whitetype; return inc(tok); } if (!(escape & E_BASH) && dch == '"') { getdesc(); if (!(escape & E_QUOTE)) { /* Start of a string, signal this with a string cell. */ escape|= E_QUOTE; tok->type= STRING; return inc(tok); } else { /* End of a string, back to normal mode. */ escape&= ~E_QUOTE; deallocate(tok); return get_token(); } } if (escape & E_BASH || strchr(escape & E_QUOTE ? "$" : "$=()+-*<>;\n", dch) == nil ) { if (dch == '\n') { fprintf(stderr, "\"%s\", line %u: missing closing quote\n", descr, lineno); escape&= ~E_QUOTE; action= 0; } if (escape & E_BASH && dch == 'n') dch= '\n'; escape&= ~E_BASH; /* A simple character. */ tok->type= LETTER; tok->letter= dch; getdesc(); escape|= E_SIMPLE; return inc(tok); } if (dch != '$') { /* Single character token. */ switch (dch) { case '=': tok->type= EQUALS; break; case '(': tok->type= OPEN; break; case ')': tok->type= CLOSE; break; case '+': tok->type= PLUS; break; case '-': tok->type= MINUS; break; case '*': tok->type= STAR; break; case '<': tok->type= INPUT; break; case '>': tok->type= OUTPUT; break; case ';': tok->type= SEMI; break; case '\n': tok->type= EOLN; break; } getdesc(); return inc(tok); } /* Substitution. */ getdesc(); if (dch == EOF || isspace(dch)) { fprintf(stderr, "\"%s\", line %u: Word expected after '$'\n", descr, lineno); action= 0; deallocate(tok); return get_token(); } name= allocate(nil, (n= 16) * sizeof(*name)); i= 0; if (dch == '{' || dch == '(' /* )} */ ) { /* $(X), ${X} */ int lpar= dch; /* ( */ int rpar= lpar == '{' ? '}' : ')'; for (;;) { getdesc(); if (dch == rpar) { getdesc(); break; } if (isspace(dch) || dch == EOF) { fprintf(stderr, "\"%s\", line %u: $%c unmatched, no '%c'\n", descr, lineno, lpar, rpar); action= 0; break; } name[i++]= dch; if (i == n) name= allocate(name, (n*= 2) * sizeof(char)); } } else if (extalnum(dch)) { /* $X */ do { name[i++]= dch; if (i == n) name= allocate(name, (n*= 2) * sizeof(char)); getdesc(); } while (extalnum(dch)); } else { /* $* */ name[i++]= dch; getdesc(); } name[i++]= 0; name= allocate(name, i * sizeof(char)); tok->type= SUBST; tok->subst= newcell(); tok->subst->type= WORD; tok->subst->name= name; tok->subst= inc(tok->subst); return inc(tok); } typedef enum how { SUPERFICIAL, PARTIAL, FULL, EXPLODE, IMPLODE } how_t; cell_t *explode(cell_t *p, how_t how); cell_t *get_string(cell_t **pp) /* Get a string: A series of letters and substs. Special tokens '=', '+', '-' * and '*' are also recognized if on their own. A finished string is "exploded" * to a word if it consists of letters only. */ { cell_t *p= *pp, *s= nil, **ps= &s; int quoted= 0; while (p != nil) { switch (p->type) { case STRING: quoted= 1; dec(p); break; case EQUALS: case PLUS: case MINUS: case STAR: case SUBST: case LETTER: *ps= cons(STRING, p); ps= &(*ps)->cdr; break; default: goto got_string; } p= get_token(); } got_string: *pp= p; /* A single special token must be folded up. */ if (!quoted && s != nil && s->cdr == nil) { switch (s->car->type) { case EQUALS: case PLUS: case MINUS: case STAR: case SUBST: return go(s, s->car); } } /* Go over the string changing '=', '+', '-', '*' to letters. */ for (p= s; p != nil; p= p->cdr) { int c= 0; switch (p->car->type) { case EQUALS: c= '='; break; case PLUS: c= '+'; break; case MINUS: c= '-'; break; case STAR: c= '*'; break; } if (c != 0) { dec(p->car); p->car= newcell(); p->car->type= LETTER; p->car->letter= c; p->car= inc(p->car); } } return explode(s, SUPERFICIAL); } cell_t *get_list(cell_t **pp, type_t stop) /* Read a series of tokens upto a token of type "stop". */ { cell_t *p= *pp, *l= nil, **pl= &l; while (p != nil && p->type != stop && !(stop == EOLN && p->type == SEMI)) { switch (p->type) { case WHITE: case COMMENT: case SEMI: case EOLN: dec(p); p= get_token(); break; case OPEN: /* '(' words ')'. */ dec(p); p= get_token(); *pl= cons(CELL, get_list(&p, CLOSE)); pl= &(*pl)->cdr; dec(p); p= get_token(); break; case CLOSE: /* Unexpected closing parenthesis. (*/ fprintf(stderr, "\"%s\", line %u: unmatched ')'\n", descr, lineno); action= 0; dec(p); p= get_token(); break; case INPUT: case OUTPUT: *pl= cons(CELL, p); pl= &(*pl)->cdr; p= get_token(); break; case STRING: case EQUALS: case PLUS: case MINUS: case STAR: case LETTER: case SUBST: *pl= cons(CELL, get_string(&p)); pl= &(*pl)->cdr; break; default: assert(0); } } if (p == nil && stop == CLOSE) { /* Couldn't get the closing parenthesis. */ fprintf(stderr, "\"%s\", lines %u-%u: unmatched '('\n", /*)*/ descr, pc->lineno, lineno); action= 0; } *pp= p; return l; } program_t *get_line(cell_t *file) { program_t *l; cell_t *p; static keep_indent= 0; static unsigned old_indent= 0; /* Skip leading whitespace to determine the indentation level. */ indent= 0; while ((p= get_token()) != nil && p->type == WHITE) dec(p); if (p == nil) return nil; /* EOF */ if (p->type == EOLN) indent= old_indent; /* Empty line. */ /* Make a program line. */ pc= l= allocate(nil, sizeof(*l)); l->next= nil; l->file= inc(file); l->indent= keep_indent ? old_indent : indent; l->lineno= lineno; l->line= get_list(&p, EOLN); /* If the line ended in a semicolon then keep the indentation level. */ keep_indent= (p != nil && p->type == SEMI); old_indent= l->indent; dec(p); if (verbose >= 4) { if (l->line == nil) fputc('\n', stdout); else { printf("%*s", (int) l->indent, ""); prin2n(l->line); } } return l; } program_t *get_prog(void) /* Read the description file into core. */ { cell_t *file; program_t *prog, **ppg= &prog; descr= copystr(descr); if (descr[0] == '-' && descr[1] == 0) { /* -descr -: Read from standard input. */ deallocate(descr); descr= copystr("stdin"); dfp= stdin; } else { char *d= descr; if (*d == '.' && *++d == '.') d++; if (*d != '/') { /* -descr name: Read /usr/lib//descr. */ d= allocate(nil, sizeof(LIB) + (strlen(descr) + 7) * sizeof(*d)); sprintf(d, "%s/%s/descr", LIB, descr); deallocate(descr); descr= d; } if ((dfp= fopen(descr, "r")) == nil) fatal(descr); } file= findword(descr); deallocate(descr); descr= file->name; /* Preread the first character. */ dch= 0; lineno= 1; indent= 0; getdesc(); while ((*ppg= get_line(file)) != nil) ppg= &(*ppg)->next; if (dfp != stdin) (void) fclose(dfp); dec(file); return prog; } void makenames(cell_t ***ppr, cell_t *s, char **name, size_t i, size_t *n) /* Turn a string of letters and lists into words. A list denotes a choice * between several paths, like a search on $PATH. */ { cell_t *p, *q; size_t len; /* Simply add letters, skip empty lists. */ while (s != nil && (s->car == nil || s->car->type == LETTER)) { if (s->car != nil) { if (i == *n) *name= allocate(*name, (*n *= 2) * sizeof(**name)); (*name)[i++]= s->car->letter; } s= s->cdr; } /* If the end is reached then make a word out of the result. */ if (s == nil) { **ppr= cons(CELL, findnword(*name, i)); *ppr= &(**ppr)->cdr; return; } /* Elements of a list must be tried one by one. */ p= s->car; s= s->cdr; while (p != nil) { if (p->type == WORD) { q= p; p= nil; } else { assert(p->type == CELL); q= p->car; p= p->cdr; assert(q != nil); assert(q->type == WORD); } len= strlen(q->name); if (i + len > *n) *name= allocate(*name, (*n += i + len) * sizeof(**name)); memcpy(*name + i, q->name, len); makenames(ppr, s, name, i+len, n); } } int constant(cell_t *p) /* See if a string has been partially evaluated to a constant so that it * can be imploded to a word. */ { while (p != nil) { switch (p->type) { case CELL: case STRING: if (!constant(p->car)) return 0; p= p->cdr; break; case SUBST: return 0; default: return 1; } } return 1; } cell_t *evaluate(cell_t *p, how_t how); cell_t *explode(cell_t *s, how_t how) /* Explode a string with several choices to just one list of choices. */ { cell_t *t, *r= nil, **pr= &r; size_t i, n; char *name; struct stat st; if (how >= PARTIAL) { /* Evaluate the string, expanding substitutions. */ while (s != nil) { assert(s->type == STRING); t= inc(s->car); s= go(s, s->cdr); t= evaluate(t, how == IMPLODE ? EXPLODE : how); /* A list of one element becomes that element. */ if (t != nil && t->type == CELL && t->cdr == nil) t= go(t, t->car); /* Append the result, trying to flatten it. */ *pr= t; /* Find the end of what has just been added. */ while ((*pr) != nil) { *pr= append(STRING, *pr); pr= &(*pr)->cdr; } } s= r; } /* Is the result a simple string of constants? */ if (how <= PARTIAL && !constant(s)) return s; /* Explode the string to all possible choices, by now the string is * a series of characters, words and lists of words. */ r= nil; pr= &r; name= allocate(nil, (n= 16) * sizeof(char)); i= 0; makenames(&pr, s, &name, i, &n); deallocate(name); assert(r != nil); dec(s); s= r; /* "How" may specify that a choice must be made. */ if (how == IMPLODE) { if (s->cdr != nil) { /* More than one choice, find the file. */ do { assert(s->car->type == WORD); if (stat(s->car->name, &st) >= 0) return go(r, s->car); /* Found. */ } while ((s= s->cdr) != nil); } /* The first name is the default if nothing is found. */ return go(r, r->car); } /* If the result is a list of one word then return that word, otherwise * turn it into a string again unless this explode has been called * by another explode. (Exploding a string inside a string, the joys * of recursion.) */ if (s->cdr == nil) return go(s, s->car); return how >= EXPLODE ? s : cons(STRING, s); } void modify(cell_t **pp, cell_t *p, type_t mode) /* Add or remove the element p from the list *pp. */ { while (*pp != nil) { *pp= append(CELL, *pp); if ((*pp)->car == p) { /* Found it, if adding then exit, else remove. */ if (mode == PLUS) break; *pp= go(*pp, (*pp)->cdr); } else pp= &(*pp)->cdr; } if (*pp == nil && mode == PLUS) { /* Not found, add it. */ *pp= cons(CELL, p); } else dec(p); } int tainted(cell_t *p) /* A variable is tainted (must be substituted) if either it is marked as a * local variable, or some subst in its value is. */ { if (p == nil) return 0; switch (p->type) { case CELL: case STRING: return tainted(p->car) || tainted(p->cdr); case SUBST: return p->subst->flags & W_LOCAL || tainted(p->subst->value); default: return 0; } } cell_t *evaluate(cell_t *p, how_t how) /* Evaluate an expression, usually the right hand side of an assignment. */ { cell_t *q, *t, *r= nil, **pr= &r; type_t mode; if (p == nil) return nil; switch (p->type) { case CELL: break; /* see below */ case STRING: return explode(p, how); case SUBST: if (how >= FULL || tainted(p)) p= evaluate(go(p, p->subst->value), how); return p; case EQUALS: fprintf(stderr, "\"%s\", line %u: Can't do nested assignments\n", descr, pc->lineno); action= 0; dec(p); return nil; case LETTER: case WORD: case INPUT: case OUTPUT: case PLUS: case MINUS: return p; default: assert(0); } /* It's a list, see if there is a '*' there forcing a full expansion, * or a '+' or '-' forcing an implosive expansion. (Yeah, right.) * Otherwise evaluate each element. */ q = inc(p); while (p != nil) { if ((t= p->car) != nil) { if (t->type == STAR) { if (how < FULL) how= FULL; dec(q); *pr= evaluate(go(p, p->cdr), how); return r; } if (how>=FULL && (t->type == PLUS || t->type == MINUS)) break; } t= evaluate(inc(t), how); assert(p->type == CELL); p= go(p, p->cdr); if (how >= FULL) { /* Flatten the list. */ *pr= t; } else { /* Keep the nested list structure. */ *pr= cons(CELL, t); } /* Find the end of what has just been added. */ while ((*pr) != nil) { *pr= append(CELL, *pr); pr= &(*pr)->cdr; } } if (p == nil) { /* No PLUS or MINUS: done. */ dec(q); return r; } /* A PLUS or MINUS, reevaluate the original list implosively. */ if (how < IMPLODE) { dec(r); dec(p); return evaluate(q, IMPLODE); } dec(q); /* Execute the PLUSes and MINUSes. */ while (p != nil) { t= inc(p->car); p= go(p, p->cdr); if (t != nil && (t->type == PLUS || t->type == MINUS)) { /* Change the add/subtract mode. */ mode= t->type; dec(t); continue; } t= evaluate(t, IMPLODE); /* Add or remove all elements of t to/from r. */ while (t != nil) { if (t->type == CELL) { modify(&r, inc(t->car), mode); } else { modify(&r, t, mode); break; } t= go(t, t->cdr); } } return r; } /* An ACD program can be in three phases: Initialization (the first run * of the program), argument scanning, and compilation. */ typedef enum phase { INIT, SCAN, COMPILE } phase_t; phase_t phase; typedef struct rule { /* Transformation rule. */ struct rule *next; char type; /* arg, transform, combine */ char flags; unsigned short npaths; /* Number of paths running through. */ # define match from /* Arg matching strings. */ cell_t *from; /* Transformation source suffixe(s) */ cell_t *to; /* Destination suffix. */ cell_t *wait; /* Files waiting to be transformed. */ program_t *prog; /* Program to execute. */ struct rule *path; /* Transformation path. */ } rule_t; typedef enum ruletype { ARG, PREFER, TRANSFORM, COMBINE } ruletype_t; #define R_PREFER 0x01 /* A preferred transformation. */ rule_t *rules= nil; void newrule(ruletype_t type, cell_t *from, cell_t *to) /* Make a new rule cell. */ { rule_t *r= nil, **pr= &rules; /* See if there is a rule with the same suffixes, probably a matching * transform and prefer, or a re-execution of the same arg command. */ while ((r= *pr) != nil) { if (r->from == from && r->to == to) break; pr= &r->next; } if (*pr == nil) { /* Add a new rule. */ *pr= r= allocate(nil, sizeof(*r)); r->next= nil; r->type= type; r->flags= 0; r->from= r->to= r->wait= nil; r->path= nil; } if (type == TRANSFORM) r->type= TRANSFORM; if (type == PREFER) r->flags|= R_PREFER; if (type != PREFER) r->prog= pc; dec(r->from); r->from= from; dec(r->to); r->to= to; } int talk(void) /* True if verbose and if so indent what is to come. */ { if (verbose < 3) return 0; printf("%*s", (int) pc->indent, ""); return 1; } void unix_exec(cell_t *c) /* Execute the list of words p as a UNIX command. */ { cell_t *v, *a; int fd[2]; int *pf; char **argv; int i, n; int r, pid, status; if (action == 0) return; /* Error mode. */ if (talk() || verbose >= 2) prin2n(c); fd[0]= fd[1]= -1; argv= allocate(nil, (n= 16) * sizeof(*argv)); i= 0; /* Gather argv[] and scan for I/O redirection. */ for (v= c; v != nil; v= v->cdr) { a= v->car; pf= nil; if (a->type == INPUT) pf= &fd[0]; if (a->type == OUTPUT) pf= &fd[1]; if (pf == nil) { /* An argument. */ argv[i++]= a->name; if (i==n) argv= allocate(argv, (n*= 2) * sizeof(*argv)); continue; } /* I/O redirection. */ if ((v= v->cdr) == nil || (a= v->car)->type != WORD) { fprintf(stderr, "\"%s\", line %u: I/O redirection without a file\n", descr, pc->lineno); action= 0; if (v == nil) break; } if (*pf >= 0) close(*pf); if (action >= 2 && (*pf= open(a->name, pf == &fd[0] ? O_RDONLY : O_WRONLY | O_CREAT | O_TRUNC, 0666)) < 0 ) { report(a->name); action= 0; } } argv[i]= nil; if (i >= 0 && action > 0 && verbose == 1) { char *name= strrchr(argv[0], '/'); if (name == nil) name= argv[0]; else name++; printf("%s\n", name); } if (i >= 0 && action >= 2) { /* Really execute the command. */ fflush(stdout); switch (pid= fork()) { case -1: fatal("fork()"); case 0: if (fd[0] >= 0) { dup2(fd[0], 0); close(fd[0]); } if (fd[1] >= 0) { dup2(fd[1], 1); close(fd[1]); } execvp(argv[0], argv); report(argv[0]); exit(-1); } } if (fd[0] >= 0) close(fd[0]); if (fd[1] >= 0) close(fd[1]); if (i >= 0 && action >= 2) { /* Wait for the command to terminate. */ while ((r= wait(&status)) != pid && (r >= 0 || errno == EINTR)); if (status != 0) { int sig= WTERMSIG(status); if (!WIFEXITED(status) && sig != SIGINT && sig != SIGPIPE) { fprintf(stderr, "%s: %s: Signal %d%s\n", program, argv[0], sig, status & 0x80 ? " - core dumped" : ""); } action= 0; } } deallocate(argv); } /* Special read-only variables ($*) and lists. */ cell_t *V_star, **pV_star; cell_t *L_files, **pL_files= &L_files; cell_t *V_in, *V_out, *V_stop, *L_args, *L_predef; typedef enum exec { DOIT, DONT } exec_t; void execute(exec_t how, unsigned indent); int equal(cell_t *p, cell_t *q) /* Two lists are equal if they contain each others elements. */ { cell_t *t, *m1, *m2; t= inc(newcell()); t->cdr= inc(newcell()); t->cdr->cdr= inc(newcell()); t->cdr->car= newcell(); t->cdr->car->type= MINUS; t->cdr->car= inc(t->cdr->car); /* Compute p - q. */ t->car= inc(p); t->cdr->cdr->car= inc(q); m1= evaluate(inc(t), IMPLODE); dec(m1); /* Compute q - p. */ t->car= q; t->cdr->cdr->car= p; m2= evaluate(t, IMPLODE); dec(m2); /* Both results must be empty. */ return m1 == nil && m2 == nil; } int wordlist(cell_t **pw, int atom) /* Check if p is a list of words, typically an imploded list. Return * the number of words seen, -1 if they are not words (INPUT/OUTPUT?). * If atom is true than a list of one word is turned into a word. */ { int n= 0; cell_t *p, **pp= pw; while (*pp != nil) { *pp= append(CELL, *pp); p= (*pp)->car; n= n >= 0 && p != nil && p->type == WORD ? n+1 : -1; pp= &(*pp)->cdr; } if (atom && n == 1) *pw= go(*pw, (*pw)->car); return n; } char *template; /* Current name of a temporary file. */ static char *tp; /* Current place withing the tempfile. */ char *maketemp(void) /* Return a name that can be used as a temporary filename. */ { int i= 0; if (tp == nil) { size_t len= strlen(template); template= allocate(template, (len+20) * sizeof(*template)); sprintf(template+len, "/acd%d", getpid()); tp= template + strlen(template); } for (;;) { switch (tp[i]) { case 0: tp[i]= 'a'; tp[i+1]= 0; return template; case 'z': tp[i++]= 'a'; break; default: tp[i]++; return template; } } } void inittemp(char *tmpdir) /* Initialize the temporary filename generator. */ { template= allocate(nil, (strlen(tmpdir)+20) * sizeof(*template)); sprintf(template, "%s/acd%d", tmpdir, getpid()); tp= template + strlen(template); /* Create a directory within tempdir that we can safely play in. */ while (action != 1 && mkdir(template, 0700) < 0) { if (errno == EEXIST) { (void) maketemp(); } else { report(template); action= 0; } } if (verbose >= 2) printf("mkdir %s\n", template); while (*tp != 0) tp++; *tp++= '/'; *tp= 0; } void deltemp(void) /* Remove our temporary temporaries directory. */ { while (*--tp != '/') {} *tp = 0; if (rmdir(template) < 0 && errno != ENOENT) report(template); if (verbose >= 2) printf("rmdir %s\n", template); deallocate(template); } cell_t *splitenv(char *env) /* Split a string from the environment into several words at whitespace * and colons. Two colons (::) become a dot. */ { cell_t *r= nil, **pr= &r; char *p; do { while (*env != 0 && isspace(*env)) env++; if (*env == 0) break; p= env; while (*p != 0 && !isspace(*p) && *p != ':') p++; *pr= cons(CELL, p == env ? findword(".") : findnword(env, p-env)); pr= &(*pr)->cdr; env= p; } while (*env++ != 0); return r; } void key_usage(char *how) { fprintf(stderr, "\"%s\", line %u: Usage: %s %s\n", descr, pc->lineno, pc->line->car->name, how); action= 0; } void inappropriate(void) { fprintf(stderr, "\"%s\", line %u: wrong execution phase for '%s'\n", descr, pc->lineno, pc->line->car->name); action= 0; } int readonly(cell_t *v) { if (v->flags & W_RDONLY) { fprintf(stderr, "\"%s\", line %u: %s is read-only\n", descr, pc->lineno, v->name); action= 0; return 1; } return 0; } void complain(cell_t *err) /* acd: err ... */ { cell_t *w; fprintf(stderr, "%s:", program); while (err != nil) { if (err->type == CELL) { w= err->car; err= err->cdr; } else { w= err; err= nil; } fprintf(stderr, " %s", w->name); } action= 0; } int keyword(char *name) /* True if the current line is headed by the given keyword. */ { cell_t *t; return (t= pc->line) != nil && t->type == CELL && (t= t->car) != nil && t->type == WORD && strcmp(t->name, name) == 0; } cell_t *getvar(cell_t *v) /* Return a word or the word referenced by a subst. */ { if (v == nil) return nil; if (v->type == WORD) return v; if (v->type == SUBST) return v->subst; return nil; } void argscan(void), compile(void); void transform(rule_t *); void exec_one(void) /* Execute one line of the program. */ { cell_t *v, *p, *q, *r, *t; unsigned n= 0; static int last_if= 1; /* Description file this line came from. */ descr= pc->file->name; for (p= pc->line; p != nil; p= p->cdr) n++; if (n == 0) return; /* Null statement. */ p= pc->line; q= p->cdr; r= q == nil ? nil : q->cdr; /* Try one by one all the different commands. */ if (n >= 2 && q->car != nil && q->car->type == EQUALS) { /* An assignment. */ int flags; if ((v= getvar(p->car)) == nil) { fprintf(stderr, "\"%s\", line %u: Usage: = expr ...\n", descr, pc->lineno); action= 0; return; } if (readonly(v)) return; flags= v->flags; v->flags|= W_LOCAL|W_RDONLY; t= evaluate(inc(r), PARTIAL); dec(v->value); v->value= t; v->flags= flags | W_SET; if (talk()) { printf("%s =\b=\b= ", v->name); prin2n(t); } } else if (keyword("unset")) { /* Set a variable to "undefined". */ if (n != 2 || (v= getvar(q->car)) == nil) { key_usage(""); return; } if (readonly(v)) return; if (talk()) prin2n(p); dec(v->value); v->value= nil; v->flags&= ~W_SET; } else if (keyword("import")) { /* Import a variable from the UNIX environment. */ char *env; if (n != 2 || (v= getvar(q->car)) == nil) { key_usage(""); return; } if (readonly(v)) return; if ((env= getenv(v->name)) == nil) return; if (talk()) printf("import %s=%s\n", v->name, env); t= splitenv(env); dec(v->value); v->value= t; v->flags|= W_SET; } else if (keyword("mktemp")) { /* Assign a variable the name of a temporary file. */ char *tmp, *suff; r= evaluate(inc(r), IMPLODE); if (n == 3 && wordlist(&r, 1) != 1) n= 0; if ((n != 2 && n != 3) || (v= getvar(q->car)) == nil) { dec(r); key_usage(" []"); return; } if (readonly(v)) { dec(r); return; } tmp= maketemp(); suff= r == nil ? "" : r->name; t= newcell(); t->type= WORD; t->name= allocate(nil, (strlen(tmp) + strlen(suff) + 1) * sizeof(*t->name)); strcpy(t->name, tmp); strcat(t->name, suff); t= inc(t); dec(r); dec(v->value); v->value= t; v->flags|= W_SET; t->flags|= W_TEMP; if (talk()) printf("mktemp %s=%s\n", v->name, t->name); } else if (keyword("temporary")) { /* Mark a word as a temporary file. */ cell_t *tmp; tmp= evaluate(inc(q), IMPLODE); if (wordlist(&tmp, 1) < 0) { dec(tmp); key_usage(""); return; } if (talk()) printf("temporary %s\n", tmp->name); tmp->flags|= W_TEMP; dec(tmp); } else if (keyword("stop")) { /* Set the suffix to stop the transformation on. */ cell_t *suff; if (phase > SCAN) { inappropriate(); return; } suff= evaluate(inc(q), IMPLODE); if (wordlist(&suff, 1) != 1) { dec(suff); key_usage(""); return; } dec(V_stop); V_stop= suff; if (talk()) printf("stop %s\n", suff->name); } else if (keyword("numeric")) { /* Check if a string denotes a number, like $n in -O$n. */ cell_t *num; char *pn; num= evaluate(inc(q), IMPLODE); if (wordlist(&num, 1) != 1) { dec(num); key_usage(""); return; } if (talk()) printf("numeric %s\n", num->name); (void) strtoul(num->name, &pn, 10); if (*pn != 0) { complain(phase == SCAN ? V_star->value : nil); if (phase == SCAN) fputc(':', stderr); fprintf(stderr, " '%s' is not a number\n", num->name); } dec(num); } else if (keyword("error")) { /* Signal an error. */ cell_t *err; err= evaluate(inc(q), IMPLODE); if (wordlist(&err, 0) < 1) { dec(err); key_usage("expr ..."); return; } if (talk()) { printf("error "); prin2n(err); } complain(err); fputc('\n', stderr); dec(err); } else if (keyword("if")) { /* if (list) = (list) using set comparison. */ int eq; if (n != 4 || r->car == nil || r->car->type != EQUALS) { key_usage(" = "); execute(DONT, pc->indent+1); last_if= 1; return; } q= q->car; r= r->cdr->car; if (talk()) { printf("if "); prin1(t= evaluate(inc(q), IMPLODE)); dec(t); printf(" = "); prin1n(t= evaluate(inc(r), IMPLODE)); dec(t); } eq= equal(q, r); execute(eq ? DOIT : DONT, pc->indent+1); last_if= eq; } else if (keyword("ifdef") || keyword("ifndef")) { /* Is a variable defined or undefined? */ int doit; if (n != 2 || (v= getvar(q->car)) == nil) { key_usage(""); execute(DONT, pc->indent+1); last_if= 1; return; } if (talk()) prin2n(p); doit= ((v->flags & W_SET) != 0) ^ (p->car->name[2] == 'n'); execute(doit ? DOIT : DONT, pc->indent+1); last_if= doit; } else if (keyword("iftemp") || keyword("ifhash")) { /* Is a file a temporary file? */ /* Does a file need preprocessing? */ cell_t *file; int doit= 0; file= evaluate(inc(q), IMPLODE); if (wordlist(&file, 1) != 1) { dec(file); key_usage(""); return; } if (talk()) printf("%s %s\n", p->car->name, file->name); if (p->car->name[2] == 't') { /* iftemp file */ if (file->flags & W_TEMP) doit= 1; } else { /* ifhash file */ int fd; char hash; if ((fd= open(file->name, O_RDONLY)) >= 0) { if (read(fd, &hash, 1) == 1 && hash == '#') doit= 1; close(fd); } } dec(file); execute(doit ? DOIT : DONT, pc->indent+1); last_if= doit; } else if (keyword("else")) { /* Else clause for an if, ifdef, or ifndef. */ if (n != 1) { key_usage(""); execute(DONT, pc->indent+1); return; } if (talk()) prin2n(p); execute(!last_if ? DOIT : DONT, pc->indent+1); } else if (keyword("treat")) { /* Treat a file as having a certain suffix. */ if (phase > SCAN) { inappropriate(); return; } if (n == 3) { q= evaluate(inc(q->car), IMPLODE); r= evaluate(inc(r->car), IMPLODE); } if (n != 3 || wordlist(&q, 1) != 1 || wordlist(&r, 1) != 1) { if (n == 3) { dec(q); dec(r); } key_usage(" "); return; } if (talk()) printf("treat %s %s\n", q->name, r->name); dec(q->suffix); q->suffix= r; q->flags|= W_SUFF; dec(q); } else if (keyword("apply")) { /* Apply a transformation rule to the current input file. */ rule_t *rule, *sav_path; cell_t *sav_wait, *sav_in, *sav_out; program_t *sav_next; if (phase != COMPILE) { inappropriate(); return; } if (V_star->value->cdr != nil) { fprintf(stderr, "\"%s\", line %u: $* is not one file\n", descr, pc->lineno); action= 0; return; } if (n == 3) { q= evaluate(inc(q->car), IMPLODE); r= evaluate(inc(r->car), IMPLODE); } if (n != 3 || wordlist(&q, 1) != 1 || wordlist(&r, 1) != 1) { if (n == 3) { dec(q); dec(r); } key_usage(" "); return; } if (talk()) printf("apply %s %s\n", q->name, r->name); /* Find a rule */ for (rule= rules; rule != nil; rule= rule->next) { if (rule->type == TRANSFORM && rule->from == q && rule->to == r) break; } if (rule == nil) { fprintf(stderr, "\"%s\", line %u: no %s %s transformation\n", descr, pc->lineno, q->name, r->name); action= 0; } dec(q); dec(r); if (rule == nil) return; /* Save the world. */ sav_path= rule->path; sav_wait= rule->wait; sav_in= V_in->value; sav_out= V_out->value; sav_next= nextpc; /* Isolate the rule and give it new input. */ rule->path= rule; rule->wait= V_star->value; V_star->value= nil; V_in->value= nil; V_out->value= nil; transform(rule); /* Retrieve the new $* and repair. */ V_star->value= rule->wait; rule->path= sav_path; rule->wait= sav_wait; V_in->value= sav_in; V_out->value= sav_out; V_out->flags= W_SET|W_LOCAL; nextpc= sav_next; } else if (keyword("include")) { /* Include another description file into this program. */ cell_t *file; program_t *incl, *prog, **ppg= &prog; file= evaluate(inc(q), IMPLODE); if (wordlist(&file, 1) != 1) { dec(file); key_usage(""); return; } if (talk()) printf("include %s\n", file->name); descr= file->name; incl= pc; prog= get_prog(); dec(file); /* Raise the program to the include's indent level. */ while (*ppg != nil) { (*ppg)->indent += incl->indent; ppg= &(*ppg)->next; } /* Kill the include and splice the included program in. */ dec(incl->line); incl->line= nil; *ppg= incl->next; incl->next= prog; pc= incl; nextpc= prog; } else if (keyword("arg")) { /* An argument scanning rule. */ if (phase > SCAN) { inappropriate(); return; } if (n < 2) { key_usage(" ..."); execute(DONT, pc->indent+1); return; } if (talk()) prin2n(p); newrule(ARG, inc(q), nil); /* Always skip the body, it comes later. */ execute(DONT, pc->indent+1); } else if (keyword("transform")) { /* A file transformation rule. */ if (phase > SCAN) { inappropriate(); return; } if (n == 3) { q= evaluate(inc(q->car), IMPLODE); r= evaluate(inc(r->car), IMPLODE); } if (n != 3 || wordlist(&q, 1) != 1 || wordlist(&r, 1) != 1) { if (n == 3) { dec(q); dec(r); } key_usage(" "); execute(DONT, pc->indent+1); return; } if (talk()) printf("transform %s %s\n", q->name, r->name); newrule(TRANSFORM, q, r); /* Body comes later. */ execute(DONT, pc->indent+1); } else if (keyword("prefer")) { /* Prefer a transformation over others. */ if (phase > SCAN) { inappropriate(); return; } if (n == 3) { q= evaluate(inc(q->car), IMPLODE); r= evaluate(inc(r->car), IMPLODE); } if (n != 3 || wordlist(&q, 1) != 1 || wordlist(&r, 1) != 1) { if (n == 3) { dec(q); dec(r); } key_usage(" "); return; } if (talk()) printf("prefer %s %s\n", q->name, r->name); newrule(PREFER, q, r); } else if (keyword("combine")) { /* A file combination (loader) rule. */ if (phase > SCAN) { inappropriate(); return; } if (n == 3) { q= evaluate(inc(q->car), IMPLODE); r= evaluate(inc(r->car), IMPLODE); } if (n != 3 || wordlist(&q, 0) < 1 || wordlist(&r, 1) != 1) { if (n == 3) { dec(q); dec(r); } key_usage(" "); execute(DONT, pc->indent+1); return; } if (talk()) { printf("combine "); prin1(q); printf(" %s\n", r->name); } newrule(COMBINE, q, r); /* Body comes later. */ execute(DONT, pc->indent+1); } else if (keyword("scan") || keyword("compile")) { program_t *next= nextpc; if (n != 1) { key_usage(""); return; } if (phase != INIT) { inappropriate(); return; } if (talk()) prin2n(p); argscan(); if (p->car->name[0] == 'c') compile(); nextpc= next; } else { /* A UNIX command. */ t= evaluate(inc(pc->line), IMPLODE); unix_exec(t); dec(t); } } void execute(exec_t how, unsigned indent) /* Execute (or skip) all lines with at least the given indent. */ { int work= 0; /* Need to execute at least one line. */ unsigned firstline; unsigned nice_indent= 0; /* 0 = Don't know what's nice yet. */ if (pc == nil) return; /* End of program. */ firstline= pc->lineno; if (how == DONT) { /* Skipping a body, but is there another guard? */ pc= pc->next; if (pc != nil && pc->indent < indent && pc->line != nil) { /* There is one! Bail out, then it get's executed. */ return; } } else { /* Skip lines with a lesser indentation, they are guards for * the same substatements. Don't go past empty lines. */ while (pc != nil && pc->indent < indent && pc->line != nil) pc= pc->next; } /* Execute all lines with an indentation of at least "indent". */ while (pc != nil && pc->indent >= indent) { if (pc->indent != nice_indent && how == DOIT) { if (nice_indent != 0) { fprintf(stderr, "\"%s\", line %u: (warning) sudden indentation shift\n", descr, pc->lineno); } nice_indent= pc->indent; } nextpc= pc->next; if (how == DOIT) exec_one(); pc= nextpc; work= 1; } if (indent > 0 && !work) { fprintf(stderr, "\"%s\", line %u: empty body, no statements\n", descr, firstline); action= 0; } } int argmatch(int shift, cell_t *match, cell_t *match1, char *arg1) /* Try to match an arg rule to the input file list L_args. Execute the arg * body (pc is set to it) on success. */ { cell_t *oldval, *v; int m, oldflags; size_t i, len; int minus= 0; if (shift) { /* An argument has been accepted and may be shifted to $*. */ cell_t **oldpstar= pV_star; *pV_star= L_args; L_args= *(pV_star= &L_args->cdr); *pV_star= nil; if (argmatch(0, match->cdr, nil, nil)) return 1; /* Undo the damage. */ *pV_star= L_args; L_args= *(pV_star= oldpstar); *pV_star= nil; return 0; } if (match == nil) { /* A full match, execute the arg body. */ /* Enable $>. */ V_out->flags= W_SET|W_LOCAL; if (verbose >= 3) { prin2(pc->line); printf(" =\b=\b= "); prin2n(V_star->value); } execute(DOIT, pc->indent+1); /* Append $> to the file list. */ if (V_out->value != nil) { *pL_files= cons(CELL, V_out->value); pL_files= &(*pL_files)->cdr; } /* Disable $>. */ V_out->value= nil; V_out->flags= W_SET|W_LOCAL|W_RDONLY; return 1; } if (L_args == nil) return 0; /* Out of arguments to match. */ /* Match is a list of words, substs and strings containing letters and * substs. Match1 is the current element of the first element of match. * Arg1 is the current character of the first element of L_args. */ if (match1 == nil) { /* match1 is at the end of a string, then arg1 must also. */ if (arg1 != nil) { if (*arg1 != 0) return 0; return argmatch(1, match, nil, nil); } /* If both are nil: Initialize. */ match1= match->car; arg1= L_args->car->name; /* A subst may not match a leading '-'. */ if (arg1[0] == '-') minus= 1; } if (match1->type == WORD && strcmp(match1->name, arg1) == 0) { /* A simple match of an argument. */ return argmatch(1, match, nil, nil); } if (match1->type == SUBST && !minus) { /* A simple match of a subst. */ /* The variable gets the first of the arguments as its value. */ v= match1->subst; if (v->flags & W_RDONLY) return 0; /* ouch */ oldflags= v->flags; v->flags= W_SET|W_LOCAL|W_RDONLY; oldval= v->value; v->value= inc(L_args->car); m= argmatch(1, match, nil, nil); /* Recover the value of the variable. */ dec(v->value); v->flags= oldflags; v->value= oldval; return m; } if (match1->type != STRING) return 0; /* Match the first item in the string. */ if (match1->car == nil) return 0; if (match1->car->type == LETTER && match1->car->letter == (unsigned char) *arg1) { /* A letter matches, try the rest of the string. */ return argmatch(0, match, match1->cdr, arg1+1); } /* It can only be a subst in a string now. */ len= strlen(arg1); if (match1->car->type != SUBST || minus || len == 0) return 0; /* The variable can match from 1 character to all of the argument. * Matching as few characters as possible happens to be the Right Thing. */ v= match1->car->subst; if (v->flags & W_RDONLY) return 0; /* ouch */ oldflags= v->flags; v->flags= W_SET|W_LOCAL|W_RDONLY; oldval= v->value; m= 0; for (i= match1->cdr == nil ? len : 1; !m && i <= len; i++) { v->value= findnword(arg1, i); m= argmatch(0, match, match1->cdr, arg1+i); dec(v->value); } /* Recover the value of the variable. */ v->flags= oldflags; v->value= oldval; return m; } void argscan(void) /* Match all the arguments to the arg rules, those that don't match are * used as files for transformation. */ { rule_t *rule; int m; phase= SCAN; /* Process all the arguments. */ while (L_args != nil) { pV_star= &V_star->value; /* Try all the arg rules. */ m= 0; for (rule= rules; !m && rule != nil; rule= rule->next) { if (rule->type != ARG) continue; pc= rule->prog; m= argmatch(0, rule->match, nil, nil); } dec(V_star->value); V_star->value= nil; /* On failure, add the first argument to the list of files. */ if (!m) { *pL_files= L_args; L_args= *(pL_files= &L_args->cdr); *pL_files= nil; } } phase= INIT; } int member(cell_t *p, cell_t *l) /* True if p is a member of list l. */ { while (l != nil && l->type == CELL) { if (p == l->car) return 1; l= l->cdr; } return p == l; } long basefind(cell_t *f, cell_t *l) /* See if f has a suffix in list l + set the base name of f. * -1 if not found, preference number for a short basename otherwise. */ { cell_t *suff; size_t blen, slen; char *base; /* Determine base name of f, with suffix. */ if ((base= strrchr(f->name, '/')) == nil) base= f->name; else base++; blen= strlen(base); /* Try suffixes. */ while (l != nil) { if (l->type == CELL) { suff= l->car; l= l->cdr; } else { suff= l; l= nil; } if (f->flags & W_SUFF) { /* F has a suffix imposed on it. */ if (f->suffix == suff) return 0; continue; } slen= strlen(suff->name); if (slen < blen && strcmp(base+blen-slen, suff->name) == 0) { /* Got it! */ dec(f->base); f->base= findnword(base, blen-slen); return 10000L * (blen - slen); } } return -1; } #define NO_PATH 2000000000 /* No path found yet. */ long shortest; /* Length of the shortest path as yet. */ rule_t *findpath(long depth, int seek, cell_t *file, rule_t *start) /* Find the path of the shortest transformation to the stop suffix. */ { rule_t *rule; if (action == 0) return nil; if (start == nil) { /* No starting point defined, find one using "file". */ for (rule= rules; rule != nil; rule= rule->next) { if (rule->type < TRANSFORM) continue; if ((depth= basefind(file, rule->from)) >= 0) { if (findpath(depth, seek, nil, rule) != nil) return rule; } } return nil; } /* Cycle? */ if (start->path != nil) { /* We can't have cycles through combines. */ if (start->type == COMBINE) { fprintf(stderr, "\"%s\": contains a combine-combine cycle\n", descr); action= 0; } return nil; } /* Preferred transformations are cheap. */ if (start->flags & R_PREFER) depth-= 100; /* Try to go from start closer to the stop suffix. */ for (rule= rules; rule != nil; rule= rule->next) { if (rule->type < TRANSFORM) continue; if (member(start->to, rule->from)) { start->path= rule; rule->npaths++; if (findpath(depth+1, seek, nil, rule) != nil) return start; start->path= nil; rule->npaths--; } } if (V_stop == nil) { fprintf(stderr, "\"%s\": no stop suffix has been defined\n", descr); action= 0; return nil; } /* End of the line? */ if (start->to == V_stop) { /* Got it. */ if (seek) { /* Second hunt, do we find the shortest? */ if (depth == shortest) return start; } else { /* Is this path shorter than the last one? */ if (depth < shortest) shortest= depth; } } return nil; /* Fail. */ } void transform(rule_t *rule) /* Transform the file(s) connected to the rule according to the rule. */ { cell_t *file, *in, *out; char *base; /* Let $* be the list of input files. */ while (rule->wait != nil) { file= rule->wait; rule->wait= file->cdr; file->cdr= V_star->value; V_star->value= file; } /* Set $< to the basename of the first input file. */ file= file->car; V_in->value= in= inc(file->flags & W_SUFF ? file : file->base); file->flags&= ~W_SUFF; /* Set $> to the output file name of the transformation. */ out= newcell(); out->type= WORD; base= rule->path == nil ? in->name : maketemp(); out->name= allocate(nil, (strlen(base)+strlen(rule->to->name)+1) * sizeof(*out->name)); strcpy(out->name, base); if (rule->path == nil || strchr(rule->to->name, '/') == nil) strcat(out->name, rule->to->name); out= inc(out); if (rule->path != nil) out->flags|= W_TEMP; V_out->value= out; V_out->flags= W_SET|W_LOCAL; /* Do a transformation. (Finally) */ if (verbose >= 3) { printf("%s ", rule->type==TRANSFORM ? "transform" : "combine"); prin2(V_star->value); printf(" %s\n", out->name); } pc= rule->prog; execute(DOIT, pc->indent+1); /* Hand $> over to the next rule, it must be a single word. */ out= evaluate(V_out->value, IMPLODE); if (wordlist(&out, 1) != 1) { fprintf(stderr, "\"%s\", line %u: $> should be returned as a single word\n", descr, rule->prog->lineno); action= 0; } if ((rule= rule->path) != nil) { /* There is a next rule. */ dec(out->base); out->base= in; /* Basename of input file. */ file= inc(newcell()); file->car= out; file->cdr= rule->wait; rule->wait= file; } else { dec(in); dec(out); } /* Undo the damage to $*, $<, and $>. */ dec(V_star->value); V_star->value= nil; V_in->value= nil; V_out->value= nil; V_out->flags= W_SET|W_LOCAL|W_RDONLY; } void compile(void) { rule_t *rule; cell_t *file, *t; phase= COMPILE; /* Implode the files list. */ L_files= evaluate(L_files, IMPLODE); if (wordlist(&L_files, 0) < 0) { fprintf(stderr, "\"%s\": An assignment to $> contained junk\n", descr); action= 0; } while (action != 0 && L_files != nil) { file= L_files->car; /* Initialize. */ shortest= NO_PATH; for (rule= rules; rule != nil; rule= rule->next) rule->path= nil; /* Try all possible transformation paths. */ (void) findpath(0L, 0, file, nil); if (shortest == NO_PATH) { /* Can't match the file. */ fprintf(stderr, "%s: %s: can't compile, no transformation applies\n", program, file->name); action= 0; return; } /* Find the first short path. */ if ((rule= findpath(0L, 1, file, nil)) == nil) return; /* Transform the file until you hit a combine. */ t= inc(newcell()); t->car= inc(file); L_files= go(L_files, L_files->cdr); t->cdr= rule->wait; rule->wait= t; while (action != 0 && rule != nil && rule->type != COMBINE) { transform(rule); rule= rule->path; } } /* All input files have been transformed to combine rule(s). Now * we need to find the combine rule with the least number of paths * running through it (this combine may be followed by another) and * transform from there. */ while (action != 0) { int least; rule_t *comb= nil; for (rule= rules; rule != nil; rule= rule->next) { rule->path= nil; if (rule->type != COMBINE || rule->wait == nil) continue; if (comb == nil || rule->npaths < least) { least= rule->npaths; comb= rule; } } /* No combine? Then we're done. */ if (comb == nil) break; /* Initialize. */ shortest= NO_PATH; /* Try all possible transformation paths. */ (void) findpath(0L, 0, nil, comb); if (shortest == NO_PATH) break; /* Find the first short path. */ if ((rule= findpath(0L, 1, nil, comb)) == nil) return; /* Transform until you hit another combine. */ do { transform(rule); rule= rule->path; } while (action != 0 && rule != nil && rule->type != COMBINE); } phase= INIT; } cell_t *predef(char *var, char *val) /* A predefined variable var with value val, or a special variable. */ { cell_t *p, *t; p= findword(var); if (val != nil) { /* Predefined. */ t= findword(val); dec(p->value); p->value= t; p->flags|= W_SET; if (verbose >= 3) { prin1(p); printf(" =\b=\b= "); prin2n(t); } } else { /* Special: $* and such. */ p->flags= W_SET|W_LOCAL|W_RDONLY; } t= inc(newcell()); t->car= p; t->cdr= L_predef; L_predef= t; return p; } void usage(void) { fprintf(stderr, "Usage: %s -v -vn -name -descr -T ...\n", program); exit(-1); } int main(int argc, char **argv) { char *tmpdir; program_t *prog; cell_t **pa; int i; /* Call name of the program, decides which description to use. */ if ((program= strrchr(argv[0], '/')) == nil) program= argv[0]; else program++; /* Directory for temporary files. */ if ((tmpdir= getenv("TMPDIR")) == nil || *tmpdir == 0) tmpdir= "/tmp"; /* Transform arguments to a list, processing the few ACD options. */ pa= &L_args; for (i= 1; i < argc; i++) { if (argv[i][0] == '-' && argv[i][1] == 'v') { char *a= argv[i]+2; if (*a == 'n') { a++; action= 1; } verbose= 2; if (*a != 0) { verbose= strtoul(a, &a, 10); if (*a != 0) usage(); } } else if (strcmp(argv[i], "-name") == 0) { if (++i == argc) usage(); program= argv[i]; } else if (strcmp(argv[i], "-descr") == 0) { if (++i == argc) usage(); descr= argv[i]; } else if (argv[i][0] == '-' && argv[i][1] == 'T') { if (argv[i][2] == 0) { if (++i == argc) usage(); tmpdir= argv[i]; } else tmpdir= argv[i]+2; } else { /* Any other argument must be processed. */ *pa= cons(CELL, findword(argv[i])); pa= &(*pa)->cdr; } } #ifndef DESCR /* Default description file is based on the program name. */ if (descr == nil) descr= program; #else /* Default description file is predefined. */ if (descr == nil) descr= DESCR; #endif inittemp(tmpdir); /* Catch user signals. */ if (signal(SIGHUP, SIG_IGN) != SIG_IGN) signal(SIGHUP, interrupt); if (signal(SIGINT, SIG_IGN) != SIG_IGN) signal(SIGINT, interrupt); if (signal(SIGTERM, SIG_IGN) != SIG_IGN) signal(SIGTERM, interrupt); /* Predefined or special variables. */ predef("PROGRAM", program); predef("VERSION", version); #ifdef ARCH predef("ARCH", ARCH); /* Cross-compilers like this. */ #endif V_star= predef("*", nil); V_in= predef("<", nil); V_out= predef(">", nil); /* Read the description file. */ if (verbose >= 3) printf("include %s\n", descr); prog= get_prog(); phase= INIT; pc= prog; execute(DOIT, 0); argscan(); compile(); /* Delete all allocated data to test inc/dec balance. */ while (prog != nil) { program_t *junk= prog; prog= junk->next; dec(junk->file); dec(junk->line); deallocate(junk); } while (rules != nil) { rule_t *junk= rules; rules= junk->next; dec(junk->from); dec(junk->to); dec(junk->wait); deallocate(junk); } deltemp(); dec(V_stop); dec(L_args); dec(L_files); dec(L_predef); quit(action == 0 ? 1 : 0); }