
#include <stdio.h>
#include <string.h>
#ifdef __STDC__
#include <stdlib.h>
#else
#include <malloc.h>
#endif
#include "cc.h"

#define CPP_DEBUG 0	/* LOTS of junk to stderr. */

/*
 * This file comprises the 'guts' of a C preprocessor.
 *
 * Functions exported from this file:
 *   gettok() Returns the next token from the source
 *            curword contains the text of the token
 *
 * Variables
 *   curword  Contains the text of the last token parsed.
 *   curfile  Currently open primary file
 *   c_fname  Name of file being parsed
 *   c_lineno Current line number in file being parsed.
 *
 *   alltok   Control flag for the kind of tokens you want (C or generic)
 *   dialect  Control flag to change the preprocessor for Ansi C.
 *
 * TODO:
 *    #asm -> asm("...") translation.
 *    ?: in #if expressions
 *    Complete #line directive.
 *    \n in "\n" in a stringized argument.
 *    Comments in stringized arguments should be deleted.
 *
 *    Poss: Seperate current directory for #include from errors (#line).
 *          (For editors that hunt down source files)
 *    Poss: C99 Variable macro args.
 */

#define KEEP_SPACE	0
#define SKIP_SPACE	1

#define EOT	4
#define SYN	22

char curword[WORDSIZE];
int  alltok = 0;
int  dialect = 0;

FILE * curfile;
char * c_fname;
int    c_lineno = 0;

#ifdef __BCC__
typedef long int_type;		/* Used for preprocessor expressions */
#else
typedef int int_type;		/* Used for preprocessor expressions */
#endif
static int  curtok = 0;		/* Used for preprocessor expressions */

static int    fi_count = 0;
static FILE * saved_files[MAX_INCLUDE];
static char * saved_fname[MAX_INCLUDE];
static int    saved_lines[MAX_INCLUDE];

static char * def_ptr = 0;
static char * def_start = 0;
static struct define_item * def_ref = 0;

static int    def_count =0;
static char * saved_def[MAX_DEFINE];
static char * saved_start[MAX_DEFINE];
static long   saved_unputc[MAX_DEFINE];
static struct define_item * saved_ref[MAX_DEFINE];
 
static long unputc = 0;

static int last_char = '\n';
static int in_preproc = 0;
static int dont_subst = 0;
static int quoted_str = 0;

static int if_count = 0;
static int if_false = 0;
static int if_has_else = 0;
static int if_hidden = 0;
static unsigned int if_stack = 0;

struct arg_store {
   char * name;
   char * value;
   int  in_define;
};

static int  chget P((void));
static int  chget_raw P((void));
static void unchget P((int));
static int  gettok_nosub P((void));
static int  get_onetok P((int));
static int  pgetc P((void));
static int  do_preproc P((void));
static int  do_proc_copy_hashline P((void));
static int  do_proc_if P((int));
static void do_proc_include P((void));
static void do_proc_define P((void));
static void do_proc_undef P((void));
static void do_proc_else P((void));
static void do_proc_endif P((void));
static void do_proc_tail P((void));
static int  get_if_expression P((void));
static int_type get_expression P((int));
static int_type get_exp_value P((void));
static void gen_substrings P((char *, char *, int));
static char * insert_substrings P((char *, struct arg_store *, int));

int
gettok()
{
   int ch;

   for(;;) 
   {
      /* Tokenised C-Preprocessing */
      if (!quoted_str)
      {
	 if (alltok)
	    ch = get_onetok(KEEP_SPACE);
	 else
	    ch = get_onetok(SKIP_SPACE);

	 if( ch == '"' || ch == '\'' )
	    quoted_str = ch;

	 if( ch == TK_WORD )
	 {
	    struct token_trans *p;
	    if( p=is_ckey(curword, strlen(curword)) )
	       return p->token;
	 }

	 if (ch == '\n') continue;
	 return ch;
      }

      /* Special for quoted strings */
      *curword = '\0';
      ch = chget();
      if( ch == EOF ) return ch;

      *curword = ch;
      curword[1] = '\0';

      if( ch == quoted_str ) {
	 if( ch == '"' )
	 {
	    if (dialect == DI_ANSI) {
	       /* Found a terminator '"' check for ansi continuation */
	       while( (ch = pgetc()) <= ' ' && ch != EOF) ;
	       if( ch == '"' ) continue;
	       unchget(ch);
	       *curword = '"';
	       curword[1] = '\0';
	    }

	    quoted_str = 0;
	    return '"';
	 } else {
	    quoted_str = 0;
	    return ch;
	 }
      }
      if( ch == '\n' ) {
	 quoted_str = 0;
	 unchget(ch);	/* Make sure error line is right */
	 return ch;
      }
      if( ch == '\\' ) {
	 unchget(ch);
	 ch = get_onetok(KEEP_SPACE);
	 return ch;
      }
      return TK_STR;
   }
}

static int
gettok_nosub()
{ int rv; dont_subst++; rv=get_onetok(SKIP_SPACE); dont_subst--; return rv; }

