/* fdevelop: convert script to intermediate language in two passes
       Input defined in script.def, output in int.def
       Pass 1 reads named file or stdin, writes intermediate file
       Pass 2 reads intermediate, writes output
       Intermediate file is like output, with these differences:
               Header lines (d v, d c, etc.) absent
               Erase commands: refer to line numbers in int file and
                       do not have geometric command following
               Geometry commands: numbers not yet scaled, slots not
                       assigned
  fdevelop is a filter, transforming stdin or one named file to stdout
       the shell script develop uses this program to develop foo.s
         into foo.i, if needed
*/

/* fdevelop.h: header file for fdevelop */

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>

typedef struct Symbol { /* symbol table entry */
       struct Symbol   *next;
       char    *instr;
       int     innum;
       int     outnum;
} Symbol;

#define eq(s, t)        (strcmp(s,t) == 0)

extern char     *cmdname;
extern int      lineno;

extern char     *NULLSTR;
extern Symbol   *lookup(char*, int);
extern void     insert(char *, int, int);
extern void     delete(char *, int);
extern void     opensymtab(void);
extern void     closesymtab(void);
extern int      hash(char*, int);

#define FATAL   1
#define WARN    0
extern void     error(int, char*);
extern char     *emalloc(int);
extern void     efree(char *);

#define MAXSTR  40      /* view names, click names, etc. */
#define MAXBUF  500     /* text strings */
#define LINEMAX 50000   /* input lines */
#define SLOTMAX 10000   /* for erasures */

extern int      moreinput(FILE *);
extern void     lex(FILE*);
extern void     lexstr(FILE*);
extern void     lexrest(FILE*);
extern void     gobble(FILE*);
extern void     gobble2(FILE*);
extern          char buf[];
extern          lexsaweof;

/* end of header */




#define SCALE   9999

extern void     dox(int);
extern void     doy(int);

/* SLOTS */
int     slotmax = SLOTMAX;

/* STATUS OF EACH LINE IN INTERMEDIATE FILE*/
int     linemax = LINEMAX;
char    *linevec;       /* malloc'ed to [linemax] before pass 1 */

#define MAXVIEWS        10
                         /* if >10 views, change one-byte getc in pass2 */
#define ERASED          11
#define NOTGEOM         12

/* VIEWS */
extern void     recordx(int, float);
extern void     recordy(int, float);
extern int      scalex(int, float);
extern int      scaley(int, float);
int     viewcnt = 0;
struct  Viewelmt {
       char    name[MAXSTR];   /* character string name */
       float   maxx;           /* range of values, both coords */
       float   minx;
       float   maxy;
       float   miny;
       float   factorx;        /* factors used by scalex and scaley */
       float   factory;
       int     initx;          /* max and min set yet? */
       int     inity;
}       viewarr[MAXVIEWS];

/* CLICKS */
#define MAXCLICKS       30
int     clickcnt = 0;
char    clickname [MAXCLICKS] [MAXSTR];

