home *** CD-ROM | disk | FTP | other *** search
/ ftp.cs.arizona.edu / ftp.cs.arizona.edu.tar / ftp.cs.arizona.edu / icon / historic / v941.tgz / icon.v941src.tar / icon.v941src / src / common / ipp.c < prev    next >
C/C++ Source or Header  |  2001-12-12  |  25KB  |  980 lines

  1. /*
  2.  * ipp.c -- the Icon preprocessor.
  3.  *
  4.  *  All Icon source passes through here before translation or compilation.
  5.  *  Directives recognized are:
  6.  *    #line n [filename]
  7.  *    $line n [filename]
  8.  *    $include filename
  9.  *    $define identifier text
  10.  *    $undef identifier
  11.  *    $ifdef identifier
  12.  *    $ifndef identifier
  13.  *    $else
  14.  *    $endif
  15.  *    $error [text]
  16.  *
  17.  *  Entry points are
  18.  *    ppinit(fname,inclpath,m4flag) -- open input file
  19.  *    ppdef(s,v) -- "$define s v", or "$undef s" if v is a null pointer
  20.  *    ppch() -- return next preprocessed character
  21.  *    ppecho() -- preprocess to stdout (for icont/iconc -E)
  22.  *
  23.  *  See ../h/features.h for the set of predefined symbols.
  24.  */
  25.  
  26. #include "../h/gsupport.h"
  27.  
  28. #define HTBINS 256            /* number of hash bins */
  29.  
  30. typedef struct fstruct {        /* input file structure */
  31.    struct fstruct *prev;        /* previous file */
  32.    char *fname;                /* file name */
  33.    long lno;                /* line number */
  34.    FILE *fp;                /* stdio file pointer */
  35.    int m4flag;                /* nz if preprocessed by m4 */
  36.    int ifdepth;                /* $if nesting depth when opened */
  37.    } infile;
  38.  
  39. typedef struct bstruct {        /* buffer pointer structure */
  40.    struct bstruct *prev;        /* previous pointer structure */
  41.    struct cd *defn;            /* definition being processed */
  42.    char *ptr;                /* saved pointer value */
  43.    char *stop;                /* saved stop value */
  44.    char *lim;                /* saved limit value */
  45.    } buffer;
  46.  
  47. typedef struct {            /* preprocessor token structure */
  48.    char *addr;                /* beginning of token */
  49.    short len;                /* length */
  50.    } ptok;
  51.  
  52. typedef struct cd {            /* structure holding a definition */
  53.    struct cd *next;            /* link to next defn */
  54.    struct cd *prev;            /* link to previous defn */
  55.    short nlen, vlen;            /* length of name & val */
  56.    char inuse;                /* nonzero if curr being expanded */
  57.    char s[1];                /* name then value, as needed, no \0 */
  58.    } cdefn;
  59.  
  60. static    int    ppopen    (char *fname, int m4);
  61. static    FILE *    m4pipe    (char *fname);
  62. static    char *    rline    (FILE *fp);
  63. static    void    pushdef    (cdefn *d);
  64. static    void    pushline (char *fname, long lno);
  65. static    void    ppdir    (char *line);
  66. static    void    pfatal    (char *s1, char *s2);
  67. static    void    skipcode (int doelse, int report);
  68. static    char *    define    (char *s);
  69. static    char *    undef    (char *s);
  70. static    char *    ifdef    (char *s);
  71. static    char *    ifndef    (char *s);
  72. static    char *    ifxdef    (char *s, int f);
  73. static    char *    elsedir    (char *s);
  74. static    char *    endif    (char *s);
  75. static    char *    errdir    (char *s);
  76. static    char *    include    (char *s);
  77. static    char *    setline    (char *s);
  78. static    char *    wskip    (char *s);
  79. static    char *    nskip    (char *s);
  80. static    char *    matchq    (char *s);
  81. static    char *    getidt    (char *dst, char *src);
  82. static    char *    getfnm    (char *dst, char *src);
  83. static    cdefn *    dlookup    (char *name, int len, char *val);
  84.  
  85. struct ppcmd {
  86.    char *name;
  87.    char *(*func)();
  88.    }
  89. pplist[] = {
  90.    { "define",  define  },
  91.    { "undef",   undef   },
  92.    { "ifdef",   ifdef   },
  93.    { "ifndef",  ifndef  },
  94.    { "else",    elsedir },
  95.    { "endif",   endif   },
  96.    { "include", include },
  97.    { "line",    setline },
  98.    { "error",   errdir  },
  99.    { 0,         0       }};
  100.  
  101. static infile nofile;            /* ancestor of all files; all zero */
  102. static infile *curfile;            /* pointer to current entry */
  103.  
  104. static buffer *bstack;            /* stack of pending buffers */
  105. static buffer *bfree;            /* pool of free bstructs */
  106.  
  107. static char *buf;            /* input line buffer */
  108. static char *bnxt;            /* next character */
  109. static char *bstop;            /* limit of preprocessed chars */
  110. static char *blim;            /* limit of all chars */
  111.  
  112. static cdefn *cbin[HTBINS];        /* hash bins for defn table */
  113.  
  114. static char *lpath;            /* LPATH for finding source files */
  115.  
  116. static int ifdepth;            /* depth of $if nesting */
  117.  
  118. extern int tfatals, nocode;        /* provided by icont, iconc */
  119.  
  120. /*
  121.  * ppinit(fname, inclpath, m4) -- initialize preprocessor to read from fname.
  122.  *
  123.  *  Returns 1 if successful, 0 if open failed.
  124.  */
  125. int ppinit(fname, inclpath, m4)
  126. char *fname;
  127. char *inclpath;
  128. int m4;
  129.    {
  130.    int i;
  131.    cdefn *d, *n;
  132.  
  133.    /*
  134.     * clear out any existing definitions from previous files
  135.     */
  136.    for (i = 0; i < HTBINS; i++) {
  137.       for (d = cbin[i]; d != NULL; d = n) {
  138.          n = d->next;
  139.          free((char *)d);
  140.          }
  141.       cbin[i] = NULL;
  142.       }
  143.  
  144.    /*
  145.     * install predefined symbols
  146.     */
  147. #define Feature(guard,symname,kwval) dlookup(symname, -1, "1");
  148. #include "../h/features.h"
  149.  
  150.    /*
  151.     * initialize variables and open source file
  152.     */
  153.    lpath = inclpath;
  154.    curfile = &nofile;            /* init file struct pointer */
  155.    return ppopen(fname, m4);        /* open main source file */
  156.    }
  157.  
  158. /*
  159.  * ppopen(fname, m4) -- open a new file for reading by the preprocessor.
  160.  *
  161.  *  Returns 1 if successful, 0 if open failed.
  162.  *
  163.  *  Open calls may be nested.  Files are closed when EOF is read.
  164.  */
  165. static int ppopen(fname, m4)
  166. char *fname;
  167. int m4;
  168.    {
  169.    FILE *f;
  170.    infile *fs;
  171.  
  172.    for (fs = curfile; fs->fname != NULL; fs = fs->prev)
  173.       if (strcmp(fname, fs->fname) == 0) {
  174.          pfatal("circular include", fname);    /* issue error message */
  175.          return 1;                /* treat as success */
  176.          }
  177.    if (m4)
  178.       f = m4pipe(fname);
  179.    else if (curfile == &nofile && strcmp(fname, "-") == 0) { /* 1st file only */
  180.       f = stdin;
  181.       fname = "stdin";
  182.       }
  183.    else
  184.       f = fopen(fname, ReadText);
  185.    if (f == NULL) {
  186.       return 0;
  187.       }
  188.    fs = alloc(sizeof(infile));
  189.    fs->prev = curfile;
  190.    fs->fp = f;
  191.    fs->fname = salloc(fname);
  192.    fs->lno = 0;
  193.    fs->m4flag = m4;
  194.    fs->ifdepth = ifdepth;
  195.    pushline(fs->fname, 0L);
  196.    curfile = fs;
  197.    return 1;
  198.    }
  199.  
  200. /*
  201.  * m4pipe -- open a pipe from m4.
  202.  */
  203. static FILE *m4pipe(filename)
  204. char *filename;
  205.    {
  206. #if ARM || UNIX
  207.       {
  208.       FILE *f;
  209.       char *s = alloc(4 + strlen(filename));
  210.       sprintf(s, "m4 %s", filename);
  211.       f = popen(s, ReadText);
  212.       free(s);
  213.       return f;
  214.       }
  215. #else                    /* ARM || UNIX */
  216.    return NULL;
  217. #endif                    /* ARM || UNIX */
  218.    }
  219.  
  220. /*
  221.  * ppdef(s,v) -- define/undefine a symbol
  222.  *
  223.  *  If v is a null pointer, undefines symbol s.
  224.  *  Otherwise, defines s to have the value v.
  225.  *  No error is given for a redefinition.
  226.  */
  227. void ppdef(s, v)
  228. char *s, *v;
  229.    {
  230.    dlookup(s, -1, (char *)NULL);
  231.    if (v != NULL)
  232.       dlookup(s, -1, v);
  233.    }
  234.  
  235. /*
  236.  * ppecho() -- run input through preprocessor and echo directly to stdout.
  237.  */
  238. void ppecho()
  239.    {
  240.    int c;
  241.  
  242.    while ((c = ppch()) != EOF)
  243.       putchar(c);
  244.    }
  245.  
  246. /*
  247.  * ppch() -- get preprocessed character.
  248.  */
  249. int ppch()
  250.    {
  251.    int c, f;
  252.    char *p;
  253.    buffer *b;
  254.    cdefn *d;
  255.    infile *fs;
  256.  
  257.    for (;;) {
  258.       if (bnxt < bstop)            /* if characters ready to go */
  259.          return ((int)*bnxt++) & 0xFF;        /* return first one */
  260.  
  261.       if (bnxt < blim) {
  262.          /*
  263.           * There are characters in the buffer, but they haven't been
  264.           *  checked for substitutions yet.  Process either one id, if
  265.           *  that's what's next, or as much else as we can.
  266.           */
  267.          f = *bnxt;
  268.          if (isalpha(f) || f == '_') {
  269.             /*
  270.              * This is the first character of an identifier.  It could
  271.              *  be the name of a definition.  If so, the name will be
  272.              *  contiguous in this buffer.  Check it.
  273.              */
  274.             p = bnxt + 1;
  275.             while (p < blim && (isalnum(c = *p) || c == '_'))    /* find end */
  276.                p++;
  277.             bstop = p;            /* safe to consume through end */
  278.             if (((d = dlookup(bnxt, p-bnxt, bnxt)) == 0)  || (d->inuse == 1)) {
  279.                bnxt++;
  280.                return f;        /* not defined; just use it */
  281.                }
  282.             /*
  283.              * We got a match.  Remove the token from the input stream and
  284.              *  push the replacement value.
  285.              */
  286.             bnxt = p;
  287.             pushdef(d);            /* make defn the curr buffer */
  288.             continue;            /* loop to preprocess */
  289.             }
  290.          else {
  291.             /*
  292.              * Not an id.  Find the end of non-id stuff and mark it as
  293.              *  having been preprocessed.  This is where we skip over
  294.              *  string and cset literals to avoid processing them.
  295.              */
  296.             p = bnxt++;
  297.             while (p < blim) {
  298.                c = *p;
  299.                if (isalpha(c) || c == '_') {    /* there's an id ahead */
  300.                   bstop = p;
  301.                   return f;
  302.                   }
  303.                else if (isdigit(c)) {        /* numeric constant */
  304.                   p = nskip(p);
  305.                   }
  306.                else if (c == '#') {        /* comment: skip to EOL */
  307.                   bstop = blim;
  308.                   return f;
  309.                   }
  310.                else if (c == '"' || c == '\''){    /* quoted literal */
  311.                   p = matchq(p);        /* skip to end */
  312.                   if (*p != '\0')
  313.                      p++;
  314.                   }
  315.                else
  316.                   p++;                /* else advance one char */
  317.                }
  318.             bstop = blim;            /* mark end of processed chrs */
  319.             return f;                /* return first char */
  320.             }
  321.          }
  322.  
  323.       /*
  324.        * The buffer is empty.  Revert to a previous buffer.
  325.        */
  326.       if (bstack != NULL) {
  327.          b = bstack;
  328.          b->defn->inuse = 0;
  329.          bnxt = b->ptr;
  330.          bstop = b->stop;
  331.          blim = b->lim;
  332.          bstack = b->prev;
  333.          b->prev = bfree;
  334.          bfree = b;
  335.          continue;                /* loop to preprocess */
  336.          }
  337.  
  338.       /*
  339.        * There's nothing at all in memory.  Read a new line.
  340.        */
  341.       if ((buf = rline(curfile->fp)) != NULL) {
  342.          /*
  343.           * The read was successful.
  344.           */
  345.          p = bnxt = bstop = blim = buf;        /* reset buffer pointers */
  346.          curfile->lno++;            /* bump line number */
  347.          while (isspace(c = *p))
  348.             p++;                /* find first nonwhite */
  349.          if (c == '$' && (!ispunct(p[1]) || p[1]==' '))
  350.             ppdir(p + 1);            /* handle preprocessor cmd */
  351.          else if (buf[1]=='l' && buf[2]=='i' && buf[3]=='n' && buf[4]=='e' &&
  352.                   buf[0]=='#' && buf[5]==' ')
  353.             ppdir(p + 1);            /* handle #line form */
  354.          else {
  355.             /*
  356.              * Not a preprocessor line; will need to scan for symbols.
  357.              */
  358.             bnxt = buf;
  359.             blim = buf + strlen(buf);
  360.             bstop = bnxt;            /* no chars scanned yet */
  361.             }
  362.          }
  363.  
  364.       else {
  365.          /*
  366.           * The read hit EOF.
  367.           */
  368.          if (curfile->ifdepth != ifdepth) {
  369.             pfatal("unterminated $if", (char *)0);
  370.             ifdepth = curfile->ifdepth;
  371.             }
  372.  
  373.          /*
  374.           * switch to previous file and close current file.
  375.           */
  376.          fs = curfile;
  377.          curfile = fs->prev;
  378.  
  379. #if ARM || UNIX
  380.          if (fs->m4flag) {            /* if m4 preprocessing */
  381.             void quit();
  382.             if (pclose(fs->fp) != 0)        /* close pipe */
  383.                quit("m4 terminated abnormally");
  384.             }
  385.          else
  386. #endif                    /* ARM || UNIX */
  387.             fclose(fs->fp);        /* close current file */
  388.  
  389.          free((char *)fs->fname);
  390.          free((char *)fs);
  391.          if (curfile == &nofile)    /* if at outer level, return EOF */
  392.             return EOF;
  393.          else                /* else generate #line comment */
  394.             pushline(curfile->fname, curfile->lno);
  395.          }
  396.       }
  397.    }
  398.  
  399. /*
  400.  * rline(fp) -- read arbitrarily long line and return pointer.
  401.  *
  402.  *  Allocates memory as needed.  Returns NULL for EOF.  Lines end with "\n\0".
  403.  */
  404. static char *rline(fp)
  405. FILE *fp;
  406.    {
  407. #define LINE_SIZE_INIT 100
  408. #define LINE_SIZE_INCR 100
  409.    static char *lbuf = NULL;    /* line buffer */
  410.    static int llen = 0;        /* current buffer length */
  411.    register char *p;
  412.    register int c, n;
  413.  
  414.    /* if first time, allocate buffer */
  415.    if (!lbuf) {
  416.       lbuf = alloc(LINE_SIZE_INIT);
  417.       llen = LINE_SIZE_INIT;
  418.       }
  419.  
  420.    /* first character is special; return NULL if hit EOF here */
  421.    c = getc(fp);
  422.    if (c == EOF)
  423.       return NULL;
  424.    if (c == '\n')
  425.       return "\n";
  426.  
  427.    p = lbuf;
  428.    n = llen - 3;
  429.    *p++ = c;
  430.  
  431.    for (;;)  {
  432.       /* read until buffer full; return after newline or EOF */
  433.       while (--n >= 0 && (c = getc(fp)) != '\n' && c != EOF)
  434.          *p++ = c;
  435.       if (n >= 0) {
  436.          *p++ = '\n';            /* always terminate with \n\0 */
  437.          *p++ = '\0';
  438.          return lbuf;
  439.          }
  440.  
  441.       /* need to read more, so we need a bigger buffer */
  442.       llen += LINE_SIZE_INCR;
  443.       lbuf = realloc(lbuf, (unsigned int)llen);
  444.       if (!lbuf) {
  445.          fprintf(stderr, "rline(%d): out of memory\n", llen);
  446.          exit(EXIT_FAILURE);
  447.          }
  448.       p = lbuf + llen - LINE_SIZE_INCR - 2;
  449.       n = LINE_SIZE_INCR;
  450.       }
  451.    }
  452.  
  453. /*
  454.  * pushdef(d) -- insert definition into the input stream.
  455.  */
  456. static void pushdef(d)
  457. cdefn *d;
  458.    {
  459.    buffer *b;
  460.  
  461.    d->inuse = 1;
  462.    b = bfree;
  463.    if (b == NULL)
  464.       b = (buffer *)alloc(sizeof(buffer));
  465.    else
  466.       bfree = b->prev;
  467.    b->prev = bstack;
  468.    b->defn = d;
  469.    b->ptr = bnxt;
  470.    b->stop = bstop;
  471.    b->lim = blim;
  472.    bstack = b;
  473.    bnxt = bstop = d->s + d->nlen;
  474.    blim = bnxt + d->vlen;
  475.    }
  476.  
  477. /*
  478.  * pushline(fname,lno) -- push #line directive into input stream.
  479.  */
  480. static void pushline(fname, lno)
  481. char *fname;
  482. long lno;
  483.    {
  484.    static char tbuf[200];
  485.  
  486.    sprintf(tbuf, "#line %ld \"%s\"\n", lno, fname);
  487.    bnxt = tbuf;
  488.    bstop = blim = tbuf + strlen(tbuf);
  489.    }
  490.  
  491. /*
  492.  * ppdir(s) -- handle preprocessing directive.
  493.  *
  494.  *  s is the portion of the line following the $.
  495.  */
  496. static void ppdir(s)
  497. char *s;
  498.    {
  499.    char b0, *cmd, *errmsg;
  500.    struct ppcmd *p;
  501.  
  502.    b0 = buf[0];                /* remember first char of line */
  503.    bnxt = "\n";                /* set buffer pointers to empty line */
  504.    bstop = blim = bnxt + 1;
  505.  
  506.    s = wskip(s);            /* skip whitespace */
  507.    s = getidt(cmd = s - 1, s);        /* get command name */
  508.    s = wskip(s);            /* skip whitespace */
  509.  
  510.    for (p = pplist; p->name != NULL; p++) /* find name in table */
  511.       if (strcmp(cmd, p->name) == 0) {
  512.          errmsg = (*p->func)(s);    /* process directive */
  513.          if (errmsg != NULL && (p->func != setline || b0 != '#'))
  514.             pfatal(errmsg, (char *)0);    /* issue err if not from #line form */
  515.       return;
  516.       }
  517.  
  518.    pfatal("invalid preprocessing directive", cmd);
  519.    }
  520.  
  521. /*
  522.  * pfatal(s1,s2) -- output a preprocessing error message.
  523.  *
  524.  *  s1 is the error message; s2 is the offending value, if any.
  525.  *  If s2 ends in a newline, the newline is truncated in place.
  526.  *
  527.  *  We can't use tfatal() because we have our own line counter which may be
  528.  *  out of sync with the lexical analyzer's.
  529.  */
  530. static void pfatal(s1, s2)
  531. char *s1, *s2;
  532.    {
  533.    int n;
  534.  
  535.    fprintf(stderr, "File %s; Line %ld # ", curfile->fname, curfile->lno);
  536.    if (s2 != NULL && *s2 != '\0') {
  537.       n = strlen(s2);
  538.       if (n > 0 && s2[n-1] == '\n')
  539.          s2[n-1] = '\0';            /* remove newline */
  540.       fprintf(stderr, "\"%s\": ", s2);        /* print offending value */
  541.       }
  542.    fprintf(stderr, "%s\n", s1);            /* print diagnostic */
  543.    tfatals++;
  544.    nocode++;
  545.    }
  546.  
  547. /*
  548.  * errdir(s) -- handle deliberate $error.
  549.  */
  550. static char *errdir(s)
  551. char *s;
  552.    {
  553.    pfatal("explicit $error", s);        /* issue msg with text */
  554.    return NULL;
  555.    }
  556.  
  557. /*
  558.  * define(s) -- handle $define directive.
  559.  */
  560. static char *define(s)
  561. char *s;
  562.    {
  563.    char c, *name, *val;
  564.  
  565.    if (isalpha(c = *s) || c == '_')
  566.       s = getidt(name = s - 1, s);        /* get name */
  567.    else
  568.       return "$define: missing name";
  569.    if (*s == '(')
  570.       return "$define: \"(\" after name requires preceding space";
  571.    val = s = wskip(s);
  572.    if (*s != '\0') {
  573.       while ((c = *s) != '\0' && c != '#') {    /* scan value */
  574.          if (c == '"' || c == '\'') {
  575.             s = matchq(s);
  576.             if (*s == '\0')
  577.                return "$define: unterminated literal";
  578.             }
  579.          s++;
  580.          }
  581.       while (isspace(s[-1]))            /* trim trailing whitespace */
  582.          s--;
  583.       }
  584.    *s = '\0';
  585.    dlookup(name, -1, val);        /* install in table */
  586.    return NULL;
  587.    }
  588.  
  589. /*
  590.  * undef(s) -- handle $undef directive.
  591.  */
  592. static char *undef(s)
  593. char *s;
  594.    {
  595.    char c, *name;
  596.  
  597.    if (isalpha(c = *s) || c == '_')
  598.       s = getidt(name = s - 1, s);        /* get name */
  599.    else
  600.       return "$undef: missing name";
  601.    if (*wskip(s) != '\0')
  602.       return "$undef: too many arguments";
  603.    dlookup(name, -1, (char *)NULL);
  604.    return NULL;
  605.    }
  606.  
  607. /*
  608.  * include(s) -- handle $include directive.
  609.  */
  610. static char *include(s)
  611. char *s;
  612.    {
  613.    char *fname;
  614.    char fullpath[MaxFileName];
  615.  
  616.    s = getfnm(fname = s - 1, s);
  617.    if (*fname == '\0')
  618.       return "$include: invalid file name";
  619.    if (*wskip(s) != '\0')
  620.       return "$include: too many arguments";
  621.    if (!pathfind(fullpath, lpath, fname, (char *)NULL) || !ppopen(fullpath, 0))
  622.       pfatal("cannot open", fname);
  623.    return NULL;
  624.    }
  625.  
  626. /*
  627.  * setline(s) -- handle $line (or #line) directive.
  628.  */
  629. static char *setline(s)
  630. char *s;
  631.    {
  632.    long n;
  633.    char c;
  634.    char *fname;
  635.  
  636.    if (!isdigit(c = *s))
  637.       return "$line: no line number";
  638.    n = c - '0';
  639.  
  640.    while (isdigit(c = *++s))        /* extract line number */
  641.       n = 10 * n + c - '0';
  642.    s = wskip(s);            /* skip whitespace */
  643.  
  644.    if (isalpha (c = *s) || c == '_' || c == '"') {    /* if filename */
  645.       s = getfnm(fname = s - 1, s);            /* extract it */
  646.       if (*fname == '\0')
  647.          return "$line: invalid file name";
  648.       }
  649.    else
  650.       fname = NULL;
  651.  
  652.    if (*wskip(s) != '\0')
  653.       return "$line: too many arguments";
  654.  
  655.    curfile->lno = n;            /* set line number */
  656.    if (fname != NULL) {            /* also set filename if given */
  657.       free(curfile->fname);
  658.       curfile->fname = salloc(fname);
  659.       }
  660.  
  661.    pushline(curfile->fname, curfile->lno);
  662.    return NULL;
  663.    }
  664.  
  665. /*
  666.  * ifdef(s), ifndef(s) -- conditional processing if s is/isn't defined.
  667.  */
  668. static char *ifdef(s)
  669. char *s;
  670.    {
  671.    return ifxdef(s, 1);
  672.    }
  673.  
  674. static char *ifndef(s)
  675. char *s;
  676.    {
  677.    return ifxdef(s, 0);
  678.    }
  679.  
  680. /*
  681.  * ifxdef(s) -- handle $ifdef (if n is 1) or $ifndef (if n is 0).
  682.  */
  683. static char *ifxdef(s, f)
  684. char *s;
  685. int f;
  686.    {
  687.    char c, *name;
  688.  
  689.    ifdepth++;
  690.    if (isalpha(c = *s) || c == '_')
  691.       s = getidt(name = s - 1, s);        /* get name */
  692.    else
  693.       return "$ifdef/$ifndef: missing name";
  694.    if (*wskip(s) != '\0')
  695.       return "$ifdef/$ifndef: too many arguments";
  696.    if ((dlookup(name, -1, name) != NULL) ^ f)
  697.       skipcode(1, 1);                /* skip to $else or $endif */
  698.    return NULL;
  699.    }
  700.  
  701. /*
  702.  * elsedir(s) -- handle $else by skipping to $endif.
  703.  */
  704. static char *elsedir(s)
  705. char *s;
  706.    {
  707.    if (ifdepth <= curfile->ifdepth)
  708.       return "unexpected $else";
  709.    if (*s != '\0')
  710.       pfatal ("extraneous arguments on $else/$endif", s);
  711.    skipcode(0, 1);            /* skip the $else section */
  712.    return NULL;
  713.    }
  714.  
  715. /*
  716.  * endif(s) -- handle $endif.
  717.  */
  718. static char *endif(s)
  719. char *s;
  720.    {
  721.    if (ifdepth <= curfile->ifdepth)
  722.       return "unexpected $endif";
  723.    if (*s != '\0')
  724.       pfatal ("extraneous arguments on $else/$endif", s);
  725.    ifdepth--;
  726.    return NULL;
  727.    }
  728.  
  729. /*
  730.  * skipcode(doelse,report) -- skip code to $else (doelse=1) or $endif (=0).
  731.  *
  732.  *  If report is nonzero, generate #line directive at end of skip.
  733.  */
  734. static void skipcode(doelse, report)
  735. int doelse, report;
  736.    {
  737.    char c, *p, *cmd;
  738.  
  739.    while ((p = buf = rline(curfile->fp)) != NULL) {
  740.       curfile->lno++;            /* bump line number */
  741.  
  742.       /*
  743.        * Handle #line form encountered while skipping.
  744.        */
  745.       if (buf[1]=='l' && buf[2]=='i' && buf[3]=='n' && buf[4]=='e' &&
  746.             buf[0]=='#' && buf[5]==' ') {
  747.          ppdir(buf + 1);            /* interpret #line */
  748.          continue;
  749.          }
  750.  
  751.       /*
  752.        * Check for any other kind of preprocessing directive.
  753.        */
  754.       while (isspace(c = *p))
  755.          p++;                /* find first nonwhite */
  756.       if (c != '$' || (ispunct(p[1]) && p[1]!=' '))
  757.          continue;            /* not a preprocessing directive */
  758.       p = wskip(p+1);            /* skip whitespace */
  759.       p = getidt(cmd = p-1, p);        /* get command name */
  760.       p = wskip(p);            /* skip whitespace */
  761.  
  762.       /*
  763.        * Check for a directive that needs special attention.
  764.        *  Deliberately accept any form of $if... as valid
  765.        *  in anticipation of possible future extensions;
  766.        *  this allows them to appear here if commented out.
  767.        */
  768.       if (cmd[0] == 'i' && cmd[1] == 'f') {
  769.          ifdepth++;
  770.          skipcode(0, 0);        /* skip to $endif */
  771.          }
  772.       else if (strcmp(cmd, "line") == 0)
  773.          setline(p);            /* process $line, ignore errors */
  774.       else if (strcmp(cmd, "endif") == 0 ||
  775.                (doelse == 1 && strcmp(cmd, "else") == 0)) {
  776.          /*
  777.           * Time to stop skipping.
  778.           */
  779.          if (*p != '\0')
  780.             pfatal ("extraneous arguments on $else/$endif", p);
  781.          if (cmd[1] == 'n')        /* if $endif */
  782.             ifdepth--;
  783.          if (report)
  784.             pushline(curfile->fname, curfile->lno);
  785.          return;
  786.          }
  787.       }
  788.  
  789.    /*
  790.     *  At EOF, just return; main loop will report unterminated $if.
  791.     */
  792.    }
  793.  
  794. /*
  795.  * Token scanning functions.
  796.  */
  797.  
  798. /*
  799.  * wskip(s) -- skip whitespace and return updated pointer
  800.  *
  801.  *  If '#' is encountered, skips to end of string.
  802.  */
  803. static char *wskip(s)
  804. char *s;
  805.    {
  806.    char c;
  807.  
  808.    while (isspace(c = *s))
  809.       s++;
  810.    if (c == '#')
  811.       while ((c = *++s) != 0)
  812.          ;
  813.    return s;
  814.    }
  815.  
  816. /*
  817.  * nskip(s) -- skip over numeric constant and return updated pointer.
  818.  */
  819. static char *nskip(s)
  820. char *s;
  821.    {
  822.       char c;
  823.  
  824.       while (isdigit(c = *++s))
  825.          ;
  826.       if (c == 'r' || c == 'R') {
  827.          while (isalnum(c = *++s))
  828.             ;
  829.          return s;
  830.          }
  831.       if (c == '.')
  832.          while (isdigit (c = *++s))
  833.             ;
  834.       if (c == 'e' || c == 'E') {
  835.          c = s[1];
  836.          if (c == '+' || c == '-')
  837.             s++;
  838.          while (isdigit (c = *++s))
  839.             ;
  840.          }
  841.       return s;
  842.    }
  843.  
  844. /*
  845.  * matchq(s) -- scan for matching quote character and return pointer.
  846.  *
  847.  *  Taking *s as the quote character, s is incremented until it points
  848.  *  to either another occurrence of the character or the '\0' terminating
  849.  *  the string.  Escaped quote characters do not stop the scan.  The
  850.  *  updated pointer is returned.
  851.  */
  852. static char *matchq(s)
  853. char *s;
  854.    {
  855.    char c, q;
  856.  
  857.    q = *s;
  858.    if (q == '\0')
  859.       return s;
  860.    while ((c = *++s) != q && c != '\0') {
  861.       if (c == '\\')
  862.          if (*++s == '\0')
  863.             return s;
  864.       }
  865.    return s;
  866.    }
  867.  
  868. /*
  869.  * getidt(dst,src) -- extract identifier, return updated pointer
  870.  *
  871.  *  The identifier (in Icon terms, "many(&letters++&digits++'_')")
  872.  *  at src is copied to dst and '\0' is appended.  A pointer to the
  873.  *  character following the identifier is returned.
  874.  *
  875.  *  dst may partially overlap src if dst has a lower address.  This
  876.  *  is typically done to avoid the need for another arbitrarily-long
  877.  *  buffer.  An offset of -1 allows room for insertion of the '\0'.
  878.  */
  879. static char *getidt(dst, src)
  880. char *dst, *src;
  881.    {
  882.    char c;
  883.  
  884.    while (isalnum(c = *src) || (c == '_')) {
  885.       *dst++ = c;
  886.       src++;
  887.       }
  888.    *dst = '\0';
  889.    return src;
  890.    }
  891.  
  892. /*
  893.  * getfnm(dst,src) -- extract filename, return updated pointer
  894.  *
  895.  *  Similarly to getidt, getfnm extracts a quoted or unquoted file name.
  896.  *  An empty string at dst indicates a missing or unterminated file name.
  897.  */
  898. static char *getfnm(dst, src)
  899. char *dst, *src;
  900.    {
  901.    char *lim;
  902.  
  903.    if (*src != '"')
  904.       return getidt(dst, src);
  905.    lim = matchq(src);
  906.    if (*lim != '"') {
  907.       *dst = '\0';
  908.       return lim;
  909.       }
  910.    while (++src < lim)
  911.       if ((*dst++ = *src) == '\\')
  912.          dst[-1] = *++src;
  913.    *dst = '\0';
  914.    return lim + 1;
  915.    }
  916.  
  917. /*
  918.  * dlookup(name, len, val) look up entry in definition table.
  919.  *
  920.  *  If val == name, return the existing value, or NULL if undefined.
  921.  *  If val == NULL, delete any existing value and undefine the name.
  922.  *  If val != NULL, install a new value, and print error if different.
  923.  *
  924.  *  If name is null, the call is ignored.
  925.  *  If len < 0, strlen(name) is taken.
  926.  */
  927. static cdefn *dlookup(name, len, val)
  928. char *name;
  929. int len;
  930. char *val;
  931.    {
  932.    int h, i, nlen, vlen;
  933.    unsigned int t;
  934.    cdefn *d, **p;
  935.  
  936.    if (len < 0)
  937.       len = strlen(name);
  938.    if (len == 0)
  939.       return NULL;
  940.    for (t = i = 0; i < len; i++)
  941.       t = 37 * t + (name[i] & 0xFF);    /* calc hash value */
  942.    h = t % HTBINS;            /* calc bin number */
  943.    p = &cbin[h];            /* get head of list */
  944.    while ((d = *p) != NULL) {
  945.       if (d->nlen == len && strncmp(name, d->s, len) == 0) {
  946.          /*
  947.           * We found a match in the table.
  948.           */
  949.          if (val == NULL) {        /* if $undef */
  950.             *p = d->next;        /* delete from table */
  951.             free((char *)d);
  952.             return NULL;
  953.             }
  954.          if (val != name && strcmp(val, d->s + d->nlen) != 0)
  955.             pfatal("value redefined", name);
  956.          return d;            /* return pointer to entry */
  957.          }
  958.       p = &d->next;
  959.       }
  960.    /*
  961.     * No match. Install a definition if that is what is wanted.
  962.     */
  963.    if (val == name || val == NULL)    /* if was reference or $undef */
  964.       return NULL;
  965.    nlen = strlen(name);
  966.    vlen = strlen(val);
  967.    d = (cdefn *)alloc(sizeof(*d) - sizeof(d->s) + nlen + vlen + 1);
  968.    d->nlen = nlen;
  969.    d->vlen = vlen;
  970.    d->inuse = 0;
  971.    strcpy(d->s, name);
  972.    strcpy(d->s + nlen, val);
  973.    d->prev = NULL;
  974.    d->next = cbin[h];
  975.    if (d->next != NULL)
  976.       d->next->prev = d;
  977.    cbin[h] = d;
  978.    return d;
  979.    }
  980.