acd.c

Go to the documentation of this file.
00001 /*      acd 1.10 - A compiler driver                    Author: Kees J. Bot
00002  *                                                              7 Jan 1993
00003  * Needs about 25kw heap + stack.
00004  */
00005 char version[] = "1.9";
00006 
00007 #define nil 0
00008 #define _POSIX_SOURCE   1
00009 #include <sys/types.h>
00010 #include <stdio.h>
00011 #include <stddef.h>
00012 #include <stdlib.h>
00013 #include <unistd.h>
00014 #include <fcntl.h>
00015 #include <string.h>
00016 #include <signal.h>
00017 #include <errno.h>
00018 #include <ctype.h>
00019 #include <assert.h>
00020 #include <sys/stat.h>
00021 #include <sys/wait.h>
00022 
00023 #ifndef LIB
00024 #define LIB     "/usr/lib"      /* Default library directory. */
00025 #endif
00026 
00027 #define arraysize(a)    (sizeof(a) / sizeof((a)[0]))
00028 #define arraylimit(a)   ((a) + arraysize(a))
00029 
00030 char *program;          /* Call name. */
00031 
00032 int verbose= 0;         /* -v0: Silent.
00033                          * -v1: Show abbreviated pass names.
00034                          * -v2: Show executed UNIX commands.
00035                          * -v3: Show executed ACD commands.
00036                          * -v4: Show descr file as it is read.
00037                          */
00038 
00039 int action= 2;          /*   0: An error occured, don't do anything anymore.
00040                          *   1: (-vn) Do not execute, play-act.
00041                          *   2: Execute UNIX commands.
00042                          */
00043 
00044 void report(char *label)
00045 {
00046         if (label == nil || label[0] == 0) {
00047                 fprintf(stderr, "%s: %s\n", program, strerror(errno));
00048         } else {
00049                 fprintf(stderr, "%s: %s: %s\n",
00050                                         program, label, strerror(errno));
00051         }
00052         action= 0;
00053 }
00054 
00055 void quit(int exit_code);
00056 
00057 void fatal(char *label)
00058 {
00059         report(label);
00060         quit(-1);
00061 }
00062 
00063 size_t heap_chunks= 0;
00064 
00065 void *allocate(void *mem, size_t size)
00066 /* Safe malloc/realloc.  (I have heard that one can call realloc with a
00067  * null first argument with the effect below, but that is of course to
00068  * ridiculous to believe.)
00069  */
00070 {
00071         assert(size > 0);
00072 
00073         if (mem != nil) {
00074                 mem= realloc(mem, size);
00075         } else {
00076                 mem= malloc(size);
00077                 heap_chunks++;
00078         }
00079         if (mem == nil) fatal(nil);
00080         return mem;
00081 }
00082 
00083 void deallocate(void *mem)
00084 {
00085         if (mem != nil) {
00086                 free(mem);
00087                 heap_chunks--;
00088         }
00089 }
00090 
00091 char *copystr(const char *s)
00092 {
00093         char *c;
00094         c= allocate(nil, (strlen(s)+1) * sizeof(*c));
00095         strcpy(c, s);
00096         return c;
00097 }
00098 
00099 /* Every object, list, letter, or variable, is made with cells. */
00100 typedef struct cell {
00101         unsigned short  refc;           /* Reference count. */
00102         char            type;           /* Type of object. */
00103         unsigned char   letter;         /* Simply a letter. */
00104         char            *name;          /* Name of a word. */
00105         struct cell     *hash;          /* Hash chain. */
00106         struct cell     *car, *cdr;     /* To form lists. */
00107 
00108 /* For a word: */
00109 #       define  value   car             /* Value of a variable. */
00110 #       define  base    cdr             /* Base-name in transformations. */
00111 #       define  suffix  cdr             /* Suffix in a treat-as. */
00112 #       define  flags   letter          /* Special flags. */
00113 
00114 /* A substitution: */
00115 #       define  subst   car
00116 
00117 } cell_t;
00118 
00119 typedef enum type {
00120         CELL,           /* A list cell. */
00121         STRING,         /* To make a list of characters and substs. */
00122         SUBST,          /* Variable to substitute. */
00123         /* Unique objects. */
00124         LETTER,         /* A letter. */
00125         WORD,           /* A string collapses to a word. */
00126         EQUALS,         /* = operator, etc. */
00127         OPEN,
00128         CLOSE,
00129         PLUS,
00130         MINUS,
00131         STAR,
00132         INPUT,
00133         OUTPUT,
00134         WHITE,
00135         COMMENT,
00136         SEMI,
00137         EOLN,
00138         N_TYPES         /* number of different types */
00139 } type_t;
00140 
00141 #define is_unique(type) ((type) >= LETTER)
00142 
00143 /* Flags on a word. */
00144 #define W_SET           0x01    /* Not undefined, e.g. assigned to. */
00145 #define W_RDONLY        0x02    /* Read only. */
00146 #define W_LOCAL         0x04    /* Local variable, immediate substitution. */
00147 #define W_TEMP          0x08    /* Name of a temporary file, delete on quit. */
00148 #define W_SUFF          0x10    /* Has a suffix set on it. */
00149 
00150 void princhar(int c)
00151 /* Print a character, escaped if important to the shell *within* quotes. */
00152 {
00153         if (strchr("\\'\"<>();~$^&*|{}[]?", c) != nil) fputc('\\', stdout);
00154         putchar(c);
00155 }
00156 
00157 void prinstr(char *s)
00158 /* Print a string, in quotes if the shell might not like it. */
00159 {
00160         int q= 0;
00161         char *s2= s;
00162 
00163         while (*s2 != 0)
00164                 if (strchr("~`$^&*()=\\|[]{};'\"<>?", *s2++) != nil) q= 1;
00165 
00166         if (q) fputc('"', stdout);
00167         while (*s != 0) princhar(*s++);
00168         if (q) fputc('"', stdout);
00169 }
00170 
00171 void prin2(cell_t *p);
00172 
00173 void prin1(cell_t *p)
00174 /* Print a cell structure for debugging purposes. */
00175 {
00176         if (p == nil) {
00177                 printf("(\b(\b()\b)\b)");
00178                 return;
00179         }
00180 
00181         switch (p->type) {
00182         case CELL:
00183                 printf("(\b(\b(");
00184                 prin2(p);
00185                 printf(")\b)\b)");
00186                 break;
00187         case STRING:
00188                 printf("\"\b\"\b\"");
00189                 prin2(p);
00190                 printf("\"\b\"\b\"");
00191                 break;
00192         case SUBST:
00193                 printf("$\b$\b${%s}", p->subst->name);
00194                 break;
00195         case LETTER:
00196                 princhar(p->letter);
00197                 break;
00198         case WORD:
00199                 prinstr(p->name);
00200                 break;
00201         case EQUALS:
00202                 printf("=\b=\b=");
00203                 break;
00204         case PLUS:
00205                 printf("+\b+\b+");
00206                 break;
00207         case MINUS:
00208                 printf("-\b-\b-");
00209                 break;
00210         case STAR:
00211                 printf("*\b*\b*");
00212                 break;
00213         case INPUT:
00214                 printf(verbose >= 3 ? "<\b<\b<" : "<");
00215                 break;
00216         case OUTPUT:
00217                 printf(verbose >= 3 ? ">\b>\b>" : ">");
00218                 break;
00219         default:
00220                 assert(0);
00221         }
00222 }
00223 
00224 void prin2(cell_t *p)
00225 /* Print a list for debugging purposes. */
00226 {
00227         while (p != nil && p->type <= STRING) {
00228                 prin1(p->car);
00229 
00230                 if (p->type == CELL && p->cdr != nil) fputc(' ', stdout);
00231 
00232                 p= p->cdr;
00233         }
00234         if (p != nil) prin1(p);         /* Dotted pair? */
00235 }
00236 
00237 void prin1n(cell_t *p) { prin1(p); fputc('\n', stdout); }
00238 
00239 void prin2n(cell_t *p) { prin2(p); fputc('\n', stdout); }
00240 
00241 /* A program is consists of a series of lists at a certain indentation level. */
00242 typedef struct program {
00243         struct program  *next;
00244         cell_t          *file;          /* Associated description file. */
00245         unsigned        indent;         /* Line indentation level. */
00246         unsigned        lineno;         /* Line number where this is found. */
00247         cell_t          *line;          /* One line of tokens. */
00248 } program_t;
00249 
00250 program_t *pc;          /* Program Counter (what else?) */
00251 program_t *nextpc;      /* Next line to execute. */
00252 
00253 cell_t *oldcells;       /* Keep a list of old cells, don't deallocate. */
00254 
00255 cell_t *newcell(void)
00256 /* Make a new empty cell. */
00257 {
00258         cell_t *p;
00259 
00260         if (oldcells != nil) {
00261                 p= oldcells;
00262                 oldcells= p->cdr;
00263                 heap_chunks++;
00264         } else {
00265                 p= allocate(nil, sizeof(*p));
00266         }
00267 
00268         p->refc= 0;
00269         p->type= CELL;
00270         p->letter= 0;
00271         p->name= nil;
00272         p->car= nil;
00273         p->cdr= nil;
00274         return p;
00275 }
00276 
00277 #define N_CHARS         (1 + (unsigned char) -1)
00278 #define HASHDENSE       0x400
00279 
00280 cell_t *oblist[HASHDENSE + N_CHARS + N_TYPES];
00281 
00282 unsigned hashfun(cell_t *p)
00283 /* Use a blender on a cell. */
00284 {
00285         unsigned h;
00286         char *name;
00287 
00288         switch (p->type) {
00289         case WORD:
00290                 h= 0;
00291                 name= p->name;
00292                 while (*name != 0) h= (h * 0x1111) + *name++;
00293                 return h % HASHDENSE;
00294         case LETTER:
00295                 return HASHDENSE + p->letter;
00296         default:
00297                 return HASHDENSE + N_CHARS + p->type;
00298         }
00299 }
00300 
00301 cell_t *search(cell_t *p, cell_t ***hook)
00302 /* Search for *p, return the one found.  *hook may be used to insert or
00303  * delete.
00304  */
00305 {
00306         cell_t *sp;
00307 
00308         sp= *(*hook= &oblist[hashfun(p)]);
00309 
00310         if (p->type == WORD) {
00311                 /* More than one name per hash slot. */
00312                 int cmp= 0;
00313 
00314                 while (sp != nil && (cmp= strcmp(p->name, sp->name)) > 0)
00315                         sp= *(*hook= &sp->hash);
00316 
00317                 if (cmp != 0) sp= nil;
00318         }
00319         return sp;
00320 }
00321 
00322 void dec(cell_t *p)
00323 /* Decrease the number of references to p, if zero delete and recurse. */
00324 {
00325         if (p == nil || --p->refc > 0) return;
00326 
00327         if (is_unique(p->type)) {
00328                 /* Remove p from the oblist. */
00329                 cell_t *o, **hook;
00330 
00331                 o= search(p, &hook);
00332 
00333                 if (o == p) {
00334                         /* It's there, remove it. */
00335                         *hook= p->hash;
00336                         p->hash= nil;
00337                 }
00338 
00339                 if (p->type == WORD && (p->flags & W_TEMP)) {
00340                         /* A filename to remove. */
00341                         if (verbose >= 2) {
00342                                 printf("rm -f ");
00343                                 prinstr(p->name);
00344                                 fputc('\n', stdout);
00345                         }
00346                         if (unlink(p->name) < 0 && errno != ENOENT)
00347                                 report(p->name);
00348                 }
00349         }
00350         deallocate(p->name);
00351         dec(p->car);
00352         dec(p->cdr);
00353         p->cdr= oldcells;
00354         oldcells= p;
00355         heap_chunks--;
00356 }
00357 
00358 cell_t *inc(cell_t *p)
00359 /* Increase the number of references to p. */
00360 {
00361         cell_t *o, **hook;
00362 
00363         if (p == nil) return nil;
00364 
00365         if (++p->refc > 1 || !is_unique(p->type)) return p;
00366 
00367         /* First appearance, put p on the oblist. */
00368         o= search(p, &hook);
00369 
00370         if (o == nil) {
00371                 /* Not there yet, add it. */
00372                 p->hash= *hook;
00373                 *hook= p;
00374         } else {
00375                 /* There is another object already there with the same info. */
00376                 o->refc++;
00377                 dec(p);
00378                 p= o;
00379         }
00380         return p;
00381 }
00382 
00383 cell_t *go(cell_t *p, cell_t *field)
00384 /* Often happening: You've got p, you want p->field. */
00385 {
00386         field= inc(field);
00387         dec(p);
00388         return field;
00389 }
00390 
00391 cell_t *cons(type_t type, cell_t *p)
00392 /* P is to be added to a list (or a string). */
00393 {
00394         cell_t *l= newcell();
00395         l->type= type;
00396         l->refc++;
00397         l->car= p;
00398         return l;
00399 }
00400 
00401 cell_t *append(type_t type, cell_t *p)
00402 /* P is to be appended to a list (or a string). */
00403 {
00404         return p == nil || p->type == type ? p : cons(type, p);
00405 }
00406 
00407 cell_t *findnword(char *name, size_t n)
00408 /* Find the word with the given name of length n. */
00409 {
00410         cell_t *w= newcell();
00411         w->type= WORD;
00412         w->name= allocate(nil, (n+1) * sizeof(*w->name));
00413         memcpy(w->name, name, n);
00414         w->name[n]= 0;
00415         return inc(w);
00416 }
00417 
00418 cell_t *findword(char *name)
00419 /* Find the word with the given null-terminated name. */
00420 {
00421         return findnword(name, strlen(name));
00422 }
00423 
00424 void quit(int exstat)
00425 /* Remove all temporary names, then exit. */
00426 {
00427         cell_t **op, *p, *v, *b;
00428         size_t chunks;
00429 
00430         /* Remove cycles, like X = X. */
00431         for (op= oblist; op < oblist + HASHDENSE; op++) {
00432                 p= *op;
00433                 while (p != nil) {
00434                         if (p->value != nil || p->base != nil) {
00435                                 v= p->value;
00436                                 b= p->base;
00437                                 p->value= nil;
00438                                 p->base= nil;
00439                                 p= *op;
00440                                 dec(v);
00441                                 dec(b);
00442                         } else {
00443                                 p= p->hash;
00444                         }
00445                 }
00446         }
00447         chunks= heap_chunks;
00448 
00449         /* Something may remain on an early quit: tempfiles. */
00450         for (op= oblist; op < oblist + HASHDENSE; op++) {
00451 
00452                 while (*op != nil) { (*op)->refc= 1; dec(*op); }
00453         }
00454 
00455         if (exstat != -1 && chunks > 0) {
00456                 fprintf(stderr,
00457                         "%s: internal fault: %d chunks still on the heap\n",
00458                                                 program, chunks);
00459         }
00460         exit(exstat);
00461 }
00462 
00463 void interrupt(int sig)
00464 {
00465         signal(sig, interrupt);
00466         if (verbose >= 2) write(1, "# interrupt\n", 12);
00467         action= 0;
00468 }
00469 
00470 int extalnum(int c)
00471 /* Uppercase, lowercase, digit, underscore or anything non-American. */
00472 {
00473         return isalnum(c) || c == '_' || c >= 0200;
00474 }
00475 
00476 char *descr;            /* Name of current description file. */
00477 FILE *dfp;              /* Open description file. */
00478 int dch;                /* Input character. */
00479 unsigned lineno;        /* Line number in file. */
00480 unsigned indent;        /* Indentation level. */
00481 
00482 void getdesc(void)
00483 {
00484         if (dch == EOF) return;
00485 
00486         if (dch == '\n') { lineno++; indent= 0; }
00487 
00488         if ((dch = getc(dfp)) == EOF && ferror(dfp)) fatal(descr);
00489 
00490         if (dch == 0) {
00491                 fprintf(stderr, "%s: %s is a binary file.\n", program, descr);
00492                 quit(-1);
00493         }
00494 }
00495 
00496 #define E_BASH          0x01    /* Escaped by backslash. */
00497 #define E_QUOTE         0x02    /* Escaped by double quote. */
00498 #define E_SIMPLE        0x04    /* More simple characters? */
00499 
00500 cell_t *get_token(void)
00501 /* Read one token from the description file. */
00502 {
00503         int whitetype= 0;
00504         static int escape= 0;
00505         cell_t *tok;
00506         char *name;
00507         int n, i;
00508 
00509         if (escape & E_SIMPLE) {
00510                 /* More simple characters?  (Note: performance hack.) */
00511                 if (isalnum(dch)) {
00512                         tok= newcell();
00513                         tok->type= LETTER;
00514                         tok->letter= dch;
00515                         getdesc();
00516                         return inc(tok);
00517                 }
00518                 escape&= ~E_SIMPLE;
00519         }
00520 
00521         /* Gather whitespace. */
00522         for (;;) {
00523                 if (dch == '\\' && whitetype == 0) {
00524                         getdesc();
00525                         if (isspace(dch)) {
00526                                 /* \ whitespace: remove. */
00527                                 do {
00528                                         getdesc();
00529                                         if (dch == '#' && !(escape & E_QUOTE)) {
00530                                                 /* \ # comment */
00531                                                 do
00532                                                         getdesc();
00533                                                 while (dch != '\n'
00534                                                                 && dch != EOF);
00535                                         }
00536                                 } while (isspace(dch));
00537                                 continue;
00538                         }
00539                         escape|= E_BASH;        /* Escaped character. */
00540                 }
00541 
00542                 if (escape != 0) break;
00543 
00544                 if (dch == '#' && (indent == 0 || whitetype != 0)) {
00545                         /* # Comment. */
00546                         do getdesc(); while (dch != '\n' && dch != EOF);
00547                         whitetype= COMMENT;
00548                         break;
00549                 }
00550 
00551                 if (!isspace(dch) || dch == '\n' || dch == EOF) break;
00552 
00553                 whitetype= WHITE;
00554 
00555                 indent++;
00556                 if (dch == '\t') indent= (indent + 7) & ~7;
00557 
00558                 getdesc();
00559         }
00560 
00561         if (dch == EOF) return nil;
00562 
00563         /* Make a token. */
00564         tok= newcell();
00565 
00566         if (whitetype != 0) {
00567                 tok->type= whitetype;
00568                 return inc(tok);
00569         }
00570 
00571         if (!(escape & E_BASH) && dch == '"') {
00572                 getdesc();
00573                 if (!(escape & E_QUOTE)) {
00574                         /* Start of a string, signal this with a string cell. */
00575                         escape|= E_QUOTE;
00576                         tok->type= STRING;
00577                         return inc(tok);
00578                 } else {
00579                         /* End of a string, back to normal mode. */
00580                         escape&= ~E_QUOTE;
00581                         deallocate(tok);
00582                         return get_token();
00583                 }
00584         }
00585 
00586         if (escape & E_BASH
00587                 || strchr(escape & E_QUOTE ? "$" : "$=()+-*<>;\n", dch) == nil
00588         ) {
00589                 if (dch == '\n') {
00590                         fprintf(stderr,
00591                                 "\"%s\", line %u: missing closing quote\n",
00592                                 descr, lineno);
00593                         escape&= ~E_QUOTE;
00594                         action= 0;
00595                 }
00596                 if (escape & E_BASH && dch == 'n') dch= '\n';
00597                 escape&= ~E_BASH;
00598 
00599                 /* A simple character. */
00600                 tok->type= LETTER;
00601                 tok->letter= dch;
00602                 getdesc();
00603                 escape|= E_SIMPLE;
00604                 return inc(tok);
00605         }
00606 
00607         if (dch != '$') {
00608                 /* Single character token. */
00609                 switch (dch) {
00610                 case '=':       tok->type= EQUALS;      break;
00611                 case '(':       tok->type= OPEN;        break;
00612                 case ')':       tok->type= CLOSE;       break;
00613                 case '+':       tok->type= PLUS;        break;
00614                 case '-':       tok->type= MINUS;       break;
00615                 case '*':       tok->type= STAR;        break;
00616                 case '<':       tok->type= INPUT;       break;
00617                 case '>':       tok->type= OUTPUT;      break;
00618                 case ';':       tok->type= SEMI;        break;
00619                 case '\n':      tok->type= EOLN;        break;
00620                 }
00621                 getdesc();
00622                 return inc(tok);
00623         }
00624 
00625         /* Substitution. */
00626         getdesc();
00627         if (dch == EOF || isspace(dch)) {
00628                 fprintf(stderr, "\"%s\", line %u: Word expected after '$'\n",
00629                         descr, lineno);
00630                 action= 0;
00631                 deallocate(tok);
00632                 return get_token();
00633         }
00634 
00635         name= allocate(nil, (n= 16) * sizeof(*name));
00636         i= 0;
00637 
00638         if (dch == '{' || dch == '('  /* )} */ ) {
00639                 /* $(X), ${X} */
00640                 int lpar= dch;          /* ( */
00641                 int rpar= lpar == '{' ? '}' : ')';
00642 
00643                 for (;;) {
00644                         getdesc();
00645                         if (dch == rpar) { getdesc(); break; }
00646                         if (isspace(dch) || dch == EOF) {
00647                                 fprintf(stderr,
00648                                 "\"%s\", line %u: $%c unmatched, no '%c'\n",
00649                                         descr, lineno, lpar, rpar);
00650                                 action= 0;
00651                                 break;
00652                         }
00653                         name[i++]= dch;
00654                         if (i == n)
00655                                 name= allocate(name, (n*= 2) * sizeof(char));
00656                 }
00657         } else
00658         if (extalnum(dch)) {
00659                 /* $X */
00660                 do {
00661                         name[i++]= dch;
00662                         if (i == n)
00663                                 name= allocate(name, (n*= 2) * sizeof(char));
00664                         getdesc();
00665                 } while (extalnum(dch));
00666         } else {
00667                 /* $* */
00668                 name[i++]= dch;
00669                 getdesc();
00670         }
00671         name[i++]= 0;
00672         name= allocate(name, i * sizeof(char));
00673         tok->type= SUBST;
00674         tok->subst= newcell();
00675         tok->subst->type= WORD;
00676         tok->subst->name= name;
00677         tok->subst= inc(tok->subst);
00678         return inc(tok);
00679 }
00680 
00681 typedef enum how { SUPERFICIAL, PARTIAL, FULL, EXPLODE, IMPLODE } how_t;
00682 
00683 cell_t *explode(cell_t *p, how_t how);
00684 
00685 cell_t *get_string(cell_t **pp)
00686 /* Get a string: A series of letters and substs.  Special tokens '=', '+', '-'
00687  * and '*' are also recognized if on their own.  A finished string is "exploded"
00688  * to a word if it consists of letters only.
00689  */
00690 {
00691         cell_t *p= *pp, *s= nil, **ps= &s;
00692         int quoted= 0;
00693 
00694         while (p != nil) {
00695                 switch (p->type) {
00696                 case STRING:
00697                         quoted= 1;
00698                         dec(p);
00699                         break;
00700                 case EQUALS:
00701                 case PLUS:
00702                 case MINUS:
00703                 case STAR:
00704                 case SUBST:
00705                 case LETTER:
00706                         *ps= cons(STRING, p);
00707                         ps= &(*ps)->cdr;
00708                         break;
00709                 default:
00710                         goto got_string;
00711                 }
00712                 p= get_token();
00713         }
00714     got_string:
00715         *pp= p;
00716 
00717         /* A single special token must be folded up. */
00718         if (!quoted && s != nil && s->cdr == nil) {
00719                 switch (s->car->type) {
00720                 case EQUALS:
00721                 case PLUS:
00722                 case MINUS:
00723                 case STAR:
00724                 case SUBST:
00725                         return go(s, s->car);
00726                 }
00727         }
00728 
00729         /* Go over the string changing '=', '+', '-', '*' to letters. */
00730         for (p= s; p != nil; p= p->cdr) {
00731                 int c= 0;
00732 
00733                 switch (p->car->type) {
00734                 case EQUALS:
00735                         c= '='; break;
00736                 case PLUS:
00737                         c= '+'; break;
00738                 case MINUS:
00739                         c= '-'; break;
00740                 case STAR:
00741                         c= '*'; break;
00742                 }
00743                 if (c != 0) {
00744                         dec(p->car);
00745                         p->car= newcell();
00746                         p->car->type= LETTER;
00747                         p->car->letter= c;
00748                         p->car= inc(p->car);
00749                 }
00750         }
00751         return explode(s, SUPERFICIAL);
00752 }
00753 
00754 cell_t *get_list(cell_t **pp, type_t stop)
00755 /* Read a series of tokens upto a token of type "stop". */
00756 {
00757         cell_t *p= *pp, *l= nil, **pl= &l;
00758 
00759         while (p != nil && p->type != stop
00760                                 && !(stop == EOLN && p->type == SEMI)) {
00761                 switch (p->type) {
00762                 case WHITE:
00763                 case COMMENT:
00764                 case SEMI:
00765                 case EOLN:
00766                         dec(p);
00767                         p= get_token();
00768                         break;
00769                 case OPEN:
00770                         /* '(' words ')'. */
00771                         dec(p);
00772                         p= get_token();
00773                         *pl= cons(CELL, get_list(&p, CLOSE));
00774                         pl= &(*pl)->cdr;
00775                         dec(p);
00776                         p= get_token();
00777                         break;
00778                 case CLOSE:
00779                         /* Unexpected closing parenthesis. (*/
00780                         fprintf(stderr, "\"%s\", line %u: unmatched ')'\n",
00781                                 descr, lineno);
00782                         action= 0;
00783                         dec(p);
00784                         p= get_token();
00785                         break;
00786                 case INPUT:
00787                 case OUTPUT:
00788                         *pl= cons(CELL, p);
00789                         pl= &(*pl)->cdr;
00790                         p= get_token();
00791                         break;
00792                 case STRING:
00793                 case EQUALS:
00794                 case PLUS:
00795                 case MINUS:
00796                 case STAR:
00797                 case LETTER:
00798                 case SUBST:
00799                         *pl= cons(CELL, get_string(&p));
00800                         pl= &(*pl)->cdr;
00801                         break;
00802                 default:
00803                         assert(0);
00804                 }
00805         }
00806 
00807         if (p == nil && stop == CLOSE) {
00808                 /* Couldn't get the closing parenthesis. */
00809                 fprintf(stderr, "\"%s\", lines %u-%u: unmatched '('\n", /*)*/
00810                         descr, pc->lineno, lineno);
00811                 action= 0;
00812         }
00813         *pp= p;
00814         return l;
00815 }
00816 
00817 program_t *get_line(cell_t *file)
00818 {
00819         program_t *l;
00820         cell_t *p;
00821         static keep_indent= 0;
00822         static unsigned old_indent= 0;
00823 
00824         /* Skip leading whitespace to determine the indentation level. */
00825         indent= 0;
00826         while ((p= get_token()) != nil && p->type == WHITE) dec(p);
00827 
00828         if (p == nil) return nil;               /* EOF */
00829 
00830         if (p->type == EOLN) indent= old_indent;        /* Empty line. */
00831 
00832         /* Make a program line. */
00833         pc= l= allocate(nil, sizeof(*l));
00834 
00835         l->next= nil;
00836         l->file= inc(file);
00837         l->indent= keep_indent ? old_indent : indent;
00838         l->lineno= lineno;
00839 
00840         l->line= get_list(&p, EOLN);
00841 
00842         /* If the line ended in a semicolon then keep the indentation level. */
00843         keep_indent= (p != nil && p->type == SEMI);
00844         old_indent= l->indent;
00845 
00846         dec(p);
00847 
00848         if (verbose >= 4) {
00849                 if (l->line == nil)
00850                         fputc('\n', stdout);
00851                 else {
00852                         printf("%*s", (int) l->indent, "");
00853                         prin2n(l->line);
00854                 }
00855         }
00856         return l;
00857 }
00858 
00859 program_t *get_prog(void)
00860 /* Read the description file into core. */
00861 {
00862         cell_t *file;
00863         program_t *prog, **ppg= &prog;
00864 
00865         descr= copystr(descr);
00866 
00867         if (descr[0] == '-' && descr[1] == 0) {
00868                 /* -descr -: Read from standard input. */
00869                 deallocate(descr);
00870                 descr= copystr("stdin");
00871                 dfp= stdin;
00872         } else {
00873                 char *d= descr;
00874 
00875                 if (*d == '.' && *++d == '.') d++;
00876                 if (*d != '/') {
00877                         /* -descr name: Read /usr/lib/<name>/descr. */
00878 
00879                         d= allocate(nil, sizeof(LIB) +
00880                                         (strlen(descr) + 7) * sizeof(*d));
00881                         sprintf(d, "%s/%s/descr", LIB, descr);
00882                         deallocate(descr);
00883                         descr= d;
00884                 }
00885                 if ((dfp= fopen(descr, "r")) == nil) fatal(descr);
00886         }
00887         file= findword(descr);
00888         deallocate(descr);
00889         descr= file->name;
00890 
00891         /* Preread the first character. */
00892         dch= 0;
00893         lineno= 1;
00894         indent= 0;
00895         getdesc();
00896 
00897         while ((*ppg= get_line(file)) != nil) ppg= &(*ppg)->next;
00898 
00899         if (dfp != stdin) (void) fclose(dfp);
00900         dec(file);
00901 
00902         return prog;
00903 }
00904 
00905 void makenames(cell_t ***ppr, cell_t *s, char **name, size_t i, size_t *n)
00906 /* Turn a string of letters and lists into words.  A list denotes a choice
00907  * between several paths, like a search on $PATH.
00908  */
00909 {
00910         cell_t *p, *q;
00911         size_t len;
00912 
00913         /* Simply add letters, skip empty lists. */
00914         while (s != nil && (s->car == nil || s->car->type == LETTER)) {
00915                 if (s->car != nil) {
00916                         if (i == *n) *name= allocate(*name,
00917                                                 (*n *= 2) * sizeof(**name));
00918                         (*name)[i++]= s->car->letter;
00919                 }
00920                 s= s->cdr;
00921         }
00922 
00923         /* If the end is reached then make a word out of the result. */
00924         if (s == nil) {
00925                 **ppr= cons(CELL, findnword(*name, i));
00926                 *ppr= &(**ppr)->cdr;
00927                 return;
00928         }
00929 
00930         /* Elements of a list must be tried one by one. */
00931         p= s->car;
00932         s= s->cdr;
00933 
00934         while (p != nil) {
00935                 if (p->type == WORD) {
00936                         q= p; p= nil;
00937                 } else {
00938                         assert(p->type == CELL);
00939                         q= p->car; p= p->cdr;
00940                         assert(q != nil);
00941                         assert(q->type == WORD);
00942                 }
00943                 len= strlen(q->name);
00944                 if (i + len > *n) *name= allocate(*name,
00945                                         (*n += i + len) * sizeof(**name));
00946                 memcpy(*name + i, q->name, len);
00947 
00948                 makenames(ppr, s, name, i+len, n);
00949         }
00950 }
00951 
00952 int constant(cell_t *p)
00953 /* See if a string has been partially evaluated to a constant so that it
00954  * can be imploded to a word.
00955  */
00956 {
00957         while (p != nil) {
00958                 switch (p->type) {
00959                 case CELL:
00960                 case STRING:
00961                         if (!constant(p->car)) return 0;
00962                         p= p->cdr;
00963                         break;
00964                 case SUBST:
00965                         return 0;
00966                 default:
00967                         return 1;
00968                 }
00969         }
00970         return 1;
00971 }
00972 
00973 cell_t *evaluate(cell_t *p, how_t how);
00974 
00975 cell_t *explode(cell_t *s, how_t how)
00976 /* Explode a string with several choices to just one list of choices. */
00977 {
00978         cell_t *t, *r= nil, **pr= &r;
00979         size_t i, n;
00980         char *name;
00981         struct stat st;
00982 
00983         if (how >= PARTIAL) {
00984                 /* Evaluate the string, expanding substitutions. */
00985                 while (s != nil) {
00986                         assert(s->type == STRING);
00987                         t= inc(s->car);
00988                         s= go(s, s->cdr);
00989 
00990                         t= evaluate(t, how == IMPLODE ? EXPLODE : how);
00991 
00992                         /* A list of one element becomes that element. */
00993                         if (t != nil && t->type == CELL && t->cdr == nil)
00994                                 t= go(t, t->car);
00995 
00996                         /* Append the result, trying to flatten it. */
00997                         *pr= t;
00998 
00999                         /* Find the end of what has just been added. */
01000                         while ((*pr) != nil) {
01001                                 *pr= append(STRING, *pr);
01002                                 pr= &(*pr)->cdr;
01003                         }
01004                 }
01005                 s= r;
01006         }
01007 
01008         /* Is the result a simple string of constants? */
01009         if (how <= PARTIAL && !constant(s)) return s;
01010 
01011         /* Explode the string to all possible choices, by now the string is
01012          * a series of characters, words and lists of words.
01013          */
01014         r= nil; pr= &r;
01015         name= allocate(nil, (n= 16) * sizeof(char));
01016         i= 0;
01017 
01018         makenames(&pr, s, &name, i, &n);
01019         deallocate(name);
01020         assert(r != nil);
01021         dec(s);
01022         s= r;
01023 
01024         /* "How" may specify that a choice must be made. */
01025         if (how == IMPLODE) {
01026                 if (s->cdr != nil) {
01027                         /* More than one choice, find the file. */
01028                         do {
01029                                 assert(s->car->type == WORD);
01030                                 if (stat(s->car->name, &st) >= 0)
01031                                         return go(r, s->car);   /* Found. */
01032                         } while ((s= s->cdr) != nil);
01033                 }
01034                 /* The first name is the default if nothing is found. */
01035                 return go(r, r->car);
01036         }
01037 
01038         /* If the result is a list of one word then return that word, otherwise
01039          * turn it into a string again unless this explode has been called
01040          * by another explode.  (Exploding a string inside a string, the joys
01041          * of recursion.)
01042          */
01043         if (s->cdr == nil) return go(s, s->car);
01044 
01045         return how >= EXPLODE ? s : cons(STRING, s);
01046 }
01047 
01048 void modify(cell_t **pp, cell_t *p, type_t mode)
01049 /* Add or remove the element p from the list *pp. */
01050 {
01051         while (*pp != nil) {
01052                 *pp= append(CELL, *pp);
01053 
01054                 if ((*pp)->car == p) {
01055                         /* Found it, if adding then exit, else remove. */
01056                         if (mode == PLUS) break;
01057                         *pp= go(*pp, (*pp)->cdr);
01058                 } else
01059                         pp= &(*pp)->cdr;
01060         }
01061 
01062         if (*pp == nil && mode == PLUS) {
01063                 /* Not found, add it. */
01064                 *pp= cons(CELL, p);
01065         } else
01066                 dec(p);
01067 }
01068 
01069 int tainted(cell_t *p)
01070 /* A variable is tainted (must be substituted) if either it is marked as a
01071  * local variable, or some subst in its value is.
01072  */
01073 {
01074         if (p == nil) return 0;
01075 
01076         switch (p->type) {
01077         case CELL:
01078         case STRING:
01079                 return tainted(p->car) || tainted(p->cdr);
01080         case SUBST:
01081                 return p->subst->flags & W_LOCAL || tainted(p->subst->value);
01082         default:
01083                 return 0;
01084         }
01085 }
01086 
01087 cell_t *evaluate(cell_t *p, how_t how)
01088 /* Evaluate an expression, usually the right hand side of an assignment. */
01089 {
01090         cell_t *q, *t, *r= nil, **pr= &r;
01091         type_t mode;
01092 
01093         if (p == nil) return nil;
01094 
01095         switch (p->type) {
01096         case CELL:
01097                 break;  /* see below */
01098         case STRING:
01099                 return explode(p, how);
01100         case SUBST:
01101                 if (how >= FULL || tainted(p))
01102                         p= evaluate(go(p, p->subst->value), how);
01103                 return p;
01104         case EQUALS:
01105                 fprintf(stderr,
01106                         "\"%s\", line %u: Can't do nested assignments\n",
01107                         descr, pc->lineno);
01108                 action= 0;
01109                 dec(p);
01110                 return nil;
01111         case LETTER:
01112         case WORD:
01113         case INPUT:
01114         case OUTPUT:
01115         case PLUS:
01116         case MINUS:
01117                 return p;
01118         default:
01119                 assert(0);
01120         }
01121 
01122         /* It's a list, see if there is a '*' there forcing a full expansion,
01123          * or a '+' or '-' forcing an implosive expansion.  (Yeah, right.)
01124          * Otherwise evaluate each element.
01125          */
01126         q = inc(p);
01127         while (p != nil) {
01128                 if ((t= p->car) != nil) {
01129                         if (t->type == STAR) {
01130                                 if (how < FULL) how= FULL;
01131                                 dec(q);
01132                                 *pr= evaluate(go(p, p->cdr), how);
01133                                 return r;
01134                         }
01135                         if (how>=FULL && (t->type == PLUS || t->type == MINUS))
01136                                 break;
01137                 }
01138 
01139                 t= evaluate(inc(t), how);
01140                 assert(p->type == CELL);
01141                 p= go(p, p->cdr);
01142 
01143                 if (how >= FULL) {
01144                         /* Flatten the list. */
01145                         *pr= t;
01146                 } else {
01147                         /* Keep the nested list structure. */
01148                         *pr= cons(CELL, t);
01149                 }
01150 
01151                 /* Find the end of what has just been added. */
01152                 while ((*pr) != nil) {
01153                         *pr= append(CELL, *pr);
01154                         pr= &(*pr)->cdr;
01155                 }
01156         }
01157 
01158         if (p == nil) {
01159                 /* No PLUS or MINUS: done. */
01160                 dec(q);
01161                 return r;
01162         }
01163 
01164         /* A PLUS or MINUS, reevaluate the original list implosively. */
01165         if (how < IMPLODE) {
01166                 dec(r);
01167                 dec(p);
01168                 return evaluate(q, IMPLODE);
01169         }
01170         dec(q);
01171 
01172         /* Execute the PLUSes and MINUSes. */
01173         while (p != nil) {
01174                 t= inc(p->car);
01175                 p= go(p, p->cdr);
01176 
01177                 if (t != nil && (t->type == PLUS || t->type == MINUS)) {
01178                         /* Change the add/subtract mode. */
01179                         mode= t->type;
01180                         dec(t);
01181                         continue;
01182                 }
01183 
01184                 t= evaluate(t, IMPLODE);
01185 
01186                 /* Add or remove all elements of t to/from r. */
01187                 while (t != nil) {
01188                         if (t->type == CELL) {
01189                                 modify(&r, inc(t->car), mode);
01190                         } else {
01191                                 modify(&r, t, mode);
01192                                 break;
01193                         }
01194                         t= go(t, t->cdr);
01195                 }
01196         }
01197         return r;
01198 }
01199 
01200 /* An ACD program can be in three phases: Initialization (the first run
01201  * of the program), argument scanning, and compilation.
01202  */
01203 typedef enum phase { INIT, SCAN, COMPILE } phase_t;
01204 
01205 phase_t phase;
01206 
01207 typedef struct rule {           /* Transformation rule. */
01208         struct rule     *next;
01209         char            type;           /* arg, transform, combine */
01210         char            flags;
01211         unsigned short  npaths;         /* Number of paths running through. */
01212 #       define  match   from            /* Arg matching strings. */
01213         cell_t          *from;          /* Transformation source suffixe(s) */
01214         cell_t          *to;            /* Destination suffix. */
01215         cell_t          *wait;          /* Files waiting to be transformed. */
01216         program_t       *prog;          /* Program to execute. */
01217         struct rule     *path;          /* Transformation path. */
01218 } rule_t;
01219 
01220 typedef enum ruletype { ARG, PREFER, TRANSFORM, COMBINE } ruletype_t;
01221 
01222 #define R_PREFER        0x01            /* A preferred transformation. */
01223 
01224 rule_t *rules= nil;
01225 
01226 void newrule(ruletype_t type, cell_t *from, cell_t *to)
01227 /* Make a new rule cell. */
01228 {
01229         rule_t *r= nil, **pr= &rules;
01230 
01231         /* See if there is a rule with the same suffixes, probably a matching
01232          * transform and prefer, or a re-execution of the same arg command.
01233          */
01234         while ((r= *pr) != nil) {
01235                 if (r->from == from && r->to == to) break;
01236                 pr= &r->next;
01237         }
01238 
01239         if (*pr == nil) {
01240                 /* Add a new rule. */
01241                 *pr= r= allocate(nil, sizeof(*r));
01242 
01243                 r->next= nil;
01244                 r->type= type;
01245                 r->flags= 0;
01246                 r->from= r->to= r->wait= nil;
01247                 r->path= nil;
01248         }
01249         if (type == TRANSFORM) r->type= TRANSFORM;
01250         if (type == PREFER) r->flags|= R_PREFER;
01251         if (type != PREFER) r->prog= pc;
01252         dec(r->from); r->from= from;
01253         dec(r->to); r->to= to;
01254 }
01255 
01256 int talk(void)
01257 /* True if verbose and if so indent what is to come. */
01258 {
01259         if (verbose < 3) return 0;
01260         printf("%*s", (int) pc->indent, "");
01261         return 1;
01262 }
01263 
01264 void unix_exec(cell_t *c)
01265 /* Execute the list of words p as a UNIX command. */
01266 {
01267         cell_t *v, *a;
01268         int fd[2];
01269         int *pf;
01270         char **argv;
01271         int i, n;
01272         int r, pid, status;
01273 
01274         if (action == 0) return;        /* Error mode. */
01275 
01276         if (talk() || verbose >= 2) prin2n(c);
01277 
01278         fd[0]= fd[1]= -1;
01279 
01280         argv= allocate(nil, (n= 16) * sizeof(*argv));
01281         i= 0;
01282 
01283         /* Gather argv[] and scan for I/O redirection. */
01284         for (v= c; v != nil; v= v->cdr) {
01285                 a= v->car;
01286                 pf= nil;
01287                 if (a->type == INPUT) pf= &fd[0];
01288                 if (a->type == OUTPUT) pf= &fd[1];
01289 
01290                 if (pf == nil) {
01291                         /* An argument. */
01292                         argv[i++]= a->name;
01293                         if (i==n) argv= allocate(argv, (n*= 2) * sizeof(*argv));
01294                         continue;
01295                 }
01296                 /* I/O redirection. */
01297                 if ((v= v->cdr) == nil || (a= v->car)->type != WORD) {
01298                         fprintf(stderr,
01299                         "\"%s\", line %u: I/O redirection without a file\n",
01300                                 descr, pc->lineno);
01301                         action= 0;
01302                         if (v == nil) break;
01303                 }
01304                 if (*pf >= 0) close(*pf);
01305 
01306                 if (action >= 2
01307                         && (*pf= open(a->name, pf == &fd[0] ? O_RDONLY
01308                                 : O_WRONLY | O_CREAT | O_TRUNC, 0666)) < 0
01309                 ) {
01310                         report(a->name);
01311                         action= 0;
01312                 }
01313         }
01314         argv[i]= nil;
01315 
01316         if (i >= 0 && action > 0 && verbose == 1) {
01317                 char *name= strrchr(argv[0], '/');
01318 
01319                 if (name == nil) name= argv[0]; else name++;
01320 
01321                 printf("%s\n", name);
01322         }
01323         if (i >= 0 && action >= 2) {
01324                 /* Really execute the command. */
01325                 fflush(stdout);
01326                 switch (pid= fork()) {
01327                 case -1:
01328                         fatal("fork()");
01329                 case 0:
01330                         if (fd[0] >= 0) { dup2(fd[0], 0); close(fd[0]); }
01331                         if (fd[1] >= 0) { dup2(fd[1], 1); close(fd[1]); }
01332                         execvp(argv[0], argv);
01333                         report(argv[0]);
01334                         exit(-1);
01335                 }
01336         }
01337         if (fd[0] >= 0) close(fd[0]);
01338         if (fd[1] >= 0) close(fd[1]);
01339 
01340         if (i >= 0 && action >= 2) {
01341                 /* Wait for the command to terminate. */
01342                 while ((r= wait(&status)) != pid && (r >= 0 || errno == EINTR));
01343 
01344                 if (status != 0) {
01345                         int sig= WTERMSIG(status);
01346 
01347                         if (!WIFEXITED(status)
01348                                         && sig != SIGINT && sig != SIGPIPE) {
01349                                 fprintf(stderr, "%s: %s: Signal %d%s\n",
01350                                         program, argv[0], sig,
01351                                         status & 0x80 ? " - core dumped" : "");
01352                         }
01353                         action= 0;
01354                 }
01355         }
01356         deallocate(argv);
01357 }
01358 
01359 /* Special read-only variables ($*) and lists. */
01360 cell_t *V_star, **pV_star;
01361 cell_t *L_files, **pL_files= &L_files;
01362 cell_t *V_in, *V_out, *V_stop, *L_args, *L_predef;
01363 
01364 typedef enum exec { DOIT, DONT } exec_t;
01365 
01366 void execute(exec_t how, unsigned indent);
01367 
01368 int equal(cell_t *p, cell_t *q)
01369 /* Two lists are equal if they contain each others elements. */
01370 {
01371         cell_t *t, *m1, *m2;
01372 
01373         t= inc(newcell());
01374         t->cdr= inc(newcell());
01375         t->cdr->cdr= inc(newcell());
01376         t->cdr->car= newcell();
01377         t->cdr->car->type= MINUS;
01378         t->cdr->car= inc(t->cdr->car);
01379 
01380         /* Compute p - q. */
01381         t->car= inc(p);
01382         t->cdr->cdr->car= inc(q);
01383         m1= evaluate(inc(t), IMPLODE);
01384         dec(m1);
01385 
01386         /* Compute q - p. */
01387         t->car= q;
01388         t->cdr->cdr->car= p;
01389         m2= evaluate(t, IMPLODE);
01390         dec(m2);
01391 
01392         /* Both results must be empty. */
01393         return m1 == nil && m2 == nil;
01394 }
01395 
01396 int wordlist(cell_t **pw, int atom)
01397 /* Check if p is a list of words, typically an imploded list.  Return
01398  * the number of words seen, -1 if they are not words (INPUT/OUTPUT?).
01399  * If atom is true than a list of one word is turned into a word.
01400  */
01401 {
01402         int n= 0;
01403         cell_t *p, **pp= pw;
01404 
01405         while (*pp != nil) {
01406                 *pp= append(CELL, *pp);
01407                 p= (*pp)->car;
01408                 n= n >= 0 && p != nil && p->type == WORD ? n+1 : -1;
01409                 pp= &(*pp)->cdr;
01410         }
01411         if (atom && n == 1) *pw= go(*pw, (*pw)->car);
01412         return n;
01413 }
01414 
01415 char *template;         /* Current name of a temporary file. */
01416 static char *tp;        /* Current place withing the tempfile. */
01417 
01418 char *maketemp(void)
01419 /* Return a name that can be used as a temporary filename. */
01420 {
01421         int i= 0;
01422 
01423         if (tp == nil) {
01424                 size_t len= strlen(template);
01425 
01426                 template= allocate(template, (len+20) * sizeof(*template));
01427                 sprintf(template+len, "/acd%d", getpid());
01428                 tp= template + strlen(template);
01429         }
01430 
01431         for (;;) {
01432                 switch (tp[i]) {
01433                 case 0:         tp[i]= 'a';
01434                                 tp[i+1]= 0;     return template;
01435                 case 'z':       tp[i++]= 'a';   break;
01436                 default:        tp[i]++;        return template;
01437                 }
01438         }
01439 }
01440 
01441 void inittemp(char *tmpdir)
01442 /* Initialize the temporary filename generator. */
01443 {
01444         template= allocate(nil, (strlen(tmpdir)+20) * sizeof(*template));
01445         sprintf(template, "%s/acd%d", tmpdir, getpid());
01446         tp= template + strlen(template);
01447 
01448         /* Create a directory within tempdir that we can safely play in. */
01449         while (action != 1 && mkdir(template, 0700) < 0) {
01450                 if (errno == EEXIST) {
01451                         (void) maketemp();
01452                 } else {
01453                         report(template);
01454                         action= 0;
01455                 }
01456         }
01457         if (verbose >= 2) printf("mkdir %s\n", template);
01458         while (*tp != 0) tp++;
01459         *tp++= '/';
01460         *tp= 0;
01461 }
01462 
01463 void deltemp(void)
01464 /* Remove our temporary temporaries directory. */
01465 {
01466         while (*--tp != '/') {}
01467         *tp = 0;
01468         if (rmdir(template) < 0 && errno != ENOENT) report(template);
01469         if (verbose >= 2) printf("rmdir %s\n", template);
01470         deallocate(template);
01471 }
01472 
01473 cell_t *splitenv(char *env)
01474 /* Split a string from the environment into several words at whitespace
01475  * and colons.  Two colons (::) become a dot.
01476  */
01477 {
01478         cell_t *r= nil, **pr= &r;
01479         char *p;
01480 
01481         do {
01482                 while (*env != 0 && isspace(*env)) env++;
01483 
01484                 if (*env == 0) break;
01485 
01486                 p= env;
01487                 while (*p != 0 && !isspace(*p) && *p != ':') p++;
01488 
01489                 *pr= cons(CELL,
01490                         p == env ? findword(".") : findnword(env, p-env));
01491                 pr= &(*pr)->cdr;
01492                 env= p;
01493         } while (*env++ != 0);
01494         return r;
01495 }
01496 
01497 void key_usage(char *how)
01498 {
01499         fprintf(stderr, "\"%s\", line %u: Usage: %s %s\n",
01500                 descr, pc->lineno, pc->line->car->name, how);
01501         action= 0;
01502 }
01503 
01504 void inappropriate(void)
01505 {
01506         fprintf(stderr, "\"%s\", line %u: wrong execution phase for '%s'\n",
01507                 descr, pc->lineno, pc->line->car->name);
01508         action= 0;
01509 }
01510 
01511 int readonly(cell_t *v)
01512 {
01513         if (v->flags & W_RDONLY) {
01514                 fprintf(stderr, "\"%s\", line %u: %s is read-only\n",
01515                         descr, pc->lineno, v->name);
01516                 action= 0;
01517                 return 1;
01518         }
01519         return 0;
01520 }
01521 
01522 void complain(cell_t *err)
01523 /* acd: err ... */
01524 {
01525         cell_t *w;
01526 
01527         fprintf(stderr, "%s:", program);
01528 
01529         while (err != nil) {
01530                 if (err->type == CELL) {
01531                         w= err->car; err= err->cdr;
01532                 } else {
01533                         w= err; err= nil;
01534                 }
01535                 fprintf(stderr, " %s", w->name);
01536         }
01537         action= 0;
01538 }
01539 
01540 int keyword(char *name)
01541 /* True if the current line is headed by the given keyword. */
01542 {
01543         cell_t *t;
01544 
01545         return (t= pc->line) != nil && t->type == CELL
01546                 && (t= t->car) != nil && t->type == WORD
01547                 && strcmp(t->name, name) == 0;
01548 }
01549 
01550 cell_t *getvar(cell_t *v)
01551 /* Return a word or the word referenced by a subst. */
01552 {
01553         if (v == nil) return nil;
01554         if (v->type == WORD) return v;
01555         if (v->type == SUBST) return v->subst;
01556         return nil;
01557 }
01558 
01559 void argscan(void), compile(void);
01560 void transform(rule_t *);
01561 
01562 void exec_one(void)
01563 /* Execute one line of the program. */
01564 {
01565         cell_t *v, *p, *q, *r, *t;
01566         unsigned n= 0;
01567         static int last_if= 1;
01568 
01569         /* Description file this line came from. */
01570         descr= pc->file->name;
01571 
01572         for (p= pc->line; p != nil; p= p->cdr) n++;
01573 
01574         if (n == 0) return;     /* Null statement. */
01575 
01576         p= pc->line;
01577         q= p->cdr;
01578         r= q == nil ? nil : q->cdr;
01579 
01580         /* Try one by one all the different commands. */
01581 
01582         if (n >= 2 && q->car != nil && q->car->type == EQUALS) {
01583                 /* An assignment. */
01584                 int flags;
01585 
01586                 if ((v= getvar(p->car)) == nil) {
01587                         fprintf(stderr,
01588                                 "\"%s\", line %u: Usage: <var> = expr ...\n",
01589                                 descr, pc->lineno);
01590                         action= 0;
01591                         return;
01592                 }
01593 
01594                 if (readonly(v)) return;
01595 
01596                 flags= v->flags;
01597                 v->flags|= W_LOCAL|W_RDONLY;
01598                 t= evaluate(inc(r), PARTIAL);
01599                 dec(v->value);
01600                 v->value= t;
01601                 v->flags= flags | W_SET;
01602                 if (talk()) {
01603                         printf("%s =\b=\b= ", v->name);
01604                         prin2n(t);
01605                 }
01606         } else
01607         if (keyword("unset")) {
01608                 /* Set a variable to "undefined". */
01609 
01610                 if (n != 2 || (v= getvar(q->car)) == nil) {
01611                         key_usage("<var>");
01612                         return;
01613                 }
01614                 if (readonly(v)) return;
01615 
01616                 if (talk()) prin2n(p);
01617 
01618                 dec(v->value);
01619                 v->value= nil;
01620                 v->flags&= ~W_SET;
01621         } else
01622         if (keyword("import")) {
01623                 /* Import a variable from the UNIX environment. */
01624                 char *env;
01625 
01626                 if (n != 2 || (v= getvar(q->car)) == nil) {
01627                         key_usage("<var>");
01628                         return;
01629                 }
01630                 if (readonly(v)) return;
01631 
01632                 if ((env= getenv(v->name)) == nil) return;
01633 
01634                 if (talk()) printf("import %s=%s\n", v->name, env);
01635 
01636                 t= splitenv(env);
01637                 dec(v->value);
01638                 v->value= t;
01639                 v->flags|= W_SET;
01640         } else
01641         if (keyword("mktemp")) {
01642                 /* Assign a variable the name of a temporary file. */
01643                 char *tmp, *suff;
01644 
01645                 r= evaluate(inc(r), IMPLODE);
01646                 if (n == 3 && wordlist(&r, 1) != 1) n= 0;
01647 
01648                 if ((n != 2 && n != 3) || (v= getvar(q->car)) == nil) {
01649                         dec(r);
01650                         key_usage("<var> [<suffix>]");
01651                         return;
01652                 }
01653                 if (readonly(v)) { dec(r); return; }
01654 
01655                 tmp= maketemp();
01656                 suff= r == nil ? "" : r->name;
01657 
01658                 t= newcell();
01659                 t->type= WORD;
01660                 t->name= allocate(nil,
01661                         (strlen(tmp) + strlen(suff) + 1) * sizeof(*t->name));
01662                 strcpy(t->name, tmp);
01663                 strcat(t->name, suff);
01664                 t= inc(t);
01665                 dec(r);
01666                 dec(v->value);
01667                 v->value= t;
01668                 v->flags|= W_SET;
01669                 t->flags|= W_TEMP;
01670                 if (talk()) printf("mktemp %s=%s\n", v->name, t->name);
01671         } else
01672         if (keyword("temporary")) {
01673                 /* Mark a word as a temporary file. */
01674                 cell_t *tmp;
01675 
01676                 tmp= evaluate(inc(q), IMPLODE);
01677 
01678                 if (wordlist(&tmp, 1) < 0) {
01679                         dec(tmp);
01680                         key_usage("<word>");
01681                         return;
01682                 }
01683                 if (talk()) printf("temporary %s\n", tmp->name);
01684 
01685                 tmp->flags|= W_TEMP;
01686                 dec(tmp);
01687         } else
01688         if (keyword("stop")) {
01689                 /* Set the suffix to stop the transformation on. */
01690                 cell_t *suff;
01691 
01692                 if (phase > SCAN) { inappropriate(); return; }
01693 
01694                 suff= evaluate(inc(q), IMPLODE);
01695 
01696                 if (wordlist(&suff, 1) != 1) {
01697                         dec(suff);
01698                         key_usage("<suffix>");
01699                         return;
01700                 }
01701                 dec(V_stop);
01702                 V_stop= suff;
01703                 if (talk()) printf("stop %s\n", suff->name);
01704         } else
01705         if (keyword("numeric")) {
01706                 /* Check if a string denotes a number, like $n in -O$n. */
01707                 cell_t *num;
01708                 char *pn;
01709 
01710                 num= evaluate(inc(q), IMPLODE);
01711 
01712                 if (wordlist(&num, 1) != 1) {
01713                         dec(num);
01714                         key_usage("<arg>");
01715                         return;
01716                 }
01717                 if (talk()) printf("numeric %s\n", num->name);
01718 
01719                 (void) strtoul(num->name, &pn, 10);
01720                 if (*pn != 0) {
01721                         complain(phase == SCAN ? V_star->value : nil);
01722                         if (phase == SCAN) fputc(':', stderr);
01723                         fprintf(stderr, " '%s' is not a number\n", num->name);
01724                 }
01725                 dec(num);
01726         } else
01727         if (keyword("error")) {
01728                 /* Signal an error. */
01729                 cell_t *err;
01730 
01731                 err= evaluate(inc(q), IMPLODE);
01732 
01733                 if (wordlist(&err, 0) < 1) {
01734                         dec(err);
01735                         key_usage("expr ...");
01736                         return;
01737                 }
01738 
01739                 if (talk()) { printf("error "); prin2n(err); }
01740 
01741                 complain(err);
01742                 fputc('\n', stderr);
01743                 dec(err);
01744         } else
01745         if (keyword("if")) {
01746                 /* if (list) = (list) using set comparison. */
01747                 int eq;
01748 
01749                 if (n != 4 || r->car == nil || r->car->type != EQUALS) {
01750                         key_usage("<expr> = <expr>");
01751                         execute(DONT, pc->indent+1);
01752                         last_if= 1;
01753                         return;
01754                 }
01755                 q= q->car;
01756                 r= r->cdr->car;
01757                 if (talk()) {
01758                         printf("if ");
01759                         prin1(t= evaluate(inc(q), IMPLODE));
01760                         dec(t);
01761                         printf(" = ");
01762                         prin1n(t= evaluate(inc(r), IMPLODE));
01763                         dec(t);
01764                 }
01765                 eq= equal(q, r);
01766                 execute(eq ? DOIT : DONT, pc->indent+1);
01767                 last_if= eq;
01768         } else
01769         if (keyword("ifdef") || keyword("ifndef")) {
01770                 /* Is a variable defined or undefined? */
01771                 int doit;
01772 
01773                 if (n != 2 || (v= getvar(q->car)) == nil) {
01774                         key_usage("<var>");
01775                         execute(DONT, pc->indent+1);
01776                         last_if= 1;
01777                         return;
01778                 }
01779                 if (talk()) prin2n(p);
01780 
01781                 doit= ((v->flags & W_SET) != 0) ^ (p->car->name[2] == 'n');
01782                 execute(doit ? DOIT : DONT, pc->indent+1);
01783                 last_if= doit;
01784         } else
01785         if (keyword("iftemp") || keyword("ifhash")) {
01786                 /* Is a file a temporary file? */
01787                 /* Does a file need preprocessing? */
01788                 cell_t *file;
01789                 int doit= 0;
01790 
01791                 file= evaluate(inc(q), IMPLODE);
01792 
01793                 if (wordlist(&file, 1) != 1) {
01794                         dec(file);
01795                         key_usage("<arg>");
01796                         return;
01797                 }
01798                 if (talk()) printf("%s %s\n", p->car->name, file->name);
01799 
01800                 if (p->car->name[2] == 't') {
01801                         /* iftemp file */
01802                         if (file->flags & W_TEMP) doit= 1;
01803                 } else {
01804                         /* ifhash file */
01805                         int fd;
01806                         char hash;
01807 
01808                         if ((fd= open(file->name, O_RDONLY)) >= 0) {
01809                                 if (read(fd, &hash, 1) == 1 && hash == '#')
01810                                         doit= 1;
01811                                 close(fd);
01812                         }
01813                 }
01814                 dec(file);
01815 
01816                 execute(doit ? DOIT : DONT, pc->indent+1);
01817                 last_if= doit;
01818         } else
01819         if (keyword("else")) {
01820                 /* Else clause for an if, ifdef, or ifndef. */
01821                 if (n != 1) {
01822                         key_usage("");
01823                         execute(DONT, pc->indent+1);
01824                         return;
01825                 }
01826                 if (talk()) prin2n(p);
01827 
01828                 execute(!last_if ? DOIT : DONT, pc->indent+1);
01829         } else
01830         if (keyword("treat")) {
01831                 /* Treat a file as having a certain suffix. */
01832 
01833                 if (phase > SCAN) { inappropriate(); return; }
01834 
01835                 if (n == 3) {
01836                         q= evaluate(inc(q->car), IMPLODE);
01837                         r= evaluate(inc(r->car), IMPLODE);
01838                 }
01839                 if (n != 3 || wordlist(&q, 1) != 1 || wordlist(&r, 1) != 1) {
01840                         if (n == 3) { dec(q); dec(r); }
01841                         key_usage("<file> <suffix>");
01842                         return;
01843                 }
01844                 if (talk()) printf("treat %s %s\n", q->name, r->name);
01845 
01846                 dec(q->suffix);
01847                 q->suffix= r;
01848                 q->flags|= W_SUFF;
01849                 dec(q);
01850         } else
01851         if (keyword("apply")) {
01852                 /* Apply a transformation rule to the current input file. */
01853                 rule_t *rule, *sav_path;
01854                 cell_t *sav_wait, *sav_in, *sav_out;
01855                 program_t *sav_next;
01856 
01857                 if (phase != COMPILE) { inappropriate(); return; }
01858 
01859                 if (V_star->value->cdr != nil) {
01860                         fprintf(stderr, "\"%s\", line %u: $* is not one file\n",
01861                                 descr, pc->lineno);
01862                         action= 0;
01863                         return;
01864                 }
01865                 if (n == 3) {
01866                         q= evaluate(inc(q->car), IMPLODE);
01867                         r= evaluate(inc(r->car), IMPLODE);
01868                 }
01869                 if (n != 3 || wordlist(&q, 1) != 1 || wordlist(&r, 1) != 1) {
01870                         if (n == 3) { dec(q); dec(r); }
01871                         key_usage("<file> <suffix>");
01872                         return;
01873                 }
01874                 if (talk()) printf("apply %s %s\n", q->name, r->name);
01875 
01876                 /* Find a rule */
01877                 for (rule= rules; rule != nil; rule= rule->next) {
01878                         if (rule->type == TRANSFORM
01879                                 && rule->from == q && rule->to == r) break;
01880                 }
01881                 if (rule == nil) {
01882                         fprintf(stderr,
01883                                 "\"%s\", line %u: no %s %s transformation\n",
01884                                 descr, pc->lineno, q->name, r->name);
01885                         action= 0;
01886                 }
01887                 dec(q);
01888                 dec(r);
01889                 if (rule == nil) return;
01890 
01891                 /* Save the world. */
01892                 sav_path= rule->path;
01893                 sav_wait= rule->wait;
01894                 sav_in= V_in->value;
01895                 sav_out= V_out->value;
01896                 sav_next= nextpc;
01897 
01898                 /* Isolate the rule and give it new input. */
01899                 rule->path= rule;
01900                 rule->wait= V_star->value;
01901                 V_star->value= nil;
01902                 V_in->value= nil;
01903                 V_out->value= nil;
01904 
01905                 transform(rule);
01906 
01907                 /* Retrieve the new $* and repair. */
01908                 V_star->value= rule->wait;
01909                 rule->path= sav_path;
01910                 rule->wait= sav_wait;
01911                 V_in->value= sav_in;
01912                 V_out->value= sav_out;
01913                 V_out->flags= W_SET|W_LOCAL;
01914                 nextpc= sav_next;
01915         } else
01916         if (keyword("include")) {
01917                 /* Include another description file into this program. */
01918                 cell_t *file;
01919                 program_t *incl, *prog, **ppg= &prog;
01920 
01921                 file= evaluate(inc(q), IMPLODE);
01922 
01923                 if (wordlist(&file, 1) != 1) {
01924                         dec(file);
01925                         key_usage("<file>");
01926                         return;
01927                 }
01928                 if (talk()) printf("include %s\n", file->name);
01929                 descr= file->name;
01930                 incl= pc;
01931                 prog= get_prog();
01932                 dec(file);
01933 
01934                 /* Raise the program to the include's indent level. */
01935                 while (*ppg != nil) {
01936                         (*ppg)->indent += incl->indent;
01937                         ppg= &(*ppg)->next;
01938                 }
01939 
01940                 /* Kill the include and splice the included program in. */
01941                 dec(incl->line);
01942                 incl->line= nil;
01943                 *ppg= incl->next;
01944                 incl->next= prog;
01945                 pc= incl;
01946                 nextpc= prog;
01947         } else
01948         if (keyword("arg")) {
01949                 /* An argument scanning rule. */
01950 
01951                 if (phase > SCAN) { inappropriate(); return; }
01952 
01953                 if (n < 2) {
01954                         key_usage("<string> ...");
01955                         execute(DONT, pc->indent+1);
01956                         return;
01957                 }
01958                 if (talk()) prin2n(p);
01959 
01960                 newrule(ARG, inc(q), nil);
01961 
01962                 /* Always skip the body, it comes later. */
01963                 execute(DONT, pc->indent+1);
01964         } else
01965         if (keyword("transform")) {
01966                 /* A file transformation rule. */
01967 
01968                 if (phase > SCAN) { inappropriate(); return; }
01969 
01970                 if (n == 3) {
01971                         q= evaluate(inc(q->car), IMPLODE);
01972                         r= evaluate(inc(r->car), IMPLODE);
01973                 }
01974                 if (n != 3 || wordlist(&q, 1) != 1 || wordlist(&r, 1) != 1) {
01975                         if (n == 3) { dec(q); dec(r); }
01976                         key_usage("<suffix1> <suffix2>");
01977                         execute(DONT, pc->indent+1);
01978                         return;
01979                 }
01980                 if (talk()) printf("transform %s %s\n", q->name, r->name);
01981 
01982                 newrule(TRANSFORM, q, r);
01983 
01984                 /* Body comes later. */
01985                 execute(DONT, pc->indent+1);
01986         } else
01987         if (keyword("prefer")) {
01988                 /* Prefer a transformation over others. */
01989 
01990                 if (phase > SCAN) { inappropriate(); return; }
01991 
01992                 if (n == 3) {
01993                         q= evaluate(inc(q->car), IMPLODE);
01994                         r= evaluate(inc(r->car), IMPLODE);
01995                 }
01996                 if (n != 3 || wordlist(&q, 1) != 1 || wordlist(&r, 1) != 1) {
01997                         if (n == 3) { dec(q); dec(r); }
01998                         key_usage("<suffix1> <suffix2>");
01999                         return;
02000                 }
02001                 if (talk()) printf("prefer %s %s\n", q->name, r->name);
02002 
02003                 newrule(PREFER, q, r);
02004         } else
02005         if (keyword("combine")) {
02006                 /* A file combination (loader) rule. */
02007 
02008                 if (phase > SCAN) { inappropriate(); return; }
02009 
02010                 if (n == 3) {
02011                         q= evaluate(inc(q->car), IMPLODE);
02012                         r= evaluate(inc(r->car), IMPLODE);
02013                 }
02014                 if (n != 3 || wordlist(&q, 0) < 1 || wordlist(&r, 1) != 1) {
02015                         if (n == 3) { dec(q); dec(r); }
02016                         key_usage("<suffix-list> <suffix>");
02017                         execute(DONT, pc->indent+1);
02018                         return;
02019                 }
02020                 if (talk()) {
02021                         printf("combine ");
02022                         prin1(q);
02023                         printf(" %s\n", r->name);
02024                 }
02025 
02026                 newrule(COMBINE, q, r);
02027 
02028                 /* Body comes later. */
02029                 execute(DONT, pc->indent+1);
02030         } else
02031         if (keyword("scan") || keyword("compile")) {
02032                 program_t *next= nextpc;
02033 
02034                 if (n != 1) { key_usage(""); return; }
02035                 if (phase != INIT) { inappropriate(); return; }
02036 
02037                 if (talk()) prin2n(p);
02038 
02039                 argscan();
02040                 if (p->car->name[0] == 'c') compile();
02041                 nextpc= next;
02042         } else {
02043                 /* A UNIX command. */
02044                 t= evaluate(inc(pc->line), IMPLODE);
02045                 unix_exec(t);
02046                 dec(t);
02047         }
02048 }
02049 
02050 void execute(exec_t how, unsigned indent)
02051 /* Execute (or skip) all lines with at least the given indent. */
02052 {
02053         int work= 0;    /* Need to execute at least one line. */
02054         unsigned firstline;
02055         unsigned nice_indent= 0;        /* 0 = Don't know what's nice yet. */
02056 
02057         if (pc == nil) return;  /* End of program. */
02058 
02059         firstline= pc->lineno;
02060 
02061         if (how == DONT) {
02062                 /* Skipping a body, but is there another guard? */
02063                 pc= pc->next;
02064                 if (pc != nil && pc->indent < indent && pc->line != nil) {
02065                         /* There is one!  Bail out, then it get's executed. */
02066                         return;
02067                 }
02068         } else {
02069                 /* Skip lines with a lesser indentation, they are guards for
02070                  * the same substatements.  Don't go past empty lines.
02071                  */
02072                 while (pc != nil && pc->indent < indent && pc->line != nil)
02073                         pc= pc->next;
02074         }
02075 
02076         /* Execute all lines with an indentation of at least "indent". */
02077         while (pc != nil && pc->indent >= indent) {
02078                 if (pc->indent != nice_indent && how == DOIT) {
02079                         if (nice_indent != 0) {
02080                                 fprintf(stderr,
02081                         "\"%s\", line %u: (warning) sudden indentation shift\n",
02082                                         descr, pc->lineno);
02083                         }
02084                         nice_indent= pc->indent;
02085                 }
02086                 nextpc= pc->next;
02087                 if (how == DOIT) exec_one();
02088                 pc= nextpc;
02089                 work= 1;
02090         }
02091 
02092         if (indent > 0 && !work) {
02093                 fprintf(stderr, "\"%s\", line %u: empty body, no statements\n",
02094                         descr, firstline);
02095                 action= 0;
02096         }
02097 }
02098 
02099 int argmatch(int shift, cell_t *match, cell_t *match1, char *arg1)
02100 /* Try to match an arg rule to the input file list L_args.  Execute the arg
02101  * body (pc is set to it) on success.
02102  */
02103 {
02104         cell_t *oldval, *v;
02105         int m, oldflags;
02106         size_t i, len;
02107         int minus= 0;
02108 
02109         if (shift) {
02110                 /* An argument has been accepted and may be shifted to $*. */
02111                 cell_t **oldpstar= pV_star;
02112                 *pV_star= L_args;
02113                 L_args= *(pV_star= &L_args->cdr);
02114                 *pV_star= nil;
02115 
02116                 if (argmatch(0, match->cdr, nil, nil)) return 1;
02117 
02118                 /* Undo the damage. */
02119                 *pV_star= L_args;
02120                 L_args= *(pV_star= oldpstar);
02121                 *pV_star= nil;
02122                 return 0;
02123         }
02124 
02125         if (match == nil) {
02126                 /* A full match, execute the arg body. */
02127 
02128                 /* Enable $>. */
02129                 V_out->flags= W_SET|W_LOCAL;
02130 
02131                 if (verbose >= 3) {
02132                         prin2(pc->line);
02133                         printf(" =\b=\b= ");
02134                         prin2n(V_star->value);
02135                 }
02136                 execute(DOIT, pc->indent+1);
02137 
02138                 /* Append $> to the file list. */
02139                 if (V_out->value != nil) {
02140                         *pL_files= cons(CELL, V_out->value);
02141                         pL_files= &(*pL_files)->cdr;
02142                 }
02143 
02144                 /* Disable $>. */
02145                 V_out->value= nil;
02146                 V_out->flags= W_SET|W_LOCAL|W_RDONLY;
02147 
02148                 return 1;
02149         }
02150 
02151         if (L_args == nil) return 0;    /* Out of arguments to match. */
02152 
02153         /* Match is a list of words, substs and strings containing letters and
02154          * substs.  Match1 is the current element of the first element of match.
02155          * Arg1 is the current character of the first element of L_args.
02156          */
02157         if (match1 == nil) {
02158                 /* match1 is at the end of a string, then arg1 must also. */
02159                 if (arg1 != nil) {
02160                         if (*arg1 != 0) return 0;
02161                         return argmatch(1, match, nil, nil);
02162                 }
02163                 /* If both are nil: Initialize. */
02164                 match1= match->car;
02165                 arg1= L_args->car->name;
02166 
02167                 /* A subst may not match a leading '-'. */
02168                 if (arg1[0] == '-') minus= 1;
02169         }
02170 
02171         if (match1->type == WORD && strcmp(match1->name, arg1) == 0) {
02172                 /* A simple match of an argument. */
02173 
02174                 return argmatch(1, match, nil, nil);
02175         }
02176 
02177         if (match1->type == SUBST && !minus) {
02178                 /* A simple match of a subst. */
02179 
02180                 /* The variable gets the first of the arguments as its value. */
02181                 v= match1->subst;
02182                 if (v->flags & W_RDONLY) return 0;      /* ouch */
02183                 oldflags= v->flags;
02184                 v->flags= W_SET|W_LOCAL|W_RDONLY;
02185                 oldval= v->value;
02186                 v->value= inc(L_args->car);
02187 
02188                 m= argmatch(1, match, nil, nil);
02189 
02190                 /* Recover the value of the variable. */
02191                 dec(v->value);
02192                 v->flags= oldflags;
02193                 v->value= oldval;
02194                 return m;
02195         }
02196         if (match1->type != STRING) return 0;
02197 
02198         /* Match the first item in the string. */
02199         if (match1->car == nil) return 0;
02200 
02201         if (match1->car->type == LETTER
02202                         && match1->car->letter == (unsigned char) *arg1) {
02203                 /* A letter matches, try the rest of the string. */
02204 
02205                 return argmatch(0, match, match1->cdr, arg1+1);
02206         }
02207 
02208         /* It can only be a subst in a string now. */
02209         len= strlen(arg1);
02210         if (match1->car->type != SUBST || minus || len == 0) return 0;
02211 
02212         /* The variable can match from 1 character to all of the argument.
02213          * Matching as few characters as possible happens to be the Right Thing.
02214          */
02215         v= match1->car->subst;
02216         if (v->flags & W_RDONLY) return 0;      /* ouch */
02217         oldflags= v->flags;
02218         v->flags= W_SET|W_LOCAL|W_RDONLY;
02219         oldval= v->value;
02220 
02221         m= 0;
02222         for (i= match1->cdr == nil ? len : 1; !m && i <= len; i++) {
02223                 v->value= findnword(arg1, i);
02224 
02225                 m= argmatch(0, match, match1->cdr, arg1+i);
02226 
02227                 dec(v->value);
02228         }
02229         /* Recover the value of the variable. */
02230         v->flags= oldflags;
02231         v->value= oldval;
02232         return m;
02233 }
02234 
02235 void argscan(void)
02236 /* Match all the arguments to the arg rules, those that don't match are
02237  * used as files for transformation.
02238  */
02239 {
02240         rule_t *rule;
02241         int m;
02242 
02243         phase= SCAN;
02244 
02245         /* Process all the arguments. */
02246         while (L_args != nil) {
02247                 pV_star= &V_star->value;
02248 
02249                 /* Try all the arg rules. */
02250                 m= 0;
02251                 for (rule= rules; !m && rule != nil; rule= rule->next) {
02252                         if (rule->type != ARG) continue;
02253 
02254                         pc= rule->prog;
02255 
02256                         m= argmatch(0, rule->match, nil, nil);
02257                 }
02258                 dec(V_star->value);
02259                 V_star->value= nil;
02260 
02261                 /* On failure, add the first argument to the list of files. */
02262                 if (!m) {
02263                         *pL_files= L_args;
02264                         L_args= *(pL_files= &L_args->cdr);
02265                         *pL_files= nil;
02266                 }
02267         }
02268         phase= INIT;
02269 }
02270 
02271 int member(cell_t *p, cell_t *l)
02272 /* True if p is a member of list l. */
02273 {
02274         while (l != nil && l->type == CELL) {
02275                 if (p == l->car) return 1;
02276                 l= l->cdr;
02277         }
02278         return p == l;
02279 }
02280 
02281 long basefind(cell_t *f, cell_t *l)
02282 /* See if f has a suffix in list l + set the base name of f.
02283  * -1 if not found, preference number for a short basename otherwise. */
02284 {
02285         cell_t *suff;
02286         size_t blen, slen;
02287         char *base;
02288 
02289         /* Determine base name of f, with suffix. */
02290         if ((base= strrchr(f->name, '/')) == nil) base= f->name; else base++;
02291         blen= strlen(base);
02292 
02293         /* Try suffixes. */
02294         while (l != nil) {
02295                 if (l->type == CELL) {
02296                         suff= l->car; l= l->cdr;
02297                 } else {
02298                         suff= l; l= nil;
02299                 }
02300                 if (f->flags & W_SUFF) {
02301                         /* F has a suffix imposed on it. */
02302                         if (f->suffix == suff) return 0;
02303                         continue;
02304                 }
02305                 slen= strlen(suff->name);
02306                 if (slen < blen && strcmp(base+blen-slen, suff->name) == 0) {
02307                         /* Got it! */
02308                         dec(f->base);
02309                         f->base= findnword(base, blen-slen);
02310                         return 10000L * (blen - slen);
02311                 }
02312         }
02313         return -1;
02314 }
02315 
02316 #define NO_PATH         2000000000      /* No path found yet. */
02317 
02318 long shortest;          /* Length of the shortest path as yet. */
02319 
02320 rule_t *findpath(long depth, int seek, cell_t *file, rule_t *start)
02321 /* Find the path of the shortest transformation to the stop suffix. */
02322 {
02323         rule_t *rule;
02324 
02325         if (action == 0) return nil;
02326 
02327         if (start == nil) {
02328                 /* No starting point defined, find one using "file". */
02329 
02330                 for (rule= rules; rule != nil; rule= rule->next) {
02331                         if (rule->type < TRANSFORM) continue;
02332 
02333                         if ((depth= basefind(file, rule->from)) >= 0) {
02334                                 if (findpath(depth, seek, nil, rule) != nil)
02335                                         return rule;
02336                         }
02337                 }
02338                 return nil;
02339         }
02340 
02341         /* Cycle? */
02342         if (start->path != nil) {
02343                 /* We can't have cycles through combines. */
02344                 if (start->type == COMBINE) {
02345                         fprintf(stderr,
02346                                 "\"%s\": contains a combine-combine cycle\n",
02347                                 descr);
02348                         action= 0;
02349                 }
02350                 return nil;
02351         }
02352 
02353         /* Preferred transformations are cheap. */
02354         if (start->flags & R_PREFER) depth-= 100;
02355 
02356         /* Try to go from start closer to the stop suffix. */
02357         for (rule= rules; rule != nil; rule= rule->next) {
02358                 if (rule->type < TRANSFORM) continue;
02359 
02360                 if (member(start->to, rule->from)) {
02361                         start->path= rule;
02362                         rule->npaths++;
02363                         if (findpath(depth+1, seek, nil, rule) != nil)
02364                                 return start;
02365                         start->path= nil;
02366                         rule->npaths--;
02367                 }
02368         }
02369 
02370         if (V_stop == nil) {
02371                 fprintf(stderr, "\"%s\": no stop suffix has been defined\n",
02372                         descr);
02373                 action= 0;
02374                 return nil;
02375         }
02376 
02377         /* End of the line? */
02378         if (start->to == V_stop) {
02379                 /* Got it. */
02380                 if (seek) {
02381                         /* Second hunt, do we find the shortest? */
02382                         if (depth == shortest) return start;
02383                 } else {
02384                         /* Is this path shorter than the last one? */
02385                         if (depth < shortest) shortest= depth;
02386                 }
02387         }
02388         return nil;     /* Fail. */
02389 }
02390 
02391 void transform(rule_t *rule)
02392 /* Transform the file(s) connected to the rule according to the rule. */
02393 {
02394         cell_t *file, *in, *out;
02395         char *base;
02396 
02397         /* Let $* be the list of input files. */
02398         while (rule->wait != nil) {
02399                 file= rule->wait;
02400                 rule->wait= file->cdr;
02401                 file->cdr= V_star->value;
02402                 V_star->value= file;
02403         }
02404 
02405         /* Set $< to the basename of the first input file. */
02406         file= file->car;
02407         V_in->value= in= inc(file->flags & W_SUFF ? file : file->base);
02408         file->flags&= ~W_SUFF;
02409 
02410         /* Set $> to the output file name of the transformation. */
02411         out= newcell();
02412         out->type= WORD;
02413         base= rule->path == nil ? in->name : maketemp();
02414         out->name= allocate(nil,
02415                 (strlen(base)+strlen(rule->to->name)+1) * sizeof(*out->name));
02416         strcpy(out->name, base);
02417         if (rule->path == nil || strchr(rule->to->name, '/') == nil)
02418                 strcat(out->name, rule->to->name);
02419         out= inc(out);
02420         if (rule->path != nil) out->flags|= W_TEMP;
02421 
02422         V_out->value= out;
02423         V_out->flags= W_SET|W_LOCAL;
02424 
02425         /* Do a transformation.  (Finally) */
02426         if (verbose >= 3) {
02427                 printf("%s ", rule->type==TRANSFORM ? "transform" : "combine");
02428                 prin2(V_star->value);
02429                 printf(" %s\n", out->name);
02430         }
02431         pc= rule->prog;
02432         execute(DOIT, pc->indent+1);
02433 
02434         /* Hand $> over to the next rule, it must be a single word. */
02435         out= evaluate(V_out->value, IMPLODE);
02436         if (wordlist(&out, 1) != 1) {
02437                 fprintf(stderr,
02438                 "\"%s\", line %u: $> should be returned as a single word\n",
02439                         descr, rule->prog->lineno);
02440                 action= 0;
02441         }
02442 
02443         if ((rule= rule->path) != nil) {
02444                 /* There is a next rule. */
02445                 dec(out->base);
02446                 out->base= in;          /* Basename of input file. */
02447                 file= inc(newcell());
02448                 file->car= out;
02449                 file->cdr= rule->wait;
02450                 rule->wait= file;
02451         } else {
02452                 dec(in);
02453                 dec(out);
02454         }
02455 
02456         /* Undo the damage to $*, $<, and $>. */
02457         dec(V_star->value);
02458         V_star->value= nil;
02459         V_in->value= nil;
02460         V_out->value= nil;
02461         V_out->flags= W_SET|W_LOCAL|W_RDONLY;
02462 }
02463 
02464 void compile(void)
02465 {
02466         rule_t *rule;
02467         cell_t *file, *t;
02468 
02469         phase= COMPILE;
02470 
02471         /* Implode the files list. */
02472         L_files= evaluate(L_files, IMPLODE);
02473         if (wordlist(&L_files, 0) < 0) {
02474                 fprintf(stderr, "\"%s\": An assignment to $> contained junk\n",
02475                         descr);
02476                 action= 0;
02477         }
02478 
02479         while (action != 0 && L_files != nil) {
02480                 file= L_files->car;
02481 
02482                 /* Initialize. */
02483                 shortest= NO_PATH;
02484                 for (rule= rules; rule != nil; rule= rule->next)
02485                         rule->path= nil;
02486 
02487                 /* Try all possible transformation paths. */
02488                 (void) findpath(0L, 0, file, nil);
02489 
02490                 if (shortest == NO_PATH) {      /* Can't match the file. */
02491                         fprintf(stderr,
02492                         "%s: %s: can't compile, no transformation applies\n",
02493                                 program, file->name);
02494                         action= 0;
02495                         return;
02496                 }
02497 
02498                 /* Find the first short path. */
02499                 if ((rule= findpath(0L, 1, file, nil)) == nil) return;
02500 
02501                 /* Transform the file until you hit a combine. */
02502                 t= inc(newcell());
02503                 t->car= inc(file);
02504                 L_files= go(L_files, L_files->cdr);
02505                 t->cdr= rule->wait;
02506                 rule->wait= t;
02507                 while (action != 0 && rule != nil && rule->type != COMBINE) {
02508                         transform(rule);
02509                         rule= rule->path;
02510                 }
02511         }
02512 
02513         /* All input files have been transformed to combine rule(s).  Now
02514          * we need to find the combine rule with the least number of paths
02515          * running through it (this combine may be followed by another) and
02516          * transform from there.
02517          */
02518         while (action != 0) {
02519                 int least;
02520                 rule_t *comb= nil;
02521 
02522                 for (rule= rules; rule != nil; rule= rule->next) {
02523                         rule->path= nil;
02524 
02525                         if (rule->type != COMBINE || rule->wait == nil)
02526                                 continue;
02527 
02528                         if (comb == nil || rule->npaths < least) {
02529                                 least= rule->npaths;
02530                                 comb= rule;
02531                         }
02532                 }
02533 
02534                 /* No combine?  Then we're done. */
02535                 if (comb == nil) break;
02536 
02537                 /* Initialize. */
02538                 shortest= NO_PATH;
02539 
02540                 /* Try all possible transformation paths. */
02541                 (void) findpath(0L, 0, nil, comb);
02542 
02543                 if (shortest == NO_PATH) break;
02544 
02545                 /* Find the first short path. */
02546                 if ((rule= findpath(0L, 1, nil, comb)) == nil) return;
02547 
02548                 /* Transform until you hit another combine. */
02549                 do {
02550                         transform(rule);
02551                         rule= rule->path;
02552                 } while (action != 0 && rule != nil && rule->type != COMBINE);
02553         }
02554         phase= INIT;
02555 }
02556 
02557 cell_t *predef(char *var, char *val)
02558 /* A predefined variable var with value val, or a special variable. */
02559 {
02560         cell_t *p, *t;
02561 
02562         p= findword(var);
02563         if (val != nil) {       /* Predefined. */
02564                 t= findword(val);
02565                 dec(p->value);
02566                 p->value= t;
02567                 p->flags|= W_SET;
02568                 if (verbose >= 3) {
02569                         prin1(p);
02570                         printf(" =\b=\b= ");
02571                         prin2n(t);
02572                 }
02573         } else {                /* Special: $* and such. */
02574                 p->flags= W_SET|W_LOCAL|W_RDONLY;
02575         }
02576         t= inc(newcell());
02577         t->car= p;
02578         t->cdr= L_predef;
02579         L_predef= t;
02580         return p;
02581 }
02582 
02583 void usage(void)
02584 {
02585         fprintf(stderr,
02586         "Usage: %s -v<n> -vn<n> -name <name> -descr <descr> -T <dir> ...\n",
02587                 program);
02588         exit(-1);
02589 }
02590 
02591 int main(int argc, char **argv)
02592 {
02593         char *tmpdir;
02594         program_t *prog;
02595         cell_t **pa;
02596         int i;
02597 
02598         /* Call name of the program, decides which description to use. */
02599         if ((program= strrchr(argv[0], '/')) == nil)
02600                 program= argv[0];
02601         else
02602                 program++;
02603 
02604         /* Directory for temporary files. */
02605         if ((tmpdir= getenv("TMPDIR")) == nil || *tmpdir == 0)
02606                 tmpdir= "/tmp";
02607 
02608         /* Transform arguments to a list, processing the few ACD options. */
02609         pa= &L_args;
02610         for (i= 1; i < argc; i++) {
02611                 if (argv[i][0] == '-' && argv[i][1] == 'v') {
02612                         char *a= argv[i]+2;
02613 
02614                         if (*a == 'n') { a++; action= 1; }
02615                         verbose= 2;
02616 
02617                         if (*a != 0) {
02618                                 verbose= strtoul(a, &a, 10);
02619                                 if (*a != 0) usage();
02620                         }
02621                 } else
02622                 if (strcmp(argv[i], "-name") == 0) {
02623                         if (++i == argc) usage();
02624                         program= argv[i];
02625                 } else
02626                 if (strcmp(argv[i], "-descr") == 0) {
02627                         if (++i == argc) usage();
02628                         descr= argv[i];
02629                 } else
02630                 if (argv[i][0] == '-' && argv[i][1] == 'T') {
02631                         if (argv[i][2] == 0) {
02632                                 if (++i == argc) usage();
02633                                 tmpdir= argv[i];
02634                         } else
02635                                 tmpdir= argv[i]+2;
02636                 } else {
02637                         /* Any other argument must be processed. */
02638                         *pa= cons(CELL, findword(argv[i]));
02639                         pa= &(*pa)->cdr;
02640                 }
02641         }
02642 #ifndef DESCR
02643         /* Default description file is based on the program name. */
02644         if (descr == nil) descr= program;
02645 #else
02646         /* Default description file is predefined. */
02647         if (descr == nil) descr= DESCR;
02648 #endif
02649 
02650         inittemp(tmpdir);
02651 
02652         /* Catch user signals. */
02653         if (signal(SIGHUP, SIG_IGN) != SIG_IGN) signal(SIGHUP, interrupt);
02654         if (signal(SIGINT, SIG_IGN) != SIG_IGN) signal(SIGINT, interrupt);
02655         if (signal(SIGTERM, SIG_IGN) != SIG_IGN) signal(SIGTERM, interrupt);
02656 
02657         /* Predefined or special variables. */
02658         predef("PROGRAM", program);
02659         predef("VERSION", version);
02660 #ifdef ARCH
02661         predef("ARCH", ARCH);           /* Cross-compilers like this. */
02662 #endif
02663         V_star= predef("*", nil);
02664         V_in= predef("<", nil);
02665         V_out= predef(">", nil);
02666 
02667         /* Read the description file. */
02668         if (verbose >= 3) printf("include %s\n", descr);
02669         prog= get_prog();
02670 
02671         phase= INIT;
02672         pc= prog;
02673         execute(DOIT, 0);
02674 
02675         argscan();
02676         compile();
02677 
02678         /* Delete all allocated data to test inc/dec balance. */
02679         while (prog != nil) {
02680                 program_t *junk= prog;
02681                 prog= junk->next;
02682                 dec(junk->file);
02683                 dec(junk->line);
02684                 deallocate(junk);
02685         }
02686         while (rules != nil) {
02687                 rule_t *junk= rules;
02688                 rules= junk->next;
02689                 dec(junk->from);
02690                 dec(junk->to);
02691                 dec(junk->wait);
02692                 deallocate(junk);
02693         }
02694         deltemp();
02695         dec(V_stop);
02696         dec(L_args);
02697         dec(L_files);
02698         dec(L_predef);
02699 
02700         quit(action == 0 ? 1 : 0);
02701 }

Generated on Fri Apr 14 22:56:55 2006 for minix by  doxygen 1.4.6