/* OPTIONS FOR GEOMETRIC OBJECTS */
extern void     getoptions(int);
#define OPTMAX 10
char    optstring[OPTMAX];
#define TEXT    0
#define LINE    1
#define BOX     2
#define CIRCLE  3
char    *defaulttab[] = { /* match with above defines */
       "cm7",  /* TEXT:   center, medium, white        */
       "s-7",  /* LINE:   solid, -, white              */
       "n7",   /* BOX:    nofill, white                */
       "n7"    /* CIRCLE: nofill, white                */
};
struct  Geoelmt {
       int     cmd;
       int     pos;
       char    *name;
       char    val;
} geooptarr[] = {
       TEXT,   0,      "center",       'c',
       TEXT,   0,      "ljust",        'l',
       TEXT,   0,      "rjust",        'r',
       TEXT,   0,      "above",        'a',
       TEXT,   0,      "below",        'b',
       TEXT,   1,      "medium",       'm',
       TEXT,   1,      "small",        's',
       TEXT,   1,      "big",          'b',
       TEXT,   1,      "bigbig",       'B',
       TEXT,   2,      "c0",           '0',
       TEXT,   2,      "c1",           '1',
       TEXT,   2,      "c2",           '2',
       TEXT,   2,      "c3",           '3',
       TEXT,   2,      "c4",           '4',
       TEXT,   2,      "c5",           '5',
       TEXT,   2,      "c6",           '6',
       TEXT,   2,      "c7",           '7',
       TEXT,   2,      "black",        '0',
       TEXT,   2,      "red",          '1',
       TEXT,   2,      "green",        '2',
       TEXT,   2,      "yellow",       '3',
       TEXT,   2,      "blue",         '4',
       TEXT,   2,      "magenta",      '5',
       TEXT,   2,      "cyan",         '6',
       TEXT,   2,      "white",        '7',
       LINE,   0,      "solid",        's',
       LINE,   0,      "fat",          'f',
       LINE,   0,      "fatfat",       'F',
       LINE,   0,      "dotted",       'o',
       LINE,   0,      "dashed",       'a',
       LINE,   1,      "-",            '-',
       LINE,   1,      "->",           '>',
       LINE,   1,      "<-",           '<',
       LINE,   1,      "<->",          'x',
       LINE,   2,      "c0",           '0',
       LINE,   2,      "c1",           '1',
       LINE,   2,      "c2",           '2',
       LINE,   2,      "c3",           '3',
       LINE,   2,      "c4",           '4',
       LINE,   2,      "c5",           '5',
       LINE,   2,      "c6",           '6',
       LINE,   2,      "c7",           '7',
       LINE,   2,      "black",        '0',
       LINE,   2,      "red",          '1',
       LINE,   2,      "green",        '2',
       LINE,   2,      "yellow",       '3',
       LINE,   2,      "blue",         '4',
       LINE,   2,      "magenta",      '5',
       LINE,   2,      "cyan",         '6',
       LINE,   2,      "white",        '7',
       BOX,    0,      "nofill",       'n',
       BOX,    0,      "fill",         'f',
       BOX,    1,      "c0",           '0',
       BOX,    1,      "c1",           '1',
       BOX,    1,      "c2",           '2',
       BOX,    1,      "c3",           '3',
       BOX,    1,      "c4",           '4',
       BOX,    1,      "c5",           '5',
       BOX,    1,      "c6",           '6',
       BOX,    1,      "c7",           '7',
       BOX,    1,      "black",        '0',
       BOX,    1,      "red",          '1',
       BOX,    1,      "green",        '2',
       BOX,    1,      "yellow",       '3',
       BOX,    1,      "blue",         '4',
       BOX,    1,      "magenta",      '5',
       BOX,    1,      "cyan",         '6',
       BOX,    1,      "white",        '7',
       CIRCLE, 0,      "nofill",       'n',
       CIRCLE, 0,      "fill",         'f',
       CIRCLE, 1,      "c0",           '0',
       CIRCLE, 1,      "c1",           '1',
       CIRCLE, 1,      "c2",           '2',
       CIRCLE, 1,      "c3",           '3',
       CIRCLE, 1,      "c4",           '4',
       CIRCLE, 1,      "c5",           '5',
       CIRCLE, 1,      "c6",           '6',
       CIRCLE, 1,      "c7",           '7',
       CIRCLE, 1,      "black",        '0',
       CIRCLE, 1,      "red",          '1',
       CIRCLE, 1,      "green",        '2',
       CIRCLE, 1,      "yellow",       '3',
       CIRCLE, 1,      "blue",         '4',
       CIRCLE, 1,      "magenta",      '5',
       CIRCLE, 1,      "cyan",         '6',
       CIRCLE, 1,      "white",        '7'
};
int     geooptcnt = (sizeof(geooptarr) / sizeof(struct Geoelmt));

/* VARIABLES FOR LEXICAL ANALYSIS */
char    buf[MAXBUF];
int     lexsaweof = 0;

