Sym*
getsym(void)
{
       int c;
       char *cp;

       c = getnsc();
       if(!isalpha(c) && c != '_') {
               unget(c);
               return S;
       }
       for(cp = symb;;) {
               if(cp <= symb+NSYMB-4)
                       *cp++ = c;
               c = getc();
               if(isalnum(c) || c == '_')
                       continue;
               unget(c);
               break;
       }
       *cp = 0;
       if(cp > symb+NSYMB-4)
               yyerror("symbol too large: %s", symb);
       return lookup();
}

void
dodefine(char *cp)
{
       Sym *s;
       char *p;
       long l;

       strcpy(symb, cp);
       p = strchr(symb, '=');
       if(p) {
               *p++ = 0;
               s = lookup();
               l = strlen(p) + 2;      /* +1 null, +1 nargs */
               while(l & 3)
                       l++;
               while(nhunk < l)
                       gethunk();
               *hunk = 0;
               strcpy(hunk+1, p);
               s->macro = hunk;
               hunk += l;
               nhunk -= l;
       } else {
               s = lookup();
               s->macro = "\0001";     /* \000 is nargs */
       }
       if(s->ref)
               s->ref->class = CMACRO+CLAST;
}

struct
{
       char    *macname;
       void    (*macf)(void);
} mactab[] =
{
       "ifdef",        0,      /* macif(0) */
       "ifndef",       0,      /* macif(1) */
       "else",         0,      /* macif(2) */

       "line",         maclin,
       "define",       macdef,
       "include",      macinc,
       "undef",        macund,

       "pragma",       macprag,
       "endif",        macend,
       0
};

void
domacro(void)
{
       int i;
       Sym *s;

       s = getsym();
       if(s == S)
               s = slookup("endif");
       for(i=0; mactab[i].macname; i++)
               if(strcmp(s->name, mactab[i].macname) == 0) {
                       if(s->ref)
                               s->ref->class = CPREPROC;
                       if(mactab[i].macf)
                               (*mactab[i].macf)();
                       else
                               macif(i);
                       return;
               }
       yyerror("unknown #: %s", s->name);
       macend();
}

void
macund(void)
{
       Sym *s;

       s = getsym();
       macend();
       if(s == S) {
               yyerror("syntax in #undef");
               return;
       }
       s->macro = 0;
       if(s->ref)
               s->ref->class = CMACRO;
}

#define NARG    25
void
macdef(void)
{
       Sym *s, *a;
       char *args[NARG], *np, *base;
       int n, i, c, len;

       s = getsym();
       if(s == S)
               goto bad;
       if(s->macro)
               yyerror("macro redefined: %s", s->name);
       if(s->ref)
               s->ref->class = CMACRO+CLAST;
       c = getc();
       n = -1;
       if(c == '(') {
               n++;
               c = getnsc();
               if(c != ')') {
                       unget(c);
                       for(;;) {
                               a = getsym();
                               if(a == S)
                                       goto bad;
                               if(n >= NARG) {
                                       yyerror("too many arguments in #define: %s", s->name);
                                       goto bad;
                               }
                               if(a->ref)
                                       a->ref->class = CMACARG+CLAST;
                               args[n++] = a->name;
                               c = getnsc();
                               if(c == ')')
                                       break;
                               if(c != ',')
                                       goto bad;
                       }
               }
               c = getc();
       }
       if(isspace(c))
               if(c != '\n')
                       c = getnsc();
       base = hunk;
       len = 1;
       for(;;) {
               if(isalpha(c) || c == '_') {
                       np = symb;
                       *np++ = c;
                       c = getc();
                       while(isalnum(c) || c == '_') {
                               *np++ = c;
                               c = getc();
                       }
                       *np = 0;
                       for(i=0; i<n; i++)
                               if(strcmp(symb, args[i]) == 0)
                                       break;
                       if(i >= n) {
                               i = strlen(symb);
                               ALLOCN(base, len, i);
                               memcpy(base+len, symb, i);
                               len += i;
                               continue;
                       }
                       ALLOCN(base, len, 2);
                       base[len++] = '#';
                       base[len++] = 'a' + i;
                       continue;
               }
               if(c == '/') {
                       c = getc();
                       if(c != '*') {
                               ALLOCN(base, len, 1);
                               base[len++] = '/';
                               continue;
                       }
                       c = getc();
                       for(;;) {
                               if(c == '*') {
                                       c = getc();
                                       if(c != '/')
                                               continue;
                                       c = getc();
                                       break;
                               }
                               if(c == '\n') {
                                       yyerror("comment and newline in define: %s", s->name);
                                       break;
                               }
                               c = getc();
                       }
                       continue;
               }
               if(c == '\\') {
                       c = getc();
                       if(c == '\n') {
                               c = getc();
                               continue;
                       }
                       ALLOCN(base, len, 1);
                       base[len++] = '\\';
                       continue;
               }
               if(c == '\n')
                       break;
               if(c == '#')
               if(n > 0) {
                       ALLOCN(base, len, 1);
                       base[len++] = c;
               }
               ALLOCN(base, len, 1);
               base[len++] = c;
               c = ((--fi.c < 0)? filbuf(): (*fi.p++ & 0xff));
               if(c == '\n')
                       lineno++;
               if(c == -1) {
                       yyerror("eof in a macro: %s", s->name);
                       break;
               }
       }
       do {
               ALLOCN(base, len, 1);
               base[len++] = 0;
       } while(len & 3);

       *base = n+1;
       s->macro = base;
       if(debug['m'])
               print("#define %s %s\n", s->name, s->macro+1);
       return;

bad:
       if(s == S)
               yyerror("syntax in #define");
       else
               yyerror("syntax in #define: %s", s->name);
       macend();
}