static int
get_onetok(keep)
int keep;
{
   char * p;
   int state;
   int ch, cc;

Try_again:
   *(p=curword) = '\0';
   state=cc=ch=0;

   /* First skip whitespace, if the arg says so then we need to keep it */
   while( (ch = pgetc()) == ' ' || ch == '\t' )
   {
      if (keep == KEEP_SPACE) {
	 if( p < curword + WORDSIZE-1 ) {
	    *p++ = ch;	/* Clip to WORDSIZE */
	    *p = '\0';
	 }
      }
   }

   if( ch > 0xFF ) return ch;
   if( p != curword ) { unchget(ch); return TK_WSPACE; }
   if( ch == '\n') return ch;
   if( ch == EOF ) return ch;
   if( ch >= 0 && ch < ' ' ) goto Try_again;

   for(;;)
   {
      switch(state)
      {
      case 0: if( (ch >= 'A' && ch <= 'Z')
               || (ch >= 'a' && ch <= 'z')
               || ch == '_' || ch == '$' )
                 state = 1;
              else if(ch == '0')
                 state = 2;
              else if(ch >= '1' && ch <= '9')
                 state = 5;
              else
                 goto break_break;
              break;
      case 1: if( (ch >= '0' && ch <= '9')
               || (ch >= 'A' && ch <= 'Z')
               || (ch >= 'a' && ch <= 'z')
               || ch == '_' || ch == '$' )
                 break;
              else
                 goto break_break;
      case 2: if( ch >= '0' && ch <= '7')
                 state = 3;
              else if( ch == 'x' || ch == 'X' )
                 state = 4;
              else
                 goto break_break;
              break;
      case 3: if( ch >= '0' && ch <= '7')
                 break;
              else
                 goto break_break;
      case 4: if( (ch >= '0' && ch <= '9')
               || (ch >= 'A' && ch <= 'F')
               || (ch >= 'a' && ch <= 'f') )
                 break;
              else
                 goto break_break;
      case 5:
      case 6: if( ch >= '0' && ch <= '9')
                 ;
              else if( ch == '.' && state != 6 )
                 state = 6;
              else if( ch == 'e' || ch == 'E' )
                 state = 7;
              else
                 goto break_break;
              break;
      case 7: if( ch == '+' || ch == '-' )
                 break;
              state = 8;
              /* FALLTHROUGH */
      case 8: if( ch >= '0' && ch <= '9')
                 break;
              else
                 goto break_break;
      }
      if( cc < WORDSIZE-1 ) *p++ = ch;	/* Clip to WORDSIZE */
      *p = '\0'; cc++;
      ch = chget();
      if (ch == SYN) ch = chget();
   }
break_break:
   /* Numbers */
   if( state >= 2 )
   {
      if( state < 6 )
      {
         if( ch == 'u' || ch == 'U' )
         {
            if( cc < WORDSIZE-1 ) *p++ = ch;	/* Clip to WORDSIZE */
            *p = '\0'; cc++;
	    ch = chget();
         }
         if( ch == 'l' || ch == 'L' )
         {
            if( cc < WORDSIZE-1 ) *p++ = ch;	/* Clip to WORDSIZE */
            *p = '\0'; cc++;
         }
         else unchget(ch);
         return TK_NUM;
      }
      unchget(ch);
      return TK_FLT;
   }

   /* Words */
   if( state == 1 )
   {
      struct define_item * ptr;
      unchget(ch);
      if( !dont_subst
         && (ptr = read_entry(0, curword)) != 0
	 && !ptr->in_use
	 )
      {
	 if ( def_count >= MAX_DEFINE ) {
	    cwarn("Preprocessor recursion overflow");
	    return TK_WORD;
	 } else if( ptr->arg_count >= 0 )
	 {
	    /* An open bracket must follow the word */
	    int ch1 = 0;
	    while ((ch = chget()) == ' ' || ch == '\t' ) ch1 = ch;
	    if (ch != '(') {
	       unchget(ch);
	       if (ch1) unchget(ch1);
	       return TK_WORD;
	    }

	    /* We have arguments to process so lets do so. */
	    gen_substrings(ptr->name, ptr->value, ptr->arg_count);

	    /* Don't mark macros with arguments as in use, it's very 
	     * difficult to say what the correct result would be so
	     * I'm letting the error happen. Also if I do block
	     * recursion then it'll also block 'pseudo' recursion
	     * where the arguments have a call to this macro.
	     *
	    def_ref = ptr;
	    ptr->in_use = 1;
	    */
	 }
	 else if (ptr->value[0])
	 {
	    /* Simple direct substitution; note the shortcut (above) for
	     * macros that are defined as null */
            saved_ref[def_count] = def_ref;
            saved_def[def_count] = def_ptr;
            saved_start[def_count] = def_start;
            saved_unputc[def_count] = unputc;
            def_count++;
            unputc = 0;
	    def_ref = ptr;
            def_ptr = ptr->value;
	    def_start = 0;
	    ptr->in_use = 1;
	 }
         goto Try_again;
      }
      return TK_WORD;
   }

   /* Quoted char for preprocessor expressions */
   if(in_preproc && ch == '\'' )
   {
      *p++ = ch; ch = chget();
      for(;;)
      {
	 if( cc < WORDSIZE-1 ) *p++ = ch;	/* Clip to WORDSIZE */
	 *p = '\0'; cc++;
	 if( ch == '\'' || ch == '\n' ) break;

	 if( ch == '\\' )
	 {
	    ch = chget();
	    if( cc < WORDSIZE-1 ) *p++ = ch;	/* Clip to WORDSIZE */
	    *p = '\0'; cc++;
	 }
	 ch = chget();
      }
      ch = TK_QUOT;
   }

   /* Collect and translate \xyx strings, (should probably translate these
    * all to some standard form (eg \ooo plus \N )
    *
    * ___________________________________________________________________
    * | new-line          NL (LF)   \n|  audible alert        BEL   \a  |
    * | horizontal tab    HT        \t|  question mark        ?     \?  |
    * | vertical tab      VT        \v|  double quote         "     \"  |
    * | backspace         BS        \b|  octal escape         ooo   \ooo|
    * | carriage return   CR        \r|  hexadecimal escape   hh    \xhh|
    * | formfeed          FF        \f|  backslash            \     \\  |
    * | single quote      '         \'|                                 |
    * |_______________________________|_________________________________|
    */

   if( ch == '\\' )
   {
      int i;

      *p++ = ch; ch = chget();
      if (ch >= '0' && ch <= '7' ) {
	 for(i=0; i<3; i++) {
	    if (ch >= '0' && ch <= '7' ) {
	       *p++ = ch; ch = chget();
	    }
	 }
	 unchget(ch);
      } else if (ch == 'x' || ch == 'X') {
	 *p++ = ch; ch = chget();
	 for(i=0; i<2; i++) {
	    if ( (ch >= '0' && ch <= '9' ) ||
	         (ch >= 'A' && ch <= 'F' ) ||
	         (ch >= 'a' && ch <= 'f' ) ) {
	       *p++ = ch; ch = chget();
	    }
	 }
	 unchget(ch);
      } else if (ch == '?') {
	 p[-1] = '?';
      } else if (ch != '\n' && ch != EOF) {
	 *p++ = ch;
      } else
	 unchget(ch);
      *p = '\0';
      return TK_STR;
   }

   /* Possible composite tokens */
   if( ch > ' ' && ch <= '~' )
   {
      struct token_trans *p;
      *curword = cc = ch;

      for(state=1; ; state++)
      {
	 curword[state] = ch = chget();
	 if( !(p=is_ctok(curword, state+1)) )
	 {
	    unchget(ch);
	    curword[state] = '\0';
	    return cc;
	 }
	 cc=p->token;
      }
   }
   return ch;
}