/* UTILITY VARIABLES */
char    *NULLSTR = "";
char    *cmdname;
int     lineno, outlineno;
char    *tempfname;
FILE    *infp, *tempfp;

extern void     pass1(void);
extern void     pass2(void);
extern int      strisnum(char*);

#define STOF(s) ((float) atof(s))

main(int argc, char *argv[])
{
       int     i;

       cmdname = argv[0];
       while (argc > 1 && argv[1][0] == '-') {
               switch (argv[1][1]) {
               case 'l':
                       linemax = atoi(&argv[1][2]);
                       break;
               case 's':
                       slotmax = atoi(&argv[1][2]);
                       break;
               }
               argc--;
               argv++;
       }
       if (argc == 1)
               infp = stdin;
       else {
               if ((infp = fopen(argv[1], "r")) == NULL)
                       error(FATAL, "can't open input file");
       }
       linevec = emalloc(linemax);
       for (i = 0; i < linemax; i++)
               linevec[i] = 0;

       tempfname = tmpnam(NULL);
       if ((tempfp = fopen(tempfname, "w")) == NULL)
               error(FATAL, "can't open temp file");
       pass1();
       fclose(tempfp);
       if ((tempfp = fopen(tempfname, "r")) == NULL)
               error(FATAL, "can't open temp file");
       pass2();
       remove(tempfname); /* DEBUG */
       exit(0);
}

