blob: 08e234de0a8ddda979f14ed027305e3d3b3b6397 [file] [log] [blame]
Nicholas Flintham1e3d3112013-04-10 10:48:38 +01001/*
2 * Copyright (c) 2002 - 2011 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 <sys/types.h>
47#include <sys/stat.h>
48
49#include <ctype.h>
50#include <err.h>
51#include <errno.h>
52#include <stdarg.h>
53#include <stdbool.h>
54#include <stdio.h>
55#include <stdlib.h>
56#include <string.h>
57#include <unistd.h>
58
59const char copyright[] =
60 "@(#) $Version: unifdef-2.5 $\n"
61 "@(#) $Author: Tony Finch (dot@dotat.at) $\n"
62 "@(#) $URL: http://dotat.at/prog/unifdef $\n"
63;
64
65typedef enum {
66 LT_TRUEI,
67 LT_FALSEI,
68 LT_IF,
69 LT_TRUE,
70 LT_FALSE,
71 LT_ELIF,
72 LT_ELTRUE,
73 LT_ELFALSE,
74 LT_ELSE,
75 LT_ENDIF,
76 LT_DODGY,
77 LT_DODGY_LAST = LT_DODGY + LT_ENDIF,
78 LT_PLAIN,
79 LT_EOF,
80 LT_ERROR,
81 LT_COUNT
82} Linetype;
83
84static char const * const linetype_name[] = {
85 "TRUEI", "FALSEI", "IF", "TRUE", "FALSE",
86 "ELIF", "ELTRUE", "ELFALSE", "ELSE", "ENDIF",
87 "DODGY TRUEI", "DODGY FALSEI",
88 "DODGY IF", "DODGY TRUE", "DODGY FALSE",
89 "DODGY ELIF", "DODGY ELTRUE", "DODGY ELFALSE",
90 "DODGY ELSE", "DODGY ENDIF",
91 "PLAIN", "EOF", "ERROR"
92};
93
94typedef enum {
95 IS_OUTSIDE,
96 IS_FALSE_PREFIX,
97 IS_TRUE_PREFIX,
98 IS_PASS_MIDDLE,
99 IS_FALSE_MIDDLE,
100 IS_TRUE_MIDDLE,
101 IS_PASS_ELSE,
102 IS_FALSE_ELSE,
103 IS_TRUE_ELSE,
104 IS_FALSE_TRAILER,
105 IS_COUNT
106} Ifstate;
107
108static char const * const ifstate_name[] = {
109 "OUTSIDE", "FALSE_PREFIX", "TRUE_PREFIX",
110 "PASS_MIDDLE", "FALSE_MIDDLE", "TRUE_MIDDLE",
111 "PASS_ELSE", "FALSE_ELSE", "TRUE_ELSE",
112 "FALSE_TRAILER"
113};
114
115typedef enum {
116 NO_COMMENT = false,
117 C_COMMENT,
118 CXX_COMMENT,
119 STARTING_COMMENT,
120 FINISHING_COMMENT,
121 CHAR_LITERAL,
122 STRING_LITERAL
123} Comment_state;
124
125static char const * const comment_name[] = {
126 "NO", "C", "CXX", "STARTING", "FINISHING", "CHAR", "STRING"
127};
128
129typedef enum {
130 LS_START,
131 LS_HASH,
132 LS_DIRTY
133} Line_state;
134
135static char const * const linestate_name[] = {
136 "START", "HASH", "DIRTY"
137};
138
139#define MAXDEPTH 64
140#define MAXLINE 4096
141#define MAXSYMS 4096
142
143#define EDITSLOP 10
144
145#define TEMPLATE "unifdef.XXXXXX"
146
147
148static bool compblank;
149static bool lnblank;
150static bool complement;
151static bool debugging;
152static bool iocccok;
153static bool strictlogic;
154static bool killconsts;
155static bool lnnum;
156static bool symlist;
157static bool symdepth;
158static bool text;
159
160static const char *symname[MAXSYMS];
161static const char *value[MAXSYMS];
162static bool ignore[MAXSYMS];
163static int nsyms;
164
165static FILE *input;
166static const char *filename;
167static int linenum;
168static FILE *output;
169static const char *ofilename;
170static bool overwriting;
171static char tempname[FILENAME_MAX];
172
173static char tline[MAXLINE+EDITSLOP];
174static char *keyword;
175
176static const char *newline;
177static const char newline_unix[] = "\n";
178static const char newline_crlf[] = "\r\n";
179
180static Comment_state incomment;
181static Line_state linestate;
182static Ifstate ifstate[MAXDEPTH];
183static bool ignoring[MAXDEPTH];
184static int stifline[MAXDEPTH];
185static int depth;
186static int delcount;
187static unsigned blankcount;
188static unsigned blankmax;
189static bool constexpr;
190static bool zerosyms = true;
191static bool firstsym;
192
193static int exitstat;
194
195static void addsym(bool, bool, char *);
196static void closeout(void);
197static void debug(const char *, ...);
198static void done(void);
199static void error(const char *);
200static int findsym(const char *);
201static void flushline(bool);
202static Linetype parseline(void);
203static Linetype ifeval(const char **);
204static void ignoreoff(void);
205static void ignoreon(void);
206static void keywordedit(const char *);
207static void nest(void);
208static void process(void);
209static const char *skipargs(const char *);
210static const char *skipcomment(const char *);
211static const char *skipsym(const char *);
212static void state(Ifstate);
213static int strlcmp(const char *, const char *, size_t);
214static void unnest(void);
215static void usage(void);
216static void version(void);
217
218#define endsym(c) (!isalnum((unsigned char)c) && c != '_')
219
220int
221main(int argc, char *argv[])
222{
223 int opt;
224
225 while ((opt = getopt(argc, argv, "i:D:U:I:o:bBcdeKklnsStV")) != -1)
226 switch (opt) {
227 case 'i':
228 opt = *optarg++;
229 if (opt == 'D')
230 addsym(true, true, optarg);
231 else if (opt == 'U')
232 addsym(true, false, optarg);
233 else
234 usage();
235 break;
236 case 'D':
237 addsym(false, true, optarg);
238 break;
239 case 'U':
240 addsym(false, false, optarg);
241 break;
242 case 'I':
243 break;
244 case 'b':
245 case 'l':
246 lnblank = true;
247 break;
248 case 'B':
249 compblank = true;
250 break;
251 case 'c':
252 complement = true;
253 break;
254 case 'd':
255 debugging = true;
256 break;
257 case 'e':
258 iocccok = true;
259 break;
260 case 'K':
261 strictlogic = true;
262 break;
263 case 'k':
264 killconsts = true;
265 break;
266 case 'n':
267 lnnum = true;
268 break;
269 case 'o':
270 ofilename = optarg;
271 break;
272 case 's':
273 symlist = true;
274 break;
275 case 'S':
276 symlist = symdepth = true;
277 break;
278 case 't':
279 text = true;
280 break;
281 case 'V':
282 version();
283 default:
284 usage();
285 }
286 argc -= optind;
287 argv += optind;
288 if (compblank && lnblank)
289 errx(2, "-B and -b are mutually exclusive");
290 if (argc > 1) {
291 errx(2, "can only do one file");
292 } else if (argc == 1 && strcmp(*argv, "-") != 0) {
293 filename = *argv;
294 input = fopen(filename, "rb");
295 if (input == NULL)
296 err(2, "can't open %s", filename);
297 } else {
298 filename = "[stdin]";
299 input = stdin;
300 }
301 if (ofilename == NULL) {
302 ofilename = "[stdout]";
303 output = stdout;
304 } else {
305 struct stat ist, ost;
306 if (stat(ofilename, &ost) == 0 &&
307 fstat(fileno(input), &ist) == 0)
308 overwriting = (ist.st_dev == ost.st_dev
309 && ist.st_ino == ost.st_ino);
310 if (overwriting) {
311 const char *dirsep;
312 int ofd;
313
314 dirsep = strrchr(ofilename, '/');
315 if (dirsep != NULL)
316 snprintf(tempname, sizeof(tempname),
317 "%.*s/" TEMPLATE,
318 (int)(dirsep - ofilename), ofilename);
319 else
320 snprintf(tempname, sizeof(tempname),
321 TEMPLATE);
322 ofd = mkstemp(tempname);
323 if (ofd != -1)
324 output = fdopen(ofd, "wb+");
325 if (output == NULL)
326 err(2, "can't create temporary file");
327 fchmod(ofd, ist.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO));
328 } else {
329 output = fopen(ofilename, "wb");
330 if (output == NULL)
331 err(2, "can't open %s", ofilename);
332 }
333 }
334 process();
335 abort();
336}
337
338static void
339version(void)
340{
341 const char *c = copyright;
342 for (;;) {
343 while (*++c != '$')
344 if (*c == '\0')
345 exit(0);
346 while (*++c != '$')
347 putc(*c, stderr);
348 putc('\n', stderr);
349 }
350}
351
352static void
353usage(void)
354{
355 fprintf(stderr, "usage: unifdef [-bBcdeKknsStV] [-Ipath]"
356 " [-Dsym[=val]] [-Usym] [-iDsym[=val]] [-iUsym] ... [file]\n");
357 exit(2);
358}
359
360typedef void state_fn(void);
361
362static void Eelif (void) { error("Inappropriate #elif"); }
363static void Eelse (void) { error("Inappropriate #else"); }
364static void Eendif(void) { error("Inappropriate #endif"); }
365static void Eeof (void) { error("Premature EOF"); }
366static void Eioccc(void) { error("Obfuscated preprocessor control line"); }
367static void print (void) { flushline(true); }
368static void drop (void) { flushline(false); }
369static void Strue (void) { drop(); ignoreoff(); state(IS_TRUE_PREFIX); }
370static void Sfalse(void) { drop(); ignoreoff(); state(IS_FALSE_PREFIX); }
371static void Selse (void) { drop(); state(IS_TRUE_ELSE); }
372static void Pelif (void) { print(); ignoreoff(); state(IS_PASS_MIDDLE); }
373static void Pelse (void) { print(); state(IS_PASS_ELSE); }
374static void Pendif(void) { print(); unnest(); }
375static void Dfalse(void) { drop(); ignoreoff(); state(IS_FALSE_TRAILER); }
376static void Delif (void) { drop(); ignoreoff(); state(IS_FALSE_MIDDLE); }
377static void Delse (void) { drop(); state(IS_FALSE_ELSE); }
378static void Dendif(void) { drop(); unnest(); }
379static void Fdrop (void) { nest(); Dfalse(); }
380static void Fpass (void) { nest(); Pelif(); }
381static void Ftrue (void) { nest(); Strue(); }
382static void Ffalse(void) { nest(); Sfalse(); }
383static void Oiffy (void) { if (!iocccok) Eioccc(); Fpass(); ignoreon(); }
384static void Oif (void) { if (!iocccok) Eioccc(); Fpass(); }
385static void Oelif (void) { if (!iocccok) Eioccc(); Pelif(); }
386static void Idrop (void) { Fdrop(); ignoreon(); }
387static void Itrue (void) { Ftrue(); ignoreon(); }
388static void Ifalse(void) { Ffalse(); ignoreon(); }
389static void Mpass (void) { strncpy(keyword, "if ", 4); Pelif(); }
390static void Mtrue (void) { keywordedit("else"); state(IS_TRUE_MIDDLE); }
391static void Melif (void) { keywordedit("endif"); state(IS_FALSE_TRAILER); }
392static void Melse (void) { keywordedit("endif"); state(IS_FALSE_ELSE); }
393
394static state_fn * const trans_table[IS_COUNT][LT_COUNT] = {
395{ Itrue, Ifalse,Fpass, Ftrue, Ffalse,Eelif, Eelif, Eelif, Eelse, Eendif,
396 Oiffy, Oiffy, Fpass, Oif, Oif, Eelif, Eelif, Eelif, Eelse, Eendif,
397 print, done, abort },
398{ Idrop, Idrop, Fdrop, Fdrop, Fdrop, Mpass, Strue, Sfalse,Selse, Dendif,
399 Idrop, Idrop, Fdrop, Fdrop, Fdrop, Mpass, Eioccc,Eioccc,Eioccc,Eioccc,
400 drop, Eeof, abort },
401{ Itrue, Ifalse,Fpass, Ftrue, Ffalse,Dfalse,Dfalse,Dfalse,Delse, Dendif,
402 Oiffy, Oiffy, Fpass, Oif, Oif, Eioccc,Eioccc,Eioccc,Eioccc,Eioccc,
403 print, Eeof, abort },
404{ Itrue, Ifalse,Fpass, Ftrue, Ffalse,Pelif, Mtrue, Delif, Pelse, Pendif,
405 Oiffy, Oiffy, Fpass, Oif, Oif, Pelif, Oelif, Oelif, Pelse, Pendif,
406 print, Eeof, abort },
407{ Idrop, Idrop, Fdrop, Fdrop, Fdrop, Pelif, Mtrue, Delif, Pelse, Pendif,
408 Idrop, Idrop, Fdrop, Fdrop, Fdrop, Eioccc,Eioccc,Eioccc,Eioccc,Eioccc,
409 drop, Eeof, abort },
410{ Itrue, Ifalse,Fpass, Ftrue, Ffalse,Melif, Melif, Melif, Melse, Pendif,
411 Oiffy, Oiffy, Fpass, Oif, Oif, Eioccc,Eioccc,Eioccc,Eioccc,Pendif,
412 print, Eeof, abort },
413{ Itrue, Ifalse,Fpass, Ftrue, Ffalse,Eelif, Eelif, Eelif, Eelse, Pendif,
414 Oiffy, Oiffy, Fpass, Oif, Oif, Eelif, Eelif, Eelif, Eelse, Pendif,
415 print, Eeof, abort },
416{ Idrop, Idrop, Fdrop, Fdrop, Fdrop, Eelif, Eelif, Eelif, Eelse, Dendif,
417 Idrop, Idrop, Fdrop, Fdrop, Fdrop, Eelif, Eelif, Eelif, Eelse, Eioccc,
418 drop, Eeof, abort },
419{ Itrue, Ifalse,Fpass, Ftrue, Ffalse,Eelif, Eelif, Eelif, Eelse, Dendif,
420 Oiffy, Oiffy, Fpass, Oif, Oif, Eelif, Eelif, Eelif, Eelse, Eioccc,
421 print, Eeof, abort },
422{ Idrop, Idrop, Fdrop, Fdrop, Fdrop, Dfalse,Dfalse,Dfalse,Delse, Dendif,
423 Idrop, Idrop, Fdrop, Fdrop, Fdrop, Dfalse,Dfalse,Dfalse,Delse, Eioccc,
424 drop, Eeof, abort }
425};
426
427static void
428ignoreoff(void)
429{
430 if (depth == 0)
431 abort();
432 ignoring[depth] = ignoring[depth-1];
433}
434static void
435ignoreon(void)
436{
437 ignoring[depth] = true;
438}
439static void
440keywordedit(const char *replacement)
441{
442 snprintf(keyword, tline + sizeof(tline) - keyword,
443 "%s%s", replacement, newline);
444 print();
445}
446static void
447nest(void)
448{
449 if (depth > MAXDEPTH-1)
450 abort();
451 if (depth == MAXDEPTH-1)
452 error("Too many levels of nesting");
453 depth += 1;
454 stifline[depth] = linenum;
455}
456static void
457unnest(void)
458{
459 if (depth == 0)
460 abort();
461 depth -= 1;
462}
463static void
464state(Ifstate is)
465{
466 ifstate[depth] = is;
467}
468
469static void
470flushline(bool keep)
471{
472 if (symlist)
473 return;
474 if (keep ^ complement) {
475 bool blankline = tline[strspn(tline, " \t\r\n")] == '\0';
476 if (blankline && compblank && blankcount != blankmax) {
477 delcount += 1;
478 blankcount += 1;
479 } else {
480 if (lnnum && delcount > 0)
481 printf("#line %d%s", linenum, newline);
482 fputs(tline, output);
483 delcount = 0;
484 blankmax = blankcount = blankline ? blankcount + 1 : 0;
485 }
486 } else {
487 if (lnblank)
488 fputs(newline, output);
489 exitstat = 1;
490 delcount += 1;
491 blankcount = 0;
492 }
493 if (debugging)
494 fflush(output);
495}
496
497static void
498process(void)
499{
500 blankmax = blankcount = 1000;
501 for (;;) {
502 Linetype lineval = parseline();
503 trans_table[ifstate[depth]][lineval]();
504 debug("process line %d %s -> %s depth %d",
505 linenum, linetype_name[lineval],
506 ifstate_name[ifstate[depth]], depth);
507 }
508}
509
510static void
511closeout(void)
512{
513 if (symdepth && !zerosyms)
514 printf("\n");
515 if (fclose(output) == EOF) {
516 warn("couldn't write to %s", ofilename);
517 if (overwriting) {
518 unlink(tempname);
519 errx(2, "%s unchanged", filename);
520 } else {
521 exit(2);
522 }
523 }
524}
525
526static void
527done(void)
528{
529 if (incomment)
530 error("EOF in comment");
531 closeout();
532 if (overwriting && rename(tempname, ofilename) == -1) {
533 warn("couldn't rename temporary file");
534 unlink(tempname);
535 errx(2, "%s unchanged", ofilename);
536 }
537 exit(exitstat);
538}
539
540static Linetype
541parseline(void)
542{
543 const char *cp;
544 int cursym;
545 int kwlen;
546 Linetype retval;
547 Comment_state wascomment;
548
549 linenum++;
550 if (fgets(tline, MAXLINE, input) == NULL)
551 return (LT_EOF);
552 if (newline == NULL) {
553 if (strrchr(tline, '\n') == strrchr(tline, '\r') + 1)
554 newline = newline_crlf;
555 else
556 newline = newline_unix;
557 }
558 retval = LT_PLAIN;
559 wascomment = incomment;
560 cp = skipcomment(tline);
561 if (linestate == LS_START) {
562 if (*cp == '#') {
563 linestate = LS_HASH;
564 firstsym = true;
565 cp = skipcomment(cp + 1);
566 } else if (*cp != '\0')
567 linestate = LS_DIRTY;
568 }
569 if (!incomment && linestate == LS_HASH) {
570 keyword = tline + (cp - tline);
571 cp = skipsym(cp);
572 kwlen = cp - keyword;
573
574 if (strncmp(cp, "\\\r\n", 3) == 0 ||
575 strncmp(cp, "\\\n", 2) == 0)
576 Eioccc();
577 if (strlcmp("ifdef", keyword, kwlen) == 0 ||
578 strlcmp("ifndef", keyword, kwlen) == 0) {
579 cp = skipcomment(cp);
580 if ((cursym = findsym(cp)) < 0)
581 retval = LT_IF;
582 else {
583 retval = (keyword[2] == 'n')
584 ? LT_FALSE : LT_TRUE;
585 if (value[cursym] == NULL)
586 retval = (retval == LT_TRUE)
587 ? LT_FALSE : LT_TRUE;
588 if (ignore[cursym])
589 retval = (retval == LT_TRUE)
590 ? LT_TRUEI : LT_FALSEI;
591 }
592 cp = skipsym(cp);
593 } else if (strlcmp("if", keyword, kwlen) == 0)
594 retval = ifeval(&cp);
595 else if (strlcmp("elif", keyword, kwlen) == 0)
596 retval = ifeval(&cp) - LT_IF + LT_ELIF;
597 else if (strlcmp("else", keyword, kwlen) == 0)
598 retval = LT_ELSE;
599 else if (strlcmp("endif", keyword, kwlen) == 0)
600 retval = LT_ENDIF;
601 else {
602 linestate = LS_DIRTY;
603 retval = LT_PLAIN;
604 }
605 cp = skipcomment(cp);
606 if (*cp != '\0') {
607 linestate = LS_DIRTY;
608 if (retval == LT_TRUE || retval == LT_FALSE ||
609 retval == LT_TRUEI || retval == LT_FALSEI)
610 retval = LT_IF;
611 if (retval == LT_ELTRUE || retval == LT_ELFALSE)
612 retval = LT_ELIF;
613 }
614 if (retval != LT_PLAIN && (wascomment || incomment)) {
615 retval += LT_DODGY;
616 if (incomment)
617 linestate = LS_DIRTY;
618 }
619 if (linestate == LS_HASH) {
620 size_t len = cp - tline;
621 if (fgets(tline + len, MAXLINE - len, input) == NULL) {
622
623 strcpy(tline + len, newline);
624 cp += strlen(newline);
625 linestate = LS_START;
626 } else {
627 linestate = LS_DIRTY;
628 }
629 }
630 }
631 if (linestate == LS_DIRTY) {
632 while (*cp != '\0')
633 cp = skipcomment(cp + 1);
634 }
635 debug("parser line %d state %s comment %s line", linenum,
636 comment_name[incomment], linestate_name[linestate]);
637 return (retval);
638}
639
640static Linetype op_strict(int *p, int v, Linetype at, Linetype bt) {
641 if(at == LT_IF || bt == LT_IF) return (LT_IF);
642 return (*p = v, v ? LT_TRUE : LT_FALSE);
643}
644static Linetype op_lt(int *p, Linetype at, int a, Linetype bt, int b) {
645 return op_strict(p, a < b, at, bt);
646}
647static Linetype op_gt(int *p, Linetype at, int a, Linetype bt, int b) {
648 return op_strict(p, a > b, at, bt);
649}
650static Linetype op_le(int *p, Linetype at, int a, Linetype bt, int b) {
651 return op_strict(p, a <= b, at, bt);
652}
653static Linetype op_ge(int *p, Linetype at, int a, Linetype bt, int b) {
654 return op_strict(p, a >= b, at, bt);
655}
656static Linetype op_eq(int *p, Linetype at, int a, Linetype bt, int b) {
657 return op_strict(p, a == b, at, bt);
658}
659static Linetype op_ne(int *p, Linetype at, int a, Linetype bt, int b) {
660 return op_strict(p, a != b, at, bt);
661}
662static Linetype op_or(int *p, Linetype at, int a, Linetype bt, int b) {
663 if (!strictlogic && (at == LT_TRUE || bt == LT_TRUE))
664 return (*p = 1, LT_TRUE);
665 return op_strict(p, a || b, at, bt);
666}
667static Linetype op_and(int *p, Linetype at, int a, Linetype bt, int b) {
668 if (!strictlogic && (at == LT_FALSE || bt == LT_FALSE))
669 return (*p = 0, LT_FALSE);
670 return op_strict(p, a && b, at, bt);
671}
672
673struct ops;
674
675typedef Linetype eval_fn(const struct ops *, int *, const char **);
676
677static eval_fn eval_table, eval_unary;
678
679static const struct ops {
680 eval_fn *inner;
681 struct op {
682 const char *str;
683 Linetype (*fn)(int *, Linetype, int, Linetype, int);
684 } op[5];
685} eval_ops[] = {
686 { eval_table, { { "||", op_or } } },
687 { eval_table, { { "&&", op_and } } },
688 { eval_table, { { "==", op_eq },
689 { "!=", op_ne } } },
690 { eval_unary, { { "<=", op_le },
691 { ">=", op_ge },
692 { "<", op_lt },
693 { ">", op_gt } } }
694};
695
696static Linetype
697eval_unary(const struct ops *ops, int *valp, const char **cpp)
698{
699 const char *cp;
700 char *ep;
701 int sym;
702 bool defparen;
703 Linetype lt;
704
705 cp = skipcomment(*cpp);
706 if (*cp == '!') {
707 debug("eval%d !", ops - eval_ops);
708 cp++;
709 lt = eval_unary(ops, valp, &cp);
710 if (lt == LT_ERROR)
711 return (LT_ERROR);
712 if (lt != LT_IF) {
713 *valp = !*valp;
714 lt = *valp ? LT_TRUE : LT_FALSE;
715 }
716 } else if (*cp == '(') {
717 cp++;
718 debug("eval%d (", ops - eval_ops);
719 lt = eval_table(eval_ops, valp, &cp);
720 if (lt == LT_ERROR)
721 return (LT_ERROR);
722 cp = skipcomment(cp);
723 if (*cp++ != ')')
724 return (LT_ERROR);
725 } else if (isdigit((unsigned char)*cp)) {
726 debug("eval%d number", ops - eval_ops);
727 *valp = strtol(cp, &ep, 0);
728 if (ep == cp)
729 return (LT_ERROR);
730 lt = *valp ? LT_TRUE : LT_FALSE;
731 cp = skipsym(cp);
732 } else if (strncmp(cp, "defined", 7) == 0 && endsym(cp[7])) {
733 cp = skipcomment(cp+7);
734 debug("eval%d defined", ops - eval_ops);
735 if (*cp == '(') {
736 cp = skipcomment(cp+1);
737 defparen = true;
738 } else {
739 defparen = false;
740 }
741 sym = findsym(cp);
742 if (sym < 0) {
743 lt = LT_IF;
744 } else {
745 *valp = (value[sym] != NULL);
746 lt = *valp ? LT_TRUE : LT_FALSE;
747 }
748 cp = skipsym(cp);
749 cp = skipcomment(cp);
750 if (defparen && *cp++ != ')')
751 return (LT_ERROR);
752 constexpr = false;
753 } else if (!endsym(*cp)) {
754 debug("eval%d symbol", ops - eval_ops);
755 sym = findsym(cp);
756 cp = skipsym(cp);
757 if (sym < 0) {
758 lt = LT_IF;
759 cp = skipargs(cp);
760 } else if (value[sym] == NULL) {
761 *valp = 0;
762 lt = LT_FALSE;
763 } else {
764 *valp = strtol(value[sym], &ep, 0);
765 if (*ep != '\0' || ep == value[sym])
766 return (LT_ERROR);
767 lt = *valp ? LT_TRUE : LT_FALSE;
768 cp = skipargs(cp);
769 }
770 constexpr = false;
771 } else {
772 debug("eval%d bad expr", ops - eval_ops);
773 return (LT_ERROR);
774 }
775
776 *cpp = cp;
777 debug("eval%d = %d", ops - eval_ops, *valp);
778 return (lt);
779}
780
781static Linetype
782eval_table(const struct ops *ops, int *valp, const char **cpp)
783{
784 const struct op *op;
785 const char *cp;
786 int val;
787 Linetype lt, rt;
788
789 debug("eval%d", ops - eval_ops);
790 cp = *cpp;
791 lt = ops->inner(ops+1, valp, &cp);
792 if (lt == LT_ERROR)
793 return (LT_ERROR);
794 for (;;) {
795 cp = skipcomment(cp);
796 for (op = ops->op; op->str != NULL; op++)
797 if (strncmp(cp, op->str, strlen(op->str)) == 0)
798 break;
799 if (op->str == NULL)
800 break;
801 cp += strlen(op->str);
802 debug("eval%d %s", ops - eval_ops, op->str);
803 rt = ops->inner(ops+1, &val, &cp);
804 if (rt == LT_ERROR)
805 return (LT_ERROR);
806 lt = op->fn(valp, lt, *valp, rt, val);
807 }
808
809 *cpp = cp;
810 debug("eval%d = %d", ops - eval_ops, *valp);
811 debug("eval%d lt = %s", ops - eval_ops, linetype_name[lt]);
812 return (lt);
813}
814
815static Linetype
816ifeval(const char **cpp)
817{
818 int ret;
819 int val = 0;
820
821 debug("eval %s", *cpp);
822 constexpr = killconsts ? false : true;
823 ret = eval_table(eval_ops, &val, cpp);
824 debug("eval = %d", val);
825 return (constexpr ? LT_IF : ret == LT_ERROR ? LT_IF : ret);
826}
827
828static const char *
829skipcomment(const char *cp)
830{
831 if (text || ignoring[depth]) {
832 for (; isspace((unsigned char)*cp); cp++)
833 if (*cp == '\n')
834 linestate = LS_START;
835 return (cp);
836 }
837 while (*cp != '\0')
838
839 if (strncmp(cp, "\\\r\n", 3) == 0)
840 cp += 3;
841 else if (strncmp(cp, "\\\n", 2) == 0)
842 cp += 2;
843 else switch (incomment) {
844 case NO_COMMENT:
845 if (strncmp(cp, "/\\\r\n", 4) == 0) {
846 incomment = STARTING_COMMENT;
847 cp += 4;
848 } else if (strncmp(cp, "/\\\n", 3) == 0) {
849 incomment = STARTING_COMMENT;
850 cp += 3;
851 } else if (strncmp(cp, "/*", 2) == 0) {
852 incomment = C_COMMENT;
853 cp += 2;
854 } else if (strncmp(cp, "//", 2) == 0) {
855 incomment = CXX_COMMENT;
856 cp += 2;
857 } else if (strncmp(cp, "\'", 1) == 0) {
858 incomment = CHAR_LITERAL;
859 linestate = LS_DIRTY;
860 cp += 1;
861 } else if (strncmp(cp, "\"", 1) == 0) {
862 incomment = STRING_LITERAL;
863 linestate = LS_DIRTY;
864 cp += 1;
865 } else if (strncmp(cp, "\n", 1) == 0) {
866 linestate = LS_START;
867 cp += 1;
868 } else if (strchr(" \r\t", *cp) != NULL) {
869 cp += 1;
870 } else
871 return (cp);
872 continue;
873 case CXX_COMMENT:
874 if (strncmp(cp, "\n", 1) == 0) {
875 incomment = NO_COMMENT;
876 linestate = LS_START;
877 }
878 cp += 1;
879 continue;
880 case CHAR_LITERAL:
881 case STRING_LITERAL:
882 if ((incomment == CHAR_LITERAL && cp[0] == '\'') ||
883 (incomment == STRING_LITERAL && cp[0] == '\"')) {
884 incomment = NO_COMMENT;
885 cp += 1;
886 } else if (cp[0] == '\\') {
887 if (cp[1] == '\0')
888 cp += 1;
889 else
890 cp += 2;
891 } else if (strncmp(cp, "\n", 1) == 0) {
892 if (incomment == CHAR_LITERAL)
893 error("unterminated char literal");
894 else
895 error("unterminated string literal");
896 } else
897 cp += 1;
898 continue;
899 case C_COMMENT:
900 if (strncmp(cp, "*\\\r\n", 4) == 0) {
901 incomment = FINISHING_COMMENT;
902 cp += 4;
903 } else if (strncmp(cp, "*\\\n", 3) == 0) {
904 incomment = FINISHING_COMMENT;
905 cp += 3;
906 } else if (strncmp(cp, "*/", 2) == 0) {
907 incomment = NO_COMMENT;
908 cp += 2;
909 } else
910 cp += 1;
911 continue;
912 case STARTING_COMMENT:
913 if (*cp == '*') {
914 incomment = C_COMMENT;
915 cp += 1;
916 } else if (*cp == '/') {
917 incomment = CXX_COMMENT;
918 cp += 1;
919 } else {
920 incomment = NO_COMMENT;
921 linestate = LS_DIRTY;
922 }
923 continue;
924 case FINISHING_COMMENT:
925 if (*cp == '/') {
926 incomment = NO_COMMENT;
927 cp += 1;
928 } else
929 incomment = C_COMMENT;
930 continue;
931 default:
932 abort();
933 }
934 return (cp);
935}
936
937static const char *
938skipargs(const char *cp)
939{
940 const char *ocp = cp;
941 int level = 0;
942 cp = skipcomment(cp);
943 if (*cp != '(')
944 return (cp);
945 do {
946 if (*cp == '(')
947 level++;
948 if (*cp == ')')
949 level--;
950 cp = skipcomment(cp+1);
951 } while (level != 0 && *cp != '\0');
952 if (level == 0)
953 return (cp);
954 else
955
956 return (ocp);
957}
958
959static const char *
960skipsym(const char *cp)
961{
962 while (!endsym(*cp))
963 ++cp;
964 return (cp);
965}
966
967static int
968findsym(const char *str)
969{
970 const char *cp;
971 int symind;
972
973 cp = skipsym(str);
974 if (cp == str)
975 return (-1);
976 if (symlist) {
977 if (symdepth && firstsym)
978 printf("%s%3d", zerosyms ? "" : "\n", depth);
979 firstsym = zerosyms = false;
980 printf("%s%.*s%s",
981 symdepth ? " " : "",
982 (int)(cp-str), str,
983 symdepth ? "" : "\n");
984
985 return (0);
986 }
987 for (symind = 0; symind < nsyms; ++symind) {
988 if (strlcmp(symname[symind], str, cp-str) == 0) {
989 debug("findsym %s %s", symname[symind],
990 value[symind] ? value[symind] : "");
991 return (symind);
992 }
993 }
994 return (-1);
995}
996
997static void
998addsym(bool ignorethis, bool definethis, char *sym)
999{
1000 int symind;
1001 char *val;
1002
1003 symind = findsym(sym);
1004 if (symind < 0) {
1005 if (nsyms >= MAXSYMS)
1006 errx(2, "too many symbols");
1007 symind = nsyms++;
1008 }
1009 symname[symind] = sym;
1010 ignore[symind] = ignorethis;
1011 val = sym + (skipsym(sym) - sym);
1012 if (definethis) {
1013 if (*val == '=') {
1014 value[symind] = val+1;
1015 *val = '\0';
1016 } else if (*val == '\0')
1017 value[symind] = "1";
1018 else
1019 usage();
1020 } else {
1021 if (*val != '\0')
1022 usage();
1023 value[symind] = NULL;
1024 }
1025 debug("addsym %s=%s", symname[symind],
1026 value[symind] ? value[symind] : "undef");
1027}
1028
1029static int
1030strlcmp(const char *s, const char *t, size_t n)
1031{
1032 while (n-- && *t != '\0')
1033 if (*s != *t)
1034 return ((unsigned char)*s - (unsigned char)*t);
1035 else
1036 ++s, ++t;
1037 return ((unsigned char)*s);
1038}
1039
1040static void
1041debug(const char *msg, ...)
1042{
1043 va_list ap;
1044
1045 if (debugging) {
1046 va_start(ap, msg);
1047 vwarnx(msg, ap);
1048 va_end(ap);
1049 }
1050}
1051
1052static void
1053error(const char *msg)
1054{
1055 if (depth == 0)
1056 warnx("%s: %d: %s", filename, linenum, msg);
1057 else
1058 warnx("%s: %d: %s (#if line %d depth %d)",
1059 filename, linenum, msg, stifline[depth], depth);
1060 closeout();
1061 errx(2, "output may be truncated");
1062}