]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - usr.bin/unifdef/unifdef.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / usr.bin / unifdef / unifdef.c
1 /*
2  * Copyright (c) 2002 - 2013 Tony Finch <dot@dotat.at>
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
14  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
16  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
17  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
18  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
19  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
20  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
21  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
22  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
23  * SUCH DAMAGE.
24  */
25
26 /*
27  * unifdef - remove ifdef'ed lines
28  *
29  * This code was derived from software contributed to Berkeley by Dave Yost.
30  * It was rewritten to support ANSI C by Tony Finch. The original version
31  * of unifdef carried the 4-clause BSD copyright licence. None of its code
32  * remains in this version (though some of the names remain) so it now
33  * carries a more liberal licence.
34  *
35  *  Wishlist:
36  *      provide an option which will append the name of the
37  *        appropriate symbol after #else's and #endif's
38  *      provide an option which will check symbols after
39  *        #else's and #endif's to see that they match their
40  *        corresponding #ifdef or #ifndef
41  *
42  *   These require better buffer handling, which would also make
43  *   it possible to handle all "dodgy" directives correctly.
44  */
45
46 #include "unifdef.h"
47
48 static const char copyright[] =
49     "@(#) $Version: unifdef-2.7 $\n"
50     "@(#) $FreeBSD$\n"
51     "@(#) $Author: Tony Finch (dot@dotat.at) $\n"
52     "@(#) $URL: http://dotat.at/prog/unifdef $\n"
53 ;
54
55 /* types of input lines: */
56 typedef enum {
57         LT_TRUEI,               /* a true #if with ignore flag */
58         LT_FALSEI,              /* a false #if with ignore flag */
59         LT_IF,                  /* an unknown #if */
60         LT_TRUE,                /* a true #if */
61         LT_FALSE,               /* a false #if */
62         LT_ELIF,                /* an unknown #elif */
63         LT_ELTRUE,              /* a true #elif */
64         LT_ELFALSE,             /* a false #elif */
65         LT_ELSE,                /* #else */
66         LT_ENDIF,               /* #endif */
67         LT_DODGY,               /* flag: directive is not on one line */
68         LT_DODGY_LAST = LT_DODGY + LT_ENDIF,
69         LT_PLAIN,               /* ordinary line */
70         LT_EOF,                 /* end of file */
71         LT_ERROR,               /* unevaluable #if */
72         LT_COUNT
73 } Linetype;
74
75 static char const * const linetype_name[] = {
76         "TRUEI", "FALSEI", "IF", "TRUE", "FALSE",
77         "ELIF", "ELTRUE", "ELFALSE", "ELSE", "ENDIF",
78         "DODGY TRUEI", "DODGY FALSEI",
79         "DODGY IF", "DODGY TRUE", "DODGY FALSE",
80         "DODGY ELIF", "DODGY ELTRUE", "DODGY ELFALSE",
81         "DODGY ELSE", "DODGY ENDIF",
82         "PLAIN", "EOF", "ERROR"
83 };
84
85 #define linetype_if2elif(lt) ((Linetype)(lt - LT_IF + LT_ELIF))
86 #define linetype_2dodgy(lt) ((Linetype)(lt + LT_DODGY))
87
88 /* state of #if processing */
89 typedef enum {
90         IS_OUTSIDE,
91         IS_FALSE_PREFIX,        /* false #if followed by false #elifs */
92         IS_TRUE_PREFIX,         /* first non-false #(el)if is true */
93         IS_PASS_MIDDLE,         /* first non-false #(el)if is unknown */
94         IS_FALSE_MIDDLE,        /* a false #elif after a pass state */
95         IS_TRUE_MIDDLE,         /* a true #elif after a pass state */
96         IS_PASS_ELSE,           /* an else after a pass state */
97         IS_FALSE_ELSE,          /* an else after a true state */
98         IS_TRUE_ELSE,           /* an else after only false states */
99         IS_FALSE_TRAILER,       /* #elifs after a true are false */
100         IS_COUNT
101 } Ifstate;
102
103 static char const * const ifstate_name[] = {
104         "OUTSIDE", "FALSE_PREFIX", "TRUE_PREFIX",
105         "PASS_MIDDLE", "FALSE_MIDDLE", "TRUE_MIDDLE",
106         "PASS_ELSE", "FALSE_ELSE", "TRUE_ELSE",
107         "FALSE_TRAILER"
108 };
109
110 /* state of comment parser */
111 typedef enum {
112         NO_COMMENT = false,     /* outside a comment */
113         C_COMMENT,              /* in a comment like this one */
114         CXX_COMMENT,            /* between // and end of line */
115         STARTING_COMMENT,       /* just after slash-backslash-newline */
116         FINISHING_COMMENT,      /* star-backslash-newline in a C comment */
117         CHAR_LITERAL,           /* inside '' */
118         STRING_LITERAL          /* inside "" */
119 } Comment_state;
120
121 static char const * const comment_name[] = {
122         "NO", "C", "CXX", "STARTING", "FINISHING", "CHAR", "STRING"
123 };
124
125 /* state of preprocessor line parser */
126 typedef enum {
127         LS_START,               /* only space and comments on this line */
128         LS_HASH,                /* only space, comments, and a hash */
129         LS_DIRTY                /* this line can't be a preprocessor line */
130 } Line_state;
131
132 static char const * const linestate_name[] = {
133         "START", "HASH", "DIRTY"
134 };
135
136 /*
137  * Minimum translation limits from ISO/IEC 9899:1999 5.2.4.1
138  */
139 #define MAXDEPTH        64                      /* maximum #if nesting */
140 #define MAXLINE         4096                    /* maximum length of line */
141 #define MAXSYMS         4096                    /* maximum number of symbols */
142
143 /*
144  * Sometimes when editing a keyword the replacement text is longer, so
145  * we leave some space at the end of the tline buffer to accommodate this.
146  */
147 #define EDITSLOP        10
148
149 /*
150  * Globals.
151  */
152
153 static bool             compblank;              /* -B: compress blank lines */
154 static bool             lnblank;                /* -b: blank deleted lines */
155 static bool             complement;             /* -c: do the complement */
156 static bool             debugging;              /* -d: debugging reports */
157 static bool             inplace;                /* -m: modify in place */
158 static bool             iocccok;                /* -e: fewer IOCCC errors */
159 static bool             strictlogic;            /* -K: keep ambiguous #ifs */
160 static bool             killconsts;             /* -k: eval constant #ifs */
161 static bool             lnnum;                  /* -n: add #line directives */
162 static bool             symlist;                /* -s: output symbol list */
163 static bool             symdepth;               /* -S: output symbol depth */
164 static bool             text;                   /* -t: this is a text file */
165
166 static const char      *symname[MAXSYMS];       /* symbol name */
167 static const char      *value[MAXSYMS];         /* -Dsym=value */
168 static bool             ignore[MAXSYMS];        /* -iDsym or -iUsym */
169 static int              nsyms;                  /* number of symbols */
170
171 static FILE            *input;                  /* input file pointer */
172 static const char      *filename;               /* input file name */
173 static int              linenum;                /* current line number */
174 static const char      *linefile;               /* file name for #line */
175 static FILE            *output;                 /* output file pointer */
176 static const char      *ofilename;              /* output file name */
177 static const char      *backext;                /* backup extension */
178 static char            *tempname;               /* avoid splatting input */
179
180 static char             tline[MAXLINE+EDITSLOP];/* input buffer plus space */
181 static char            *keyword;                /* used for editing #elif's */
182
183 static const char      *newline;                /* input file format */
184 static const char       newline_unix[] = "\n";
185 static const char       newline_crlf[] = "\r\n";
186
187 static Comment_state    incomment;              /* comment parser state */
188 static Line_state       linestate;              /* #if line parser state */
189 static Ifstate          ifstate[MAXDEPTH];      /* #if processor state */
190 static bool             ignoring[MAXDEPTH];     /* ignore comments state */
191 static int              stifline[MAXDEPTH];     /* start of current #if */
192 static int              depth;                  /* current #if nesting */
193 static int              delcount;               /* count of deleted lines */
194 static unsigned         blankcount;             /* count of blank lines */
195 static unsigned         blankmax;               /* maximum recent blankcount */
196 static bool             constexpr;              /* constant #if expression */
197 static bool             zerosyms;               /* to format symdepth output */
198 static bool             firstsym;               /* ditto */
199
200 static int              exitmode;               /* exit status mode */
201 static int              exitstat;               /* program exit status */
202
203 static void             addsym(bool, bool, char *);
204 static char            *astrcat(const char *, const char *);
205 static void             cleantemp(void);
206 static void             closeout(void);
207 static void             debug(const char *, ...);
208 static void             done(void);
209 static void             error(const char *);
210 static int              findsym(const char *);
211 static void             flushline(bool);
212 static void             hashline(void);
213 static void             help(void);
214 static Linetype         ifeval(const char **);
215 static void             ignoreoff(void);
216 static void             ignoreon(void);
217 static void             keywordedit(const char *);
218 static void             nest(void);
219 static Linetype         parseline(void);
220 static void             process(void);
221 static void             processinout(const char *, const char *);
222 static const char      *skipargs(const char *);
223 static const char      *skipcomment(const char *);
224 static const char      *skipsym(const char *);
225 static void             state(Ifstate);
226 static int              strlcmp(const char *, const char *, size_t);
227 static void             unnest(void);
228 static void             usage(void);
229 static void             version(void);
230
231 #define endsym(c) (!isalnum((unsigned char)c) && c != '_')
232
233 /*
234  * The main program.
235  */
236 int
237 main(int argc, char *argv[])
238 {
239         int opt;
240
241         while ((opt = getopt(argc, argv, "i:D:U:I:M:o:x:bBcdehKklmnsStV")) != -1)
242                 switch (opt) {
243                 case 'i': /* treat stuff controlled by these symbols as text */
244                         /*
245                          * For strict backwards-compatibility the U or D
246                          * should be immediately after the -i but it doesn't
247                          * matter much if we relax that requirement.
248                          */
249                         opt = *optarg++;
250                         if (opt == 'D')
251                                 addsym(true, true, optarg);
252                         else if (opt == 'U')
253                                 addsym(true, false, optarg);
254                         else
255                                 usage();
256                         break;
257                 case 'D': /* define a symbol */
258                         addsym(false, true, optarg);
259                         break;
260                 case 'U': /* undef a symbol */
261                         addsym(false, false, optarg);
262                         break;
263                 case 'I': /* no-op for compatibility with cpp */
264                         break;
265                 case 'b': /* blank deleted lines instead of omitting them */
266                 case 'l': /* backwards compatibility */
267                         lnblank = true;
268                         break;
269                 case 'B': /* compress blank lines around removed section */
270                         compblank = true;
271                         break;
272                 case 'c': /* treat -D as -U and vice versa */
273                         complement = true;
274                         break;
275                 case 'd':
276                         debugging = true;
277                         break;
278                 case 'e': /* fewer errors from dodgy lines */
279                         iocccok = true;
280                         break;
281                 case 'h':
282                         help();
283                         break;
284                 case 'K': /* keep ambiguous #ifs */
285                         strictlogic = true;
286                         break;
287                 case 'k': /* process constant #ifs */
288                         killconsts = true;
289                         break;
290                 case 'm': /* modify in place */
291                         inplace = true;
292                         break;
293                 case 'M': /* modify in place and keep backup */
294                         inplace = true;
295                         backext = optarg;
296                         break;
297                 case 'n': /* add #line directive after deleted lines */
298                         lnnum = true;
299                         break;
300                 case 'o': /* output to a file */
301                         ofilename = optarg;
302                         break;
303                 case 's': /* only output list of symbols that control #ifs */
304                         symlist = true;
305                         break;
306                 case 'S': /* list symbols with their nesting depth */
307                         symlist = symdepth = true;
308                         break;
309                 case 't': /* don't parse C comments */
310                         text = true;
311                         break;
312                 case 'V':
313                         version();
314                         break;
315                 case 'x':
316                         exitmode = atoi(optarg);
317                         if(exitmode < 0 || exitmode > 2)
318                                 usage();
319                         break;
320                 default:
321                         usage();
322                 }
323         argc -= optind;
324         argv += optind;
325         if (compblank && lnblank)
326                 errx(2, "-B and -b are mutually exclusive");
327         if (symlist && (ofilename != NULL || inplace || argc > 1))
328                 errx(2, "-s only works with one input file");
329         if (argc > 1 && ofilename != NULL)
330                 errx(2, "-o cannot be used with multiple input files");
331         if (argc > 1 && !inplace)
332                 errx(2, "multiple input files require -m or -M");
333         if (argc == 0)
334                 argc = 1;
335         if (argc == 1 && !inplace && ofilename == NULL)
336                 ofilename = "-";
337
338         atexit(cleantemp);
339         if (ofilename != NULL)
340                 processinout(*argv, ofilename);
341         else while (argc-- > 0) {
342                 processinout(*argv, *argv);
343                 argv++;
344         }
345         switch(exitmode) {
346         case(0): exit(exitstat);
347         case(1): exit(!exitstat);
348         case(2): exit(0);
349         default: abort(); /* bug */
350         }
351 }
352
353 /*
354  * File logistics.
355  */
356 static void
357 processinout(const char *ifn, const char *ofn)
358 {
359         struct stat st;
360
361         if (ifn == NULL || strcmp(ifn, "-") == 0) {
362                 filename = "[stdin]";
363                 linefile = NULL;
364                 input = fbinmode(stdin);
365         } else {
366                 filename = ifn;
367                 linefile = ifn;
368                 input = fopen(ifn, "rb");
369                 if (input == NULL)
370                         err(2, "can't open %s", ifn);
371         }
372         if (strcmp(ofn, "-") == 0) {
373                 output = fbinmode(stdout);
374                 process();
375                 return;
376         }
377         if (stat(ofn, &st) < 0) {
378                 output = fopen(ofn, "wb");
379                 if (output == NULL)
380                         err(2, "can't create %s", ofn);
381                 process();
382                 return;
383         }
384
385         tempname = astrcat(ofn, ".XXXXXX");
386         output = mktempmode(tempname, st.st_mode);
387         if (output == NULL)
388                 err(2, "can't create %s", tempname);
389
390         process();
391
392         if (backext != NULL) {
393                 char *backname = astrcat(ofn, backext);
394                 if (rename(ofn, backname) < 0)
395                         err(2, "can't rename \"%s\" to \"%s%s\"", ofn, ofn, backext);
396                 free(backname);
397         }
398         if (rename(tempname, ofn) < 0)
399                 err(2, "can't rename \"%s\" to \"%s\"", tempname, ofn);
400         free(tempname);
401         tempname = NULL;
402 }
403
404 /*
405  * For cleaning up if there is an error.
406  */
407 static void
408 cleantemp(void)
409 {
410         if (tempname != NULL)
411                 remove(tempname);
412 }
413
414 /*
415  * Self-identification functions.
416  */
417
418 static void
419 version(void)
420 {
421         const char *c = copyright;
422         for (;;) {
423                 while (*++c != '$')
424                         if (*c == '\0')
425                                 exit(0);
426                 while (*++c != '$')
427                         putc(*c, stderr);
428                 putc('\n', stderr);
429         }
430 }
431
432 static void
433 synopsis(FILE *fp)
434 {
435         fprintf(fp,
436             "usage:     unifdef [-bBcdehKkmnsStV] [-x{012}] [-Mext] [-opath] \\\n"
437             "           [-[i]Dsym[=val]] [-[i]Usym] ... [file] ...\n");
438 }
439
440 static void
441 usage(void)
442 {
443         synopsis(stderr);
444         exit(2);
445 }
446
447 static void
448 help(void)
449 {
450         synopsis(stdout);
451         printf(
452             "   -Dsym=val  define preprocessor symbol with given value\n"
453             "   -Dsym      define preprocessor symbol with value 1\n"
454             "   -Usym      preprocessor symbol is undefined\n"
455             "   -iDsym=val \\  ignore C strings and comments\n"
456             "   -iDsym      ) in sections controlled by these\n"
457             "   -iUsym     /  preprocessor symbols\n"
458             "   -b      blank lines instead of deleting them\n"
459             "   -B      compress blank lines around deleted section\n"
460             "   -c      complement (invert) keep vs. delete\n"
461             "   -d      debugging mode\n"
462             "   -e      ignore multiline preprocessor directives\n"
463             "   -h      print help\n"
464             "   -Ipath  extra include file path (ignored)\n"
465             "   -K      disable && and || short-circuiting\n"
466             "   -k      process constant #if expressions\n"
467             "   -Mext   modify in place and keep backups\n"
468             "   -m      modify input files in place\n"
469             "   -n      add #line directives to output\n"
470             "   -opath  output file name\n"
471             "   -S      list #if control symbols with nesting\n"
472             "   -s      list #if control symbols\n"
473             "   -t      ignore C strings and comments\n"
474             "   -V      print version\n"
475             "   -x{012} exit status mode\n"
476         );
477         exit(0);
478 }
479
480 /*
481  * A state transition function alters the global #if processing state
482  * in a particular way. The table below is indexed by the current
483  * processing state and the type of the current line.
484  *
485  * Nesting is handled by keeping a stack of states; some transition
486  * functions increase or decrease the depth. They also maintain the
487  * ignore state on a stack. In some complicated cases they have to
488  * alter the preprocessor directive, as follows.
489  *
490  * When we have processed a group that starts off with a known-false
491  * #if/#elif sequence (which has therefore been deleted) followed by a
492  * #elif that we don't understand and therefore must keep, we edit the
493  * latter into a #if to keep the nesting correct. We use memcpy() to
494  * overwrite the 4 byte token "elif" with "if  " without a '\0' byte.
495  *
496  * When we find a true #elif in a group, the following block will
497  * always be kept and the rest of the sequence after the next #elif or
498  * #else will be discarded. We edit the #elif into a #else and the
499  * following directive to #endif since this has the desired behaviour.
500  *
501  * "Dodgy" directives are split across multiple lines, the most common
502  * example being a multi-line comment hanging off the right of the
503  * directive. We can handle them correctly only if there is no change
504  * from printing to dropping (or vice versa) caused by that directive.
505  * If the directive is the first of a group we have a choice between
506  * failing with an error, or passing it through unchanged instead of
507  * evaluating it. The latter is not the default to avoid questions from
508  * users about unifdef unexpectedly leaving behind preprocessor directives.
509  */
510 typedef void state_fn(void);
511
512 /* report an error */
513 static void Eelif (void) { error("Inappropriate #elif"); }
514 static void Eelse (void) { error("Inappropriate #else"); }
515 static void Eendif(void) { error("Inappropriate #endif"); }
516 static void Eeof  (void) { error("Premature EOF"); }
517 static void Eioccc(void) { error("Obfuscated preprocessor control line"); }
518 /* plain line handling */
519 static void print (void) { flushline(true); }
520 static void drop  (void) { flushline(false); }
521 /* output lacks group's start line */
522 static void Strue (void) { drop();  ignoreoff(); state(IS_TRUE_PREFIX); }
523 static void Sfalse(void) { drop();  ignoreoff(); state(IS_FALSE_PREFIX); }
524 static void Selse (void) { drop();               state(IS_TRUE_ELSE); }
525 /* print/pass this block */
526 static void Pelif (void) { print(); ignoreoff(); state(IS_PASS_MIDDLE); }
527 static void Pelse (void) { print();              state(IS_PASS_ELSE); }
528 static void Pendif(void) { print(); unnest(); }
529 /* discard this block */
530 static void Dfalse(void) { drop();  ignoreoff(); state(IS_FALSE_TRAILER); }
531 static void Delif (void) { drop();  ignoreoff(); state(IS_FALSE_MIDDLE); }
532 static void Delse (void) { drop();               state(IS_FALSE_ELSE); }
533 static void Dendif(void) { drop();  unnest(); }
534 /* first line of group */
535 static void Fdrop (void) { nest();  Dfalse(); }
536 static void Fpass (void) { nest();  Pelif(); }
537 static void Ftrue (void) { nest();  Strue(); }
538 static void Ffalse(void) { nest();  Sfalse(); }
539 /* variable pedantry for obfuscated lines */
540 static void Oiffy (void) { if (!iocccok) Eioccc(); Fpass(); ignoreon(); }
541 static void Oif   (void) { if (!iocccok) Eioccc(); Fpass(); }
542 static void Oelif (void) { if (!iocccok) Eioccc(); Pelif(); }
543 /* ignore comments in this block */
544 static void Idrop (void) { Fdrop();  ignoreon(); }
545 static void Itrue (void) { Ftrue();  ignoreon(); }
546 static void Ifalse(void) { Ffalse(); ignoreon(); }
547 /* modify this line */
548 static void Mpass (void) { memcpy(keyword, "if  ", 4); Pelif(); }
549 static void Mtrue (void) { keywordedit("else");  state(IS_TRUE_MIDDLE); }
550 static void Melif (void) { keywordedit("endif"); state(IS_FALSE_TRAILER); }
551 static void Melse (void) { keywordedit("endif"); state(IS_FALSE_ELSE); }
552
553 static state_fn * const trans_table[IS_COUNT][LT_COUNT] = {
554 /* IS_OUTSIDE */
555 { Itrue, Ifalse,Fpass, Ftrue, Ffalse,Eelif, Eelif, Eelif, Eelse, Eendif,
556   Oiffy, Oiffy, Fpass, Oif,   Oif,   Eelif, Eelif, Eelif, Eelse, Eendif,
557   print, done,  abort },
558 /* IS_FALSE_PREFIX */
559 { Idrop, Idrop, Fdrop, Fdrop, Fdrop, Mpass, Strue, Sfalse,Selse, Dendif,
560   Idrop, Idrop, Fdrop, Fdrop, Fdrop, Mpass, Eioccc,Eioccc,Eioccc,Eioccc,
561   drop,  Eeof,  abort },
562 /* IS_TRUE_PREFIX */
563 { Itrue, Ifalse,Fpass, Ftrue, Ffalse,Dfalse,Dfalse,Dfalse,Delse, Dendif,
564   Oiffy, Oiffy, Fpass, Oif,   Oif,   Eioccc,Eioccc,Eioccc,Eioccc,Eioccc,
565   print, Eeof,  abort },
566 /* IS_PASS_MIDDLE */
567 { Itrue, Ifalse,Fpass, Ftrue, Ffalse,Pelif, Mtrue, Delif, Pelse, Pendif,
568   Oiffy, Oiffy, Fpass, Oif,   Oif,   Pelif, Oelif, Oelif, Pelse, Pendif,
569   print, Eeof,  abort },
570 /* IS_FALSE_MIDDLE */
571 { Idrop, Idrop, Fdrop, Fdrop, Fdrop, Pelif, Mtrue, Delif, Pelse, Pendif,
572   Idrop, Idrop, Fdrop, Fdrop, Fdrop, Eioccc,Eioccc,Eioccc,Eioccc,Eioccc,
573   drop,  Eeof,  abort },
574 /* IS_TRUE_MIDDLE */
575 { Itrue, Ifalse,Fpass, Ftrue, Ffalse,Melif, Melif, Melif, Melse, Pendif,
576   Oiffy, Oiffy, Fpass, Oif,   Oif,   Eioccc,Eioccc,Eioccc,Eioccc,Pendif,
577   print, Eeof,  abort },
578 /* IS_PASS_ELSE */
579 { Itrue, Ifalse,Fpass, Ftrue, Ffalse,Eelif, Eelif, Eelif, Eelse, Pendif,
580   Oiffy, Oiffy, Fpass, Oif,   Oif,   Eelif, Eelif, Eelif, Eelse, Pendif,
581   print, Eeof,  abort },
582 /* IS_FALSE_ELSE */
583 { Idrop, Idrop, Fdrop, Fdrop, Fdrop, Eelif, Eelif, Eelif, Eelse, Dendif,
584   Idrop, Idrop, Fdrop, Fdrop, Fdrop, Eelif, Eelif, Eelif, Eelse, Eioccc,
585   drop,  Eeof,  abort },
586 /* IS_TRUE_ELSE */
587 { Itrue, Ifalse,Fpass, Ftrue, Ffalse,Eelif, Eelif, Eelif, Eelse, Dendif,
588   Oiffy, Oiffy, Fpass, Oif,   Oif,   Eelif, Eelif, Eelif, Eelse, Eioccc,
589   print, Eeof,  abort },
590 /* IS_FALSE_TRAILER */
591 { Idrop, Idrop, Fdrop, Fdrop, Fdrop, Dfalse,Dfalse,Dfalse,Delse, Dendif,
592   Idrop, Idrop, Fdrop, Fdrop, Fdrop, Dfalse,Dfalse,Dfalse,Delse, Eioccc,
593   drop,  Eeof,  abort }
594 /*TRUEI  FALSEI IF     TRUE   FALSE  ELIF   ELTRUE ELFALSE ELSE  ENDIF
595   TRUEI  FALSEI IF     TRUE   FALSE  ELIF   ELTRUE ELFALSE ELSE  ENDIF (DODGY)
596   PLAIN  EOF    ERROR */
597 };
598
599 /*
600  * State machine utility functions
601  */
602 static void
603 ignoreoff(void)
604 {
605         if (depth == 0)
606                 abort(); /* bug */
607         ignoring[depth] = ignoring[depth-1];
608 }
609 static void
610 ignoreon(void)
611 {
612         ignoring[depth] = true;
613 }
614 static void
615 keywordedit(const char *replacement)
616 {
617         snprintf(keyword, tline + sizeof(tline) - keyword,
618             "%s%s", replacement, newline);
619         print();
620 }
621 static void
622 nest(void)
623 {
624         if (depth > MAXDEPTH-1)
625                 abort(); /* bug */
626         if (depth == MAXDEPTH-1)
627                 error("Too many levels of nesting");
628         depth += 1;
629         stifline[depth] = linenum;
630 }
631 static void
632 unnest(void)
633 {
634         if (depth == 0)
635                 abort(); /* bug */
636         depth -= 1;
637 }
638 static void
639 state(Ifstate is)
640 {
641         ifstate[depth] = is;
642 }
643
644 /*
645  * The last state transition function. When this is called,
646  * lineval == LT_EOF, so the process() loop will terminate.
647  */
648 static void
649 done(void)
650 {
651         if (incomment)
652                 error("EOF in comment");
653         closeout();
654 }
655
656 /*
657  * Write a line to the output or not, according to command line options.
658  * If writing fails, closeout() will print the error and exit.
659  */
660 static void
661 flushline(bool keep)
662 {
663         if (symlist)
664                 return;
665         if (keep ^ complement) {
666                 bool blankline = tline[strspn(tline, " \t\r\n")] == '\0';
667                 if (blankline && compblank && blankcount != blankmax) {
668                         delcount += 1;
669                         blankcount += 1;
670                 } else {
671                         if (lnnum && delcount > 0)
672                                 hashline();
673                         if (fputs(tline, output) == EOF)
674                                 closeout();
675                         delcount = 0;
676                         blankmax = blankcount = blankline ? blankcount + 1 : 0;
677                 }
678         } else {
679                 if (lnblank && fputs(newline, output) == EOF)
680                         closeout();
681                 exitstat = 1;
682                 delcount += 1;
683                 blankcount = 0;
684         }
685         if (debugging && fflush(output) == EOF)
686                 closeout();
687 }
688
689 /*
690  * Format of #line directives depends on whether we know the input filename.
691  */
692 static void
693 hashline(void)
694 {
695         int e;
696
697         if (linefile == NULL)
698                 e = fprintf(output, "#line %d%s", linenum, newline);
699         else
700                 e = fprintf(output, "#line %d \"%s\"%s",
701                     linenum, linefile, newline);
702         if (e < 0)
703                 closeout();
704 }
705
706 /*
707  * Flush the output and handle errors.
708  */
709 static void
710 closeout(void)
711 {
712         /* Tidy up after findsym(). */
713         if (symdepth && !zerosyms)
714                 printf("\n");
715         if (ferror(output) || fclose(output) == EOF)
716                 err(2, "%s: can't write to output", filename);
717 }
718
719 /*
720  * The driver for the state machine.
721  */
722 static void
723 process(void)
724 {
725         Linetype lineval = LT_PLAIN;
726         /* When compressing blank lines, act as if the file
727            is preceded by a large number of blank lines. */
728         blankmax = blankcount = 1000;
729         zerosyms = true;
730         while (lineval != LT_EOF) {
731                 lineval = parseline();
732                 trans_table[ifstate[depth]][lineval]();
733                 debug("process line %d %s -> %s depth %d",
734                     linenum, linetype_name[lineval],
735                     ifstate_name[ifstate[depth]], depth);
736         }
737 }
738
739 /*
740  * Parse a line and determine its type. We keep the preprocessor line
741  * parser state between calls in the global variable linestate, with
742  * help from skipcomment().
743  */
744 static Linetype
745 parseline(void)
746 {
747         const char *cp;
748         int cursym;
749         int kwlen;
750         Linetype retval;
751         Comment_state wascomment;
752
753         linenum++;
754         if (fgets(tline, MAXLINE, input) == NULL) {
755                 if (ferror(input))
756                         err(2, "can't read %s", filename);
757                 else
758                         return (LT_EOF);
759         }
760         if (newline == NULL) {
761                 if (strrchr(tline, '\n') == strrchr(tline, '\r') + 1)
762                         newline = newline_crlf;
763                 else
764                         newline = newline_unix;
765         }
766         retval = LT_PLAIN;
767         wascomment = incomment;
768         cp = skipcomment(tline);
769         if (linestate == LS_START) {
770                 if (*cp == '#') {
771                         linestate = LS_HASH;
772                         firstsym = true;
773                         cp = skipcomment(cp + 1);
774                 } else if (*cp != '\0')
775                         linestate = LS_DIRTY;
776         }
777         if (!incomment && linestate == LS_HASH) {
778                 keyword = tline + (cp - tline);
779                 cp = skipsym(cp);
780                 kwlen = cp - keyword;
781                 /* no way can we deal with a continuation inside a keyword */
782                 if (strncmp(cp, "\\\r\n", 3) == 0 ||
783                     strncmp(cp, "\\\n", 2) == 0)
784                         Eioccc();
785                 if (strlcmp("ifdef", keyword, kwlen) == 0 ||
786                     strlcmp("ifndef", keyword, kwlen) == 0) {
787                         cp = skipcomment(cp);
788                         if ((cursym = findsym(cp)) < 0)
789                                 retval = LT_IF;
790                         else {
791                                 retval = (keyword[2] == 'n')
792                                     ? LT_FALSE : LT_TRUE;
793                                 if (value[cursym] == NULL)
794                                         retval = (retval == LT_TRUE)
795                                             ? LT_FALSE : LT_TRUE;
796                                 if (ignore[cursym])
797                                         retval = (retval == LT_TRUE)
798                                             ? LT_TRUEI : LT_FALSEI;
799                         }
800                         cp = skipsym(cp);
801                 } else if (strlcmp("if", keyword, kwlen) == 0)
802                         retval = ifeval(&cp);
803                 else if (strlcmp("elif", keyword, kwlen) == 0)
804                         retval = linetype_if2elif(ifeval(&cp));
805                 else if (strlcmp("else", keyword, kwlen) == 0)
806                         retval = LT_ELSE;
807                 else if (strlcmp("endif", keyword, kwlen) == 0)
808                         retval = LT_ENDIF;
809                 else {
810                         linestate = LS_DIRTY;
811                         retval = LT_PLAIN;
812                 }
813                 cp = skipcomment(cp);
814                 if (*cp != '\0') {
815                         linestate = LS_DIRTY;
816                         if (retval == LT_TRUE || retval == LT_FALSE ||
817                             retval == LT_TRUEI || retval == LT_FALSEI)
818                                 retval = LT_IF;
819                         if (retval == LT_ELTRUE || retval == LT_ELFALSE)
820                                 retval = LT_ELIF;
821                 }
822                 if (retval != LT_PLAIN && (wascomment || incomment)) {
823                         retval = linetype_2dodgy(retval);
824                         if (incomment)
825                                 linestate = LS_DIRTY;
826                 }
827                 /* skipcomment normally changes the state, except
828                    if the last line of the file lacks a newline, or
829                    if there is too much whitespace in a directive */
830                 if (linestate == LS_HASH) {
831                         size_t len = cp - tline;
832                         if (fgets(tline + len, MAXLINE - len, input) == NULL) {
833                                 if (ferror(input))
834                                         err(2, "can't read %s", filename);
835                                 /* append the missing newline at eof */
836                                 strcpy(tline + len, newline);
837                                 cp += strlen(newline);
838                                 linestate = LS_START;
839                         } else {
840                                 linestate = LS_DIRTY;
841                         }
842                 }
843         }
844         if (linestate == LS_DIRTY) {
845                 while (*cp != '\0')
846                         cp = skipcomment(cp + 1);
847         }
848         debug("parser line %d state %s comment %s line", linenum,
849             comment_name[incomment], linestate_name[linestate]);
850         return (retval);
851 }
852
853 /*
854  * These are the binary operators that are supported by the expression
855  * evaluator.
856  */
857 static Linetype op_strict(int *p, int v, Linetype at, Linetype bt) {
858         if(at == LT_IF || bt == LT_IF) return (LT_IF);
859         return (*p = v, v ? LT_TRUE : LT_FALSE);
860 }
861 static Linetype op_lt(int *p, Linetype at, int a, Linetype bt, int b) {
862         return op_strict(p, a < b, at, bt);
863 }
864 static Linetype op_gt(int *p, Linetype at, int a, Linetype bt, int b) {
865         return op_strict(p, a > b, at, bt);
866 }
867 static Linetype op_le(int *p, Linetype at, int a, Linetype bt, int b) {
868         return op_strict(p, a <= b, at, bt);
869 }
870 static Linetype op_ge(int *p, Linetype at, int a, Linetype bt, int b) {
871         return op_strict(p, a >= b, at, bt);
872 }
873 static Linetype op_eq(int *p, Linetype at, int a, Linetype bt, int b) {
874         return op_strict(p, a == b, at, bt);
875 }
876 static Linetype op_ne(int *p, Linetype at, int a, Linetype bt, int b) {
877         return op_strict(p, a != b, at, bt);
878 }
879 static Linetype op_or(int *p, Linetype at, int a, Linetype bt, int b) {
880         if (!strictlogic && (at == LT_TRUE || bt == LT_TRUE))
881                 return (*p = 1, LT_TRUE);
882         return op_strict(p, a || b, at, bt);
883 }
884 static Linetype op_and(int *p, Linetype at, int a, Linetype bt, int b) {
885         if (!strictlogic && (at == LT_FALSE || bt == LT_FALSE))
886                 return (*p = 0, LT_FALSE);
887         return op_strict(p, a && b, at, bt);
888 }
889
890 /*
891  * An evaluation function takes three arguments, as follows: (1) a pointer to
892  * an element of the precedence table which lists the operators at the current
893  * level of precedence; (2) a pointer to an integer which will receive the
894  * value of the expression; and (3) a pointer to a char* that points to the
895  * expression to be evaluated and that is updated to the end of the expression
896  * when evaluation is complete. The function returns LT_FALSE if the value of
897  * the expression is zero, LT_TRUE if it is non-zero, LT_IF if the expression
898  * depends on an unknown symbol, or LT_ERROR if there is a parse failure.
899  */
900 struct ops;
901
902 typedef Linetype eval_fn(const struct ops *, int *, const char **);
903
904 static eval_fn eval_table, eval_unary;
905
906 /*
907  * The precedence table. Expressions involving binary operators are evaluated
908  * in a table-driven way by eval_table. When it evaluates a subexpression it
909  * calls the inner function with its first argument pointing to the next
910  * element of the table. Innermost expressions have special non-table-driven
911  * handling.
912  */
913 struct op {
914         const char *str;
915         Linetype (*fn)(int *, Linetype, int, Linetype, int);
916 };
917 struct ops {
918         eval_fn *inner;
919         struct op op[5];
920 };
921 static const struct ops eval_ops[] = {
922         { eval_table, { { "||", op_or } } },
923         { eval_table, { { "&&", op_and } } },
924         { eval_table, { { "==", op_eq },
925                         { "!=", op_ne } } },
926         { eval_unary, { { "<=", op_le },
927                         { ">=", op_ge },
928                         { "<", op_lt },
929                         { ">", op_gt } } }
930 };
931
932 /* Current operator precedence level */
933 static int prec(const struct ops *ops)
934 {
935         return (ops - eval_ops);
936 }
937
938 /*
939  * Function for evaluating the innermost parts of expressions,
940  * viz. !expr (expr) number defined(symbol) symbol
941  * We reset the constexpr flag in the last two cases.
942  */
943 static Linetype
944 eval_unary(const struct ops *ops, int *valp, const char **cpp)
945 {
946         const char *cp;
947         char *ep;
948         int sym;
949         bool defparen;
950         Linetype lt;
951
952         cp = skipcomment(*cpp);
953         if (*cp == '!') {
954                 debug("eval%d !", prec(ops));
955                 cp++;
956                 lt = eval_unary(ops, valp, &cp);
957                 if (lt == LT_ERROR)
958                         return (LT_ERROR);
959                 if (lt != LT_IF) {
960                         *valp = !*valp;
961                         lt = *valp ? LT_TRUE : LT_FALSE;
962                 }
963         } else if (*cp == '(') {
964                 cp++;
965                 debug("eval%d (", prec(ops));
966                 lt = eval_table(eval_ops, valp, &cp);
967                 if (lt == LT_ERROR)
968                         return (LT_ERROR);
969                 cp = skipcomment(cp);
970                 if (*cp++ != ')')
971                         return (LT_ERROR);
972         } else if (isdigit((unsigned char)*cp)) {
973                 debug("eval%d number", prec(ops));
974                 *valp = strtol(cp, &ep, 0);
975                 if (ep == cp)
976                         return (LT_ERROR);
977                 lt = *valp ? LT_TRUE : LT_FALSE;
978                 cp = skipsym(cp);
979         } else if (strncmp(cp, "defined", 7) == 0 && endsym(cp[7])) {
980                 cp = skipcomment(cp+7);
981                 debug("eval%d defined", prec(ops));
982                 if (*cp == '(') {
983                         cp = skipcomment(cp+1);
984                         defparen = true;
985                 } else {
986                         defparen = false;
987                 }
988                 sym = findsym(cp);
989                 if (sym < 0) {
990                         lt = LT_IF;
991                 } else {
992                         *valp = (value[sym] != NULL);
993                         lt = *valp ? LT_TRUE : LT_FALSE;
994                 }
995                 cp = skipsym(cp);
996                 cp = skipcomment(cp);
997                 if (defparen && *cp++ != ')')
998                         return (LT_ERROR);
999                 constexpr = false;
1000         } else if (!endsym(*cp)) {
1001                 debug("eval%d symbol", prec(ops));
1002                 sym = findsym(cp);
1003                 cp = skipsym(cp);
1004                 if (sym < 0) {
1005                         lt = LT_IF;
1006                         cp = skipargs(cp);
1007                 } else if (value[sym] == NULL) {
1008                         *valp = 0;
1009                         lt = LT_FALSE;
1010                 } else {
1011                         *valp = strtol(value[sym], &ep, 0);
1012                         if (*ep != '\0' || ep == value[sym])
1013                                 return (LT_ERROR);
1014                         lt = *valp ? LT_TRUE : LT_FALSE;
1015                         cp = skipargs(cp);
1016                 }
1017                 constexpr = false;
1018         } else {
1019                 debug("eval%d bad expr", prec(ops));
1020                 return (LT_ERROR);
1021         }
1022
1023         *cpp = cp;
1024         debug("eval%d = %d", prec(ops), *valp);
1025         return (lt);
1026 }
1027
1028 /*
1029  * Table-driven evaluation of binary operators.
1030  */
1031 static Linetype
1032 eval_table(const struct ops *ops, int *valp, const char **cpp)
1033 {
1034         const struct op *op;
1035         const char *cp;
1036         int val;
1037         Linetype lt, rt;
1038
1039         debug("eval%d", prec(ops));
1040         cp = *cpp;
1041         lt = ops->inner(ops+1, valp, &cp);
1042         if (lt == LT_ERROR)
1043                 return (LT_ERROR);
1044         for (;;) {
1045                 cp = skipcomment(cp);
1046                 for (op = ops->op; op->str != NULL; op++)
1047                         if (strncmp(cp, op->str, strlen(op->str)) == 0)
1048                                 break;
1049                 if (op->str == NULL)
1050                         break;
1051                 cp += strlen(op->str);
1052                 debug("eval%d %s", prec(ops), op->str);
1053                 rt = ops->inner(ops+1, &val, &cp);
1054                 if (rt == LT_ERROR)
1055                         return (LT_ERROR);
1056                 lt = op->fn(valp, lt, *valp, rt, val);
1057         }
1058
1059         *cpp = cp;
1060         debug("eval%d = %d", prec(ops), *valp);
1061         debug("eval%d lt = %s", prec(ops), linetype_name[lt]);
1062         return (lt);
1063 }
1064
1065 /*
1066  * Evaluate the expression on a #if or #elif line. If we can work out
1067  * the result we return LT_TRUE or LT_FALSE accordingly, otherwise we
1068  * return just a generic LT_IF.
1069  */
1070 static Linetype
1071 ifeval(const char **cpp)
1072 {
1073         Linetype ret;
1074         int val = 0;
1075
1076         debug("eval %s", *cpp);
1077         constexpr = killconsts ? false : true;
1078         ret = eval_table(eval_ops, &val, cpp);
1079         debug("eval = %d", val);
1080         return (constexpr ? LT_IF : ret == LT_ERROR ? LT_IF : ret);
1081 }
1082
1083 /*
1084  * Skip over comments, strings, and character literals and stop at the
1085  * next character position that is not whitespace. Between calls we keep
1086  * the comment state in the global variable incomment, and we also adjust
1087  * the global variable linestate when we see a newline.
1088  * XXX: doesn't cope with the buffer splitting inside a state transition.
1089  */
1090 static const char *
1091 skipcomment(const char *cp)
1092 {
1093         if (text || ignoring[depth]) {
1094                 for (; isspace((unsigned char)*cp); cp++)
1095                         if (*cp == '\n')
1096                                 linestate = LS_START;
1097                 return (cp);
1098         }
1099         while (*cp != '\0')
1100                 /* don't reset to LS_START after a line continuation */
1101                 if (strncmp(cp, "\\\r\n", 3) == 0)
1102                         cp += 3;
1103                 else if (strncmp(cp, "\\\n", 2) == 0)
1104                         cp += 2;
1105                 else switch (incomment) {
1106                 case NO_COMMENT:
1107                         if (strncmp(cp, "/\\\r\n", 4) == 0) {
1108                                 incomment = STARTING_COMMENT;
1109                                 cp += 4;
1110                         } else if (strncmp(cp, "/\\\n", 3) == 0) {
1111                                 incomment = STARTING_COMMENT;
1112                                 cp += 3;
1113                         } else if (strncmp(cp, "/*", 2) == 0) {
1114                                 incomment = C_COMMENT;
1115                                 cp += 2;
1116                         } else if (strncmp(cp, "//", 2) == 0) {
1117                                 incomment = CXX_COMMENT;
1118                                 cp += 2;
1119                         } else if (strncmp(cp, "\'", 1) == 0) {
1120                                 incomment = CHAR_LITERAL;
1121                                 linestate = LS_DIRTY;
1122                                 cp += 1;
1123                         } else if (strncmp(cp, "\"", 1) == 0) {
1124                                 incomment = STRING_LITERAL;
1125                                 linestate = LS_DIRTY;
1126                                 cp += 1;
1127                         } else if (strncmp(cp, "\n", 1) == 0) {
1128                                 linestate = LS_START;
1129                                 cp += 1;
1130                         } else if (strchr(" \r\t", *cp) != NULL) {
1131                                 cp += 1;
1132                         } else
1133                                 return (cp);
1134                         continue;
1135                 case CXX_COMMENT:
1136                         if (strncmp(cp, "\n", 1) == 0) {
1137                                 incomment = NO_COMMENT;
1138                                 linestate = LS_START;
1139                         }
1140                         cp += 1;
1141                         continue;
1142                 case CHAR_LITERAL:
1143                 case STRING_LITERAL:
1144                         if ((incomment == CHAR_LITERAL && cp[0] == '\'') ||
1145                             (incomment == STRING_LITERAL && cp[0] == '\"')) {
1146                                 incomment = NO_COMMENT;
1147                                 cp += 1;
1148                         } else if (cp[0] == '\\') {
1149                                 if (cp[1] == '\0')
1150                                         cp += 1;
1151                                 else
1152                                         cp += 2;
1153                         } else if (strncmp(cp, "\n", 1) == 0) {
1154                                 if (incomment == CHAR_LITERAL)
1155                                         error("unterminated char literal");
1156                                 else
1157                                         error("unterminated string literal");
1158                         } else
1159                                 cp += 1;
1160                         continue;
1161                 case C_COMMENT:
1162                         if (strncmp(cp, "*\\\r\n", 4) == 0) {
1163                                 incomment = FINISHING_COMMENT;
1164                                 cp += 4;
1165                         } else if (strncmp(cp, "*\\\n", 3) == 0) {
1166                                 incomment = FINISHING_COMMENT;
1167                                 cp += 3;
1168                         } else if (strncmp(cp, "*/", 2) == 0) {
1169                                 incomment = NO_COMMENT;
1170                                 cp += 2;
1171                         } else
1172                                 cp += 1;
1173                         continue;
1174                 case STARTING_COMMENT:
1175                         if (*cp == '*') {
1176                                 incomment = C_COMMENT;
1177                                 cp += 1;
1178                         } else if (*cp == '/') {
1179                                 incomment = CXX_COMMENT;
1180                                 cp += 1;
1181                         } else {
1182                                 incomment = NO_COMMENT;
1183                                 linestate = LS_DIRTY;
1184                         }
1185                         continue;
1186                 case FINISHING_COMMENT:
1187                         if (*cp == '/') {
1188                                 incomment = NO_COMMENT;
1189                                 cp += 1;
1190                         } else
1191                                 incomment = C_COMMENT;
1192                         continue;
1193                 default:
1194                         abort(); /* bug */
1195                 }
1196         return (cp);
1197 }
1198
1199 /*
1200  * Skip macro arguments.
1201  */
1202 static const char *
1203 skipargs(const char *cp)
1204 {
1205         const char *ocp = cp;
1206         int level = 0;
1207         cp = skipcomment(cp);
1208         if (*cp != '(')
1209                 return (cp);
1210         do {
1211                 if (*cp == '(')
1212                         level++;
1213                 if (*cp == ')')
1214                         level--;
1215                 cp = skipcomment(cp+1);
1216         } while (level != 0 && *cp != '\0');
1217         if (level == 0)
1218                 return (cp);
1219         else
1220         /* Rewind and re-detect the syntax error later. */
1221                 return (ocp);
1222 }
1223
1224 /*
1225  * Skip over an identifier.
1226  */
1227 static const char *
1228 skipsym(const char *cp)
1229 {
1230         while (!endsym(*cp))
1231                 ++cp;
1232         return (cp);
1233 }
1234
1235 /*
1236  * Look for the symbol in the symbol table. If it is found, we return
1237  * the symbol table index, else we return -1.
1238  */
1239 static int
1240 findsym(const char *str)
1241 {
1242         const char *cp;
1243         int symind;
1244
1245         cp = skipsym(str);
1246         if (cp == str)
1247                 return (-1);
1248         if (symlist) {
1249                 if (symdepth && firstsym)
1250                         printf("%s%3d", zerosyms ? "" : "\n", depth);
1251                 firstsym = zerosyms = false;
1252                 printf("%s%.*s%s",
1253                     symdepth ? " " : "",
1254                     (int)(cp-str), str,
1255                     symdepth ? "" : "\n");
1256                 /* we don't care about the value of the symbol */
1257                 return (0);
1258         }
1259         for (symind = 0; symind < nsyms; ++symind) {
1260                 if (strlcmp(symname[symind], str, cp-str) == 0) {
1261                         debug("findsym %s %s", symname[symind],
1262                             value[symind] ? value[symind] : "");
1263                         return (symind);
1264                 }
1265         }
1266         return (-1);
1267 }
1268
1269 /*
1270  * Add a symbol to the symbol table.
1271  */
1272 static void
1273 addsym(bool ignorethis, bool definethis, char *sym)
1274 {
1275         int symind;
1276         char *val;
1277
1278         symind = findsym(sym);
1279         if (symind < 0) {
1280                 if (nsyms >= MAXSYMS)
1281                         errx(2, "too many symbols");
1282                 symind = nsyms++;
1283         }
1284         symname[symind] = sym;
1285         ignore[symind] = ignorethis;
1286         val = sym + (skipsym(sym) - sym);
1287         if (definethis) {
1288                 if (*val == '=') {
1289                         value[symind] = val+1;
1290                         *val = '\0';
1291                 } else if (*val == '\0')
1292                         value[symind] = "1";
1293                 else
1294                         usage();
1295         } else {
1296                 if (*val != '\0')
1297                         usage();
1298                 value[symind] = NULL;
1299         }
1300         debug("addsym %s=%s", symname[symind],
1301             value[symind] ? value[symind] : "undef");
1302 }
1303
1304 /*
1305  * Compare s with n characters of t.
1306  * The same as strncmp() except that it checks that s[n] == '\0'.
1307  */
1308 static int
1309 strlcmp(const char *s, const char *t, size_t n)
1310 {
1311         while (n-- && *t != '\0')
1312                 if (*s != *t)
1313                         return ((unsigned char)*s - (unsigned char)*t);
1314                 else
1315                         ++s, ++t;
1316         return ((unsigned char)*s);
1317 }
1318
1319 /*
1320  * Concatenate two strings into new memory, checking for failure.
1321  */
1322 static char *
1323 astrcat(const char *s1, const char *s2)
1324 {
1325         char *s;
1326         int len;
1327
1328         len = 1 + snprintf(NULL, 0, "%s%s", s1, s2);
1329         s = (char *)malloc(len);
1330         if (s == NULL)
1331                 err(2, "malloc");
1332         snprintf(s, len, "%s%s", s1, s2);
1333         return (s);
1334 }
1335
1336 /*
1337  * Diagnostics.
1338  */
1339 static void
1340 debug(const char *msg, ...)
1341 {
1342         va_list ap;
1343
1344         if (debugging) {
1345                 va_start(ap, msg);
1346                 vwarnx(msg, ap);
1347                 va_end(ap);
1348         }
1349 }
1350
1351 static void
1352 error(const char *msg)
1353 {
1354         if (depth == 0)
1355                 warnx("%s: %d: %s", filename, linenum, msg);
1356         else
1357                 warnx("%s: %d: %s (#if line %d depth %d)",
1358                     filename, linenum, msg, stifline[depth], depth);
1359         closeout();
1360         errx(2, "output may be truncated");
1361 }