void pass1(void)
{
       int     currentview;            /* view number */
       int     erased[MAXVIEWS];       /* last line erased */
       int     badlabel;               /* 1 if label, 0 if geom */
       char    savelabel[MAXSTR];      /* label */
       int     geomalready = 0;        /* seen any geometry yet? */
       struct Symbol   *sp;
       int     i;

#define DOGEOM          badlabel = 0;\
                       geomalready = 1;\
                       linevec[outlineno] = currentview;

#define DOERASE(L)      linevec[(L)] = ERASED;\
                       linevec[outlineno] = NOTGEOM;\
                       fprintf(tempfp, "e\t%d\n", (L));\
                       outlineno++;

       /* init */
       currentview = 0;
       for (i = 0; i < MAXVIEWS; i++)
               erased[i] = 0;
       opensymtab();

       /* read and process file */
       lineno = 0;
       outlineno = 1;
       while (moreinput(infp)) {
               if (++lineno > linemax)
                       error(FATAL, "too many input lines");
               /* fprintf(stderr, "STARTING TO PROCESS INPUT LINE %d\n",
                       lineno); */
               linevec[outlineno] = NOTGEOM;
               lex(infp);
               if (buf[0] == '#') {
                       gobble2(infp);
                       continue;
               }
               badlabel = 0;
               if (buf[strlen(buf)-1] == ':') {
                       buf[strlen(buf)-1] = '\0';
                       badlabel = 1;
                       strcpy(savelabel, buf);
                       sp = lookup(buf, currentview);
                       if (sp != NULL) {
                               if (linevec[sp->outnum] == currentview) {
                                       DOERASE(sp->outnum)
                               }
                               sp->outnum = outlineno;
                       } else {
                               insert(buf, currentview, outlineno);
                       }
                       lex(infp);
               }
               if (eq(buf, "text")) {
                       DOGEOM
                       getoptions(TEXT);
                       fprintf(tempfp, "g\t0\tt\t%d\t%s",
                               currentview, optstring);
                       dox(currentview);
                       lex(infp);
                       doy(currentview);
                       lexstr(infp);
                       fprintf(tempfp, "\t%s\n", buf);
                       outlineno++;
               } else if (eq(buf, "circle")) {
                       float savex, rad;
                       DOGEOM
                       getoptions(CIRCLE);
                       fprintf(tempfp, "g\t0\tc\t%d\t%s",
                               currentview, optstring);
                       dox(currentview);
                       savex = STOF(buf);
                       lex(infp);
                       doy(currentview);
                       lex(infp);
                       if (!strisnum(buf)) {
                               error(WARN, "radius not a number");
                               strcpy(buf, "0");
                       }
                       rad = STOF(buf);
                       if (rad < 0.0)
                               error(WARN, "radius is negative");
                       recordx(currentview, savex-rad);
                       recordx(currentview, savex+rad);
                       fprintf(tempfp, "\t%s\n", buf);
                       outlineno++;
                       gobble(infp);
               } else if (eq(buf, "line") || eq(buf, "box")) {
                       char cmdchar;
                       DOGEOM
                       if (eq(buf, "line")) {
                               getoptions(LINE);
                               cmdchar = 'l';
                       } else {
                               getoptions(BOX);
                               cmdchar = 'b';
                       }
                       fprintf(tempfp, "g\t0\t%c\t%d\t%s",
                               cmdchar, currentview, optstring);
                       dox(currentview);
                       lex(infp);
                       doy(currentview);
                       lex(infp);
                       dox(currentview);
                       lex(infp);
                       doy(currentview);
                       fprintf(tempfp, "\n");
                       outlineno++;
                       gobble(infp);
               } else if (eq(buf, "view")) {
                       lex(infp);
                       if (eq(buf,NULLSTR)) {
                               error(WARN, "no name in view statement");
                               strcpy(buf, "def.view");
                       }
                       if (viewcnt == 0 && geomalready)
                               error(WARN, "first view after geom");
                       for (i = 0; i < viewcnt; i++)
                               if (eq(buf, viewarr[i].name))
                                       break;
                       if (i >= viewcnt) {
                               viewcnt++;
                               if (i >= MAXVIEWS)
                                       error(FATAL, "too many views");
                               strcpy(viewarr[i].name, buf);
                               viewarr[i].initx = viewarr[i].inity = 0;
                       }
                       currentview = i;
                       gobble(infp);
               } else if (eq(buf, "click")) {
                       lex(infp);
                       if (eq(buf,NULLSTR))
                               strcpy(buf, "def.click");
                       for (i = 0; i < clickcnt; i++)
                               if (eq(buf, clickname[i]))
                                       break;
                       if (i >= clickcnt) {
                               clickcnt++;
                               if (i >= MAXCLICKS)
                                       error(FATAL, "too many click names");
                               strcpy(clickname[i], buf);
                       }
                       fprintf(tempfp, "c\t%d\n", i);
                       outlineno++;
                       gobble(infp);
               } else if (eq(buf, "erase")) {
                       lex(infp);
                       if (eq(buf,NULLSTR))
                               error(WARN, "no label in erase statement");
                       sp = lookup(buf, currentview);
                       if (sp != NULL) {
                               if (linevec[sp->outnum] == currentview) {
                                       DOERASE(sp->outnum)
                               }
                               delete(buf, currentview);
                       } else {
                               error(WARN, "undefined label");
                       }
                       gobble(infp);
               } else if (eq(buf, "clear")) {
                       int i, endline;
                       fprintf(tempfp, "b\ts\t%d\n", currentview);
                       endline = outlineno++;
                       for (i = erased[currentview]+1; i <= endline; i++) {
                               if (linevec[i] == currentview) {
                                       DOERASE(i)
                               }
                       }
                       erased[currentview] = outlineno;
                       fprintf(tempfp, "b\te\t%d\n", currentview);
                       linevec[outlineno] = NOTGEOM;
                       outlineno++;
                       gobble(infp);
               } else {
                       if (!eq(buf, NULLSTR))
                               error(WARN, "unrecognized command");
                       gobble(infp);
               }
               if (badlabel) {
                       error(WARN, "label on nongeometric object");
                       delete(savelabel, currentview);
               }
       }
       /* tidy up */
       lineno = 0;
       closesymtab();
       if (viewcnt == 0) {
               viewcnt = 1;
               strcpy(viewarr[0].name, "def.view");
       }
}


