1*83d4b28aSchristos /* $NetBSD: unifdef.c,v 1.22 2012/10/13 18:26:03 christos Exp $ */
289aaa1bbSagc
389aaa1bbSagc /*
489aaa1bbSagc * Copyright (c) 1985, 1993
589aaa1bbSagc * The Regents of the University of California. All rights reserved.
689aaa1bbSagc *
789aaa1bbSagc * This code is derived from software contributed to Berkeley by
889aaa1bbSagc * Dave Yost. It was rewritten to support ANSI C by Tony Finch.
989aaa1bbSagc *
1089aaa1bbSagc * Redistribution and use in source and binary forms, with or without
1189aaa1bbSagc * modification, are permitted provided that the following conditions
1289aaa1bbSagc * are met:
1389aaa1bbSagc * 1. Redistributions of source code must retain the above copyright
1489aaa1bbSagc * notice, this list of conditions and the following disclaimer.
1589aaa1bbSagc * 2. Redistributions in binary form must reproduce the above copyright
1689aaa1bbSagc * notice, this list of conditions and the following disclaimer in the
1789aaa1bbSagc * documentation and/or other materials provided with the distribution.
1889aaa1bbSagc * 3. Neither the name of the University nor the names of its contributors
1989aaa1bbSagc * may be used to endorse or promote products derived from this software
2089aaa1bbSagc * without specific prior written permission.
2189aaa1bbSagc *
2289aaa1bbSagc * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
2389aaa1bbSagc * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
2489aaa1bbSagc * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
2589aaa1bbSagc * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
2689aaa1bbSagc * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
2789aaa1bbSagc * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
2889aaa1bbSagc * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
2989aaa1bbSagc * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
3089aaa1bbSagc * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
3189aaa1bbSagc * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3289aaa1bbSagc * SUCH DAMAGE.
3389aaa1bbSagc */
3455ad6872Sjtc
3561f28255Scgd /*
367c02fa33Sitojun * Copyright (c) 2002, 2003 Tony Finch <dot@dotat.at>
3761f28255Scgd *
3861f28255Scgd * This code is derived from software contributed to Berkeley by
397c02fa33Sitojun * Dave Yost. It was rewritten to support ANSI C by Tony Finch.
4061f28255Scgd *
4161f28255Scgd * Redistribution and use in source and binary forms, with or without
4261f28255Scgd * modification, are permitted provided that the following conditions
4361f28255Scgd * are met:
4461f28255Scgd * 1. Redistributions of source code must retain the above copyright
4561f28255Scgd * notice, this list of conditions and the following disclaimer.
4661f28255Scgd * 2. Redistributions in binary form must reproduce the above copyright
4761f28255Scgd * notice, this list of conditions and the following disclaimer in the
4861f28255Scgd * documentation and/or other materials provided with the distribution.
4961f28255Scgd * 3. All advertising materials mentioning features or use of this software
5061f28255Scgd * must display the following acknowledgement:
5161f28255Scgd * This product includes software developed by the University of
5261f28255Scgd * California, Berkeley and its contributors.
5361f28255Scgd * 4. Neither the name of the University nor the names of its contributors
5461f28255Scgd * may be used to endorse or promote products derived from this software
5561f28255Scgd * without specific prior written permission.
5661f28255Scgd *
5761f28255Scgd * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
5861f28255Scgd * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
5961f28255Scgd * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
6061f28255Scgd * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
6161f28255Scgd * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
6261f28255Scgd * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
6361f28255Scgd * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
6461f28255Scgd * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
6561f28255Scgd * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
6661f28255Scgd * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
6761f28255Scgd * SUCH DAMAGE.
6861f28255Scgd */
6961f28255Scgd
708d321745Slukem #include <sys/cdefs.h>
7161f28255Scgd
7261f28255Scgd #ifndef lint
7355ad6872Sjtc #if 0
747c02fa33Sitojun static const char copyright[] =
757c02fa33Sitojun "@(#) Copyright (c) 1985, 1993\n\
767c02fa33Sitojun The Regents of the University of California. All rights reserved.\n";
7755ad6872Sjtc #endif
787c02fa33Sitojun #ifdef __IDSTRING
797c02fa33Sitojun __IDSTRING(Berkeley, "@(#)unifdef.c 8.1 (Berkeley) 6/6/93");
80*83d4b28aSchristos __IDSTRING(NetBSD, "$NetBSD: unifdef.c,v 1.22 2012/10/13 18:26:03 christos Exp $");
817c02fa33Sitojun __IDSTRING(dotat, "$dotat: things/unifdef.c,v 1.161 2003/07/01 15:32:48 fanf2 Exp $");
827c02fa33Sitojun #endif
8361f28255Scgd #endif /* not lint */
847c02fa33Sitojun #ifdef __FBSDID
857c02fa33Sitojun __FBSDID("$FreeBSD: src/usr.bin/unifdef/unifdef.c,v 1.18 2003/07/01 15:30:43 fanf Exp $");
867c02fa33Sitojun #endif
8761f28255Scgd
8861f28255Scgd /*
8961f28255Scgd * unifdef - remove ifdef'ed lines
9061f28255Scgd *
9161f28255Scgd * Wishlist:
9261f28255Scgd * provide an option which will append the name of the
9361f28255Scgd * appropriate symbol after #else's and #endif's
9461f28255Scgd * provide an option which will check symbols after
9561f28255Scgd * #else's and #endif's to see that they match their
9661f28255Scgd * corresponding #ifdef or #ifndef
977c02fa33Sitojun * generate #line directives in place of deleted code
987c02fa33Sitojun *
997c02fa33Sitojun * The first two items above require better buffer handling, which would
1007c02fa33Sitojun * also make it possible to handle all "dodgy" directives correctly.
10161f28255Scgd */
10261f28255Scgd
1037c02fa33Sitojun #include <ctype.h>
1047c02fa33Sitojun #include <err.h>
1052248a776Sginsbach #include <libgen.h>
1067c02fa33Sitojun #include <stdarg.h>
10761f28255Scgd #include <stdio.h>
108fcd0fb11Smatt #include <stdlib.h>
109fcd0fb11Smatt #include <string.h>
1107c02fa33Sitojun #include <unistd.h>
11161f28255Scgd
1122248a776Sginsbach #include <sys/param.h>
1132248a776Sginsbach #include <sys/stat.h>
1142248a776Sginsbach
1157c02fa33Sitojun #include "stdbool.h"
11661f28255Scgd
1177c02fa33Sitojun /* types of input lines: */
1187c02fa33Sitojun typedef enum {
1197c02fa33Sitojun LT_TRUEI, /* a true #if with ignore flag */
1207c02fa33Sitojun LT_FALSEI, /* a false #if with ignore flag */
1217c02fa33Sitojun LT_IF, /* an unknown #if */
1227c02fa33Sitojun LT_TRUE, /* a true #if */
1237c02fa33Sitojun LT_FALSE, /* a false #if */
1247c02fa33Sitojun LT_ELIF, /* an unknown #elif */
1257c02fa33Sitojun LT_ELTRUE, /* a true #elif */
1267c02fa33Sitojun LT_ELFALSE, /* a false #elif */
1277c02fa33Sitojun LT_ELSE, /* #else */
1287c02fa33Sitojun LT_ENDIF, /* #endif */
1297c02fa33Sitojun LT_DODGY, /* flag: directive is not on one line */
1307c02fa33Sitojun LT_DODGY_LAST = LT_DODGY + LT_ENDIF,
1317c02fa33Sitojun LT_PLAIN, /* ordinary line */
1327c02fa33Sitojun LT_EOF, /* end of file */
1337c02fa33Sitojun LT_COUNT
1347c02fa33Sitojun } Linetype;
13561f28255Scgd
1367c02fa33Sitojun static char const * const linetype_name[] = {
1377c02fa33Sitojun "TRUEI", "FALSEI", "IF", "TRUE", "FALSE",
1387c02fa33Sitojun "ELIF", "ELTRUE", "ELFALSE", "ELSE", "ENDIF",
1397c02fa33Sitojun "DODGY TRUEI", "DODGY FALSEI",
1407c02fa33Sitojun "DODGY IF", "DODGY TRUE", "DODGY FALSE",
1417c02fa33Sitojun "DODGY ELIF", "DODGY ELTRUE", "DODGY ELFALSE",
1427c02fa33Sitojun "DODGY ELSE", "DODGY ENDIF",
1437c02fa33Sitojun "PLAIN", "EOF"
1447c02fa33Sitojun };
14561f28255Scgd
1467c02fa33Sitojun /* state of #if processing */
1477c02fa33Sitojun typedef enum {
1487c02fa33Sitojun IS_OUTSIDE,
1497c02fa33Sitojun IS_FALSE_PREFIX, /* false #if followed by false #elifs */
1507c02fa33Sitojun IS_TRUE_PREFIX, /* first non-false #(el)if is true */
1517c02fa33Sitojun IS_PASS_MIDDLE, /* first non-false #(el)if is unknown */
1527c02fa33Sitojun IS_FALSE_MIDDLE, /* a false #elif after a pass state */
1537c02fa33Sitojun IS_TRUE_MIDDLE, /* a true #elif after a pass state */
1547c02fa33Sitojun IS_PASS_ELSE, /* an else after a pass state */
1557c02fa33Sitojun IS_FALSE_ELSE, /* an else after a true state */
1567c02fa33Sitojun IS_TRUE_ELSE, /* an else after only false states */
1577c02fa33Sitojun IS_FALSE_TRAILER, /* #elifs after a true are false */
1587c02fa33Sitojun IS_COUNT
1597c02fa33Sitojun } Ifstate;
16061f28255Scgd
1617c02fa33Sitojun static char const * const ifstate_name[] = {
1627c02fa33Sitojun "OUTSIDE", "FALSE_PREFIX", "TRUE_PREFIX",
1637c02fa33Sitojun "PASS_MIDDLE", "FALSE_MIDDLE", "TRUE_MIDDLE",
1647c02fa33Sitojun "PASS_ELSE", "FALSE_ELSE", "TRUE_ELSE",
1657c02fa33Sitojun "FALSE_TRAILER"
1667c02fa33Sitojun };
16761f28255Scgd
1687c02fa33Sitojun /* state of comment parser */
1697c02fa33Sitojun typedef enum {
1707c02fa33Sitojun NO_COMMENT = false, /* outside a comment */
1717c02fa33Sitojun C_COMMENT, /* in a comment like this one */
1727c02fa33Sitojun CXX_COMMENT, /* between // and end of line */
1737c02fa33Sitojun STARTING_COMMENT, /* just after slash-backslash-newline */
1747c02fa33Sitojun FINISHING_COMMENT /* star-backslash-newline in a C comment */
1757c02fa33Sitojun } Comment_state;
1765de96604Sjtc
1777c02fa33Sitojun static char const * const comment_name[] = {
1787c02fa33Sitojun "NO", "C", "CXX", "STARTING", "FINISHING"
1797c02fa33Sitojun };
1807c02fa33Sitojun
1817c02fa33Sitojun /* state of preprocessor line parser */
1827c02fa33Sitojun typedef enum {
1837c02fa33Sitojun LS_START, /* only space and comments on this line */
1847c02fa33Sitojun LS_HASH, /* only space, comments, and a hash */
1857c02fa33Sitojun LS_DIRTY /* this line can't be a preprocessor line */
1867c02fa33Sitojun } Line_state;
1877c02fa33Sitojun
1887c02fa33Sitojun static char const * const linestate_name[] = {
1897c02fa33Sitojun "START", "HASH", "DIRTY"
1907c02fa33Sitojun };
1917c02fa33Sitojun
1927c02fa33Sitojun /*
1937c02fa33Sitojun * Minimum translation limits from ISO/IEC 9899:1999 5.2.4.1
1947c02fa33Sitojun */
1957c02fa33Sitojun #define MAXDEPTH 64 /* maximum #if nesting */
1967c02fa33Sitojun #define MAXLINE 4096 /* maximum length of line */
1977c02fa33Sitojun #define MAXSYMS 4096 /* maximum number of symbols */
1987c02fa33Sitojun
1997c02fa33Sitojun /*
2007c02fa33Sitojun * Sometimes when editing a keyword the replacement text is longer, so
2017c02fa33Sitojun * we leave some space at the end of the tline buffer to accommodate this.
2027c02fa33Sitojun */
2037c02fa33Sitojun #define EDITSLOP 10
2047c02fa33Sitojun
2057c02fa33Sitojun /*
2067c02fa33Sitojun * Globals.
2077c02fa33Sitojun */
2087c02fa33Sitojun
2097c02fa33Sitojun static bool complement; /* -c: do the complement */
2107c02fa33Sitojun static bool debugging; /* -d: debugging reports */
2117c02fa33Sitojun static bool iocccok; /* -e: fewer IOCCC errors */
2127c02fa33Sitojun static bool killconsts; /* -k: eval constant #ifs */
2137c02fa33Sitojun static bool lnblank; /* -l: blank deleted lines */
2147c02fa33Sitojun static bool symlist; /* -s: output symbol list */
2157c02fa33Sitojun static bool text; /* -t: this is a text file */
2167c02fa33Sitojun
2177c02fa33Sitojun static const char *symname[MAXSYMS]; /* symbol name */
2187c02fa33Sitojun static const char *value[MAXSYMS]; /* -Dsym=value */
2197c02fa33Sitojun static bool ignore[MAXSYMS]; /* -iDsym or -iUsym */
2207c02fa33Sitojun static int nsyms; /* number of symbols */
2217c02fa33Sitojun
2227c02fa33Sitojun static FILE *input; /* input file pointer */
2232248a776Sginsbach static FILE *output; /* output file pointer */
2247c02fa33Sitojun static const char *filename; /* input file name */
2252248a776Sginsbach static char *ofilename; /* output file name */
2262248a776Sginsbach static char tmpname[MAXPATHLEN]; /* used when overwriting */
2277c02fa33Sitojun static int linenum; /* current line number */
2282248a776Sginsbach static int overwriting; /* output overwrites input */
2297c02fa33Sitojun
2307c02fa33Sitojun static char tline[MAXLINE+EDITSLOP];/* input buffer plus space */
2317c02fa33Sitojun static char *keyword; /* used for editing #elif's */
2327c02fa33Sitojun
2337c02fa33Sitojun static Comment_state incomment; /* comment parser state */
2347c02fa33Sitojun static Line_state linestate; /* #if line parser state */
2357c02fa33Sitojun static Ifstate ifstate[MAXDEPTH]; /* #if processor state */
2367c02fa33Sitojun static bool ignoring[MAXDEPTH]; /* ignore comments state */
2377c02fa33Sitojun static int stifline[MAXDEPTH]; /* start of current #if */
2387c02fa33Sitojun static int depth; /* current #if nesting */
2397c02fa33Sitojun static bool keepthis; /* don't delete constant #if */
2407c02fa33Sitojun
2417c02fa33Sitojun static int exitstat; /* program exit status */
2427c02fa33Sitojun
2437c02fa33Sitojun static void addsym(bool, bool, char *);
244eb085c76Sjoerg static void debug(const char *, ...) __printflike(1, 2);
2456818646aSjoerg __dead static void done(void);
2466818646aSjoerg __dead static void error(const char *);
2477c02fa33Sitojun static int findsym(const char *);
2487c02fa33Sitojun static void flushline(bool);
2497027866aSroy static Linetype get_line(void);
2507c02fa33Sitojun static Linetype ifeval(const char **);
2517c02fa33Sitojun static void ignoreoff(void);
2527c02fa33Sitojun static void ignoreon(void);
2537c02fa33Sitojun static void keywordedit(const char *);
2547c02fa33Sitojun static void nest(void);
2556818646aSjoerg __dead static void process(void);
2567c02fa33Sitojun static const char *skipcomment(const char *);
2577c02fa33Sitojun static const char *skipsym(const char *);
2587c02fa33Sitojun static void state(Ifstate);
2597c02fa33Sitojun static int strlcmp(const char *, const char *, size_t);
2606818646aSjoerg __dead static void usage(void);
2617c02fa33Sitojun
2627c02fa33Sitojun #define endsym(c) (!isalpha((unsigned char)c) && !isdigit((unsigned char)c) && c != '_')
2637c02fa33Sitojun
2647c02fa33Sitojun /*
2657c02fa33Sitojun * The main program.
2667c02fa33Sitojun */
2675de96604Sjtc int
main(int argc,char * argv[])2687c02fa33Sitojun main(int argc, char *argv[])
26961f28255Scgd {
2707c02fa33Sitojun int opt;
2712248a776Sginsbach struct stat isb, osb;
27261f28255Scgd
2732248a776Sginsbach while ((opt = getopt(argc, argv, "i:D:U:I:o:cdeklst")) != -1)
2747c02fa33Sitojun switch (opt) {
2757c02fa33Sitojun case 'i': /* treat stuff controlled by these symbols as text */
2767c02fa33Sitojun /*
2777c02fa33Sitojun * For strict backwards-compatibility the U or D
2787c02fa33Sitojun * should be immediately after the -i but it doesn't
2797c02fa33Sitojun * matter much if we relax that requirement.
2807c02fa33Sitojun */
2817c02fa33Sitojun opt = *optarg++;
2827c02fa33Sitojun if (opt == 'D')
2837c02fa33Sitojun addsym(true, true, optarg);
2847c02fa33Sitojun else if (opt == 'U')
2857c02fa33Sitojun addsym(true, false, optarg);
2867c02fa33Sitojun else
2877c02fa33Sitojun usage();
28861f28255Scgd break;
2897c02fa33Sitojun case 'D': /* define a symbol */
2907c02fa33Sitojun addsym(false, true, optarg);
2917c02fa33Sitojun break;
2927c02fa33Sitojun case 'U': /* undef a symbol */
2937c02fa33Sitojun addsym(false, false, optarg);
2947c02fa33Sitojun break;
2957c02fa33Sitojun case 'I':
2967c02fa33Sitojun /* no-op for compatibility with cpp */
2977c02fa33Sitojun break;
2987c02fa33Sitojun case 'c': /* treat -D as -U and vice versa */
2997c02fa33Sitojun complement = true;
3007c02fa33Sitojun break;
3017c02fa33Sitojun case 'd':
3027c02fa33Sitojun debugging = true;
3037c02fa33Sitojun break;
3047c02fa33Sitojun case 'e': /* fewer errors from dodgy lines */
3057c02fa33Sitojun iocccok = true;
3067c02fa33Sitojun break;
3077c02fa33Sitojun case 'k': /* process constant #ifs */
3087c02fa33Sitojun killconsts = true;
3097c02fa33Sitojun break;
3107c02fa33Sitojun case 'l': /* blank deleted lines instead of omitting them */
3117c02fa33Sitojun lnblank = true;
3127c02fa33Sitojun break;
3132248a776Sginsbach case 'o': /* output to a file */
3142248a776Sginsbach ofilename = optarg;
3152248a776Sginsbach break;
3167c02fa33Sitojun case 's': /* only output list of symbols that control #ifs */
3177c02fa33Sitojun symlist = true;
3187c02fa33Sitojun break;
3197c02fa33Sitojun case 't': /* don't parse C comments */
3207c02fa33Sitojun text = true;
3217c02fa33Sitojun break;
3227c02fa33Sitojun default:
3237c02fa33Sitojun usage();
32461f28255Scgd }
3257c02fa33Sitojun argc -= optind;
3267c02fa33Sitojun argv += optind;
3277c02fa33Sitojun if (nsyms == 0 && !symlist) {
3287c02fa33Sitojun warnx("must -D or -U at least one symbol");
3297c02fa33Sitojun usage();
33061f28255Scgd }
33161f28255Scgd if (argc > 1) {
3327c02fa33Sitojun errx(2, "can only do one file");
3337c02fa33Sitojun } else if (argc == 1 && strcmp(*argv, "-") != 0) {
3347c02fa33Sitojun filename = *argv;
3357c02fa33Sitojun input = fopen(filename, "r");
3367c02fa33Sitojun if (input == NULL)
3377c02fa33Sitojun err(2, "can't open %s", filename);
33861f28255Scgd } else {
33961f28255Scgd filename = "[stdin]";
34061f28255Scgd input = stdin;
3417c02fa33Sitojun }
3422248a776Sginsbach if (ofilename == NULL) {
3432248a776Sginsbach output = stdout;
3442248a776Sginsbach } else {
3452dfe7f31Sginsbach if (stat(ofilename, &osb) == 0) {
3462248a776Sginsbach if (fstat(fileno(input), &isb) != 0)
3472248a776Sginsbach err(2, "can't fstat %s", filename);
3482248a776Sginsbach
3492248a776Sginsbach overwriting = (osb.st_dev == isb.st_dev &&
350b4abf20dSginsbach osb.st_ino == isb.st_ino);
3512dfe7f31Sginsbach }
3522248a776Sginsbach if (overwriting) {
3532248a776Sginsbach int ofd;
3542248a776Sginsbach
3552248a776Sginsbach snprintf(tmpname, sizeof(tmpname), "%s/unifdef.XXXXXX",
3562248a776Sginsbach dirname(ofilename));
3572248a776Sginsbach if ((ofd = mkstemp(tmpname)) != -1)
3582248a776Sginsbach output = fdopen(ofd, "w+");
3592248a776Sginsbach if (output == NULL)
3602248a776Sginsbach err(2, "can't create temporary file");
3612248a776Sginsbach fchmod(ofd, isb.st_mode & ACCESSPERMS);
3622248a776Sginsbach } else {
3632248a776Sginsbach output = fopen(ofilename, "w");
3642248a776Sginsbach if (output == NULL)
3652248a776Sginsbach err(2, "can't open %s", ofilename);
3662248a776Sginsbach }
3672248a776Sginsbach }
3687c02fa33Sitojun process();
3697c02fa33Sitojun abort(); /* bug */
37061f28255Scgd }
37161f28255Scgd
3727c02fa33Sitojun static void
usage(void)3737c02fa33Sitojun usage(void)
3747c02fa33Sitojun {
3752248a776Sginsbach fprintf(stderr, "usage: unifdef [-cdeklst] [-o output]"
3767c02fa33Sitojun " [-Dsym[=val]] [-Usym] [-iDsym[=val]] [-iUsym] ... [file]\n");
3777c02fa33Sitojun exit(2);
3787c02fa33Sitojun }
3797c02fa33Sitojun
3807c02fa33Sitojun /*
3817c02fa33Sitojun * A state transition function alters the global #if processing state
3827c02fa33Sitojun * in a particular way. The table below is indexed by the current
3837c02fa33Sitojun * processing state and the type of the current line.
3847c02fa33Sitojun *
3857c02fa33Sitojun * Nesting is handled by keeping a stack of states; some transition
3867c02fa33Sitojun * functions increase or decrease the depth. They also maintain the
3877c02fa33Sitojun * ignore state on a stack. In some complicated cases they have to
3887c02fa33Sitojun * alter the preprocessor directive, as follows.
3897c02fa33Sitojun *
3907c02fa33Sitojun * When we have processed a group that starts off with a known-false
3917c02fa33Sitojun * #if/#elif sequence (which has therefore been deleted) followed by a
3927c02fa33Sitojun * #elif that we don't understand and therefore must keep, we edit the
3937c02fa33Sitojun * latter into a #if to keep the nesting correct.
3947c02fa33Sitojun *
3957c02fa33Sitojun * When we find a true #elif in a group, the following block will
3967c02fa33Sitojun * always be kept and the rest of the sequence after the next #elif or
3977c02fa33Sitojun * #else will be discarded. We edit the #elif into a #else and the
3987c02fa33Sitojun * following directive to #endif since this has the desired behaviour.
3997c02fa33Sitojun *
4007c02fa33Sitojun * "Dodgy" directives are split across multiple lines, the most common
4017c02fa33Sitojun * example being a multi-line comment hanging off the right of the
4027c02fa33Sitojun * directive. We can handle them correctly only if there is no change
4037c02fa33Sitojun * from printing to dropping (or vice versa) caused by that directive.
4047c02fa33Sitojun * If the directive is the first of a group we have a choice between
4057c02fa33Sitojun * failing with an error, or passing it through unchanged instead of
4067c02fa33Sitojun * evaluating it. The latter is not the default to avoid questions from
4077c02fa33Sitojun * users about unifdef unexpectedly leaving behind preprocessor directives.
4087c02fa33Sitojun */
4097c02fa33Sitojun typedef void state_fn(void);
4107c02fa33Sitojun
4117c02fa33Sitojun /* report an error */
Eelif(void)4126818646aSjoerg __dead static void Eelif (void) { error("Inappropriate #elif"); }
Eelse(void)4136818646aSjoerg __dead static void Eelse (void) { error("Inappropriate #else"); }
Eendif(void)4146818646aSjoerg __dead static void Eendif(void) { error("Inappropriate #endif"); }
Eeof(void)4156818646aSjoerg __dead static void Eeof (void) { error("Premature EOF"); }
Eioccc(void)4166818646aSjoerg __dead static void Eioccc(void) { error("Obfuscated preprocessor control line"); }
4177c02fa33Sitojun /* plain line handling */
print(void)4187c02fa33Sitojun static void print (void) { flushline(true); }
drop(void)4197c02fa33Sitojun static void drop (void) { flushline(false); }
4207c02fa33Sitojun /* output lacks group's start line */
Strue(void)4217c02fa33Sitojun static void Strue (void) { drop(); ignoreoff(); state(IS_TRUE_PREFIX); }
Sfalse(void)4227c02fa33Sitojun static void Sfalse(void) { drop(); ignoreoff(); state(IS_FALSE_PREFIX); }
Selse(void)4237c02fa33Sitojun static void Selse (void) { drop(); state(IS_TRUE_ELSE); }
4247c02fa33Sitojun /* print/pass this block */
Pelif(void)4257c02fa33Sitojun static void Pelif (void) { print(); ignoreoff(); state(IS_PASS_MIDDLE); }
Pelse(void)4267c02fa33Sitojun static void Pelse (void) { print(); state(IS_PASS_ELSE); }
Pendif(void)4277c02fa33Sitojun static void Pendif(void) { print(); --depth; }
4287c02fa33Sitojun /* discard this block */
Dfalse(void)4297c02fa33Sitojun static void Dfalse(void) { drop(); ignoreoff(); state(IS_FALSE_TRAILER); }
Delif(void)4307c02fa33Sitojun static void Delif (void) { drop(); ignoreoff(); state(IS_FALSE_MIDDLE); }
Delse(void)4317c02fa33Sitojun static void Delse (void) { drop(); state(IS_FALSE_ELSE); }
Dendif(void)4327c02fa33Sitojun static void Dendif(void) { drop(); --depth; }
4337c02fa33Sitojun /* first line of group */
Fdrop(void)4347c02fa33Sitojun static void Fdrop (void) { nest(); Dfalse(); }
Fpass(void)4357c02fa33Sitojun static void Fpass (void) { nest(); Pelif(); }
Ftrue(void)4367c02fa33Sitojun static void Ftrue (void) { nest(); Strue(); }
Ffalse(void)4377c02fa33Sitojun static void Ffalse(void) { nest(); Sfalse(); }
4387c02fa33Sitojun /* variable pedantry for obfuscated lines */
Oiffy(void)4397c02fa33Sitojun static void Oiffy (void) { if (iocccok) Fpass(); else Eioccc(); ignoreon(); }
Oif(void)4407c02fa33Sitojun static void Oif (void) { if (iocccok) Fpass(); else Eioccc(); }
Oelif(void)4417c02fa33Sitojun static void Oelif (void) { if (iocccok) Pelif(); else Eioccc(); }
4427c02fa33Sitojun /* ignore comments in this block */
Idrop(void)4437c02fa33Sitojun static void Idrop (void) { Fdrop(); ignoreon(); }
Itrue(void)4447c02fa33Sitojun static void Itrue (void) { Ftrue(); ignoreon(); }
Ifalse(void)4457c02fa33Sitojun static void Ifalse(void) { Ffalse(); ignoreon(); }
4467c02fa33Sitojun /* edit this line */
Mpass(void)4477c02fa33Sitojun static void Mpass (void) { strncpy(keyword, "if ", 4); Pelif(); }
Mtrue(void)4487c02fa33Sitojun static void Mtrue (void) { keywordedit("else\n"); state(IS_TRUE_MIDDLE); }
Melif(void)4497c02fa33Sitojun static void Melif (void) { keywordedit("endif\n"); state(IS_FALSE_TRAILER); }
Melse(void)4507c02fa33Sitojun static void Melse (void) { keywordedit("endif\n"); state(IS_FALSE_ELSE); }
4517c02fa33Sitojun
4527c02fa33Sitojun static state_fn * const trans_table[IS_COUNT][LT_COUNT] = {
4537c02fa33Sitojun /* IS_OUTSIDE */
4547c02fa33Sitojun { Itrue, Ifalse,Fpass, Ftrue, Ffalse,Eelif, Eelif, Eelif, Eelse, Eendif,
4557c02fa33Sitojun Oiffy, Oiffy, Fpass, Oif, Oif, Eelif, Eelif, Eelif, Eelse, Eendif,
4567c02fa33Sitojun print, done },
4577c02fa33Sitojun /* IS_FALSE_PREFIX */
4587c02fa33Sitojun { Idrop, Idrop, Fdrop, Fdrop, Fdrop, Mpass, Strue, Sfalse,Selse, Dendif,
4597c02fa33Sitojun Idrop, Idrop, Fdrop, Fdrop, Fdrop, Mpass, Eioccc,Eioccc,Eioccc,Eioccc,
4607c02fa33Sitojun drop, Eeof },
4617c02fa33Sitojun /* IS_TRUE_PREFIX */
4627c02fa33Sitojun { Itrue, Ifalse,Fpass, Ftrue, Ffalse,Dfalse,Dfalse,Dfalse,Delse, Dendif,
4637c02fa33Sitojun Oiffy, Oiffy, Fpass, Oif, Oif, Eioccc,Eioccc,Eioccc,Eioccc,Eioccc,
4647c02fa33Sitojun print, Eeof },
4657c02fa33Sitojun /* IS_PASS_MIDDLE */
4667c02fa33Sitojun { Itrue, Ifalse,Fpass, Ftrue, Ffalse,Pelif, Mtrue, Delif, Pelse, Pendif,
4677c02fa33Sitojun Oiffy, Oiffy, Fpass, Oif, Oif, Pelif, Oelif, Oelif, Pelse, Pendif,
4687c02fa33Sitojun print, Eeof },
4697c02fa33Sitojun /* IS_FALSE_MIDDLE */
4707c02fa33Sitojun { Idrop, Idrop, Fdrop, Fdrop, Fdrop, Pelif, Mtrue, Delif, Pelse, Pendif,
4717c02fa33Sitojun Idrop, Idrop, Fdrop, Fdrop, Fdrop, Eioccc,Eioccc,Eioccc,Eioccc,Eioccc,
4727c02fa33Sitojun drop, Eeof },
4737c02fa33Sitojun /* IS_TRUE_MIDDLE */
4747c02fa33Sitojun { Itrue, Ifalse,Fpass, Ftrue, Ffalse,Melif, Melif, Melif, Melse, Pendif,
4757c02fa33Sitojun Oiffy, Oiffy, Fpass, Oif, Oif, Eioccc,Eioccc,Eioccc,Eioccc,Pendif,
4767c02fa33Sitojun print, Eeof },
4777c02fa33Sitojun /* IS_PASS_ELSE */
4787c02fa33Sitojun { Itrue, Ifalse,Fpass, Ftrue, Ffalse,Eelif, Eelif, Eelif, Eelse, Pendif,
4797c02fa33Sitojun Oiffy, Oiffy, Fpass, Oif, Oif, Eelif, Eelif, Eelif, Eelse, Pendif,
4807c02fa33Sitojun print, Eeof },
4817c02fa33Sitojun /* IS_FALSE_ELSE */
4827c02fa33Sitojun { Idrop, Idrop, Fdrop, Fdrop, Fdrop, Eelif, Eelif, Eelif, Eelse, Dendif,
4837c02fa33Sitojun Idrop, Idrop, Fdrop, Fdrop, Fdrop, Eelif, Eelif, Eelif, Eelse, Eioccc,
4847c02fa33Sitojun drop, Eeof },
4857c02fa33Sitojun /* IS_TRUE_ELSE */
4867c02fa33Sitojun { Itrue, Ifalse,Fpass, Ftrue, Ffalse,Eelif, Eelif, Eelif, Eelse, Dendif,
4877c02fa33Sitojun Oiffy, Oiffy, Fpass, Oif, Oif, Eelif, Eelif, Eelif, Eelse, Eioccc,
4887c02fa33Sitojun print, Eeof },
4897c02fa33Sitojun /* IS_FALSE_TRAILER */
4907c02fa33Sitojun { Idrop, Idrop, Fdrop, Fdrop, Fdrop, Dfalse,Dfalse,Dfalse,Delse, Dendif,
4917c02fa33Sitojun Idrop, Idrop, Fdrop, Fdrop, Fdrop, Dfalse,Dfalse,Dfalse,Delse, Eioccc,
4927c02fa33Sitojun drop, Eeof }
4937c02fa33Sitojun /*TRUEI FALSEI IF TRUE FALSE ELIF ELTRUE ELFALSE ELSE ENDIF
4947c02fa33Sitojun TRUEI FALSEI IF TRUE FALSE ELIF ELTRUE ELFALSE ELSE ENDIF (DODGY)
4957c02fa33Sitojun PLAIN EOF */
4967c02fa33Sitojun };
4977c02fa33Sitojun
4987c02fa33Sitojun /*
4997c02fa33Sitojun * State machine utility functions
5007c02fa33Sitojun */
5017c02fa33Sitojun static void
done(void)5027c02fa33Sitojun done(void)
5037c02fa33Sitojun {
5047c02fa33Sitojun if (incomment)
5057c02fa33Sitojun error("EOF in comment");
5062248a776Sginsbach if (fclose(output)) {
5072248a776Sginsbach if (overwriting) {
5082248a776Sginsbach unlink(tmpname);
509b4abf20dSginsbach errx(2, "%s unchanged", ofilename);
5102248a776Sginsbach }
5112248a776Sginsbach }
512b4abf20dSginsbach if (overwriting && rename(tmpname, ofilename)) {
5132248a776Sginsbach unlink(tmpname);
514b4abf20dSginsbach errx(2, "%s unchanged", ofilename);
5152248a776Sginsbach }
51661f28255Scgd exit(exitstat);
51761f28255Scgd }
5187c02fa33Sitojun static void
ignoreoff(void)5197c02fa33Sitojun ignoreoff(void)
52061f28255Scgd {
5217c02fa33Sitojun ignoring[depth] = ignoring[depth-1];
5227c02fa33Sitojun }
5237c02fa33Sitojun static void
ignoreon(void)5247c02fa33Sitojun ignoreon(void)
5257c02fa33Sitojun {
5267c02fa33Sitojun ignoring[depth] = true;
5277c02fa33Sitojun }
5287c02fa33Sitojun static void
keywordedit(const char * replacement)5297c02fa33Sitojun keywordedit(const char *replacement)
5307c02fa33Sitojun {
5317c02fa33Sitojun strlcpy(keyword, replacement, tline + sizeof(tline) - keyword);
5327c02fa33Sitojun print();
5337c02fa33Sitojun }
5347c02fa33Sitojun static void
nest(void)5357c02fa33Sitojun nest(void)
5367c02fa33Sitojun {
5377c02fa33Sitojun depth += 1;
5387c02fa33Sitojun if (depth >= MAXDEPTH)
5397c02fa33Sitojun error("Too many levels of nesting");
5407c02fa33Sitojun stifline[depth] = linenum;
5417c02fa33Sitojun }
5427c02fa33Sitojun static void
state(Ifstate is)5437c02fa33Sitojun state(Ifstate is)
5447c02fa33Sitojun {
5457c02fa33Sitojun ifstate[depth] = is;
5467c02fa33Sitojun }
5477c02fa33Sitojun
5487c02fa33Sitojun /*
5497c02fa33Sitojun * Write a line to the output or not, according to command line options.
5507c02fa33Sitojun */
5517c02fa33Sitojun static void
flushline(bool keep)5527c02fa33Sitojun flushline(bool keep)
5537c02fa33Sitojun {
5547c02fa33Sitojun if (symlist)
55561f28255Scgd return;
5567c02fa33Sitojun if (keep ^ complement)
5572248a776Sginsbach fputs(tline, output);
55861f28255Scgd else {
5598d321745Slukem if (lnblank)
5602248a776Sginsbach putc('\n', output);
5617c02fa33Sitojun exitstat = 1;
5627c02fa33Sitojun }
56361f28255Scgd }
56461f28255Scgd
5657c02fa33Sitojun /*
5667c02fa33Sitojun * The driver for the state machine.
5677c02fa33Sitojun */
5687c02fa33Sitojun static void
process(void)5697c02fa33Sitojun process(void)
57061f28255Scgd {
5717c02fa33Sitojun Linetype lineval;
5727c02fa33Sitojun
5737c02fa33Sitojun for (;;) {
5747c02fa33Sitojun linenum++;
5757027866aSroy lineval = get_line();
5767c02fa33Sitojun trans_table[ifstate[depth]][lineval]();
5777c02fa33Sitojun debug("process %s -> %s depth %d",
5787c02fa33Sitojun linetype_name[lineval],
5797c02fa33Sitojun ifstate_name[ifstate[depth]], depth);
5807c02fa33Sitojun }
58161f28255Scgd }
58261f28255Scgd
5837c02fa33Sitojun /*
5847c02fa33Sitojun * Parse a line and determine its type. We keep the preprocessor line
5857c02fa33Sitojun * parser state between calls in the global variable linestate, with
5867c02fa33Sitojun * help from skipcomment().
5877c02fa33Sitojun */
5887c02fa33Sitojun static Linetype
get_line(void)5897027866aSroy get_line(void)
59061f28255Scgd {
5917c02fa33Sitojun const char *cp;
5927c02fa33Sitojun int cursym;
5937c02fa33Sitojun int kwlen;
5947c02fa33Sitojun Linetype retval;
5957c02fa33Sitojun Comment_state wascomment;
59661f28255Scgd
5977c02fa33Sitojun if (fgets(tline, MAXLINE, input) == NULL)
5987c02fa33Sitojun return (LT_EOF);
5997c02fa33Sitojun retval = LT_PLAIN;
6007c02fa33Sitojun wascomment = incomment;
6017c02fa33Sitojun cp = skipcomment(tline);
6027c02fa33Sitojun if (linestate == LS_START) {
6037c02fa33Sitojun if (*cp == '#') {
6047c02fa33Sitojun linestate = LS_HASH;
6057c02fa33Sitojun cp = skipcomment(cp + 1);
6067c02fa33Sitojun } else if (*cp != '\0')
6077c02fa33Sitojun linestate = LS_DIRTY;
6087c02fa33Sitojun }
6097c02fa33Sitojun if (!incomment && linestate == LS_HASH) {
6107c02fa33Sitojun keyword = tline + (cp - tline);
6117c02fa33Sitojun cp = skipsym(cp);
6127c02fa33Sitojun kwlen = cp - keyword;
6137c02fa33Sitojun /* no way can we deal with a continuation inside a keyword */
6147c02fa33Sitojun if (strncmp(cp, "\\\n", 2) == 0)
6157c02fa33Sitojun Eioccc();
6167c02fa33Sitojun if (strlcmp("ifdef", keyword, kwlen) == 0 ||
6177c02fa33Sitojun strlcmp("ifndef", keyword, kwlen) == 0) {
6187c02fa33Sitojun cp = skipcomment(cp);
6197c02fa33Sitojun if ((cursym = findsym(cp)) < 0)
6207c02fa33Sitojun retval = LT_IF;
6217c02fa33Sitojun else {
6227c02fa33Sitojun retval = (keyword[2] == 'n')
6237c02fa33Sitojun ? LT_FALSE : LT_TRUE;
6247c02fa33Sitojun if (value[cursym] == NULL)
6257c02fa33Sitojun retval = (retval == LT_TRUE)
6267c02fa33Sitojun ? LT_FALSE : LT_TRUE;
6277c02fa33Sitojun if (ignore[cursym])
6287c02fa33Sitojun retval = (retval == LT_TRUE)
6297c02fa33Sitojun ? LT_TRUEI : LT_FALSEI;
6307c02fa33Sitojun }
6317c02fa33Sitojun cp = skipsym(cp);
6327c02fa33Sitojun } else if (strlcmp("if", keyword, kwlen) == 0)
6337c02fa33Sitojun retval = ifeval(&cp);
6347c02fa33Sitojun else if (strlcmp("elif", keyword, kwlen) == 0)
6357c02fa33Sitojun retval = ifeval(&cp) - LT_IF + LT_ELIF;
6367c02fa33Sitojun else if (strlcmp("else", keyword, kwlen) == 0)
6377c02fa33Sitojun retval = LT_ELSE;
6387c02fa33Sitojun else if (strlcmp("endif", keyword, kwlen) == 0)
6397c02fa33Sitojun retval = LT_ENDIF;
6407c02fa33Sitojun else {
6417c02fa33Sitojun linestate = LS_DIRTY;
6427c02fa33Sitojun retval = LT_PLAIN;
6437c02fa33Sitojun }
6447c02fa33Sitojun cp = skipcomment(cp);
6457c02fa33Sitojun if (*cp != '\0') {
6467c02fa33Sitojun linestate = LS_DIRTY;
6477c02fa33Sitojun if (retval == LT_TRUE || retval == LT_FALSE ||
6487c02fa33Sitojun retval == LT_TRUEI || retval == LT_FALSEI)
6497c02fa33Sitojun retval = LT_IF;
6507c02fa33Sitojun if (retval == LT_ELTRUE || retval == LT_ELFALSE)
6517c02fa33Sitojun retval = LT_ELIF;
6527c02fa33Sitojun }
6537c02fa33Sitojun if (retval != LT_PLAIN && (wascomment || incomment)) {
6547c02fa33Sitojun retval += LT_DODGY;
6557c02fa33Sitojun if (incomment)
6567c02fa33Sitojun linestate = LS_DIRTY;
6577c02fa33Sitojun }
6587c02fa33Sitojun }
6597c02fa33Sitojun if (linestate == LS_DIRTY) {
6607c02fa33Sitojun while (*cp != '\0')
6617c02fa33Sitojun cp = skipcomment(cp + 1);
6627c02fa33Sitojun }
6637c02fa33Sitojun debug("parser %s comment %s line",
6647c02fa33Sitojun comment_name[incomment], linestate_name[linestate]);
6657c02fa33Sitojun return (retval);
6667c02fa33Sitojun }
66761f28255Scgd
6687c02fa33Sitojun /*
6697c02fa33Sitojun * These are the binary operators that are supported by the expression
6707c02fa33Sitojun * evaluator. Note that if support for division is added then we also
6717c02fa33Sitojun * need short-circuiting booleans because of divide-by-zero.
6727c02fa33Sitojun */
op_lt(int a,int b)6737c02fa33Sitojun static int op_lt(int a, int b) { return (a < b); }
op_gt(int a,int b)6747c02fa33Sitojun static int op_gt(int a, int b) { return (a > b); }
op_le(int a,int b)6757c02fa33Sitojun static int op_le(int a, int b) { return (a <= b); }
op_ge(int a,int b)6767c02fa33Sitojun static int op_ge(int a, int b) { return (a >= b); }
op_eq(int a,int b)6777c02fa33Sitojun static int op_eq(int a, int b) { return (a == b); }
op_ne(int a,int b)6787c02fa33Sitojun static int op_ne(int a, int b) { return (a != b); }
op_or(int a,int b)6797c02fa33Sitojun static int op_or(int a, int b) { return (a || b); }
op_and(int a,int b)6807c02fa33Sitojun static int op_and(int a, int b) { return (a && b); }
68161f28255Scgd
6827c02fa33Sitojun /*
6837c02fa33Sitojun * An evaluation function takes three arguments, as follows: (1) a pointer to
6847c02fa33Sitojun * an element of the precedence table which lists the operators at the current
6857c02fa33Sitojun * level of precedence; (2) a pointer to an integer which will receive the
6867c02fa33Sitojun * value of the expression; and (3) a pointer to a char* that points to the
6877c02fa33Sitojun * expression to be evaluated and that is updated to the end of the expression
6887c02fa33Sitojun * when evaluation is complete. The function returns LT_FALSE if the value of
6897c02fa33Sitojun * the expression is zero, LT_TRUE if it is non-zero, or LT_IF if the
6907c02fa33Sitojun * expression could not be evaluated.
6917c02fa33Sitojun */
6927c02fa33Sitojun struct ops;
6937c02fa33Sitojun
6947c02fa33Sitojun typedef Linetype eval_fn(const struct ops *, int *, const char **);
6957c02fa33Sitojun
6967c02fa33Sitojun static eval_fn eval_table, eval_unary;
6977c02fa33Sitojun
6987c02fa33Sitojun /*
6997c02fa33Sitojun * The precedence table. Expressions involving binary operators are evaluated
7007c02fa33Sitojun * in a table-driven way by eval_table. When it evaluates a subexpression it
7017c02fa33Sitojun * calls the inner function with its first argument pointing to the next
7027c02fa33Sitojun * element of the table. Innermost expressions have special non-table-driven
7037c02fa33Sitojun * handling.
7047c02fa33Sitojun */
7057c02fa33Sitojun static const struct ops {
7067c02fa33Sitojun eval_fn *inner;
7077c02fa33Sitojun struct op {
7087c02fa33Sitojun const char *str;
7097c02fa33Sitojun int (*fn)(int, int);
7107c02fa33Sitojun } op[5];
7117c02fa33Sitojun } eval_ops[] = {
7127c02fa33Sitojun { eval_table, { { "||", op_or } } },
7137c02fa33Sitojun { eval_table, { { "&&", op_and } } },
7147c02fa33Sitojun { eval_table, { { "==", op_eq },
7157c02fa33Sitojun { "!=", op_ne } } },
7167c02fa33Sitojun { eval_unary, { { "<=", op_le },
7177c02fa33Sitojun { ">=", op_ge },
7187c02fa33Sitojun { "<", op_lt },
7197c02fa33Sitojun { ">", op_gt } } }
7207c02fa33Sitojun };
7217c02fa33Sitojun
7227c02fa33Sitojun /*
7237c02fa33Sitojun * Function for evaluating the innermost parts of expressions,
7247c02fa33Sitojun * viz. !expr (expr) defined(symbol) symbol number
7257c02fa33Sitojun * We reset the keepthis flag when we find a non-constant subexpression.
7267c02fa33Sitojun */
7277c02fa33Sitojun static Linetype
eval_unary(const struct ops * ops,int * valp,const char ** cpp)7287c02fa33Sitojun eval_unary(const struct ops *ops, int *valp, const char **cpp)
7297c02fa33Sitojun {
7307c02fa33Sitojun const char *cp;
7317c02fa33Sitojun char *ep;
7327c02fa33Sitojun int sym;
7337c02fa33Sitojun
7347c02fa33Sitojun cp = skipcomment(*cpp);
7357c02fa33Sitojun if (*cp == '!') {
736eb085c76Sjoerg debug("eval%td !", ops - eval_ops);
7377c02fa33Sitojun cp++;
7387c02fa33Sitojun if (eval_unary(ops, valp, &cp) == LT_IF)
7397c02fa33Sitojun return (LT_IF);
7407c02fa33Sitojun *valp = !*valp;
7417c02fa33Sitojun } else if (*cp == '(') {
7427c02fa33Sitojun cp++;
743eb085c76Sjoerg debug("eval%td (", ops - eval_ops);
7447c02fa33Sitojun if (eval_table(eval_ops, valp, &cp) == LT_IF)
7457c02fa33Sitojun return (LT_IF);
7467c02fa33Sitojun cp = skipcomment(cp);
7477c02fa33Sitojun if (*cp++ != ')')
7487c02fa33Sitojun return (LT_IF);
7497c02fa33Sitojun } else if (isdigit((unsigned char)*cp)) {
750eb085c76Sjoerg debug("eval%td number", ops - eval_ops);
7517c02fa33Sitojun *valp = strtol(cp, &ep, 0);
7527c02fa33Sitojun cp = skipsym(cp);
7537c02fa33Sitojun } else if (strncmp(cp, "defined", 7) == 0 && endsym(cp[7])) {
7547c02fa33Sitojun cp = skipcomment(cp+7);
755eb085c76Sjoerg debug("eval%td defined", ops - eval_ops);
7567c02fa33Sitojun if (*cp++ != '(')
7577c02fa33Sitojun return (LT_IF);
7587c02fa33Sitojun cp = skipcomment(cp);
7597c02fa33Sitojun sym = findsym(cp);
760a6b956aaSchristos if (sym < 0 || symlist)
7617c02fa33Sitojun return (LT_IF);
7627c02fa33Sitojun *valp = (value[sym] != NULL);
7637c02fa33Sitojun cp = skipsym(cp);
7647c02fa33Sitojun cp = skipcomment(cp);
7657c02fa33Sitojun if (*cp++ != ')')
7667c02fa33Sitojun return (LT_IF);
7677c02fa33Sitojun keepthis = false;
7687c02fa33Sitojun } else if (!endsym(*cp)) {
769eb085c76Sjoerg debug("eval%td symbol", ops - eval_ops);
7707c02fa33Sitojun sym = findsym(cp);
771a6b956aaSchristos if (sym < 0 || symlist)
7727c02fa33Sitojun return (LT_IF);
7737c02fa33Sitojun if (value[sym] == NULL)
7747c02fa33Sitojun *valp = 0;
7757c02fa33Sitojun else {
7767c02fa33Sitojun *valp = strtol(value[sym], &ep, 0);
7777c02fa33Sitojun if (*ep != '\0' || ep == value[sym])
7787c02fa33Sitojun return (LT_IF);
7797c02fa33Sitojun }
7807c02fa33Sitojun cp = skipsym(cp);
7817c02fa33Sitojun keepthis = false;
7827c02fa33Sitojun } else {
783eb085c76Sjoerg debug("eval%td bad expr", ops - eval_ops);
7847c02fa33Sitojun return (LT_IF);
7857c02fa33Sitojun }
7867c02fa33Sitojun
7877c02fa33Sitojun *cpp = cp;
788eb085c76Sjoerg debug("eval%td = %d", ops - eval_ops, *valp);
7897c02fa33Sitojun return (*valp ? LT_TRUE : LT_FALSE);
7907c02fa33Sitojun }
7917c02fa33Sitojun
7927c02fa33Sitojun /*
7937c02fa33Sitojun * Table-driven evaluation of binary operators.
7947c02fa33Sitojun */
7957c02fa33Sitojun static Linetype
eval_table(const struct ops * ops,int * valp,const char ** cpp)7967c02fa33Sitojun eval_table(const struct ops *ops, int *valp, const char **cpp)
7977c02fa33Sitojun {
7987c02fa33Sitojun const struct op *op;
7997c02fa33Sitojun const char *cp;
8007c02fa33Sitojun int val;
8017c02fa33Sitojun
802eb085c76Sjoerg debug("eval%td", ops - eval_ops);
8037c02fa33Sitojun cp = *cpp;
8047c02fa33Sitojun if (ops->inner(ops+1, valp, &cp) == LT_IF)
8057c02fa33Sitojun return (LT_IF);
8067c02fa33Sitojun for (;;) {
8077c02fa33Sitojun cp = skipcomment(cp);
8087c02fa33Sitojun for (op = ops->op; op->str != NULL; op++)
8097c02fa33Sitojun if (strncmp(cp, op->str, strlen(op->str)) == 0)
8107c02fa33Sitojun break;
8117c02fa33Sitojun if (op->str == NULL)
8127c02fa33Sitojun break;
8137c02fa33Sitojun cp += strlen(op->str);
814eb085c76Sjoerg debug("eval%td %s", ops - eval_ops, op->str);
8157c02fa33Sitojun if (ops->inner(ops+1, &val, &cp) == LT_IF)
8167c02fa33Sitojun return (LT_IF);
8177c02fa33Sitojun *valp = op->fn(*valp, val);
8187c02fa33Sitojun }
8197c02fa33Sitojun
8207c02fa33Sitojun *cpp = cp;
821eb085c76Sjoerg debug("eval%td = %d", ops - eval_ops, *valp);
8227c02fa33Sitojun return (*valp ? LT_TRUE : LT_FALSE);
8237c02fa33Sitojun }
8247c02fa33Sitojun
8257c02fa33Sitojun /*
8267c02fa33Sitojun * Evaluate the expression on a #if or #elif line. If we can work out
8277c02fa33Sitojun * the result we return LT_TRUE or LT_FALSE accordingly, otherwise we
8287c02fa33Sitojun * return just a generic LT_IF.
8297c02fa33Sitojun */
8307c02fa33Sitojun static Linetype
ifeval(const char ** cpp)8317c02fa33Sitojun ifeval(const char **cpp)
8327c02fa33Sitojun {
8337c02fa33Sitojun int ret;
8347c02fa33Sitojun int val;
8357c02fa33Sitojun
8367c02fa33Sitojun debug("eval %s", *cpp);
8377c02fa33Sitojun keepthis = killconsts ? false : true;
8387c02fa33Sitojun ret = eval_table(eval_ops, &val, cpp);
8397c02fa33Sitojun debug("eval = %d", val);
8407c02fa33Sitojun return (keepthis ? LT_IF : ret);
8417c02fa33Sitojun }
8427c02fa33Sitojun
8437c02fa33Sitojun /*
8447c02fa33Sitojun * Skip over comments and stop at the next character position that is
8457c02fa33Sitojun * not whitespace. Between calls we keep the comment state in the
8467c02fa33Sitojun * global variable incomment, and we also adjust the global variable
8477c02fa33Sitojun * linestate when we see a newline.
8487c02fa33Sitojun * XXX: doesn't cope with the buffer splitting inside a state transition.
8497c02fa33Sitojun */
8507c02fa33Sitojun static const char *
skipcomment(const char * cp)8517c02fa33Sitojun skipcomment(const char *cp)
8527c02fa33Sitojun {
8537c02fa33Sitojun if (text || ignoring[depth]) {
8547c02fa33Sitojun for (; isspace((unsigned char)*cp); cp++)
8557c02fa33Sitojun if (*cp == '\n')
8567c02fa33Sitojun linestate = LS_START;
8577c02fa33Sitojun return (cp);
8587c02fa33Sitojun }
8597c02fa33Sitojun while (*cp != '\0')
8607c02fa33Sitojun /* don't reset to LS_START after a line continuation */
8617c02fa33Sitojun if (strncmp(cp, "\\\n", 2) == 0)
8627c02fa33Sitojun cp += 2;
8637c02fa33Sitojun else switch (incomment) {
8647c02fa33Sitojun case NO_COMMENT:
8657c02fa33Sitojun if (strncmp(cp, "/\\\n", 3) == 0) {
8667c02fa33Sitojun incomment = STARTING_COMMENT;
8677c02fa33Sitojun cp += 3;
8687c02fa33Sitojun } else if (strncmp(cp, "/*", 2) == 0) {
8697c02fa33Sitojun incomment = C_COMMENT;
8707c02fa33Sitojun cp += 2;
8717c02fa33Sitojun } else if (strncmp(cp, "//", 2) == 0) {
8727c02fa33Sitojun incomment = CXX_COMMENT;
8737c02fa33Sitojun cp += 2;
8747c02fa33Sitojun } else if (strncmp(cp, "\n", 1) == 0) {
8757c02fa33Sitojun linestate = LS_START;
8767c02fa33Sitojun cp += 1;
8777c02fa33Sitojun } else if (strchr(" \t", *cp) != NULL) {
8787c02fa33Sitojun cp += 1;
8797c02fa33Sitojun } else
8807c02fa33Sitojun return (cp);
8817c02fa33Sitojun continue;
8827c02fa33Sitojun case CXX_COMMENT:
8837c02fa33Sitojun if (strncmp(cp, "\n", 1) == 0) {
8847c02fa33Sitojun incomment = NO_COMMENT;
8857c02fa33Sitojun linestate = LS_START;
8867c02fa33Sitojun }
8877c02fa33Sitojun cp += 1;
8887c02fa33Sitojun continue;
8897c02fa33Sitojun case C_COMMENT:
8907c02fa33Sitojun if (strncmp(cp, "*\\\n", 3) == 0) {
8917c02fa33Sitojun incomment = FINISHING_COMMENT;
8927c02fa33Sitojun cp += 3;
8937c02fa33Sitojun } else if (strncmp(cp, "*/", 2) == 0) {
8947c02fa33Sitojun incomment = NO_COMMENT;
8957c02fa33Sitojun cp += 2;
8967c02fa33Sitojun } else
8977c02fa33Sitojun cp += 1;
8987c02fa33Sitojun continue;
8997c02fa33Sitojun case STARTING_COMMENT:
9007c02fa33Sitojun if (*cp == '*') {
9017c02fa33Sitojun incomment = C_COMMENT;
9027c02fa33Sitojun cp += 1;
9037c02fa33Sitojun } else if (*cp == '/') {
9047c02fa33Sitojun incomment = CXX_COMMENT;
9057c02fa33Sitojun cp += 1;
9067c02fa33Sitojun } else {
9077c02fa33Sitojun incomment = NO_COMMENT;
9087c02fa33Sitojun linestate = LS_DIRTY;
9097c02fa33Sitojun }
9107c02fa33Sitojun continue;
9117c02fa33Sitojun case FINISHING_COMMENT:
9127c02fa33Sitojun if (*cp == '/') {
9137c02fa33Sitojun incomment = NO_COMMENT;
9147c02fa33Sitojun cp += 1;
9157c02fa33Sitojun } else
9167c02fa33Sitojun incomment = C_COMMENT;
9177c02fa33Sitojun continue;
9187c02fa33Sitojun default:
9197c02fa33Sitojun abort(); /* bug */
9207c02fa33Sitojun }
9217c02fa33Sitojun return (cp);
9227c02fa33Sitojun }
9237c02fa33Sitojun
9247c02fa33Sitojun /*
9257c02fa33Sitojun * Skip over an identifier.
9267c02fa33Sitojun */
9277c02fa33Sitojun static const char *
skipsym(const char * cp)9287c02fa33Sitojun skipsym(const char *cp)
9297c02fa33Sitojun {
9307c02fa33Sitojun while (!endsym(*cp))
9317c02fa33Sitojun ++cp;
9327c02fa33Sitojun return (cp);
9337c02fa33Sitojun }
9347c02fa33Sitojun
9357c02fa33Sitojun /*
9360f58fac9Smbalmer * Look for the symbol in the symbol table. If it is found, we return
9377c02fa33Sitojun * the symbol table index, else we return -1.
9387c02fa33Sitojun */
9397c02fa33Sitojun static int
findsym(const char * str)9407c02fa33Sitojun findsym(const char *str)
9417c02fa33Sitojun {
9427c02fa33Sitojun const char *cp;
9437c02fa33Sitojun int symind;
9447c02fa33Sitojun
9457c02fa33Sitojun cp = skipsym(str);
9467c02fa33Sitojun if (cp == str)
9477c02fa33Sitojun return (-1);
9487c02fa33Sitojun if (symlist)
9497c02fa33Sitojun printf("%.*s\n", (int)(cp-str), str);
9507c02fa33Sitojun for (symind = 0; symind < nsyms; ++symind) {
9517c02fa33Sitojun if (strlcmp(symname[symind], str, cp-str) == 0) {
9527c02fa33Sitojun debug("findsym %s %s", symname[symind],
9537c02fa33Sitojun value[symind] ? value[symind] : "");
9547c02fa33Sitojun return (symind);
9557c02fa33Sitojun }
9567c02fa33Sitojun }
9577c02fa33Sitojun return (-1);
9587c02fa33Sitojun }
9597c02fa33Sitojun
9607c02fa33Sitojun /*
9617c02fa33Sitojun * Add a symbol to the symbol table.
9627c02fa33Sitojun */
9637c02fa33Sitojun static void
addsym(bool ignorethis,bool definethis,char * sym)9647c02fa33Sitojun addsym(bool ignorethis, bool definethis, char *sym)
9657c02fa33Sitojun {
9667c02fa33Sitojun int symind;
9677c02fa33Sitojun char *val;
9687c02fa33Sitojun
9697c02fa33Sitojun symind = findsym(sym);
9707c02fa33Sitojun if (symind < 0) {
9717c02fa33Sitojun if (nsyms >= MAXSYMS)
9727c02fa33Sitojun errx(2, "too many symbols");
9737c02fa33Sitojun symind = nsyms++;
9747c02fa33Sitojun }
9757c02fa33Sitojun symname[symind] = sym;
9767c02fa33Sitojun ignore[symind] = ignorethis;
9777c02fa33Sitojun val = sym + (skipsym(sym) - sym);
9787c02fa33Sitojun if (definethis) {
9797c02fa33Sitojun if (*val == '=') {
9807c02fa33Sitojun value[symind] = val+1;
9817c02fa33Sitojun *val = '\0';
9827c02fa33Sitojun } else if (*val == '\0')
9837c02fa33Sitojun value[symind] = "";
9847c02fa33Sitojun else
9857c02fa33Sitojun usage();
9867c02fa33Sitojun } else {
9877c02fa33Sitojun if (*val != '\0')
9887c02fa33Sitojun usage();
9897c02fa33Sitojun value[symind] = NULL;
9907c02fa33Sitojun }
9917c02fa33Sitojun }
9927c02fa33Sitojun
9937c02fa33Sitojun /*
9947c02fa33Sitojun * Compare s with n characters of t.
9957c02fa33Sitojun * The same as strncmp() except that it checks that s[n] == '\0'.
9967c02fa33Sitojun */
9977c02fa33Sitojun static int
strlcmp(const char * s,const char * t,size_t n)9987c02fa33Sitojun strlcmp(const char *s, const char *t, size_t n)
9997c02fa33Sitojun {
10007c02fa33Sitojun while (n-- && *t != '\0')
10017c02fa33Sitojun if (*s != *t)
10027c02fa33Sitojun return ((unsigned char)*s - (unsigned char)*t);
10037c02fa33Sitojun else
10047c02fa33Sitojun ++s, ++t;
10057c02fa33Sitojun return ((unsigned char)*s);
10067c02fa33Sitojun }
10077c02fa33Sitojun
10087c02fa33Sitojun /*
10097c02fa33Sitojun * Diagnostics.
10107c02fa33Sitojun */
10117c02fa33Sitojun static void
debug(const char * msg,...)10127c02fa33Sitojun debug(const char *msg, ...)
10137c02fa33Sitojun {
10147c02fa33Sitojun va_list ap;
10157c02fa33Sitojun
10167c02fa33Sitojun if (debugging) {
10177c02fa33Sitojun va_start(ap, msg);
10187c02fa33Sitojun vwarnx(msg, ap);
10197c02fa33Sitojun va_end(ap);
10207c02fa33Sitojun }
10217c02fa33Sitojun }
10227c02fa33Sitojun
10237c02fa33Sitojun static void
error(const char * msg)10247c02fa33Sitojun error(const char *msg)
10257c02fa33Sitojun {
10267c02fa33Sitojun if (depth == 0)
10277c02fa33Sitojun warnx("%s: %d: %s", filename, linenum, msg);
10287c02fa33Sitojun else
10297c02fa33Sitojun warnx("%s: %d: %s (#if line %d depth %d)",
10307c02fa33Sitojun filename, linenum, msg, stifline[depth], depth);
10312248a776Sginsbach fclose(output);
10322248a776Sginsbach if (overwriting) {
10332248a776Sginsbach unlink(tmpname);
1034b4abf20dSginsbach errx(2, "%s unchanged", ofilename);
10352248a776Sginsbach }
10367c02fa33Sitojun errx(2, "output may be truncated");
103761f28255Scgd }
1038