[BACK]Return to unifdef.c CVS log [TXT][DIR] Up to [local] / src / usr.bin / unifdef

Diff for /src/usr.bin/unifdef/unifdef.c between version 1.7 and 1.8

version 1.7, 2002/12/02 07:16:23 version 1.8, 2003/01/18 23:42:51
Line 49 
Line 49 
 /*  /*
  * unifdef - remove ifdef'ed lines   * unifdef - remove ifdef'ed lines
  *   *
  *  Warning: will not work correctly if input contains nul characters.  
  *  
  *  Wishlist:   *  Wishlist:
  *      provide an option which will append the name of the   *      provide an option which will append the name of the
  *        appropriate symbol after #else's and #endif's   *        appropriate symbol after #else's and #endif's
Line 72 
Line 70 
 /* types of input lines: */  /* types of input lines: */
 typedef enum {  typedef enum {
         LT_PLAIN,               /* ordinary line */          LT_PLAIN,               /* ordinary line */
           LT_TRUEI,               /* a true #if with ignore flag */
           LT_FALSEI,              /* a false #if with ignore flag */
           LT_IF,                  /* an unknown #if */
         LT_TRUE,                /* a true #if */          LT_TRUE,                /* a true #if */
         LT_FALSE,               /* a false #if */          LT_FALSE,               /* a false #if */
           LT_ELIF,                /* an unknown #elif */
         LT_ELTRUE,              /* a true #elif */          LT_ELTRUE,              /* a true #elif */
         LT_ELFALSE,             /* a false #elif */          LT_ELFALSE,             /* a false #elif */
         LT_IF,                  /* an unknown #if */  
         LT_ELIF,                /* an unknown #elif */  
         LT_ELSE,                /* #else */          LT_ELSE,                /* #else */
         LT_ENDIF,               /* #endif */          LT_ENDIF,               /* #endif */
         LT_EOF                  /* end of file */          LT_EOF,                 /* end of file */
           LT_COUNT
 } Linetype;  } Linetype;
   
 typedef enum {          /* 0 or 1: pass thru; 1 or 2: ignore comments */  static char const * const linetype_name[] = {
         REJ_NO,          "PLAIN", "TRUEI", "FALSEI", "IF", "TRUE", "FALSE",
         REJ_IGNORE,          "ELIF", "ELTRUE", "ELFALSE", "ELSE", "ENDIF", "EOF"
         REJ_YES  };
 } Reject_level;  
   
   /* state of #if processing */
 typedef enum {  typedef enum {
         NO_COMMENT = false,          IS_OUTSIDE,
         C_COMMENT,          IS_FALSE_PREFIX,        /* false #if followed by false #elifs */
         CXX_COMMENT          IS_TRUE_PREFIX,         /* first non-false #(el)if is true */
 } Comment_state;          IS_PASS_MIDDLE,         /* first non-false #(el)if is unknown */
           IS_FALSE_MIDDLE,        /* a false #elif after a pass state */
           IS_TRUE_MIDDLE,         /* a true #elif after a pass state */
           IS_PASS_ELSE,           /* an else after a pass state */
           IS_FALSE_ELSE,          /* an else after a true state */
           IS_TRUE_ELSE,           /* an else after only false states */
           IS_FALSE_TRAILER,       /* #elifs after a true are false */
           IS_COUNT
   } Ifstate;
   
   static char const * const ifstate_name[] = {
           "OUTSIDE", "FALSE_PREFIX", "TRUE_PREFIX",
           "PASS_MIDDLE", "FALSE_MIDDLE", "TRUE_MIDDLE",
           "PASS_ELSE", "FALSE_ELSE", "TRUE_ELSE",
           "FALSE_TRAILER"
   };
   
   /* state of comment parser */
 typedef enum {  typedef enum {
         QUOTE_NONE = false,          NO_COMMENT = false,     /* outside a comment */
         QUOTE_SINGLE,          C_COMMENT,              /* in a comment like this one */
         QUOTE_DOUBLE          CXX_COMMENT,            /* between // and end of line */
 } Quote_state;          STARTING_COMMENT,       /* just after slash-backslash-newline */
           FINISHING_COMMENT       /* star-backslash-newline in a C comment */
   } Comment_state;
   
 const char *const errs[] = {  static char const * const comment_name[] = {
 #define NO_ERR      0          "NO", "C", "CXX", "STARTING", "FINISHING"
         "",  
 #define END_ERR     1  
         "",  
 #define ELIF_ERR    2  
         "Inappropriate elif",  
 #define ELSE_ERR    3  
         "Inappropriate else",  
 #define ENDIF_ERR   4  
         "Inappropriate endif",  
 #define IEOF_ERR    5  
         "Premature EOF in ifdef",  
 #define CEOF_ERR    6  
         "Premature EOF in comment",  
 #define Q1EOF_ERR   7  
         "Premature EOF in quoted character",  
 #define Q2EOF_ERR   8  
         "Premature EOF in quoted string"  
 };  };
   
 /*  /* state of preprocessor line parser */
  * These are the operators that are supported by the expression evaluator.  typedef enum {
  */          LS_START,               /* only space and comments on this line */
 static int op_lt(int a, int b) { return a < b; }          LS_HASH,                /* only space, comments, and a hash */
 static int op_gt(int a, int b) { return a > b; }          LS_DIRTY                /* this line can't be a preprocessor line */
 static int op_le(int a, int b) { return a <= b; }  } Line_state;
 static int op_ge(int a, int b) { return a >= b; }  
 static int op_eq(int a, int b) { return a == b; }  
 static int op_ne(int a, int b) { return a != b; }  
 static int op_or(int a, int b) { return a || b; }  
 static int op_and(int a, int b) { return a && b; }  
   
 struct ops;  static char const * const linestate_name[] = {
           "START", "HASH", "DIRTY"
   };
   
 /*  /*
  * An evaluation function takes three arguments, as follows: (1) a pointer to   * Minimum translation limits from ISO/IEC 9899:1999 5.2.4.1
  * an element of the precedence table which lists the operators at the current  
  * level of precedence; (2) a pointer to an integer which will receive the  
  * value of the expression; and (3) a pointer to a char* that points to the  
  * expression to be evaluated and that is updated to the end of the expression  
  * when evaluation is complete. The function returns LT_FALSE if the value of  
  * the expression is zero, LT_TRUE if it is non-zero, or LT_IF if the  
  * expression could not be evaluated.  
  */   */
 typedef Linetype eval_fn(struct ops *, int *, const char **);  #define MAXDEPTH        64                      /* maximum #if nesting */
   #define MAXLINE         4096                    /* maximum length of line */
   #define MAXSYMS         4096                    /* maximum number of symbols */
   
 eval_fn eval_table, eval_unary;  
   
 /*  /*
  * The precedence table. Expressions involving binary operators are evaluated   * Globals.
  * in a table-driven way by eval_table. When it evaluates a subexpression it  
  * calls the inner function with its first argument pointing to the next  
  * element of the table. Innermost expressions have special non-table-driven  
  * handling.  
  */   */
 struct ops {  
         eval_fn *inner;  
         struct op {  
                 const char *str;  
                 int (*fn)(int, int);  
         } op[5];  
 } eval_ops[] = {  
         { eval_table, { { "||", op_or } } },  
         { eval_table, { { "&&", op_and } } },  
         { eval_table, { { "==", op_eq },  
                         { "!=", op_ne } } },  
         { eval_unary, { { "<=", op_le },  
                         { ">=", op_ge },  
                         { "<", op_lt },  
                         { ">", op_gt } } }  
 };  
   
 FILE           *input;  static bool             complement;             /* -c: do the complement */
 const char     *filename;  static bool             debugging;              /* -d: debugging reports */
 int             linenum;        /* current line number */  static bool             killconsts;             /* -k: eval constant #ifs */
 int             stifline;       /* start of current #if */  static bool             lnblank;                /* -l: blank deleted lines */
 int             stqcline;       /* start of current coment or quote */  static bool             symlist;                /* -s: output symbol list */
 bool            keepthis;       /* ignore this #if's value 'cause it's const */  static bool             text;                   /* -t: this is a text file */
   
 #define MAXLINE 1024  static const char      *symname[MAXSYMS];       /* symbol name */
 #define KWSIZE 8  static const char      *value[MAXSYMS];         /* -Dsym=value */
 /* tline has extra space so that it isn't overflowed when editing #elifs */  static bool             ignore[MAXSYMS];        /* -iDsym or -iUsym */
 char    tline[MAXLINE+KWSIZE];  /* input buffer */  static int              nsyms;                  /* number of symbols */
 char   *keyword;                /* used for editing #elif's */  
   
 bool            complement;     /* -c option in effect: do the complement */  static FILE            *input;                  /* input file pointer */
 bool            debugging;      /* -d option in effect: debugging reports */  static const char      *filename;               /* input file name */
 bool            killconsts;     /* -k option in effect: eval constant #ifs */  static int              linenum;                /* current line number */
 bool            lnblank;        /* -l option in effect: blank deleted lines */  
 bool            symlist;        /* -s option in effect: output symbol list */  
 bool            text;           /* -t option in effect: this is a text file */  
   
 #define MAXSYMS 1000  static char             tline[MAXLINE + 10];    /* input buffer plus space */
 const char     *symname[MAXSYMS];       /* symbol name */  static const char      *endtline = &tline[MAXLINE + 9]; /* tline ends here */
 const char     *value[MAXSYMS];         /* -Dsym=value */  
 bool            ignore[MAXSYMS];        /* -iDsym or -iUsym */  
   
 int             nsyms = 1;      /* symbol 0 is used for tracking #ifs */  static char            *keyword;                /* used for editing #elif's */
   
 Reject_level    reject;         /* what kind of filtering we are doing */  static Comment_state    incomment;              /* comment parser state */
 Comment_state   incomment;      /* inside C comment */  static Line_state       linestate;              /* #if line parser state */
 Quote_state     inquote;        /* inside single or double quotes */  static Ifstate          ifstate[MAXDEPTH];      /* #if processor state */
   static bool             ignoring[MAXDEPTH];     /* ignore comments state */
   static int              stifline[MAXDEPTH];     /* start of current #if */
   static int              depth;                  /* current #if nesting */
   static bool             keepthis;               /* don't delete constant #if */
   
 Linetype        checkline(int *);  static int              exitstat;               /* program exit status */
 void            debug(const char *, ...);  
 Linetype        process(int);  
 void            doif(int, Linetype, bool);  
 void            elif2if(void);  
 void            elif2endif(void);  
 void            error(int, int);  
 void            addsym(bool, bool, char *);  
 int             findsym(const char *);  
 void            flushline(bool);  
 #if 0  
 int             getline(char *, int, FILE *, bool);  
 #endif  
 Linetype        ifeval(const char **);  
 const char     *skipcomment(const char *);  
 const char     *skipquote(const char *, Quote_state);  
 const char     *skipsym(const char *);  
 void            usage(void);  
   
   static void             addsym(bool, bool, char *);
   static void             debug(const char *, ...);
   static void             error(const char *);
   static int              findsym(const char *);
   static void             flushline(bool);
   static Linetype         getline(void);
   static Linetype         ifeval(const char **);
   static void             nest(void);
   static void             process(void);
   static const char      *skipcomment(const char *);
   static const char      *skipsym(const char *);
   static void             state(Ifstate);
   static int              strlcmp(const char *, const char *, size_t);
   static void             unignore(void);
   static void             usage(void);
   
 #define endsym(c) (!isalpha((unsigned char)c) && !isdigit((unsigned char)c) && c != '_')  #define endsym(c) (!isalpha((unsigned char)c) && !isdigit((unsigned char)c) && c != '_')
   
   /*
    * The main program.
    */
 int  int
 main(int argc, char *argv[])  main(int argc, char *argv[])
 {  {
Line 254 
Line 226 
                         addsym(false, false, optarg);                          addsym(false, false, optarg);
                         break;                          break;
                 case 'I':                  case 'I':
                         /* ignore for compatibility with cpp */                          /* no-op for compatibility with cpp */
                         break;                          break;
                 case 'c': /* treat -D as -U and vice versa */                  case 'c': /* treat -D as -U and vice versa */
                         complement = true;                          complement = true;
                         break;                          break;
                 case 'k': /* process constant #ifs */  
                         killconsts = true;  
                         break;  
                 case 'd':                  case 'd':
                         debugging = true;                          debugging = true;
                         break;                          break;
                   case 'k': /* process constant #ifs */
                           killconsts = true;
                           break;
                 case 'l': /* blank deleted lines instead of omitting them */                  case 'l': /* blank deleted lines instead of omitting them */
                         lnblank = true;                          lnblank = true;
                         break;                          break;
                 case 's': /* only output list of symbols that control #ifs */                  case 's': /* only output list of symbols that control #ifs */
                         symlist = true;                          symlist = true;
                         break;                          break;
                 case 't': /* don't parse C comments or strings */                  case 't': /* don't parse C comments */
                         text = true;                          text = true;
                         break;                          break;
                 default:                  default:
Line 279 
Line 251 
                 }                  }
         argc -= optind;          argc -= optind;
         argv += optind;          argv += optind;
         if (nsyms == 1 && !symlist) {          if (nsyms == 0 && !symlist) {
                 warnx("must -D or -U at least one symbol");                  warnx("must -D or -U at least one symbol");
                 usage();                  usage();
         }          }