void pass2(void)
{
       typedef struct Slot {
               union {
                       int     i;
                       char    *p;
               } v;
       } Slot;
       char    cmd;            /* first char on a line */
       int     tlineno;        /* line number in temp file */
       int     slothead;       /* ptr to free list within slots */
       Slot    *slotarr;       /* init by malloc to slotmax elmts */
       int     i;
       int     c;
       int     v;
       struct Viewelmt *vp;

       /* Init */
       opensymtab();
       slotarr = (Slot *) emalloc(slotmax * sizeof(Slot));
       for (i = 1; i <= slotmax-2; i++)
               slotarr[i].v.i = i+1;
       slothead = 1;
       /* Write header for output file */
       for (i = 0; i < viewcnt; i++) {
               vp = &viewarr[i];
               printf("d\tv\t%d\t%s\t%g\t%g\t%g\t%g\n", i,
                       vp->name, vp->minx, vp->miny, vp->maxx, vp->maxy);
       }
       for (i = 0; i < clickcnt; i++)
               printf("d\tc\t%d\t%s\n", i, clickname[i]);
       printf("d\tp\te\n");
       /* Calculate view factors used to scale */
       for (v = 0; v < viewcnt; v++) {
               vp = &viewarr[v];
               if (vp->minx == vp->maxx) {
                       vp->minx = 0.0;
                       vp->maxx = 2*vp->maxx;
                       if (vp->maxx == 0.0) {
                               vp->minx = -1.0;
                               vp->maxx =  1.0;
                       }
               }
               vp->factorx = SCALE / (vp->maxx-vp->minx);
               if (vp->miny == vp->maxy) {
                       vp->miny = 0.0;
                       vp->maxy = 2*vp->maxy;
                       if (vp->maxy == 0.0) {
                               vp->miny = -1.0;
                               vp->maxy =  1.0;
                       }
               }
               vp->factory = SCALE / (vp->maxy-vp->miny);
       }
       /* Read and process intermediate file */
       tlineno = 0;
       while ((c = getc(tempfp)) != EOF) {
               tlineno++;
               cmd = c;
               getc(tempfp);   /* gobble tab */
               putchar(cmd);
               putchar('\t');
               if (cmd == 'g') {
                       int snum, vnum;
                       char gcmd;
                       char opts[OPTMAX];
                       float x1, y1, x2, y2;
                       int   i1, j1, i2, j2;
                       char bufa[MAXBUF+100];
                       /* fscanf(tempfp, "%d %c %d", &snum, &gcmd, &vnum); */
                         getc(tempfp); /* don't need snum -- always zero */
                         getc(tempfp); /* gobble tab */
                         gcmd = getc(tempfp);
                         getc(tempfp); /* gobble tab */
                         vnum = getc(tempfp) - '0';
                       fscanf(tempfp, "%s %f %f", opts, &x1, &y1);
                       if (linevec[tlineno] != ERASED)
                               snum = 0;
                       else {
                               snum = slothead;
                               slothead = slotarr[snum].v.i;
                               if (slothead == 0)
                                       error(FATAL, "ran out of slots");
                       }
                       insert(NULLSTR, tlineno, snum);
                       i1 = scalex(vnum, x1);
                       j1 = scaley(vnum, y1);
                       if (gcmd == 'b' || gcmd == 'l') {
                               fscanf(tempfp, "%f %f", &x2, &y2);
                               if (getc(tempfp) != '\n')
                                       error(FATAL, "develop bug: missing newline");
                               i2 = scalex(vnum, x2);
                               j2 = scaley(vnum, y2);
                               if (gcmd == 'b') {
                                       int t; /* normalize: min, max */
                                       if (i1 > i2) { t=i1; i1=i2; i2=t; }
                                       if (j1 > j2) { t=j1; j1=j2; j2=t; }
                               }
                               sprintf(bufa, "%d\t%c\t%d\t%s\t%d\t%d\t%d\t%d",
                                       snum, gcmd, vnum, opts, i1, j1, i2, j2);
                       } else if (gcmd == 'c') {
                               fscanf(tempfp, "%f", &x2);
                               if (getc(tempfp) != '\n')
                                       error(FATAL, "develop bug: missing newline");
                               i2 = scalex(vnum, x1+x2) - i1;
                               if (i2 < 0)
                                       i2 = -i2;
                               if (i2 == 0)
                                       i2 = 1;
                               sprintf(bufa, "%d\t%c\t%d\t%s\t%d\t%d\t%d",
                                       snum, gcmd, vnum, opts, i1, j1, i2);
                       } else if (gcmd == 't') {
                               getc(tempfp); /* gobble tab */
                               lexrest(tempfp);
                               sprintf(bufa, "%d\t%c\t%d\t%s\t%d\t%d\t%s",
                                       snum, gcmd, vnum, opts, i1, j1, buf);
                       } else error(FATAL, "develop bug: invalid g cmd");
                       slotarr[snum].v.p = emalloc(strlen(bufa)+1);
                       strcpy(slotarr[snum].v.p, bufa);
                       puts(bufa);
               } else if (cmd == 'e') {
                       int linenum, slotnum;
                       Symbol *sp;
                       fscanf(tempfp, "%d", &linenum);
                       if (getc(tempfp) != '\n')
                               error(FATAL, "develop bug: missing newline");
                       sp = lookup(NULLSTR, linenum);
                       if (sp == NULL)
                               error(FATAL, "develop bug: bad erase lookup");
                       slotnum = sp->outnum;
                       puts(slotarr[slotnum].v.p);
                       efree(slotarr[slotnum].v.p);
                       slotarr[slotnum].v.i = slothead;
                       slothead = slotnum;
                       delete(NULLSTR, linenum);
               } else {
                       lexrest(tempfp);
                       puts(buf);
               }
       }
       /* tidy up */
               /* closesymtab(); delete stuff in slots? */
}

