xref: /netbsd-src/usr.bin/unifdef/unifdef.c (revision 83d4b28a8e34d47cdc13d59fe5c6789e86a4da5c)
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