Line 288 
Line 260 
         } else if (argc == 1 && strcmp(*argv, "-") != 0) {          } else if (argc == 1 && strcmp(*argv, "-") != 0) {
                 filename = *argv;                  filename = *argv;
                 if ((input = fopen(filename, "r")) != NULL) {                  if ((input = fopen(filename, "r")) != NULL) {
                         (void) process(0);                          process();
                         (void) fclose(input);                          (void) fclose(input);
                 } else                  } else
                         err(2, "can't open %s", *argv);                          err(2, "can't open %s", *argv);
         } else {          } else {
                 filename = "[stdin]";                  filename = "[stdin]";
                 input = stdin;                  input = stdin;
                 (void) process(0);                  process();
         }          }
   
         exit(0);          exit(exitstat);
 }  }
   
 void  static void
 usage(void)  usage(void)
 {  {
         fprintf (stderr, "usage: unifdef [-cdklst] [[-Dsym[=val]]"          fprintf(stderr, "usage: unifdef [-cdklst] [[-Dsym[=val]]"
             "[-Usym] [-iDsym[=val]] [-iUsym]] ... [file]\n");              " [-Usym] [-iDsym[=val]] [-iUsym]] ... [file]\n");
         exit (2);          exit(2);
 }  }
   
 /*  /*
  * This function processes #if lines and alters the pass-through   * A state transition function alters the global #if processing state
  * state accordingly. All the complicated state transition suff is   * in a particular way. The table below is indexed by the current
  * dealt with in this function, as well as checking that the   * processing state and the type of the current line. A NULL entry
  * #if/#elif/#else/#endif lines happen in the correct order. Lines   * indicate that processing is complete.
  * between #if lines are handled by a recursive call to process().   *
    * Nesting is handled by keeping a stack of states; some transition
    * functions increase or decrease the depth. They also maintin the
    * ignore state on a stack. In some complicated cases they have to
    * alter the preprocessor directive, as follows.
    *
    * When we have processed a group that starts off with a known-false
    * #if/#elif sequence (which has therefore been deleted) followed by a
    * #elif that we don't understand and therefore must keep, we turn the
    * latter into a #if to keep the nesting correct.
    *
    * When we find a true #elif in a group, the following block will
    * always be kept and the rest of the sequence after the next #elif or
    * #else will be discarded. We change the #elif to #else and the
    * following directive to #endif since this has the desired behaviour.
  */   */
 void  typedef void state_fn(void);
 doif(int depth, Linetype lineval, bool ignoring)  
   /* report an error */
   static void
   Eelif(void)
 {  {
         Reject_level savereject;          error("Inappropriate #elif");
         bool active;  }
         bool donetrue;  
         bool inelse;  
         int saveline;  
   
         debug("#if line %d code %d depth %d",  static void
             linenum, lineval, depth);  Eelse(void)
         saveline = stifline;  {
         stifline = linenum;          error("Inappropriate #else");
         savereject = reject;  }
         inelse = false;  
         donetrue = false;  static void
         if (lineval == LT_IF || reject != REJ_NO) {  Eendif(void)
                 active = false;  {
                 ignoring = false;          error("Inappropriate #endif");
                 flushline(true);  }
         } else if (ignoring) {  
                 active = false;  static void
                 flushline(true);  Eeof(void)
                 if (lineval == LT_FALSE)  {
                         reject = REJ_IGNORE;          error("Premature EOF");
                 else  }
                         donetrue = true;  
         } else {  static void
                 active = true;  Eioccc(void)
                 flushline(false);  {
                 if (lineval == LT_FALSE)          error("Obfuscated preprocessor control line");
                         reject = REJ_YES;  }
                 else  
                         donetrue = true;  /* plain line handling */
   static void
   print(void)
   {
           flushline(true);
   }
   
   static void
   drop(void)
   {
           flushline(false);
   }
   
   /* output lacks group's start line */
   static void
   Strue(void)
   {
           drop();
           unignore();
           state(IS_TRUE_PREFIX);
   }
   
   static void
   Sfalse(void)
   {
           drop();
           unignore();
           state(IS_FALSE_PREFIX);
   }
   
   static void
   Selse(void)
   {
           drop();
           state(IS_TRUE_ELSE);
   }
   
   /* print/pass this block */
   static void
   Pelif(void)
   {
           print();
           unignore();
           state(IS_PASS_MIDDLE);
   }
   
   static void
   Pelse(void)
   {
           print();
           state(IS_PASS_ELSE);
   }
   
   static void
   Pendif(void)
   {
           print();
           --depth;
   }
   
   /* discard this block */
   static void
   Dfalse(void)
   {
           drop();
           unignore();
           state(IS_FALSE_TRAILER);
   }
   
   static void
   Delif(void)
   {
           drop();
           unignore();
           state(IS_FALSE_MIDDLE);
   }
   
   static void
   Delse(void)
   {
           drop();
           state(IS_FALSE_ELSE);
   }
   
   static void
   Dendif(void)
   {
           drop();
           --depth;
   }
   
   /* first line of group */
   static void
   Fdrop(void)
   {
           nest();
           Dfalse();
   }
   
   static void
   Fpass(void)
   {
           nest();
           Pelif();
   }
   
   static void
   Ftrue(void)
   {
           nest();
           Strue();
   }
   
   static void
   Ffalse(void)
   {
           nest();
           Sfalse();
   }
   
   /* ignore comments in this block */
   static void
   Idrop(void)
   {
           Fdrop();
           ignore[depth] = true;
   }
   
   static void
   Itrue(void) {
           Ftrue();
           ignore[depth] = true;
   }
   
   static void
   Ifalse(void)
   {
           Ffalse();
           ignore[depth] = true;
   }
   
   /* modify this line */
   static void
   Mpass (void)
   {
           strncpy(keyword, "if  ", 4);
           Pelif();
   }
   
   static void
   Mtrue (void)
   {
           strlcpy(keyword, "else\n", endtline - keyword);
           print();
           state(IS_TRUE_MIDDLE);
   }
   
   static void
   Melif (void)
   {
           strlcpy(keyword, "endif\n", endtline - keyword);
           print();
           state(IS_FALSE_TRAILER);
   }
   
   static void
   Melse (void)
   {
           strlcpy(keyword, "endif\n", endtline - keyword);
           print();
           state(IS_FALSE_ELSE);
   }
   
   static state_fn * const trans_table[IS_COUNT][LT_COUNT] = {
   /* IS_OUTSIDE */
   {print,Itrue,Ifalse,Fpass,Ftrue,Ffalse,Eelif, Eelif, Eelif, Eelse,Eendif,NULL},
   /* IS_FALSE_PREFIX */
   {drop, Idrop,Idrop, Fdrop,Fdrop,Fdrop, Mpass, Strue, Sfalse,Selse,Dendif,Eeof},
   /* IS_TRUE_PREFIX */
   {print,Itrue,Ifalse,Fpass,Ftrue,Ffalse,Dfalse,Dfalse,Dfalse,Delse,Dendif,Eeof},
   /* IS_PASS_MIDDLE */
   {print,Itrue,Ifalse,Fpass,Ftrue,Ffalse,Pelif, Mtrue, Delif, Pelse,Pendif,Eeof},
   /* IS_FALSE_MIDDLE */
   {drop, Idrop,Idrop, Fdrop,Fdrop,Fdrop, Pelif, Mtrue, Delif, Pelse,Pendif,Eeof},
   /* IS_TRUE_MIDDLE */
   {print,Itrue,Ifalse,Fpass,Ftrue,Ffalse,Melif, Melif, Melif, Melse,Pendif,Eeof},
   /* IS_PASS_ELSE */
   {print,Itrue,Ifalse,Fpass,Ftrue,Ffalse,Eelif, Eelif, Eelif, Eelse,Pendif,Eeof},
   /* IS_FALSE_ELSE */
   {drop, Idrop,Idrop, Fdrop,Fdrop,Fdrop, Eelif, Eelif, Eelif, Eelse,Dendif,Eeof},
   /* IS_TRUE_ELSE */
   {print,Itrue,Ifalse,Fpass,Ftrue,Ffalse,Eelif, Eelif, Eelif, Eelse,Dendif,Eeof},
   /* IS_FALSE_TRAILER */
   {drop, Idrop,Idrop, Fdrop,Fdrop,Fdrop, Dfalse,Dfalse,Dfalse,Delse,Dendif,Eeof}
   /*PLAIN TRUEI FALSEI IF   TRUE  FALSE  ELIF  ELTRUE ELFALSE ELSE  ENDIF  EOF*/
   };
   
   /*
    * State machine utility functions
    */
   static void
   nest(void)
   {
           depth += 1;
           if (depth >= MAXDEPTH)
                   error("Too many levels of nesting");
           stifline[depth] = linenum;
   }
   
   static void
   state(Ifstate is)
   {
           ifstate[depth] = is;
   }
   
   static void
   unignore(void)
   {
           ignore[depth] = ignore[depth-1];
   }
   
   /*
    * Write a line to the output or not, according to command line options.
    */
   static void
   flushline(bool keep)
   {
           if (symlist)
                   return;
           if (keep ^ complement)
                   fputs(tline, stdout);
           else {
                   if (lnblank)
                           putc('\n', stdout);
                   exitstat = 1;
         }          }
         debug("active %d ignore %d", active, ignoring);  
         for (;;) {  
                 switch (lineval = process(depth)) {  
                 case LT_ELIF:  
                         debug("#elif start %d line %d code %d depth %d",  
                             stifline, linenum, lineval, depth);  
                         if (inelse)  
                                 error(ELIF_ERR, depth);  
                         donetrue = false;  
                         reject = savereject;  
                         if (active) {  
                                 active = false;  
                                 elif2if();  
                                 flushline(true);  
                         } else {  
                                 ignoring = false;  
                                 flushline(true);  
                         }  
                         debug("active %d ignore %d", active, ignoring);  
                         break;  
                 case LT_ELTRUE:  
                 case LT_ELFALSE:  
                         debug("#elif start %d line %d code %d depth %d",  
                             stifline, linenum, lineval, depth);  
                         if (inelse)  
                                 error(ELIF_ERR, depth);  
                         if (active)  
                                 flushline(false);  
                         else {  
                                 ignoring = false;  
                                 active = true;  
                                 elif2endif();  
                                 flushline(true);  
                         }  
                         if (lineval == LT_ELFALSE)  
                                 reject = REJ_YES;  
                         else {  
                                 reject = REJ_NO;  
                                 donetrue = true;  
                         }  
                         debug("active %d ignore %d", active, ignoring);  
                         break;  
                 case LT_ELSE:  
                         debug("#else start %d line %d code %d depth %d",  
                             stifline, linenum, lineval, depth);  
                         if (inelse)  
                                 error(ELSE_ERR, depth);  
                         if (active) {  
                                 flushline(false);  
                                 reject = REJ_YES;  
                                 if (reject == REJ_YES && !donetrue)  
                                         reject = REJ_NO;  
                         } else {  
                                 flushline(true);  
                                 if (ignoring) {  
                                         if (reject == REJ_IGNORE)  
                                                 reject = REJ_NO;  
                                 }  
                         }  
                         inelse = true;  
                         debug("active %d ignore %d", active, ignoring);  
                         break;  
                 case LT_ENDIF:  
                         debug("#endif start %d line %d code %d depth %d",  
                             stifline, linenum, lineval, depth);  
                         if (active)  
                                 flushline(false);  
                         else  
                                 flushline(true);  
                         reject = savereject;  
                         stifline = saveline;  
                         return;  
                 default:  
                         /* bug */  
                         abort();  
                 }  
         }  
 }  }
   
 /*  /*
  * The main file processing routine. This function deals with passing   * The driver for the state machine.
  * through normal non-#if lines, correct nesting of #if sections, and  
  * checking that things terminate correctly at the end of file. The  
  * complicated stuff is delegated to doif().  
  */   */
 Linetype  static void
 process(int depth)  process(void)
 {  {
         Linetype lineval;          Linetype lineval;
         int cursym;          state_fn *trans;
   
         for (;;) {          for (;;) {
                 linenum++;                  linenum++;
                 if (fgets(tline, MAXLINE, input) == NULL) {                  lineval = getline();
                         if (incomment)                  trans = trans_table[ifstate[depth]][lineval];
                                 error(CEOF_ERR, depth);                  if (trans == NULL)
                         if (inquote == QUOTE_SINGLE)  
                                 error(Q1EOF_ERR, depth);  
                         if (inquote == QUOTE_DOUBLE)  
                                 error(Q2EOF_ERR, depth);  
                         if (depth != 0)  
                                 error(IEOF_ERR, depth);  
                         return (LT_EOF);  
                 }  
                 switch (lineval = checkline(&cursym)) {  
                 case LT_PLAIN:  
                         flushline(true);  
                         break;                          break;
                 case LT_IF:                  trans();
                 case LT_TRUE:                  debug("process %s -> %s depth %d",
                 case LT_FALSE:                      linetype_name[lineval],
                         doif(depth + 1, lineval, ignore[cursym]);                      ifstate_name[ifstate[depth]], depth);
                         break;  
                 case LT_ELIF:  
                 case LT_ELTRUE:  
                 case LT_ELFALSE:  
                 case LT_ELSE:  
                 case LT_ENDIF:  
                         if (depth != 0)  
                                 return (lineval);  
                         if (lineval == LT_ENDIF)  
                                 error(ENDIF_ERR, depth);  
                         if (lineval == LT_ELSE)  
                                 error(ELSE_ERR, depth);  
                         error(ELIF_ERR, depth);  
                 default:  
                         /* bug */  
                         abort();  
                 }  
         }          }
           if (incomment)
                   error("EOF in comment");
 }  }
   
 /*  /*
  * Parse a line and determine its type.   * Parse a line and determine its type. We keep the preprocessor line
    * parser state between calls in a global variable.
  */   */
 Linetype  static Linetype
 checkline(int *cursym)  getline(void)
 {  {
         const char *cp;          const char *cp;
         char *symp;          int cursym;
           int kwlen;
         Linetype retval;          Linetype retval;
         char kw[KWSIZE];          Comment_state wascomment;
   
           if (fgets(tline, MAXLINE, input) == NULL)
                   return (LT_EOF);
         retval = LT_PLAIN;          retval = LT_PLAIN;
           wascomment = incomment;
         cp = skipcomment(tline);          cp = skipcomment(tline);
         if (*cp != '#' || incomment || inquote == QUOTE_SINGLE ||          if (linestate == LS_START) {
             inquote == QUOTE_DOUBLE)                  if (*cp == '#') {
                 goto eol;                          linestate = LS_HASH;
                           cp = skipcomment(cp + 1);
         cp = skipcomment(++cp);                  } else if (*cp != '\0')
         keyword = (char *)cp;                          linestate = LS_DIRTY;
         symp = kw;  
         while (!endsym(*cp)) {  
                 *symp = *cp++;  
                 if (++symp >= &kw[KWSIZE])  
                         goto eol;  
         }          }
         *symp = '\0';          if (!incomment && linestate == LS_HASH) {
                   keyword = tline + (cp - tline);
         if (strcmp(kw, "ifdef") == 0) {                  cp = skipsym(cp);
                 retval = LT_TRUE;                  kwlen = cp - keyword;
                 goto ifdef;                  if (strncmp(cp, "\\\n", 2) == 0)
         } else if (strcmp(kw, "ifndef") == 0) {                          Eioccc();
                 retval = LT_FALSE;                  if (strlcmp("ifdef", keyword, kwlen) == 0 ||
 ifdef:                      strlcmp("ifndef", keyword, kwlen) == 0) {
                 cp = skipcomment(++cp);                          cp = skipcomment(cp);
                 if (incomment) {                          if ((cursym = findsym(cp)) < 0)
                                   retval = LT_IF;
                           else {
                                   retval = (keyword[2] == 'n')
                                       ? LT_FALSE : LT_TRUE;
                                   if (value[cursym] == NULL)
                                           retval = (retval == LT_TRUE)
                                               ? LT_FALSE : LT_TRUE;
                                   if (ignore[cursym])
                                           retval = (retval == LT_TRUE)
                                               ? LT_TRUEI : LT_FALSEI;
                           }
                           cp = skipsym(cp);
                   } else if (strlcmp("if", keyword, kwlen) == 0)
                           retval = ifeval(&cp);
                   else if (strlcmp("elif", keyword, kwlen) == 0)
                           retval = ifeval(&cp) - LT_IF + LT_ELIF;
                   else if (strlcmp("else", keyword, kwlen) == 0)
                           retval = LT_ELSE;
                   else if (strlcmp("endif", keyword, kwlen) == 0)
                           retval = LT_ENDIF;
                   else {
                           linestate = LS_DIRTY;
                         retval = LT_PLAIN;                          retval = LT_PLAIN;
                         goto eol;  
                 }                  }
                 if ((*cursym = findsym(cp)) == 0)  
                         retval = LT_IF;  
                 else if (value[*cursym] == NULL)  
                         retval = (retval == LT_TRUE)  
                             ? LT_FALSE : LT_TRUE;  
         } else if (strcmp(kw, "if") == 0) {  
                 retval = ifeval(&cp);  
                 cp = skipcomment(cp);                  cp = skipcomment(cp);
                 if (*cp != '\n' || keepthis)                  if (*cp != '\0') {
                         retval = LT_IF;                          linestate = LS_DIRTY;
                 *cursym = 0;                          if (retval == LT_TRUE || retval == LT_FALSE ||
         } else if (strcmp(kw, "elif") == 0) {                              retval == LT_TRUEI || retval == LT_FALSEI)
                 retval = ifeval(&cp);                                  retval = LT_IF;
                 cp = skipcomment(cp);                          if (retval == LT_ELTRUE || retval == LT_ELFALSE)
                 if (*cp != '\n' || keepthis)                                  retval = LT_ELIF;
                         retval = LT_ELIF;  
                 if (retval == LT_IF)  
                         retval = LT_ELIF;  
                 if (retval == LT_TRUE)  
                         retval = LT_ELTRUE;  
                 if (retval == LT_FALSE)  
                         retval = LT_ELFALSE;  
                 *cursym = 0;  
         } else if (strcmp(kw, "else") == 0)  
                 retval = LT_ELSE;  
         else if (strcmp(kw, "endif") == 0)  
                 retval = LT_ENDIF;  
   
 eol:  
         if (!text && reject != REJ_IGNORE) {  
                 for (; *cp;) {  
                         if (incomment)  
                                 cp = skipcomment(cp);  
                         else if (inquote == QUOTE_SINGLE)  
                                 cp = skipquote(cp, QUOTE_SINGLE);  
                         else if (inquote == QUOTE_DOUBLE)  
                                 cp = skipquote(cp, QUOTE_DOUBLE);  
                         else if (*cp == '/' && (cp[1] == '*' || cp[1] == '/'))  
                                 cp = skipcomment(cp);  
                         else if (*cp == '\'')  
                                 cp = skipquote(cp, QUOTE_SINGLE);  
                         else if (*cp == '"')  
                                 cp = skipquote(cp, QUOTE_DOUBLE);  
                         else  
                                 cp++;  
                 }                  }
                   if (retval != LT_PLAIN && (wascomment || incomment))
                           Eioccc();
                   if (linestate == LS_HASH)
                           abort(); /* bug */
         }          }
           if (linestate == LS_DIRTY) {
                   while (*cp != '\0')
                           cp = skipcomment(cp + 1);
           }
           debug("parser %s comment %s line",
               comment_name[incomment], linestate_name[linestate]);
         return (retval);          return (retval);
 }  }
   
 /*  /*
  * Turn a #elif line into a #if. This function is used when we are   * These are the operators that are supported by the expression evaluator.
  * processing a #if/#elif/#else/#endif sequence that starts off with a  
  * #if that we understand (and therefore it has been deleted) which is  
  * followed by a #elif that we don't understand and therefore must be  
  * kept. We turn it into a #if to keep the nesting correct.  
  */   */
 void  static int
 elif2if(void)  op_lt(int a, int b)
 {  {
         strncpy(keyword, "if  ", 4);          return (a < b);
 }  }
   
 /*  static int
  * Turn a #elif line into a #endif. This is used in the opposite  op_gt(int a, int b)
  * situation to elif2if, i.e. a #if that we don't understand is  
  * followed by a #elif that we do; rather than deleting the #elif (as  
  * we would for a #if) we turn it into a #endif to keep the nesting  
  * correct.  
  */  
 void  
 elif2endif(void)  
 {  {
         strcpy(keyword, "endif\n");          return (a > b);
 }  }
   
   static int
   op_le(int a, int b)
   {
           return (a <= b);
   }
   
   static int
   op_ge(int a, int b)
   {
           return (a >= b);
   }
   
   static int
   op_eq(int a, int b)
   {
           return (a == b);
   }
   
   static int
   op_ne(int a, int b)
   {
           return (a != b);
   }
   
   static int
   op_or(int a, int b)
   {
           return (a || b);
   }
   
   static int
   op_and(int a, int b)
   {
           return (a && b);
   }
   
 /*  /*
    * An evaluation function takes three arguments, as follows: (1) a pointer to
    * an element of the precedence table which lists the operators at the current
    * level of precedence; (2) a pointer to an integer which will receive the
    * value of the expression; and (3) a pointer to a char* that points to the
    * expression to be evaluated and that is updated to the end of the expression
    * when evaluation is complete. The function returns LT_FALSE if the value of
    * the expression is zero, LT_TRUE if it is non-zero, or LT_IF if the
    * expression could not be evaluated.
    */
   struct ops;
   
   typedef Linetype eval_fn(const struct ops *, int *, const char **);
   
   static eval_fn eval_table, eval_unary;
   
   /*
    * The precedence table. Expressions involving binary operators are evaluated
    * in a table-driven way by eval_table. When it evaluates a subexpression it
    * calls the inner function with its first argument pointing to the next
    * element of the table. Innermost expressions have special non-table-driven
    * handling.
    */
   static const struct ops {
           eval_fn *inner;
           struct op {
                   const char *str;
                   int (*fn)(int, int);
           } op[5];
   } eval_ops[] = {
           { eval_table, { { "||", op_or } } },
           { eval_table, { { "&&", op_and } } },
           { eval_table, { { "==", op_eq },
                           { "!=", op_ne } } },
           { eval_unary, { { "<=", op_le },
                           { ">=", op_ge },
                           { "<", op_lt },
                           { ">", op_gt } } }
   };
   
   /*
  * Function for evaluating the innermost parts of expressions,   * Function for evaluating the innermost parts of expressions,
  * viz. !expr (expr) defined(symbol) symbol number   * viz. !expr (expr) defined(symbol) symbol number
  * We reset the keepthis flag when we find a non-constant subexpression.   * We reset the keepthis flag when we find a non-constant subexpression.
  */   */
 Linetype  static Linetype
 eval_unary(struct ops *ops, int *valp, const char **cpp)  eval_unary(const struct ops *ops, int *valp, const char **cpp)
 {  {
         const char *cp;          const char *cp;
         char *ep;          char *ep;
         int sym;          int sym;
   
         cp = skipcomment(*cpp);          cp = skipcomment(*cpp);
         if(*cp == '!') {          if (*cp == '!') {
                 debug("eval%d !", ops - eval_ops);                  debug("eval%d !", ops - eval_ops);
                 cp++;                  cp++;
                 if (eval_unary(ops, valp, &cp) == LT_IF)                  if (eval_unary(ops, valp, &cp) == LT_IF)
Line 635 
Line 808 
                         return (LT_IF);                          return (LT_IF);
                 cp = skipcomment(cp);                  cp = skipcomment(cp);
                 sym = findsym(cp);                  sym = findsym(cp);
                 if (sym == 0 && !symlist)                  if (sym < 0 && !symlist)
                         return (LT_IF);                          return (LT_IF);
                 *valp = (value[sym] != NULL);                  *valp = (value[sym] != NULL);
                 cp = skipsym(cp);                  cp = skipsym(cp);
Line 646 
Line 819 
         } else if (!endsym(*cp)) {          } else if (!endsym(*cp)) {
                 debug("eval%d symbol", ops - eval_ops);                  debug("eval%d symbol", ops - eval_ops);
                 sym = findsym(cp);                  sym = findsym(cp);
                 if (sym == 0 && !symlist)                  if (sym < 0 && !symlist)
                         return (LT_IF);                          return (LT_IF);
                 if (value[sym] == NULL)                  if (value[sym] == NULL)
                         *valp = 0;                          *valp = 0;
Line 668 
Line 841 
 /*  /*
  * Table-driven evaluation of binary operators.   * Table-driven evaluation of binary operators.
  */   */
 Linetype  static Linetype
 eval_table(struct ops *ops, int *valp, const char **cpp)  eval_table(const struct ops *ops, int *valp, const char **cpp)
 {  {
           const struct op *op;
         const char *cp;          const char *cp;
         struct op *op;  
         int val;          int val;
   
         debug("eval%d", ops - eval_ops);          debug("eval%d", ops - eval_ops);
Line 689 
Line 862 
                 cp += strlen(op->str);                  cp += strlen(op->str);
                 debug("eval%d %s", ops - eval_ops, op->str);                  debug("eval%d %s", ops - eval_ops, op->str);
                 if (ops->inner(ops+1, &val, &cp) == LT_IF)                  if (ops->inner(ops+1, &val, &cp) == LT_IF)
                         return LT_IF;                          return (LT_IF);
                 *valp = op->fn(*valp, val);                  *valp = op->fn(*valp, val);
         }          }
   
Line 701 
Line 874 
 /*  /*
  * Evaluate the expression on a #if or #elif line. If we can work out   * Evaluate the expression on a #if or #elif line. If we can work out
  * the result we return LT_TRUE or LT_FALSE accordingly, otherwise we   * the result we return LT_TRUE or LT_FALSE accordingly, otherwise we
  * return just a generic LT_IF. If the expression is constant and   * return just a generic LT_IF.
  * we are not processing constant #ifs then the keepthis flag is true.  
  */   */
 Linetype  static Linetype
 ifeval(const char **cpp)  ifeval(const char **cpp)
 {  {
           int ret;
         int val;          int val;
   
         debug("eval %s", *cpp);          debug("eval %s", *cpp);
         keepthis = killconsts ? false : true;          keepthis = killconsts ? false : true;
         return (eval_table(eval_ops, &val, cpp));          ret = eval_table(eval_ops, &val, cpp);
           return (keepthis ? LT_IF : ret);
 }  }
   
 /*  /*
  * Skip over comments and stop at the next character position that is   * Skip over comments and stop at the next character position that is
  * not whitespace.   * not whitespace. Between calls we keep the comment state in a global
    * variable, and we also make a note when we get a proper end-of-line.
    * XXX: doesn't cope with the buffer splitting inside a state transition.
  */   */
 const char *  static const char *
 skipcomment(const char *cp)  skipcomment(const char *cp)
 {  {
         if (incomment)          if (text || ignoring[depth]) {
                 goto inside;                  while (isspace((unsigned char)*cp))
         for (;; cp++) {                          cp += 1;
                 while (*cp == ' ' || *cp == '\t')                  return (cp);
                         cp++;          }
                 if (text)          while (*cp != '\0')
                         return (cp);                  if (strncmp(cp, "\\\n", 2) == 0)
                 if (cp[0] != '/')                          cp += 2;
                         return (cp);                  else switch (incomment) {
                   case NO_COMMENT:
                 if (cp[1] == '*') {                          if (strncmp(cp, "/\\\n", 3) == 0) {
                         if (!incomment) {                                  incomment = STARTING_COMMENT;
                                   cp += 3;
                           } else if (strncmp(cp, "/*", 2) == 0) {
                                 incomment = C_COMMENT;                                  incomment = C_COMMENT;
                                 stqcline = linenum;                                  cp += 2;
                           } else if (strncmp(cp, "//", 2) == 0) {
                                   incomment = CXX_COMMENT;
                                   cp += 2;
                           } else if (strncmp(cp, "\n", 1) == 0) {
                                   linestate = LS_START;
                                   cp += 1;
                           } else if (strchr(" \t", *cp) != NULL) {
                                   cp += 1;
                           } else
                                   return (cp);
                           continue;
                   case CXX_COMMENT:
                           if (strncmp(cp, "\n", 1) == 0) {
                                   incomment = NO_COMMENT;
                                   linestate = LS_START;
                         }                          }
                 } else if (cp[1] == '/') {                          cp += 1;
                         if (!incomment) {                          continue;
                   case C_COMMENT:
                           if (strncmp(cp, "*\\\n", 3) == 0) {
                                   incomment = FINISHING_COMMENT;
                                   cp += 3;
                           } else if (strncmp(cp, "*/", 2) == 0) {
                                   incomment = NO_COMMENT;
                                   cp += 2;
                           } else
                                   cp += 1;
                           continue;
                   case STARTING_COMMENT:
                           if (*cp == '*') {
                                   incomment = C_COMMENT;
                                   cp += 1;
                           } else if (*cp == '/') {
                                 incomment = CXX_COMMENT;                                  incomment = CXX_COMMENT;
                                 stqcline = linenum;                                  cp += 1;
                           } else {
                                   incomment = NO_COMMENT;
                                   linestate = LS_DIRTY;
                         }                          }
                 } else                          continue;
                         return (cp);                  case FINISHING_COMMENT:
                           if (*cp == '/') {
                 cp += 2;                                  incomment = NO_COMMENT;
 inside:                                  cp += 1;
                 if (incomment == C_COMMENT) {                          } else
                         for (;;) {                                  incomment = C_COMMENT;
                                 for (; *cp != '*'; cp++)                          continue;
                                         if (*cp == '\0')                  default:
                                                 return (cp);                          /* bug */
                                 if (*++cp == '/') {                          abort();
                                         incomment = NO_COMMENT;  
                                         break;  
                                 }  
                         }  
                 } else if (incomment == CXX_COMMENT) {  
                         for (; *cp != '\n'; cp++)  
                                 if (*cp == '\0')  
                                         return (cp);  
                         incomment = NO_COMMENT;  
                 }                  }
         }          return (cp);
 }  }
   
 /*  /*
  * Skip over a quoted string or character and stop at the next charaacter  
  * position that is not whitespace.  
  */  
 const char *  
 skipquote(const char *cp, Quote_state type)  
 {  
         char qchar;  
   
         qchar = type == QUOTE_SINGLE ? '\'' : '"';  
   
         if (inquote == type)  
                 goto inside;  
         for (;; cp++) {  
                 if (*cp != qchar)  
                         return (cp);  
                 cp++;  
                 inquote = type;  
                 stqcline = linenum;  
 inside:  
                 for (;; cp++) {  
                         if (*cp == qchar)  
                                 break;  
                         if (*cp == '\0' || (*cp == '\\' && *++cp == '\0'))  
                                 return (cp);  
                 }  
                 inquote = QUOTE_NONE;  
         }  
 }  
   
 /*  
  * Skip over an identifier.   * Skip over an identifier.
  */   */
 const char *  static const char *
 skipsym(const char *cp)  skipsym(const char *cp)
 {  {
         while (!endsym(*cp))          while (!endsym(*cp))
Line 808 
Line 980 
   
 /*  /*
  * Look for the symbol in the symbol table. If is is found, we return   * Look for the symbol in the symbol table. If is is found, we return
  * the symbol table index, else we return 0.   * the symbol table index, else we return -1.
  */   */
 int  static int
 findsym(const char *str)  findsym(const char *str)
 {  {
         const char *cp;          const char *cp;
         const char *symp;  
         int symind;          int symind;
   
         if (symlist) {          cp = skipsym(str);
                 for (cp = str; !endsym(*cp); cp++)          if (cp == str)
                         continue;                  return (-1);
           if (symlist)
                 printf("%.*s\n", (int)(cp-str), str);                  printf("%.*s\n", (int)(cp-str), str);
         }          for (symind = 0; symind < nsyms; ++symind) {
         for (symind = 1; symind < nsyms; ++symind) {                  if (strlcmp(symname[symind], str, cp-str) == 0) {
                 for (cp = str, symp = symname[symind];  
                     *cp && *symp && *cp == *symp; cp++, symp++)  
                         continue;  
                 if (*symp == '\0' && endsym(*cp)) {  
                         debug("findsym %s %s", symname[symind],                          debug("findsym %s %s", symname[symind],
                             value[symind] ? value[symind] : "");                              value[symind] ? value[symind] : "");
                         return (symind);                          return (symind);
                 }                  }
         }          }
         return (0);          return (-1);
 }  }
   
 /*  /*
  * Add a symbol to the symbol table.   * Add a symbol to the symbol table.
  */   */
 void  static void
 addsym(bool ignorethis, bool definethis, char *sym)  addsym(bool ignorethis, bool definethis, char *sym)
 {  {
         int symind;          int symind;
         char *val;          char *val;
   
         symind = findsym(sym);          symind = findsym(sym);
         if (symind == 0) {          if (symind < 0) {
                 if (nsyms >= MAXSYMS)                  if (nsyms >= MAXSYMS)
                         errx(2, "too many symbols");                          errx(2, "too many symbols");
                 symind = nsyms++;                  symind = nsyms++;
         }          }
         symname[symind] = sym;          symname[symind] = sym;
         ignore[symind] = ignorethis;          ignore[symind] = ignorethis;
         val = (char *)skipsym(sym);          val = sym + (skipsym(sym) - sym);
         if (definethis) {          if (definethis) {
                 if (*val == '=') {                  if (*val == '=') {
                         value[symind] = val+1;                          value[symind] = val+1;
Line 868 
Line 1036 
         }          }
 }  }
   
 #if 0  
 /*  /*
  * Read a line from the input and expand tabs if requested and (if   * Compare s with n characters of t.
  * compiled in) treats form-feed as an end-of-line.   * The same as strncmp() except that it checks that s[n] == '\0'.
  */   */
 int  static int
 getline(char *line, int maxline, FILE *inp, bool expandtabs)  strlcmp(const char *s, const char *t, size_t n)
 {  {
         int tmp;          while (n-- && *t != '\0')
         int num;                  if (*s != *t)
         int chr;                          return ((unsigned char)*s - (unsigned char)*t);
 #ifdef  FFSPECIAL                  else
         static bool havechar = false;   /* have leftover char from last time */                          ++s, ++t;
         static char svchar;          return ((unsigned char)*s);
 #endif  /* FFSPECIAL */  
   
         num = 0;  
 #ifdef  FFSPECIAL  
         if (havechar) {  
                 havechar = false;  
                 chr = svchar;  
                 goto ent;  
         }  
 #endif  /* FFSPECIAL */  
         while (num + 8 < maxline) {     /* leave room for tab */  
                 chr = getc(inp);  
                 if (chr == EOF)  
                         return (EOF);  
                 if (0 && isprint(chr)) {  
 #ifdef  FFSPECIAL  
 ent:  
 #endif  /* FFSPECIAL */  
                         *line++ = chr;  
                         num++;  
                 } else  
                         switch (chr) {  
                         case EOF:  
                                 return (EOF);  
   
                         case '\t':  
                                 if (expandtabs) {  
                                         num += tmp = 8 - (num & 7);  
                                         do  
                                                 *line++ = ' ';  
                                         while (--tmp);  
                                         break;  
                                 }  
   
                         case '\n':  
                                 *line = '\n';  
                                 num++;  
                                 goto end;  
   
 #ifdef  FFSPECIAL  
                         case '\f':  
                                 if (++num == 1)  
                                         *line = '\f';  
                                 else {  
                                         *line = '\n';  
                                         havechar = true;  
                                         svchar = chr;  
                                 }  
                                 goto end;  
 #endif  /* FFSPECIAL */  
                         default:  
                                 *line++ = chr;  
                                 num++;  
                                 break;  
                         }  
         }  
 end:  
         *++line = '\0';  
         return (num);  
 }  }
 #endif  
   
 /*  /*
  * Write a line to the output or not, according to the current   * Diagnostics.
  * filtering state.  
  */   */
 void  static void
 flushline(bool keep)  
 {  
         if (symlist)  
                 return;  
         if ((keep && reject != REJ_YES) ^ complement)  
                 fputs(tline, stdout);  
         else if (lnblank)  
                 putc('\n', stdout);  
 }  
   
 void  
 debug(const char *msg, ...)  debug(const char *msg, ...)
 {  {
         va_list ap;          va_list ap;
Line 971 
Line 1066 
         }          }
 }  }
   
 void  static void
 error(int code, int depth)  error(const char *msg)
 {  {
         if (incomment || inquote)          if (depth == 0)
                 errx(2, "error in %s line %d: %s (#if depth %d)",                  errx(2, "%s: %d: %s", filename, linenum, msg);
                     filename, stqcline, errs[code], depth);  
         else          else
                 errx(2, "error in %s line %d: %s"                  errx(2, "%s: %d: %s (#if line %d depth %d)",
                     " (#if depth %d start line %d)",                      filename, linenum, msg, stifline[depth], depth);
                     filename, linenum, errs[code], depth, stifline);  
 }  }

Legend:
Removed from v.1.7  
changed lines
  Added in v.1.8