void recordx(int vnum, float t)
{
       struct Viewelmt *vp;

       vp = &viewarr[vnum];
       if (vp->initx != 1) {
               vp->initx = 1;
               vp->minx = t;
               vp->maxx = t;
       } else {
               if (t < vp->minx)
                       vp->minx = t;
               if (t > vp->maxx)
                       vp->maxx = t;
       }
}

void recordy(int vnum, float t)
{
       struct Viewelmt *vp;

       vp = &viewarr[vnum];
       if (vp->inity != 1) {
               vp->inity = 1;
               vp->miny = t;
               vp->maxy = t;
       } else {
               if (t < vp->miny)
                       vp->miny = t;
               if (t > vp->maxy)
                       vp->maxy = t;
       }
}

int scalex(int vnum, float t)
{
       struct Viewelmt *vp;

       vp = &viewarr[vnum];
       return (int) ((t - vp->minx) * vp->factorx);
}

int scaley(int vnum, float t)
{
       struct Viewelmt *vp;

       vp = &viewarr[vnum];
       return (int) ((t - vp->miny) * vp->factory);
}

void getoptions(int cmdtype)    /* put options into optstring */
{
       int i;
       struct Geoelmt *gp;

       strcpy(optstring, defaulttab[cmdtype]);
       for (lex(infp); !eq(buf,NULLSTR) && !strisnum(buf); lex(infp)) {
               for (i = 0; i < geooptcnt; i++) {
                       gp = &geooptarr[i];
                       if (cmdtype == gp->cmd && eq(buf, gp->name))
                               break;
               }
               if (i < geooptcnt)
                       optstring[gp->pos] = gp -> val;
               else
                       error(WARN, "unrecognized option");
       }
}

void dox(int view)      /* handle x in input file */
{
       if (!strisnum(buf)) {
               error(WARN, "x value not a number");
               strcpy(buf, "0");
       }
       recordx(view, STOF(buf));
       fprintf(tempfp, "\t%s", buf);
}

void doy(int view)      /* handle y in input file */
{
       if (!strisnum(buf)) {
               error(WARN, "y value not a number");
               strcpy(buf, "0");
       }
       recordy(view, STOF(buf));
       fprintf(tempfp, "\t%s", buf);
}