static int
pgetc()
{
   int ch, ch1;

   for(;;)
   {
      if ((ch = chget()) == EOF) return ch;

      if( !in_preproc && last_char == '\n' && ch == '#' )
      {
	 in_preproc = 1;
	 ch = do_preproc();
	 in_preproc = 0;
	 if(if_false || ch == 0) continue;

	 last_char = '\n';
	 return ch;
      }
      if( last_char != '\n' || (ch != ' ' && ch != '\t') )
	 last_char = ch;

      /* Remove comments ... */
      if( ch != '/' )
         { if(if_false && !in_preproc) continue; return ch; }
      ch1 = chget(); /* Allow "/\\\n*" as comment start too!? */

      if( ch1 == '/' ) /* Double slash style comments */
      {
	 do { ch = chget(); } while(ch != '\n' && ch != EOF);
	 return ch;	/* Keep the return. */
      }

      if( ch1 != '*' )
      {
	 unchget(ch1);
	 if(if_false && !in_preproc) continue;
	 return ch;
      }

      for(;;)
      {
	 if( ch == '*' )
	 {
	    ch = chget();
	    if( ch == EOF ) return EOF;
	    if( ch == '/' ) break;
	 }
	 else ch = chget();
      }
      if (dialect == DI_ANSI)
	   return ' ';	/* If comments become " " */
      else return SYN;  /* Comments become nulls, but we need a
			 * marker so I can do token concat properly. */
   }
}

/* This function handles the first and second translation phases of Ansi-C */
static int 
chget()
{
   int ch, ch1;
   for(;;) {
      ch = chget_raw();
      if (ch == '\\') {
	 ch1 = chget_raw();
	 if (ch1 == '\n') continue;
	 unchget(ch1);
      }

      /* Ansi trigraphs -- Ewww, it needs lots of 'unchget' space too. */
      if (dialect == DI_ANSI && ch == '?') {
	 ch1 = chget_raw();
	 if (ch1 != '?')
	    unchget(ch1);
	 else {
	    static char trig1[] = "()<>/!'-=";
	    static char trig2[] = "[]{}\\|^~#";
	    char * s;
	    ch1 = chget_raw();
	    s = strchr(trig1, ch1);
	    if (s) {
	       unchget(trig2[s-trig1]);	/* Unchget so that ??/ can be used as */
	       continue;		/* a real backslash at EOL. */
	    } else {
	       unchget(ch1);
	       unchget('?');
	    }
	 }
      }

      return ch;
   }
}

static void 
unchget(ch)
{
#if CPP_DEBUG
   fprintf(stderr, "\b", ch);
#endif
   if(ch == 0) return;  /* Hummm */
   if(ch == EOF) ch=EOT; /* EOF is pushed back as ^Z */
   ch &= 0xFF;

   if(unputc&0xFF000000) 
      cerror("Internal character pushback stack overflow");
   else       unputc = (unputc<<8) + (ch);
   if( ch == '\n' ) c_lineno--;
}