void
macexpand(Sym *s, char *b)
{
       char buf[2000];
       int n, l, c, nargs;
       char *arg[NARG], *cp, *ob, *ecp;

       ob = b;
       nargs = *s->macro - 1;
       if(nargs < 0) {
               strcpy(b, s->macro+1);
               if(debug['m'])
                       print("#expand %s %s\n", s->name, ob);
               return;
       }
       c = getnsc();
       if(c != '(')
               goto bad;
       n = 0;
       c = getc();
       if(c != ')') {
               unget(c);
               l = 0;
               cp = buf;
               ecp = cp + sizeof(buf)-4;
               arg[n++] = cp;
               for(;;) {
                       if(cp >= ecp)
                               goto toobig;
                       c = getc();
                       if(c == '"')
                               for(;;) {
                                       if(cp >= ecp)
                                               goto toobig;
                                       *cp++ = c;
                                       c = getc();
                                       if(c == '\\') {
                                               *cp++ = c;
                                               c = getc();
                                               continue;
                                       }
                                       if(c == '\n')
                                               goto bad;
                                       if(c == '"')
                                               break;
                               }
                       if(c == '\'')
                               for(;;) {
                                       if(cp >= ecp)
                                               goto toobig;
                                       *cp++ = c;
                                       c = getc();
                                       if(c == '\\') {
                                               *cp++ = c;
                                               c = getc();
                                               continue;
                                       }
                                       if(c == '\n')
                                               goto bad;
                                       if(c == '\'')
                                               break;
                               }
                       if(l == 0) {
                               if(c == ',') {
                                       *cp++ = 0;
                                       arg[n++] = cp;
                                       if(n > nargs)
                                               break;
                                       continue;
                               }
                               if(c == ')')
                                       break;
                       }
                       if(c == '\n')
                               c = ' ';
                       *cp++ = c;
                       if(c == '(')
                               l++;
                       if(c == ')')
                               l--;
               }
               *cp = 0;
       }
       if(n != nargs) {
               yyerror("argument mismatch expanding: %s", s->name);
               *b = 0;
               return;
       }
       cp = s->macro+1;
       for(;;) {
               c = *cp++;
               if(c != '#') {
                       *b++ = c;
                       if(c == 0)
                               break;
                       continue;
               }
               c = *cp++;
               if(c == 0)
                       goto bad;
               if(c == '#') {
                       *b++ = c;
                       continue;
               }
               c -= 'a';
               if(c < 0 || c >= n)
                       continue;
               strcpy(b, arg[c]);
               b += strlen(arg[c]);
       }
       *b = 0;
       if(debug['m'])
               print("#expand %s %s\n", s->name, ob);
       return;

bad:
       yyerror("syntax in macro expansion: %s", s->name);
       *b = 0;
       return;

toobig:
       yyerror("too much text in macro expansion: %s", s->name);
       *b = 0;
}

void
macinc(void)
{
       int c0, c, i, f;
       char str[STRINGSZ], *hp;


       c0 = getnsc();
       if(c0 != '"') {
               c = c0;
               if(c0 != '<')
                       goto bad;
               c0 = '>';
       }
       for(hp = str;;) {
               c = getc();
               if(c == c0)
                       break;
               if(c == '\n')
                       goto bad;
               *hp++ = c;
       }
       *hp = 0;
       c = getnsc();
       if(c != '\n')
               goto bad;
       f = -1;
       for(i=0; i<ninclude; i++) {
               if(i == 0 && c0 == '>')
                       continue;
               strcpy(symb, include[i]);
               strcat(symb, "/");
               if(strcmp(symb, "./") == 0)
                       symb[0] = 0;
               strcat(symb, str);
               f = open(symb, 0);
               if(f >= 0)
                       break;
       }
       if(f < 0)
               strcpy(symb, str);
       c = strlen(symb) + 1;
       while(c & 3)
               c++;
       while(nhunk < c)
               gethunk();
       hp = hunk;
       memcpy(hunk, symb, c);
       nhunk -= c;
       hunk += c;
       newio();
       pushio();
       newfile(hp, f);
       return;

bad:
       unget(c);
       yyerror("syntax in #include");
       macend();
}