/* symbol.c:
       General: functions for mapping {string} x {int} -> {int}
       In pass 1: {name} x {viewnum} -> {int file line num}
       In pass 2: {} x {int file line num} -> {slot num}
                  Therefore be careful with blank strings
*/

#define steq(s, n, p) (p->innum == n && ((p->instr == s) || eq(p->instr,s)))

#define SIZE    2053
Symbol  *head[SIZE];

int hash(char *s, int n)        /* form hash value */
{
       int hashval;

       for (hashval = 0; *s != '\0'; s++)
               hashval = (*s + 31 * hashval) % SIZE;
       hashval = (31 * hashval + 1259 * n) % SIZE;
       return hashval;
}

Symbol *lookup(char *s, int n)  /* return element with s, n */
{
       Symbol *p;
       for (p = head[hash(s, n)]; p != NULL; p = p->next)
               if (steq(s, n, p))
                       return p;
       return NULL;
}

void insert(char *s, int n, int v)      /* insert s, n with value v */
{
       Symbol *p;
       char *q;
       int i;

       /* fprintf(stderr, "Inserting: |%s|, %d with hash %d\n",
               s, n, hash(s,n)); */
       if (*s == '\0')
               q = NULLSTR;
       else {
               q = emalloc(strlen(s)+1);
               strcpy(q, s);
       }
       p = (Symbol *) emalloc(sizeof(Symbol));
       p->instr = q;
       p->innum = n;
       p->outnum = v;
       i = hash(s, n);
       p->next = head[i];
       head[i] = p;
}

void delete(char *s, int n)     /* remove s, n */
{
       Symbol *p, *pp;
       int i;

       /* fprintf(stderr, "Deleting:  |%s|, %d with hash %d\n",
               s, n, hash(s,n)); */
       i = hash(s, n);
       pp = NULL;
       for (p = head[i]; p != NULL; p = p->next) {
               if (steq(s, n, p))
                       break;
               pp = p;
       }
       if (p == NULL)
               error(FATAL, "symtab bug: bad delete");
       if (p->instr != NULLSTR)
               efree(p->instr);
       if (pp == NULL) {
               head[i] = p->next;
       } else {
               pp->next = p->next;
       }
       efree((char *) p);
}

void opensymtab(void)   /* init table */
{
       int i;

       for (i = 0; i < SIZE; i++)
               head[i] = NULL;
}

void closesymtab(void)  /* reclaim storage */
{
       int i;
       Symbol *p, *np;

       for (i = 0; i < SIZE; i++)
               for (p = head[i]; p != NULL; p = np) {
                       if (p->instr != NULLSTR)
                               efree(p->instr);
                       np = p->next;
                       efree((char *) p);
               }
}



/* util.c: utility routines */


void error(int f, char *s)
{
       fprintf(stderr, "%s: %s\n", cmdname, s);
       if (lineno)
               fprintf(stderr, " source line number %d\n", lineno);
       if (f)
               exit(1);
}

int strisnum(char *p)           /* 1 if string p represents a float */
{
       int digits = 0;
       int n;
                                               /* REG EXPR:    */
       if (*p == '-')                          /* -?           */
               p++;
       while (isdigit(*p)) {
               digits++;                       /* [0-9]*       */
               p++;
       }
       if (*p == '.') {                        /* (.[0-9]*)?   */
               p++;
               while (isdigit(*p)) {
                       digits++;
                       p++;
               }
       }
       if (digits == 0)                        /* >0 digits    */
               return 0;
       if (tolower(*p) == 'e') {               /* ([eE]        */
               *p++;
               if (*p == '+' || *p == '-')     /*  [+-]?       */
                       p++;
               digits = 1;
               if (!isdigit(*p))               /*  [0-9]       */
                       return 0;
               n = *p++ - '0';
               while (isdigit(*p)) {           /*  [0-9]+      */
                       digits++;
                       n = 10*n + *p++ - '0';
                       if (n > 30)
                               return 0;
               }
               if (digits == 0)
                       return 0;               /*        )?    */
       }
       return (*p == '\0');
}