static int 
chget_raw()
#if CPP_DEBUG
{
   int ch;
static int last_def = 0;
static int last_fi = 0;
   if (last_fi != fi_count)   fprintf(stderr, "<INC%d>", fi_count);
   if (last_def != def_count) fprintf(stderr, "<DEF%d>", def_count);
   last_def = def_count; last_fi = fi_count;

   ch = realchget();
   if (ch == EOF) fprintf(stderr, "<EOF>"); else fprintf(stderr, "%c", ch);

   if (last_def != def_count) fprintf(stderr, "<DEF%d>", def_count);
   if (last_fi != fi_count)   fprintf(stderr, "<INC%d>", fi_count);
   last_def = def_count; last_fi = fi_count;

   return ch;
}

static int 
realchget()
#endif
{
   int ch;
   for(;;)
   {
      if( unputc )
      {
	 if((unputc&0xFF)==EOT && in_preproc) return '\n';
	 ch=(unputc&0xFF); unputc>>=8;
	 if( ch == EOT ) ch = EOF;
	 if( ch == '\n' ) c_lineno++;
	 return ch;
      }

      if( def_ptr )
      {
	 ch = *def_ptr++; if(ch) return (unsigned char)ch;
	 if( def_start ) free(def_start);
	 if( def_ref ) def_ref->in_use = 0;

	 def_count--;
	 def_ref   = saved_ref[def_count];
	 def_ptr   = saved_def[def_count];
	 def_start = saved_start[def_count];
	 unputc    = saved_unputc[def_count];
	 continue;
      }

      ch = getc(curfile);
      if( ch == EOF && fi_count != 0)
      {
	 fclose(curfile);
	 fi_count--;
	 curfile  = saved_files[fi_count];
	 if(c_fname) free(c_fname);
	 c_fname  = saved_fname[fi_count];
	 c_lineno = saved_lines[fi_count];
	 ch = '\n'; /* Ensure end of line on end of file */
      }
      else if( ch == '\n' ) c_lineno++;

      /* Treat all control characters, except the standard whitespace
       * characters of TAB and NL as completely invisible. 
       */
      if( ch >= 0 && ch < ' ' && ch!='\n' && ch!='\t' && ch!=EOF ) continue;

      if( ch == EOF ) { unchget(ch); return '\n'; } /* Ensure EOL before EOF */
      return (unsigned char)ch;
   }
}

static int
do_preproc()
{
   int val, no_match=0;

   if( (val=get_onetok(SKIP_SPACE)) == TK_WORD )
   {
      if( strcmp(curword, "ifdef") == 0 )
         do_proc_if(0);
      else if( strcmp(curword, "ifndef") == 0 )
         do_proc_if(1);
      else if( strcmp(curword, "if") == 0 )
         do_proc_if(2);
      else if( strcmp(curword, "elif") == 0 )
         do_proc_if(3);
      else if( strcmp(curword, "else") == 0 )
         do_proc_else();
      else if( strcmp(curword, "endif") == 0 )
         do_proc_endif();
      else if(if_false)
         no_match=1;
      else
      {
         if( strcmp(curword, "include") == 0 )
            do_proc_include();
         else if( strcmp(curword, "define") == 0 )
            do_proc_define();
         else if( strcmp(curword, "undef") == 0 )
            do_proc_undef();
         else if( strcmp(curword, "error") == 0 ) {
	    strcpy(curword, "#error");
            do_proc_copy_hashline(); pgetc();
	    cerror(curword);
	 } else if( strcmp(curword, "warning") == 0 ) {
	    strcpy(curword, "#warning");
            do_proc_copy_hashline(); pgetc();
	    cwarn(curword);
	 } else if( strcmp(curword, "pragma") == 0 ) {
            do_proc_copy_hashline(); pgetc();
	    /* Ignore #pragma ? */
	 } else if( strcmp(curword, "line") == 0 ) {
            do_proc_copy_hashline(); pgetc();
	    /* Ignore #line for now. */
	 } else if( strcmp(curword, "asm") == 0 ) {
	    alltok |= 0x100;
            return do_proc_copy_hashline();
	 } else if( strcmp(curword, "endasm") == 0 ) {
	    alltok &= ~0x100;
            return do_proc_copy_hashline();
	 } else 
	    no_match=1;
      }
   }
   else no_match=1;
   
   if( no_match )
   {
      if(!if_false) cerror("Unknown preprocessor directive");
      while( val != '\n' ) val = pgetc();
   }

   *curword = 0; /* Just in case */
   return 0;
}

static int
do_proc_copy_hashline()
{
   int off, ch;

   off = strlen(curword);

   while( (ch=pgetc()) != '\n' )
   {
      if( off < WORDSIZE ) curword[off++] = ch;
   }
   if( off == WORDSIZE )
   {
      cerror("Preprocessor directive too long");
      curword[WORDSIZE-1] = '\0';
   }
   else
      curword[off] = '\0';

   unchget('\n');
   return TK_COPY;
}

