1 /* $NetBSD: unifdef.c,v 1.22 2012/10/13 18:26:03 christos Exp $ */
4 * Copyright (c) 1985, 1993
5 * The Regents of the University of California. All rights reserved.
7 * This code is derived from software contributed to Berkeley by
8 * Dave Yost. It was rewritten to support ANSI C by Tony Finch.
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 * 3. Neither the name of the University nor the names of its contributors
19 * may be used to endorse or promote products derived from this software
20 * without specific prior written permission.
22 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
36 * Copyright (c) 2002, 2003 Tony Finch <dot@dotat.at>
38 * This code is derived from software contributed to Berkeley by
39 * Dave Yost. It was rewritten to support ANSI C by Tony Finch.
41 * Redistribution and use in source and binary forms, with or without
42 * modification, are permitted provided that the following conditions
44 * 1. Redistributions of source code must retain the above copyright
45 * notice, this list of conditions and the following disclaimer.
46 * 2. Redistributions in binary form must reproduce the above copyright
47 * notice, this list of conditions and the following disclaimer in the
48 * documentation and/or other materials provided with the distribution.
49 * 3. All advertising materials mentioning features or use of this software
50 * must display the following acknowledgement:
51 * This product includes software developed by the University of
52 * California, Berkeley and its contributors.
53 * 4. Neither the name of the University nor the names of its contributors
54 * may be used to endorse or promote products derived from this software
55 * without specific prior written permission.
57 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
58 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
59 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
60 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
61 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
62 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
63 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
64 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
65 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
66 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
70 #include <sys/cdefs.h>
74 static const char copyright
[] =
75 "@(#) Copyright (c) 1985, 1993\n\
76 The Regents of the University of California. All rights reserved.\n";
79 __IDSTRING(Berkeley
, "@(#)unifdef.c 8.1 (Berkeley) 6/6/93");
80 __IDSTRING(NetBSD
, "$NetBSD: unifdef.c,v 1.22 2012/10/13 18:26:03 christos Exp $");
81 __IDSTRING(dotat
, "$dotat: things/unifdef.c,v 1.161 2003/07/01 15:32:48 fanf2 Exp $");
85 __FBSDID("$FreeBSD: src/usr.bin/unifdef/unifdef.c,v 1.18 2003/07/01 15:30:43 fanf Exp $");
89 * unifdef - remove ifdef'ed lines
92 * provide an option which will append the name of the
93 * appropriate symbol after #else's and #endif's
94 * provide an option which will check symbols after
95 * #else's and #endif's to see that they match their
96 * corresponding #ifdef or #ifndef
97 * generate #line directives in place of deleted code
99 * The first two items above require better buffer handling, which would
100 * also make it possible to handle all "dodgy" directives correctly.
112 #include <sys/param.h>
113 #include <sys/stat.h>
117 /* types of input lines: */
119 LT_TRUEI
, /* a true #if with ignore flag */
120 LT_FALSEI
, /* a false #if with ignore flag */
121 LT_IF
, /* an unknown #if */
122 LT_TRUE
, /* a true #if */
123 LT_FALSE
, /* a false #if */
124 LT_ELIF
, /* an unknown #elif */
125 LT_ELTRUE
, /* a true #elif */
126 LT_ELFALSE
, /* a false #elif */
128 LT_ENDIF
, /* #endif */
129 LT_DODGY
, /* flag: directive is not on one line */
130 LT_DODGY_LAST
= LT_DODGY
+ LT_ENDIF
,
131 LT_PLAIN
, /* ordinary line */
132 LT_EOF
, /* end of file */
136 static char const * const linetype_name
[] = {
137 "TRUEI", "FALSEI", "IF", "TRUE", "FALSE",
138 "ELIF", "ELTRUE", "ELFALSE", "ELSE", "ENDIF",
139 "DODGY TRUEI", "DODGY FALSEI",
140 "DODGY IF", "DODGY TRUE", "DODGY FALSE",
141 "DODGY ELIF", "DODGY ELTRUE", "DODGY ELFALSE",
142 "DODGY ELSE", "DODGY ENDIF",
146 /* state of #if processing */
149 IS_FALSE_PREFIX
, /* false #if followed by false #elifs */
150 IS_TRUE_PREFIX
, /* first non-false #(el)if is true */
151 IS_PASS_MIDDLE
, /* first non-false #(el)if is unknown */
152 IS_FALSE_MIDDLE
, /* a false #elif after a pass state */
153 IS_TRUE_MIDDLE
, /* a true #elif after a pass state */
154 IS_PASS_ELSE
, /* an else after a pass state */
155 IS_FALSE_ELSE
, /* an else after a true state */
156 IS_TRUE_ELSE
, /* an else after only false states */
157 IS_FALSE_TRAILER
, /* #elifs after a true are false */
161 static char const * const ifstate_name
[] = {
162 "OUTSIDE", "FALSE_PREFIX", "TRUE_PREFIX",
163 "PASS_MIDDLE", "FALSE_MIDDLE", "TRUE_MIDDLE",
164 "PASS_ELSE", "FALSE_ELSE", "TRUE_ELSE",
168 /* state of comment parser */
170 NO_COMMENT
= false, /* outside a comment */
171 C_COMMENT
, /* in a comment like this one */
172 CXX_COMMENT
, /* between // and end of line */
173 STARTING_COMMENT
, /* just after slash-backslash-newline */
174 FINISHING_COMMENT
/* star-backslash-newline in a C comment */
177 static char const * const comment_name
[] = {
178 "NO", "C", "CXX", "STARTING", "FINISHING"
181 /* state of preprocessor line parser */
183 LS_START
, /* only space and comments on this line */
184 LS_HASH
, /* only space, comments, and a hash */
185 LS_DIRTY
/* this line can't be a preprocessor line */
188 static char const * const linestate_name
[] = {
189 "START", "HASH", "DIRTY"
193 * Minimum translation limits from ISO/IEC 9899:1999 5.2.4.1
195 #define MAXDEPTH 64 /* maximum #if nesting */
196 #define MAXLINE 4096 /* maximum length of line */
197 #define MAXSYMS 4096 /* maximum number of symbols */
200 * Sometimes when editing a keyword the replacement text is longer, so
201 * we leave some space at the end of the tline buffer to accommodate this.
209 static bool complement
; /* -c: do the complement */
210 static bool debugging
; /* -d: debugging reports */
211 static bool iocccok
; /* -e: fewer IOCCC errors */
212 static bool killconsts
; /* -k: eval constant #ifs */
213 static bool lnblank
; /* -l: blank deleted lines */
214 static bool symlist
; /* -s: output symbol list */
215 static bool text
; /* -t: this is a text file */
217 static const char *symname
[MAXSYMS
]; /* symbol name */
218 static const char *value
[MAXSYMS
]; /* -Dsym=value */
219 static bool ignore
[MAXSYMS
]; /* -iDsym or -iUsym */
220 static int nsyms
; /* number of symbols */
222 static FILE *input
; /* input file pointer */
223 static FILE *output
; /* output file pointer */
224 static const char *filename
; /* input file name */
225 static char *ofilename
; /* output file name */
226 static char tmpname
[MAXPATHLEN
]; /* used when overwriting */
227 static int linenum
; /* current line number */
228 static int overwriting
; /* output overwrites input */
230 static char tline
[MAXLINE
+EDITSLOP
];/* input buffer plus space */
231 static char *keyword
; /* used for editing #elif's */
233 static Comment_state incomment
; /* comment parser state */
234 static Line_state linestate
; /* #if line parser state */
235 static Ifstate ifstate
[MAXDEPTH
]; /* #if processor state */
236 static bool ignoring
[MAXDEPTH
]; /* ignore comments state */
237 static int stifline
[MAXDEPTH
]; /* start of current #if */
238 static int depth
; /* current #if nesting */
239 static bool keepthis
; /* don't delete constant #if */
241 static int exitstat
; /* program exit status */
243 static void addsym(bool, bool, char *);
244 static void debug(const char *, ...) __printflike(1, 2);
245 __dead
static void done(void);
246 __dead
static void error(const char *);
247 static int findsym(const char *);
248 static void flushline(bool);
249 static Linetype
get_line(void);
250 static Linetype
ifeval(const char **);
251 static void ignoreoff(void);
252 static void ignoreon(void);
253 static void keywordedit(const char *);
254 static void nest(void);
255 __dead
static void process(void);
256 static const char *skipcomment(const char *);
257 static const char *skipsym(const char *);
258 static void state(Ifstate
);
259 static int strlcmp(const char *, const char *, size_t);
260 __dead
static void usage(void);
262 #define endsym(c) (!isalpha((unsigned char)c) && !isdigit((unsigned char)c) && c != '_')
268 main(int argc
, char *argv
[])
271 struct stat isb
, osb
;
273 while ((opt
= getopt(argc
, argv
, "i:D:U:I:o:cdeklst")) != -1)
275 case 'i': /* treat stuff controlled by these symbols as text */
277 * For strict backwards-compatibility the U or D
278 * should be immediately after the -i but it doesn't
279 * matter much if we relax that requirement.
283 addsym(true, true, optarg
);
285 addsym(true, false, optarg
);
289 case 'D': /* define a symbol */
290 addsym(false, true, optarg
);
292 case 'U': /* undef a symbol */
293 addsym(false, false, optarg
);
296 /* no-op for compatibility with cpp */
298 case 'c': /* treat -D as -U and vice versa */
304 case 'e': /* fewer errors from dodgy lines */
307 case 'k': /* process constant #ifs */
310 case 'l': /* blank deleted lines instead of omitting them */
313 case 'o': /* output to a file */
316 case 's': /* only output list of symbols that control #ifs */
319 case 't': /* don't parse C comments */
327 if (nsyms
== 0 && !symlist
) {
328 warnx("must -D or -U at least one symbol");
332 errx(2, "can only do one file");
333 } else if (argc
== 1 && strcmp(*argv
, "-") != 0) {
335 input
= fopen(filename
, "r");
337 err(2, "can't open %s", filename
);
339 filename
= "[stdin]";
342 if (ofilename
== NULL
) {
345 if (stat(ofilename
, &osb
) == 0) {
346 if (fstat(fileno(input
), &isb
) != 0)
347 err(2, "can't fstat %s", filename
);
349 overwriting
= (osb
.st_dev
== isb
.st_dev
&&
350 osb
.st_ino
== isb
.st_ino
);
355 snprintf(tmpname
, sizeof(tmpname
), "%s/unifdef.XXXXXX",
357 if ((ofd
= mkstemp(tmpname
)) != -1)
358 output
= fdopen(ofd
, "w+");
360 err(2, "can't create temporary file");
361 fchmod(ofd
, isb
.st_mode
& ACCESSPERMS
);
363 output
= fopen(ofilename
, "w");
365 err(2, "can't open %s", ofilename
);
375 fprintf(stderr
, "usage: unifdef [-cdeklst] [-o output]"
376 " [-Dsym[=val]] [-Usym] [-iDsym[=val]] [-iUsym] ... [file]\n");
381 * A state transition function alters the global #if processing state
382 * in a particular way. The table below is indexed by the current
383 * processing state and the type of the current line.
385 * Nesting is handled by keeping a stack of states; some transition
386 * functions increase or decrease the depth. They also maintain the
387 * ignore state on a stack. In some complicated cases they have to
388 * alter the preprocessor directive, as follows.
390 * When we have processed a group that starts off with a known-false
391 * #if/#elif sequence (which has therefore been deleted) followed by a
392 * #elif that we don't understand and therefore must keep, we edit the
393 * latter into a #if to keep the nesting correct.
395 * When we find a true #elif in a group, the following block will
396 * always be kept and the rest of the sequence after the next #elif or
397 * #else will be discarded. We edit the #elif into a #else and the
398 * following directive to #endif since this has the desired behaviour.
400 * "Dodgy" directives are split across multiple lines, the most common
401 * example being a multi-line comment hanging off the right of the
402 * directive. We can handle them correctly only if there is no change
403 * from printing to dropping (or vice versa) caused by that directive.
404 * If the directive is the first of a group we have a choice between
405 * failing with an error, or passing it through unchanged instead of
406 * evaluating it. The latter is not the default to avoid questions from
407 * users about unifdef unexpectedly leaving behind preprocessor directives.
409 typedef void state_fn(void);
411 /* report an error */
412 __dead
static void Eelif (void) { error("Inappropriate #elif"); }
413 __dead
static void Eelse (void) { error("Inappropriate #else"); }
414 __dead
static void Eendif(void) { error("Inappropriate #endif"); }
415 __dead
static void Eeof (void) { error("Premature EOF"); }
416 __dead
static void Eioccc(void) { error("Obfuscated preprocessor control line"); }
417 /* plain line handling */
418 static void print (void) { flushline(true); }
419 static void drop (void) { flushline(false); }
420 /* output lacks group's start line */
421 static void Strue (void) { drop(); ignoreoff(); state(IS_TRUE_PREFIX
); }
422 static void Sfalse(void) { drop(); ignoreoff(); state(IS_FALSE_PREFIX
); }
423 static void Selse (void) { drop(); state(IS_TRUE_ELSE
); }
424 /* print/pass this block */
425 static void Pelif (void) { print(); ignoreoff(); state(IS_PASS_MIDDLE
); }
426 static void Pelse (void) { print(); state(IS_PASS_ELSE
); }
427 static void Pendif(void) { print(); --depth
; }
428 /* discard this block */
429 static void Dfalse(void) { drop(); ignoreoff(); state(IS_FALSE_TRAILER
); }
430 static void Delif (void) { drop(); ignoreoff(); state(IS_FALSE_MIDDLE
); }
431 static void Delse (void) { drop(); state(IS_FALSE_ELSE
); }
432 static void Dendif(void) { drop(); --depth
; }
433 /* first line of group */
434 static void Fdrop (void) { nest(); Dfalse(); }
435 static void Fpass (void) { nest(); Pelif(); }
436 static void Ftrue (void) { nest(); Strue(); }
437 static void Ffalse(void) { nest(); Sfalse(); }
438 /* variable pedantry for obfuscated lines */
439 static void Oiffy (void) { if (iocccok
) Fpass(); else Eioccc(); ignoreon(); }
440 static void Oif (void) { if (iocccok
) Fpass(); else Eioccc(); }
441 static void Oelif (void) { if (iocccok
) Pelif(); else Eioccc(); }
442 /* ignore comments in this block */
443 static void Idrop (void) { Fdrop(); ignoreon(); }
444 static void Itrue (void) { Ftrue(); ignoreon(); }
445 static void Ifalse(void) { Ffalse(); ignoreon(); }
447 static void Mpass (void) { strncpy(keyword
, "if ", 4); Pelif(); }
448 static void Mtrue (void) { keywordedit("else\n"); state(IS_TRUE_MIDDLE
); }
449 static void Melif (void) { keywordedit("endif\n"); state(IS_FALSE_TRAILER
); }
450 static void Melse (void) { keywordedit("endif\n"); state(IS_FALSE_ELSE
); }
452 static state_fn
* const trans_table
[IS_COUNT
][LT_COUNT
] = {
454 { Itrue
, Ifalse
,Fpass
, Ftrue
, Ffalse
,Eelif
, Eelif
, Eelif
, Eelse
, Eendif
,
455 Oiffy
, Oiffy
, Fpass
, Oif
, Oif
, Eelif
, Eelif
, Eelif
, Eelse
, Eendif
,
457 /* IS_FALSE_PREFIX */
458 { Idrop
, Idrop
, Fdrop
, Fdrop
, Fdrop
, Mpass
, Strue
, Sfalse
,Selse
, Dendif
,
459 Idrop
, Idrop
, Fdrop
, Fdrop
, Fdrop
, Mpass
, Eioccc
,Eioccc
,Eioccc
,Eioccc
,
462 { Itrue
, Ifalse
,Fpass
, Ftrue
, Ffalse
,Dfalse
,Dfalse
,Dfalse
,Delse
, Dendif
,
463 Oiffy
, Oiffy
, Fpass
, Oif
, Oif
, Eioccc
,Eioccc
,Eioccc
,Eioccc
,Eioccc
,
466 { Itrue
, Ifalse
,Fpass
, Ftrue
, Ffalse
,Pelif
, Mtrue
, Delif
, Pelse
, Pendif
,
467 Oiffy
, Oiffy
, Fpass
, Oif
, Oif
, Pelif
, Oelif
, Oelif
, Pelse
, Pendif
,
469 /* IS_FALSE_MIDDLE */
470 { Idrop
, Idrop
, Fdrop
, Fdrop
, Fdrop
, Pelif
, Mtrue
, Delif
, Pelse
, Pendif
,
471 Idrop
, Idrop
, Fdrop
, Fdrop
, Fdrop
, Eioccc
,Eioccc
,Eioccc
,Eioccc
,Eioccc
,
474 { Itrue
, Ifalse
,Fpass
, Ftrue
, Ffalse
,Melif
, Melif
, Melif
, Melse
, Pendif
,
475 Oiffy
, Oiffy
, Fpass
, Oif
, Oif
, Eioccc
,Eioccc
,Eioccc
,Eioccc
,Pendif
,
478 { Itrue
, Ifalse
,Fpass
, Ftrue
, Ffalse
,Eelif
, Eelif
, Eelif
, Eelse
, Pendif
,
479 Oiffy
, Oiffy
, Fpass
, Oif
, Oif
, Eelif
, Eelif
, Eelif
, Eelse
, Pendif
,
482 { Idrop
, Idrop
, Fdrop
, Fdrop
, Fdrop
, Eelif
, Eelif
, Eelif
, Eelse
, Dendif
,
483 Idrop
, Idrop
, Fdrop
, Fdrop
, Fdrop
, Eelif
, Eelif
, Eelif
, Eelse
, Eioccc
,
486 { Itrue
, Ifalse
,Fpass
, Ftrue
, Ffalse
,Eelif
, Eelif
, Eelif
, Eelse
, Dendif
,
487 Oiffy
, Oiffy
, Fpass
, Oif
, Oif
, Eelif
, Eelif
, Eelif
, Eelse
, Eioccc
,
489 /* IS_FALSE_TRAILER */
490 { Idrop
, Idrop
, Fdrop
, Fdrop
, Fdrop
, Dfalse
,Dfalse
,Dfalse
,Delse
, Dendif
,
491 Idrop
, Idrop
, Fdrop
, Fdrop
, Fdrop
, Dfalse
,Dfalse
,Dfalse
,Delse
, Eioccc
,
493 /*TRUEI FALSEI IF TRUE FALSE ELIF ELTRUE ELFALSE ELSE ENDIF
494 TRUEI FALSEI IF TRUE FALSE ELIF ELTRUE ELFALSE ELSE ENDIF (DODGY)
499 * State machine utility functions
505 error("EOF in comment");
506 if (fclose(output
)) {
509 errx(2, "%s unchanged", ofilename
);
512 if (overwriting
&& rename(tmpname
, ofilename
)) {
514 errx(2, "%s unchanged", ofilename
);
521 ignoring
[depth
] = ignoring
[depth
-1];
526 ignoring
[depth
] = true;
529 keywordedit(const char *replacement
)
531 strlcpy(keyword
, replacement
, tline
+ sizeof(tline
) - keyword
);
538 if (depth
>= MAXDEPTH
)
539 error("Too many levels of nesting");
540 stifline
[depth
] = linenum
;
549 * Write a line to the output or not, according to command line options.
556 if (keep
^ complement
)
557 fputs(tline
, output
);
566 * The driver for the state machine.
575 lineval
= get_line();
576 trans_table
[ifstate
[depth
]][lineval
]();
577 debug("process %s -> %s depth %d",
578 linetype_name
[lineval
],
579 ifstate_name
[ifstate
[depth
]], depth
);
584 * Parse a line and determine its type. We keep the preprocessor line
585 * parser state between calls in the global variable linestate, with
586 * help from skipcomment().
595 Comment_state wascomment
;
597 if (fgets(tline
, MAXLINE
, input
) == NULL
)
600 wascomment
= incomment
;
601 cp
= skipcomment(tline
);
602 if (linestate
== LS_START
) {
605 cp
= skipcomment(cp
+ 1);
606 } else if (*cp
!= '\0')
607 linestate
= LS_DIRTY
;
609 if (!incomment
&& linestate
== LS_HASH
) {
610 keyword
= tline
+ (cp
- tline
);
612 kwlen
= cp
- keyword
;
613 /* no way can we deal with a continuation inside a keyword */
614 if (strncmp(cp
, "\\\n", 2) == 0)
616 if (strlcmp("ifdef", keyword
, kwlen
) == 0 ||
617 strlcmp("ifndef", keyword
, kwlen
) == 0) {
618 cp
= skipcomment(cp
);
619 if ((cursym
= findsym(cp
)) < 0)
622 retval
= (keyword
[2] == 'n')
623 ? LT_FALSE
: LT_TRUE
;
624 if (value
[cursym
] == NULL
)
625 retval
= (retval
== LT_TRUE
)
626 ? LT_FALSE
: LT_TRUE
;
628 retval
= (retval
== LT_TRUE
)
629 ? LT_TRUEI
: LT_FALSEI
;
632 } else if (strlcmp("if", keyword
, kwlen
) == 0)
633 retval
= ifeval(&cp
);
634 else if (strlcmp("elif", keyword
, kwlen
) == 0)
635 retval
= ifeval(&cp
) - LT_IF
+ LT_ELIF
;
636 else if (strlcmp("else", keyword
, kwlen
) == 0)
638 else if (strlcmp("endif", keyword
, kwlen
) == 0)
641 linestate
= LS_DIRTY
;
644 cp
= skipcomment(cp
);
646 linestate
= LS_DIRTY
;
647 if (retval
== LT_TRUE
|| retval
== LT_FALSE
||
648 retval
== LT_TRUEI
|| retval
== LT_FALSEI
)
650 if (retval
== LT_ELTRUE
|| retval
== LT_ELFALSE
)
653 if (retval
!= LT_PLAIN
&& (wascomment
|| incomment
)) {
656 linestate
= LS_DIRTY
;
659 if (linestate
== LS_DIRTY
) {
661 cp
= skipcomment(cp
+ 1);
663 debug("parser %s comment %s line",
664 comment_name
[incomment
], linestate_name
[linestate
]);
669 * These are the binary operators that are supported by the expression
670 * evaluator. Note that if support for division is added then we also
671 * need short-circuiting booleans because of divide-by-zero.
673 static int op_lt(int a
, int b
) { return (a
< b
); }
674 static int op_gt(int a
, int b
) { return (a
> b
); }
675 static int op_le(int a
, int b
) { return (a
<= b
); }
676 static int op_ge(int a
, int b
) { return (a
>= b
); }
677 static int op_eq(int a
, int b
) { return (a
== b
); }
678 static int op_ne(int a
, int b
) { return (a
!= b
); }
679 static int op_or(int a
, int b
) { return (a
|| b
); }
680 static int op_and(int a
, int b
) { return (a
&& b
); }
683 * An evaluation function takes three arguments, as follows: (1) a pointer to
684 * an element of the precedence table which lists the operators at the current
685 * level of precedence; (2) a pointer to an integer which will receive the
686 * value of the expression; and (3) a pointer to a char* that points to the
687 * expression to be evaluated and that is updated to the end of the expression
688 * when evaluation is complete. The function returns LT_FALSE if the value of
689 * the expression is zero, LT_TRUE if it is non-zero, or LT_IF if the
690 * expression could not be evaluated.
694 typedef Linetype
eval_fn(const struct ops
*, int *, const char **);
696 static eval_fn eval_table
, eval_unary
;
699 * The precedence table. Expressions involving binary operators are evaluated
700 * in a table-driven way by eval_table. When it evaluates a subexpression it
701 * calls the inner function with its first argument pointing to the next
702 * element of the table. Innermost expressions have special non-table-driven
705 static const struct ops
{
712 { eval_table
, { { "||", op_or
} } },
713 { eval_table
, { { "&&", op_and
} } },
714 { eval_table
, { { "==", op_eq
},
716 { eval_unary
, { { "<=", op_le
},
723 * Function for evaluating the innermost parts of expressions,
724 * viz. !expr (expr) defined(symbol) symbol number
725 * We reset the keepthis flag when we find a non-constant subexpression.
728 eval_unary(const struct ops
*ops
, int *valp
, const char **cpp
)
734 cp
= skipcomment(*cpp
);
736 debug("eval%td !", ops
- eval_ops
);
738 if (eval_unary(ops
, valp
, &cp
) == LT_IF
)
741 } else if (*cp
== '(') {
743 debug("eval%td (", ops
- eval_ops
);
744 if (eval_table(eval_ops
, valp
, &cp
) == LT_IF
)
746 cp
= skipcomment(cp
);
749 } else if (isdigit((unsigned char)*cp
)) {
750 debug("eval%td number", ops
- eval_ops
);
751 *valp
= strtol(cp
, &ep
, 0);
753 } else if (strncmp(cp
, "defined", 7) == 0 && endsym(cp
[7])) {
754 cp
= skipcomment(cp
+7);
755 debug("eval%td defined", ops
- eval_ops
);
758 cp
= skipcomment(cp
);
760 if (sym
< 0 || symlist
)
762 *valp
= (value
[sym
] != NULL
);
764 cp
= skipcomment(cp
);
768 } else if (!endsym(*cp
)) {
769 debug("eval%td symbol", ops
- eval_ops
);
771 if (sym
< 0 || symlist
)
773 if (value
[sym
] == NULL
)
776 *valp
= strtol(value
[sym
], &ep
, 0);
777 if (*ep
!= '\0' || ep
== value
[sym
])
783 debug("eval%td bad expr", ops
- eval_ops
);
788 debug("eval%td = %d", ops
- eval_ops
, *valp
);
789 return (*valp
? LT_TRUE
: LT_FALSE
);
793 * Table-driven evaluation of binary operators.
796 eval_table(const struct ops
*ops
, int *valp
, const char **cpp
)
802 debug("eval%td", ops
- eval_ops
);
804 if (ops
->inner(ops
+1, valp
, &cp
) == LT_IF
)
807 cp
= skipcomment(cp
);
808 for (op
= ops
->op
; op
->str
!= NULL
; op
++)
809 if (strncmp(cp
, op
->str
, strlen(op
->str
)) == 0)
813 cp
+= strlen(op
->str
);
814 debug("eval%td %s", ops
- eval_ops
, op
->str
);
815 if (ops
->inner(ops
+1, &val
, &cp
) == LT_IF
)
817 *valp
= op
->fn(*valp
, val
);
821 debug("eval%td = %d", ops
- eval_ops
, *valp
);
822 return (*valp
? LT_TRUE
: LT_FALSE
);
826 * Evaluate the expression on a #if or #elif line. If we can work out
827 * the result we return LT_TRUE or LT_FALSE accordingly, otherwise we
828 * return just a generic LT_IF.
831 ifeval(const char **cpp
)
836 debug("eval %s", *cpp
);
837 keepthis
= killconsts
? false : true;
838 ret
= eval_table(eval_ops
, &val
, cpp
);
839 debug("eval = %d", val
);
840 return (keepthis
? LT_IF
: ret
);
844 * Skip over comments and stop at the next character position that is
845 * not whitespace. Between calls we keep the comment state in the
846 * global variable incomment, and we also adjust the global variable
847 * linestate when we see a newline.
848 * XXX: doesn't cope with the buffer splitting inside a state transition.
851 skipcomment(const char *cp
)
853 if (text
|| ignoring
[depth
]) {
854 for (; isspace((unsigned char)*cp
); cp
++)
856 linestate
= LS_START
;
860 /* don't reset to LS_START after a line continuation */
861 if (strncmp(cp
, "\\\n", 2) == 0)
863 else switch (incomment
) {
865 if (strncmp(cp
, "/\\\n", 3) == 0) {
866 incomment
= STARTING_COMMENT
;
868 } else if (strncmp(cp
, "/*", 2) == 0) {
869 incomment
= C_COMMENT
;
871 } else if (strncmp(cp
, "//", 2) == 0) {
872 incomment
= CXX_COMMENT
;
874 } else if (strncmp(cp
, "\n", 1) == 0) {
875 linestate
= LS_START
;
877 } else if (strchr(" \t", *cp
) != NULL
) {
883 if (strncmp(cp
, "\n", 1) == 0) {
884 incomment
= NO_COMMENT
;
885 linestate
= LS_START
;
890 if (strncmp(cp
, "*\\\n", 3) == 0) {
891 incomment
= FINISHING_COMMENT
;
893 } else if (strncmp(cp
, "*/", 2) == 0) {
894 incomment
= NO_COMMENT
;
899 case STARTING_COMMENT
:
901 incomment
= C_COMMENT
;
903 } else if (*cp
== '/') {
904 incomment
= CXX_COMMENT
;
907 incomment
= NO_COMMENT
;
908 linestate
= LS_DIRTY
;
911 case FINISHING_COMMENT
:
913 incomment
= NO_COMMENT
;
916 incomment
= C_COMMENT
;
925 * Skip over an identifier.
928 skipsym(const char *cp
)
936 * Look for the symbol in the symbol table. If it is found, we return
937 * the symbol table index, else we return -1.
940 findsym(const char *str
)
949 printf("%.*s\n", (int)(cp
-str
), str
);
950 for (symind
= 0; symind
< nsyms
; ++symind
) {
951 if (strlcmp(symname
[symind
], str
, cp
-str
) == 0) {
952 debug("findsym %s %s", symname
[symind
],
953 value
[symind
] ? value
[symind
] : "");
961 * Add a symbol to the symbol table.
964 addsym(bool ignorethis
, bool definethis
, char *sym
)
969 symind
= findsym(sym
);
971 if (nsyms
>= MAXSYMS
)
972 errx(2, "too many symbols");
975 symname
[symind
] = sym
;
976 ignore
[symind
] = ignorethis
;
977 val
= sym
+ (skipsym(sym
) - sym
);
980 value
[symind
] = val
+1;
982 } else if (*val
== '\0')
989 value
[symind
] = NULL
;
994 * Compare s with n characters of t.
995 * The same as strncmp() except that it checks that s[n] == '\0'.
998 strlcmp(const char *s
, const char *t
, size_t n
)
1000 while (n
-- && *t
!= '\0')
1002 return ((unsigned char)*s
- (unsigned char)*t
);
1005 return ((unsigned char)*s
);
1012 debug(const char *msg
, ...)
1024 error(const char *msg
)
1027 warnx("%s: %d: %s", filename
, linenum
, msg
);
1029 warnx("%s: %d: %s (#if line %d depth %d)",
1030 filename
, linenum
, msg
, stifline
[depth
], depth
);
1034 errx(2, "%s unchanged", ofilename
);
1036 errx(2, "output may be truncated");