int moreinput(FILE *fp)
{
       int c;

       if (lexsaweof)
               return 0;
       c = getc(fp);
       if (c == EOF)
               return 0;
       ungetc(c, fp);
       return 1;
}

#define CHECKEOF                if (c == EOF) {\
               lexsaweof = 1;\
               error(WARN, "file does not end with newline");\
               return;\
       }


void lex(FILE *fp)      /* put next string of non-white into buf */
{
       int c;
       char *p, *danger;

       danger = &buf[MAXSTR-1];
       while ((c = getc(fp)) == ' ' || c == '\t')
               ;
       CHECKEOF
       if (c == '\n') {
               ungetc(c, fp);
               buf[0] = '\0';
               return;
       }
       p = buf;
       *p++ = c;
       while ((c = getc(fp)) != EOF && c != ' ' && c != '\t' && c != '\n') {
               if (p < danger)
                       *p++ = c;
               else if (p == danger)
                       error(WARN, "string too long -- truncated");
       }
       *p = '\0';
       CHECKEOF
       if (c == '\n')
               ungetc(c, fp);
/* fprintf(stderr, "lex returning: %s\n", buf); */
}


void lexstr(FILE *fp)   /* like lex, but go til newline and handle quotes */
{
       int c;
       int quoted = 0;
       char *p, *danger;

       while ((c = getc(fp)) == ' ' || c == '\t')
               ;
       CHECKEOF
       if (c == '\"') {
               quoted = 1;
               c = getc(fp);
               CHECKEOF
       }
       if (c == '\n') {
               buf[0] = '\0';
               return;
       }
       p = buf;
       *p++ = c;
       danger = &buf[MAXBUF-1];
       while ((c = getc(fp)) != EOF && c != '\n') {
               if (p < danger)
                       *p++ = c;
               else if (p == danger)
                       error(WARN, "text string too long -- truncated");
       }
       CHECKEOF
       *p = '\0';
       if (quoted && *(--p) == '\"')
               *p = '\0';
}

void lexrest(FILE *fp)  /* get rest of line into buf */
                       /* no error checking; error free from Pass 1 */
{
       int c;
       char *p;

       p = buf;
       while ((c = getc(fp)) != EOF && c != '\n') {
               *p++ = c;
       }
       *p = '\0';
}

void gobble(FILE *fp)   /* chew space til EOF; complain if nonwhite */
{
       int c;

       while ((c = getc(fp)) == ' ' || c == '\t')
               ;
       CHECKEOF
       if (c != '\n') {
               error(WARN, "garbage at end of line");
               while ((c = getc(fp)) != EOF && c != '\n')
                       ;
               CHECKEOF
       }
}

void gobble2(FILE *fp)          /* chew space til EOF, no complaints */
{
       int c;

       while ((c = getc(fp)) != EOF && c != '\n')
               ;
       CHECKEOF
}

#define WANTPROF 0
#if WANTPROF
       int     hmallocinit;
       FILE    *hmallocfp;
#endif

char *emalloc(int n)    /* check return from malloc */
{
       char *p;

#if WANTPROF
       if (hmallocinit == 0) {
               hmallocinit = 1;
               if ((hmallocfp = fopen("/tmp/malloc.hist", "w")) == NULL)
                       error(FATAL, "malloc history bug: can't open file");
       }
#endif
       p = malloc((unsigned) n);
       if (p == NULL)
               error(FATAL, "out of memory");
#if WANTPROF
       fprintf(hmallocfp, "m\t%d\t%d\n", (int) p, n);
#endif
       return p;
}

void efree(char *p)             /* personal version of free to match emalloc */
{
#if WANTPROF
       if (hmallocinit == 0)
               error(FATAL, "malloc history bug: first free before malloc");
       fprintf(hmallocfp, "f\t%d\n", (int) p);
#endif
       free(p);
}