static void
do_proc_include()
{
   int ch, ch1;
   char * p;
   FILE * fd;

   ch = get_onetok(SKIP_SPACE);
   if( ch == '<' || ch == '"' )
   {
      if( ch == '"' ) ch1 = ch; else ch1 = '>';
      p = curword;
      while(p< curword+WORDSIZE-1)
      {
         ch = pgetc();
         if( ch == '\n' ) break;
         if( ch == ch1 )
         {
            *p = '\0';
	    p = strdup(curword);

	    do { ch1 = pgetc(); } while(ch1 == ' ' || ch1 == '\t');
	    unchget(ch1);
            do_proc_tail();

	    saved_files[fi_count] = curfile;
	    saved_fname[fi_count] = c_fname;
	    saved_lines[fi_count] = c_lineno;

            fd = open_include(p, "r", (ch=='"'));
            if( fd ) {
	       fi_count++;
	       curfile = fd;
	    } else
               cerror("Cannot open include file");

            return;
         }
         *p++ = ch;
      }
   }
   cerror("Bad #include command");
   while(ch != '\n') ch = pgetc();
   return;
}

static void
do_proc_define()
{
   int ch, ch1;
   struct define_item * ptr, * old_value = 0;
   int  cc, len;
   char name[WORDSIZE];

   if( (ch=gettok_nosub()) == TK_WORD )
   {
      strcpy(name, curword);
      ptr = read_entry(0, name);
      if(ptr)
      {
         set_entry(0, name, (void*)0); /* Unset var */
         if (ptr->in_use) 
	    /* Eeeek! This shouldn't happen; so just let it leak. */
	    cwarn("macro redefined while it was in use!?");
	 else
	    old_value = ptr;
      }

      /* Skip blanks */
      for(ch=ch1=pgetc(); ch == ' ' || ch == '\t' ; ch=pgetc()) ;

      len = WORDSIZE; 
      ptr = malloc(sizeof(struct define_item) + WORDSIZE);
      if(ptr==0) cfatal("Preprocessor out of memory");
      ptr->value[cc=0] = '\0';

      /* Add in arguments */
      if( ch1 == '(' )
      {
         ptr->arg_count=0;
	 for(;;)
	 {
	    ch=gettok_nosub();
	    if( ptr->arg_count==0 && ch == ')' ) break;
	    if( ch == TK_WORD ) 
	    {
	       if( cc+strlen(curword)+4 >= len)
	       {
		  len = cc + WORDSIZE;
		  ptr = (struct define_item *) realloc(ptr, sizeof(struct define_item) + len);
		  if(ptr==0) cfatal("Preprocessor out of memory");
	       }
	       if( cc+strlen(curword) < len)
	       {
		  strcpy(ptr->value+cc, curword);
		  cc+=strlen(curword);
		  strcpy(ptr->value+cc, ",");
		  cc++;
		  ptr->arg_count++;
		  ch=gettok_nosub();
		  if( ch == ')' ) break;
		  if( ch == ',' ) continue;
	       }
	    }
            cerror("Bad #define command");
	    free(ptr);
            while(ch != '\n') ch = pgetc();
	    set_entry(0, name, (void*)old_value); /* Return var to old. */
	    return;
	 }
	 while((ch=pgetc())==' ' || ch=='\t');
      }
      else ptr->arg_count = -1;

      /* And the substitution string */
      while(ch != '\n')
      {
         if( cc+4 > len )
         {
            len = cc + WORDSIZE;
            ptr = (struct define_item *) realloc(ptr, sizeof(struct define_item) + len);
            if(ptr==0) cfatal("Preprocessor out of memory");
         }
         ptr->value[cc++] = ch;
         ch = pgetc();
      }
      if (cc)
	 ptr->value[cc++] = ' ';/* Byte of lookahead for recursive macros */
      ptr->value[cc++] = '\0';

#if CPP_DEBUG
      if (cc == 1)
	 fprintf(stderr, "\n### Define '%s' as null\n", name);
      else if (ptr->arg_count<0)
	 fprintf(stderr, "\n### Define '%s' as '%s'\n", 
		 name, ptr->value);
      else
	 fprintf(stderr, "\n### Define '%s' as %d args '%s'\n", 
		 name, ptr->arg_count, ptr->value);
#endif

      /* Clip to correct size and save */
      ptr = (struct define_item *) realloc(ptr, sizeof(struct define_item) + cc);
      ptr->name = set_entry(0, name, ptr);
      ptr->in_use = 0;
      ptr->next = 0;

      if (old_value) {
	 if (strcmp(old_value->value, ptr->value) != 0)
	    cwarn("#define redefined macro");
	 free(old_value);
      }
   }
   else cerror("Bad #define command");
   while(ch != '\n') ch = pgetc();
}

static void
do_proc_undef()
{
   int ch;
   struct define_item * ptr;
   if( (ch=gettok_nosub()) == TK_WORD )
   {
      ptr = read_entry(0, curword);
      if(ptr)
      {
         set_entry(0, curword, (void*)0); /* Unset var */
         if (ptr->in_use) 
	    /* Eeeek! This shouldn't happen; so just let it leak. */
	    cwarn("macro undefined while it was in use!?");
	 else
	    free(ptr);
      }
      do_proc_tail();
   }
   else
   {
      cerror("Bad #undef command");
      while(ch != '\n') ch = pgetc();
   }
}

