minix/commands/acd/acd.c
2010-05-12 16:28:54 +00:00

2701 lines
57 KiB
C

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