void
maclin(void)
{
       char *cp;
       int c;
       long n;

       c = getnsc();
       if(c < '0' || c > '9')
               goto bad;
       n = 0;
       while(c >= '0' && c <= '9') {
               n = n*10 + c-'0';
               c = getc();
       }
       for(;;) {
               if(c == ' ' || c == '\t') {
                       c = getc();
                       continue;
               }
               if(c == '"')
                       break;
               if(c == '\n') {
                       strcpy(symb, "<noname>");
                       goto nn;
               }
               goto bad;
       }
       cp = symb;
       for(;;) {
               c = getc();
               if(c == '"')
                       break;
               *cp++ = c;
       }
       *cp = 0;
       c = getnsc();
       if(c != '\n')
               goto bad;

nn:
       c = strlen(symb) + 1;
       while(c & 3)
               c++;
       while(nhunk < c)
               gethunk();
       cp = hunk;
       memcpy(hunk, symb, c);
       nhunk -= c;
       hunk += c;

       linehist(cp, n);
       return;

bad:
       unget(c);
       yyerror("syntax in #line");
       macend();
}

void
macif(int f)
{
       int c, l, bol;
       Sym *s;

       if(f == 2)
               goto skip;
       s = getsym();
       if(s == S || getnsc() != '\n')
               goto bad;
       if((s->macro != 0) ^ f)
               return;
       if(s->ref)
               s->ref->class = CMACRO;

skip:
       bol = 1;
       l = 0;
       for(;;) {
               c = getc();
               if(c != '#') {
                       if(!isspace(c))
                               bol = 0;
                       if(c == '\n')
                               bol = 1;
                       continue;
               }
               if(!bol)
                       continue;
               s = getsym();
               if(s == S)
                       continue;
               if(s->ref)
                       s->ref->class = CPREPROC;
               if(strcmp(s->name, "endif") == 0) {
                       if(l) {
                               l--;
                               continue;
                       }
                       macend();
                       return;
               }
               if(strcmp(s->name, "ifdef") == 0 || strcmp(s->name, "ifndef") == 0) {
                       l++;
                       continue;
               }
               if(l == 0 && f != 2 && strcmp(s->name, "else") == 0) {
                       macend();
                       return;
               }
       }

bad:
       yyerror("syntax in #if(n)def");
       macend();
}

void
macprag(void)
{
       Sym *s;
       int c0, c;
       char *hp;
       Hist *h;

       s = getsym();
       if(s == S || strcmp(s->name, "lib") != 0) {
               while(getnsc() != '\n')
                       ;
               return;
       }

       c0 = getnsc();
       if(c0 != '"') {
               c = c0;
               if(c0 != '<')
                       goto bad;
               c0 = '>';
       }
       for(hp = symb;;) {
               c = getc();
               if(c == c0)
                       break;
               if(c == '\n')
                       goto bad;
               *hp++ = c;
       }
       *hp = 0;
       c = getnsc();
       if(c != '\n')
               goto bad;

       /*
        * put pragma-line in as a funny history
        */
       c = strlen(symb) + 1;
       while(c & 3)
               c++;
       while(nhunk < c)
               gethunk();
       hp = hunk;
       memcpy(hunk, symb, c);
       nhunk -= c;
       hunk += c;

       ALLOC(h, Hist);
       h->name = hp;
       h->line = lineno;
       h->offset = -1;
       h->link = H;
       if(ehist == H) {
               hist = h;
               ehist = h;
               return;
       }
       ehist->link = h;
       ehist = h;
       return;

bad:
       unget(c);
       yyerror("syntax in #pragma lib");
       macend();
}

void
macend(void)
{
       int c;

       for(;;) {
               c = getnsc();
               if(c < 0 || c == '\n')
                       return;
       }
}

void
linehist(char *f, int offset)
{
       Hist *h;

       if(debug['f'])
               if(f) {
                       if(offset)
                               print("%4d: %s (#line %d)\n", lineno, f, offset);
                       else
                               print("%4d: %s\n", lineno, f);
               } else
                       print("%4d: <pop>\n", lineno);

       /*
        * overwrite the last #line directive if
        * no alloc has happened since the last one
        */
       if(newflag == 0 && ehist != H && offset != 0 && ehist->offset != 0)
               if(f && ehist->name && strcmp(f, ehist->name) == 0) {
                       ehist->line = lineno;
                       ehist->offset = offset;
                       return;
               }
       newflag = 0;

       ALLOC(h, Hist);
       h->name = f;
       h->line = lineno;
       h->offset = offset;
       h->link = H;
       if(ehist == H) {
               hist = h;
               ehist = h;
               return;
       }
       ehist->link = h;
       ehist = h;
}

void
gethunk(void)
{
       char *h;
       long nh;

       nh = NHUNK;
       if(thunk >= 10L*NHUNK)
               nh = 10L*NHUNK;
       h = (char*)sbrk(nh);
       if(h == (char*)-1) {
               yyerror("out of memory");
               errorexit();
       }
       hunk = h;
       nhunk = nh;
       thunk += nh;
}