static int
do_proc_if(type)
int type;
{
   int ch = 0;
   if(if_false && if_hidden)
   {
      if( type != 3 ) if_hidden++;
      do_proc_tail();
      return 0;
   }

   if( type == 3 )
   {
      if( if_count == 0 )
	 cerror("#elif without matching #if");
      else
      {
	 if( if_has_else )
	    cerror("#elif following #else for one #if");
	 if( if_has_else || if_false != 1 )
	 {
	    if_false=2;
	    while(ch != '\n') ch = pgetc();
	    return 0;
	 }
	 if_false=0;
      }
      if_has_else = 0;
   }
   if(if_false)
   {
      if( type != 3 ) if_hidden++;
      do_proc_tail();
   }
   else
   {
      if( type != 3 )
      {
         if_count++;
         if_stack <<= 1;
         if_stack |= if_has_else;
         if_has_else = 0;
      }
      if(type > 1)
      {
	 ch = get_if_expression();
	 if_false=!ch;
      }
      else
      {
         ch = gettok_nosub();
         if( ch == TK_WORD )
         {
            do_proc_tail();
            if_false = (read_entry(0, curword) == 0);
            if(type == 1) if_false = !if_false;
         }
         else
         {
            cerror("Bad #if command");
            if_false = 0;
            while(ch != '\n') ch = pgetc();
         }
      }
   }
   return 0;
}

static void 
do_proc_else()
{
   if( if_hidden == 0 )
   {
      if( if_count == 0 )
         cerror("#else without matching #if");
      else
         if_false = (if_false^1);
      if( if_has_else )
         cerror("Multiple #else's for one #if");
      if_has_else = 1;
   }
   do_proc_tail();
}

static void 
do_proc_endif()
{
   if( if_hidden )
      if_hidden--;
   else
   {
      if( if_count == 0 )
         cerror("Unmatched #endif");
      else
      {
         if_count--;
         if_false=0;
         if_has_else = (if_stack&1);
         if_stack >>=1;
      }
   }
   do_proc_tail();
}

static void
do_proc_tail()
{
   int ch, flg=1;
   while((ch = pgetc()) != '\n') if(ch > ' ')
   {
      if (!if_false && flg)
         cwarn("Unexpected text following preprocessor command");
      flg=0;
   }
}

static int
get_if_expression()
{
   int value = get_expression(0);

   if (curtok != '\n')
      do_proc_tail();

   return value;
}

static int_type
get_expression(prio)
int prio;
{
   int_type lvalue;
   int_type rvalue;
   int no_op = 0;

   curtok = get_onetok(SKIP_SPACE);
   lvalue = get_exp_value();

   do
   {
      switch(curtok)
      {
	 case '*': case '/': case '%':
	    if (prio >= 10) return lvalue;
	    break;
	 case '+': case '-':
	    if (prio >= 9) return lvalue;
	    break;
	 case TK_RIGHT_OP: case TK_LEFT_OP:
	    if (prio >= 8) return lvalue;
	    break;
	 case '<': case '>': case TK_LE_OP: case TK_GE_OP:
	    if (prio >= 7) return lvalue;
	    break;
	 case TK_EQ_OP: case TK_NE_OP:
	    if (prio >= 6) return lvalue;
	    break;
	 case '&':
	    if (prio >= 5) return lvalue;
	    break;
	 case '^':
	    if (prio >= 4) return lvalue;
	    break;
	 case '|':
	    if (prio >= 3) return lvalue;
	    break;
	 case TK_AND_OP:
	    if (prio >= 2) return lvalue;
	    break;
	 case TK_OR_OP:
	    if (prio >= 1) return lvalue;
	    break;
      }
      switch(curtok)
      {
	 case '*':
	    rvalue = get_expression(10);
	    lvalue *= rvalue;
	    break;
	 case '/':
	    rvalue = get_expression(10);
	    if (rvalue)
	       lvalue /= rvalue;
	    break;
	 case '%':
	    rvalue = get_expression(10);
	    if (rvalue)
	       lvalue %= rvalue;
	    break;
	 case '+':
	    rvalue = get_expression(9);
	    lvalue += rvalue;
	    break;
	 case '-':
	    rvalue = get_expression(9);
	    lvalue -= rvalue;
	    break;
	 case TK_RIGHT_OP:
	    rvalue = get_expression(8);
	    lvalue >>= rvalue;
	    break;
	 case TK_LEFT_OP:
	    rvalue = get_expression(8);
	    lvalue <<= rvalue;
	    break;
	 case '<':
	    rvalue = get_expression(7);
	    lvalue = (lvalue < rvalue);
	    break;
	 case '>':
	    rvalue = get_expression(7);
	    lvalue = (lvalue > rvalue);
	    break;
	 case TK_LE_OP:
	    rvalue = get_expression(7);
	    lvalue = (lvalue <= rvalue);
	    break;
	 case TK_GE_OP:
	    rvalue = get_expression(7);
	    lvalue = (lvalue >= rvalue);
	    break;
	 case TK_EQ_OP:
	    rvalue = get_expression(6);
	    lvalue = (lvalue == rvalue);
	    break;
	 case TK_NE_OP:
	    rvalue = get_expression(6);
	    lvalue = (lvalue != rvalue);
	    break;
	 case '&':
	    rvalue = get_expression(5);
	    lvalue = (lvalue & rvalue);
	    break;
	 case '^':
	    rvalue = get_expression(4);
	    lvalue = (lvalue ^ rvalue);
	    break;
	 case '|':
	    rvalue = get_expression(3);
	    lvalue = (lvalue | rvalue);
	    break;
	 case TK_AND_OP:
	    rvalue = get_expression(2);
	    lvalue = (lvalue && rvalue);
	    break;
	 case TK_OR_OP:
	    rvalue = get_expression(1);
	    lvalue = (lvalue || rvalue);
	    break;

	 case '?': /* XXX: To add */

	 default:
	    no_op = 1;
      }
   }
   while(prio == 0 && !no_op);

   return lvalue;
}

static int_type
get_exp_value()
{
   int_type value = 0;
   int sign = 1;

   if (curtok == '!') {
      curtok = get_onetok(SKIP_SPACE);
      return !get_exp_value();
   }
   if (curtok == '~') {
      curtok = get_onetok(SKIP_SPACE);
      return ~get_exp_value();
   }

   while (curtok == '+' || curtok == '-') {
      if (curtok == '-') sign = -sign;
      curtok = get_onetok(SKIP_SPACE);
   }

   if (curtok == TK_NUM) {
      value = strtoul(curword, (void*)0, 0);
      curtok = get_onetok(SKIP_SPACE);
   } else if (curtok == TK_QUOT) {
      value = curword[1];
      if (value == '\\') {
	 if (curword[2] >= '0' && curword[2] <= '7') {
	    value = curword[2] - '0';
	    if (curword[3] >= '0' && curword[3] <= '7') {
	       value = (value<<3) + curword[3] - '0';
	       if (curword[4] >= '0' && curword[4] <= '7') {
		  value = (value<<3) + curword[4] - '0';
	       }
	    }
	 } else switch(curword[2]) {
	    case 'n': value = '\n'; break;
	    case 'f': value = '\f'; break;
	    case 't': value = '\t'; break;
	    default:  value = curword[2]; break;
	 }
      }
#ifdef NATIVE_CPP
      value = (char) value; /* Fix range */
#elif SIGNED_CHAR
      value = (signed char) value;
#else
      value = (unsigned char) value;
#endif
      curtok = get_onetok(SKIP_SPACE);
   } else if (curtok == TK_WORD) {
      value = 0;
      if (strcmp("defined", curword) == 0) {
	 curtok = gettok_nosub();
	 if (curtok == '(' && gettok_nosub() != TK_WORD)
	    cerror("'defined' keyword requires argument");
	 else {
	    value = (read_entry(0, curword) != 0);
	    if (curtok == '(' && gettok_nosub() != ')')
	       cerror("'defined' keyword requires closing ')'");
	    else
	       curtok = get_onetok(SKIP_SPACE);
	 }
      }
      else
	 curtok = get_onetok(SKIP_SPACE);

   } else if (curtok == '(') {
      value = get_expression(0);
      if (curtok == ')')
	 curtok = get_onetok(SKIP_SPACE);
      else {
	 curtok = '$';
	 cerror("Expected ')'");
      }
   }

   return sign<0 ? -value: value;
}

void
gen_substrings(macname, data_str, arg_count)
char * macname;
char * data_str;
int arg_count;
{
   char * mac_text = 0;
   struct arg_store *arg_list;
   int ac, ch, cc, len;

   int paren_count = 0;
   int in_quote = 0;
   int quote_char = 0;
   int commas_found = 0;
   int args_found = 0;

   arg_list = malloc(sizeof(struct arg_store) * arg_count);
   memset(arg_list, 0, sizeof(struct arg_store) * arg_count);

   for(ac=0; *data_str && ac < arg_count; data_str++) {
      if( *data_str == ',' ) { ac++; continue; }

      if (arg_list[ac].name == 0) cc = len = 0;

      if (cc+2 >= len) {
	 len += 20;
	 arg_list[ac].name = realloc(arg_list[ac].name, len);
      }
      arg_list[ac].name[cc++] = *data_str;
      arg_list[ac].name[cc] = '\0';
   }

   for(;;) {
      if ((ch = chget()) == EOF) break;
      if(in_quote == 2) {
	 in_quote = 1;
      } else if (in_quote) {
	 if ( ch == quote_char ) in_quote = 0;
	 if ( ch == '\\') in_quote = 2;
      } else {
	 if ( ch == '(' ) paren_count++;
	 if ( ch == '"' || ch == '\'' ) { in_quote = 1; quote_char = ch; }
	 if (paren_count == 0 && ch == ',' ) {
	    commas_found++; continue;
	 }
	 if ( ch == ')' ) {
	    if (paren_count == 0) break;
	    paren_count--;
	 }
      }
      args_found = 1;
      /* Too many args; ignore rest */
      if (commas_found >= arg_count ) continue;
      ac = commas_found;
      if (arg_list[ac].value == 0) {
	 cc = len = 0;
	 arg_list[ac].in_define = def_count;
      }

      if (cc+2 >= len) {
	 len += 20;
	 arg_list[ac].value = realloc(arg_list[ac].value, len);
      }

#if 0
      if (ch == '\n' && cc>0 && arg_list[ac].value[cc-1] == '\n' ) {
	 ... ?
      }
#endif

      arg_list[ac].value[cc++] = ch;
      arg_list[ac].value[cc] = '\0';
   }

   if (commas_found || args_found) args_found = commas_found+1;

   if( arg_count != args_found )
      cerror("Incorrect number of macro arguments");

   mac_text = insert_substrings(data_str, arg_list, arg_count);

   /* 
    * At this point 'mac_text' contains the full expansion of the macro.
    *
    * So we could scan this for calls to this macro and if we find one
    * that _exactly_ matches this call (including arguments) then we mark
    * this call's in_use flag.
    * 
    * OTOH, it would probably be best to throw away this expansion and 
    * pretend we never noticed this macro expansion in the first place.
    *
    * Still this is mostly academic as the error trapping works and
    * recursive macros _with_arguments_ are both rare and unpredictable.
    */

   if (arg_list) {
      for (ac=0; ac<arg_count; ac++) {
	 if (arg_list[ac].name) free(arg_list[ac].name);
	 if (arg_list[ac].value) free(arg_list[ac].value);
      }
      free(arg_list);
   }

   saved_ref[def_count] = def_ref;
   saved_def[def_count] = def_ptr;
   saved_start[def_count] = def_start;
   saved_unputc[def_count] = unputc;
   def_count++;
   unputc = 0;
   def_ptr = mac_text;
   def_start = mac_text;
   def_ref = 0;
#if CPP_DEBUG
   fprintf(stderr, "\n### <DEF%d='%s'>\n", def_count, mac_text);
#endif
}

static char * 
insert_substrings(data_str, arg_list, arg_count)
char * data_str;
struct arg_store *arg_list;
int arg_count;
{
   int ac, ch;
   char * p, * s;
   char * rv = 0;
   int    len = 0;
   int    cc = 0;
   int    in_quote = 0;
   int    quote_char = 0;
   int    ansi_stringize = 0;

#if CPP_DEBUG
   fprintf(stderr, "\n### Macro substitution in '%s'\n", data_str);
   for (ac=0; ac<arg_count; ac++) {
      fprintf(stderr, "### Argument %d (%s) = '%s'\n", 
	 ac+1, arg_list[ac].name, arg_list[ac].value);
   }
#endif

   rv = malloc(4); *rv = '\0'; len = 4;

   while(*data_str) {
      p = curword;

      if (dialect == DI_ANSI) {
	 if (in_quote == 2)
	    in_quote = 1;
	 else if (in_quote) {
	    if (*data_str == quote_char) in_quote = 0;
	    if (*data_str == '\\') in_quote = 2;
	 } else {
	    if (*data_str == '"' || *data_str == '\'')
	    { in_quote = 1; quote_char = *data_str; }
	 }
      }

      if (!in_quote) for(;;) {
	 ch = *data_str;
         if( (ch >= '0' && ch <= '9')
	  || (ch >= 'A' && ch <= 'Z')
	  || (ch >= 'a' && ch <= 'z')
	  || ch == '_' || ch == '$' )
	    *p++ = *data_str++;
	 else
	    break;
      }

      if (p == curword) {
	 /* Ansi Stringize and concat */
	 if (*data_str == '#' && dialect != DI_KNR) {
	    if (data_str[1] == '#') {
	       while(cc>0 && (rv[cc-1] == ' ' || rv[cc-1] == '\t'))
		  cc--;
	       data_str+=2;
	       while(*data_str == ' ' || *data_str == '\t')
		  data_str++;
	       if (*data_str == '\0') { /* Hummm */
		  data_str--;
		  cerror("'##' operator at end of macro");
	       }
	       continue;
	    }
	    data_str++;
	    ansi_stringize = 1;
	    continue;
	 }

	 if (ansi_stringize) {
	    ansi_stringize = 0;
	    cerror("'#' operator should be followed by a macro argument name");
	 }

	 /* Other characters ... */
	 if (cc+2 > len) { len += 20; rv = realloc(rv, len); }
	 rv[cc++] = *data_str++;
	 continue;
      }
      *p = '\0'; s = curword;
      for (ac=0; ac<arg_count; ac++) {
	 if (*curword == arg_list[ac].name[0] &&
	     strcmp(curword, arg_list[ac].name) == 0)
	 {
	    s = arg_list[ac].value;
	    if (!s) s = ""; else

	    /* Ansi stringize operation, this is very messy! */
	    if (ansi_stringize) {
	       if (arg_list[ac].in_define) {
		  struct define_item * ptr;
                  if ((ptr = read_entry(0, s)) &&  
		       ptr->arg_count == -1) {
		     s = ptr->value;
		  }
	       }

	       rv[cc++] = '"';
	       while(*s == ' ' || *s == '\t') s++;
	       while (*s) {
		  if (cc+4 > len) { len += 20; rv = realloc(rv, len); }
		  if (*s == '"') rv[cc++] = '\\';
		  rv[cc++] = *s++;
	       }
	       while(cc>0 && (rv[cc-1] == ' ' || rv[cc-1] == '\t'))
		  cc--;
	       rv[cc++] = '"';
	       rv[cc++] = '\0';
	       ansi_stringize = 0;
	       s = "";
	       break;
	    }

	    break;
	 }
      }

      if (ansi_stringize) {
	 ansi_stringize = 0;
	 cerror("'#' operator should be followed by a macro argument name");
      }

      if (cc+2+strlen(s) > len) { len += strlen(s)+20; rv = realloc(rv, len); }
      strcpy(rv+cc, s);
      cc = strlen(rv);
   }

   rv[cc] = '\0';
   return rv;
}
