Merge branches 'doc', 'multitouch', 'upstream' and 'upstream-fixes' into for-linus
[linux-2.6.git] / scripts / unifdef.c
index 5384b43..7493c0e 100644 (file)
@@ -1,13 +1,5 @@
 /*
- * Copyright (c) 2002 - 2005 Tony Finch <dot@dotat.at>.  All rights reserved.
- *
- * This code is derived from software contributed to Berkeley by Dave Yost.
- * It was rewritten to support ANSI C by Tony Finch. The original version of
- * unifdef carried the following copyright notice. None of its code remains
- * in this version (though some of the names remain).
- *
- * Copyright (c) 1985, 1993
- *     The Regents of the University of California.  All rights reserved.
+ * Copyright (c) 2002 - 2011 Tony Finch <dot@dotat.at>
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
  * SUCH DAMAGE.
  */
 
-#include <sys/cdefs.h>
-
-#ifndef lint
-#if 0
-static const char copyright[] =
-"@(#) Copyright (c) 1985, 1993\n\
-       The Regents of the University of California.  All rights reserved.\n";
-#endif
-#ifdef __IDSTRING
-__IDSTRING(Berkeley, "@(#)unifdef.c    8.1 (Berkeley) 6/6/93");
-__IDSTRING(NetBSD, "$NetBSD: unifdef.c,v 1.8 2000/07/03 02:51:36 matt Exp $");
-__IDSTRING(dotat, "$dotat: things/unifdef.c,v 1.171 2005/03/08 12:38:48 fanf2 Exp $");
-#endif
-#endif /* not lint */
-#ifdef __FBSDID
-__FBSDID("$FreeBSD: /repoman/r/ncvs/src/usr.bin/unifdef/unifdef.c,v 1.20 2005/05/21 09:55:09 ru Exp $");
-#endif
-
 /*
  * unifdef - remove ifdef'ed lines
  *
+ * This code was derived from software contributed to Berkeley by Dave Yost.
+ * It was rewritten to support ANSI C by Tony Finch. The original version
+ * of unifdef carried the 4-clause BSD copyright licence. None of its code
+ * remains in this version (though some of the names remain) so it now
+ * carries a more liberal licence.
+ *
  *  Wishlist:
  *      provide an option which will append the name of the
  *        appropriate symbol after #else's and #endif's
@@ -59,12 +39,16 @@ __FBSDID("$FreeBSD: /repoman/r/ncvs/src/usr.bin/unifdef/unifdef.c,v 1.20 2005/05
  *        #else's and #endif's to see that they match their
  *        corresponding #ifdef or #ifndef
  *
- *   The first two items above require better buffer handling, which would
- *     also make it possible to handle all "dodgy" directives correctly.
+ *   These require better buffer handling, which would also make
+ *   it possible to handle all "dodgy" directives correctly.
  */
 
+#include <sys/types.h>
+#include <sys/stat.h>
+
 #include <ctype.h>
 #include <err.h>
+#include <errno.h>
 #include <stdarg.h>
 #include <stdbool.h>
 #include <stdio.h>
@@ -72,7 +56,11 @@ __FBSDID("$FreeBSD: /repoman/r/ncvs/src/usr.bin/unifdef/unifdef.c,v 1.20 2005/05
 #include <string.h>
 #include <unistd.h>
 
-size_t strlcpy(char *dst, const char *src, size_t siz);
+const char copyright[] =
+    "@(#) $Version: unifdef-2.5 $\n"
+    "@(#) $Author: Tony Finch (dot@dotat.at) $\n"
+    "@(#) $URL: http://dotat.at/prog/unifdef $\n"
+;
 
 /* types of input lines: */
 typedef enum {
@@ -90,6 +78,7 @@ typedef enum {
        LT_DODGY_LAST = LT_DODGY + LT_ENDIF,
        LT_PLAIN,               /* ordinary line */
        LT_EOF,                 /* end of file */
+       LT_ERROR,               /* unevaluable #if */
        LT_COUNT
 } Linetype;
 
@@ -100,7 +89,7 @@ static char const * const linetype_name[] = {
        "DODGY IF", "DODGY TRUE", "DODGY FALSE",
        "DODGY ELIF", "DODGY ELTRUE", "DODGY ELFALSE",
        "DODGY ELSE", "DODGY ENDIF",
-       "PLAIN", "EOF"
+       "PLAIN", "EOF", "ERROR"
 };
 
 /* state of #if processing */
@@ -165,16 +154,24 @@ static char const * const linestate_name[] = {
 #define        EDITSLOP        10
 
 /*
+ * For temporary filenames
+ */
+#define TEMPLATE        "unifdef.XXXXXX"
+
+/*
  * Globals.
  */
 
+static bool             compblank;             /* -B: compress blank lines */
+static bool             lnblank;               /* -b: blank deleted lines */
 static bool             complement;            /* -c: do the complement */
 static bool             debugging;             /* -d: debugging reports */
 static bool             iocccok;               /* -e: fewer IOCCC errors */
+static bool             strictlogic;           /* -K: keep ambiguous #ifs */
 static bool             killconsts;            /* -k: eval constant #ifs */
-static bool             lnblank;               /* -l: blank deleted lines */
 static bool             lnnum;                 /* -n: add #line directives */
 static bool             symlist;               /* -s: output symbol list */
+static bool             symdepth;              /* -S: output symbol depth */
 static bool             text;                  /* -t: this is a text file */
 
 static const char      *symname[MAXSYMS];      /* symbol name */
@@ -185,10 +182,18 @@ static int              nsyms;                    /* number of symbols */
 static FILE            *input;                 /* input file pointer */
 static const char      *filename;              /* input file name */
 static int              linenum;               /* current line number */
+static FILE            *output;                        /* output file pointer */
+static const char      *ofilename;             /* output file name */
+static bool             overwriting;           /* output overwrites input */
+static char             tempname[FILENAME_MAX];        /* used when overwriting */
 
 static char             tline[MAXLINE+EDITSLOP];/* input buffer plus space */
 static char            *keyword;               /* used for editing #elif's */
 
+static const char      *newline;               /* input file format */
+static const char       newline_unix[] = "\n";
+static const char       newline_crlf[] = "\r\n";
+
 static Comment_state    incomment;             /* comment parser state */
 static Line_state       linestate;             /* #if line parser state */
 static Ifstate          ifstate[MAXDEPTH];     /* #if processor state */
@@ -196,31 +201,38 @@ static bool             ignoring[MAXDEPTH];       /* ignore comments state */
 static int              stifline[MAXDEPTH];    /* start of current #if */
 static int              depth;                 /* current #if nesting */
 static int              delcount;              /* count of deleted lines */
-static bool             keepthis;              /* don't delete constant #if */
+static unsigned         blankcount;            /* count of blank lines */
+static unsigned         blankmax;              /* maximum recent blankcount */
+static bool             constexpr;             /* constant #if expression */
+static bool             zerosyms = true;       /* to format symdepth output */
+static bool             firstsym;              /* ditto */
 
 static int              exitstat;              /* program exit status */
 
 static void             addsym(bool, bool, char *);
+static void             closeout(void);
 static void             debug(const char *, ...);
 static void             done(void);
 static void             error(const char *);
 static int              findsym(const char *);
 static void             flushline(bool);
-static Linetype         getline(void);
+static Linetype         parseline(void);
 static Linetype         ifeval(const char **);
 static void             ignoreoff(void);
 static void             ignoreon(void);
 static void             keywordedit(const char *);
 static void             nest(void);
 static void             process(void);
+static const char      *skipargs(const char *);
 static const char      *skipcomment(const char *);
 static const char      *skipsym(const char *);
 static void             state(Ifstate);
 static int              strlcmp(const char *, const char *, size_t);
 static void             unnest(void);
 static void             usage(void);
+static void             version(void);
 
-#define endsym(c) (!isalpha((unsigned char)c) && !isdigit((unsigned char)c) && c != '_')
+#define endsym(c) (!isalnum((unsigned char)c) && c != '_')
 
 /*
  * The main program.
@@ -230,7 +242,7 @@ main(int argc, char *argv[])
 {
        int opt;
 
-       while ((opt = getopt(argc, argv, "i:D:U:I:cdeklnst")) != -1)
+       while ((opt = getopt(argc, argv, "i:D:U:I:o:bBcdeKklnsStV")) != -1)
                switch (opt) {
                case 'i': /* treat stuff controlled by these symbols as text */
                        /*
@@ -252,8 +264,14 @@ main(int argc, char *argv[])
                case 'U': /* undef a symbol */
                        addsym(false, false, optarg);
                        break;
-               case 'I':
-                       /* no-op for compatibility with cpp */
+               case 'I': /* no-op for compatibility with cpp */
+                       break;
+               case 'b': /* blank deleted lines instead of omitting them */
+               case 'l': /* backwards compatibility */
+                       lnblank = true;
+                       break;
+               case 'B': /* compress blank lines around removed section */
+                       compblank = true;
                        break;
                case 'c': /* treat -D as -U and vice versa */
                        complement = true;
@@ -264,45 +282,102 @@ main(int argc, char *argv[])
                case 'e': /* fewer errors from dodgy lines */
                        iocccok = true;
                        break;
+               case 'K': /* keep ambiguous #ifs */
+                       strictlogic = true;
+                       break;
                case 'k': /* process constant #ifs */
                        killconsts = true;
                        break;
-               case 'l': /* blank deleted lines instead of omitting them */
-                       lnblank = true;
-                       break;
                case 'n': /* add #line directive after deleted lines */
                        lnnum = true;
                        break;
+               case 'o': /* output to a file */
+                       ofilename = optarg;
+                       break;
                case 's': /* only output list of symbols that control #ifs */
                        symlist = true;
                        break;
+               case 'S': /* list symbols with their nesting depth */
+                       symlist = symdepth = true;
+                       break;
                case 't': /* don't parse C comments */
                        text = true;
                        break;
+               case 'V': /* print version */
+                       version();
                default:
                        usage();
                }
        argc -= optind;
        argv += optind;
+       if (compblank && lnblank)
+               errx(2, "-B and -b are mutually exclusive");
        if (argc > 1) {
                errx(2, "can only do one file");
        } else if (argc == 1 && strcmp(*argv, "-") != 0) {
                filename = *argv;
-               input = fopen(filename, "r");
+               input = fopen(filename, "rb");
                if (input == NULL)
                        err(2, "can't open %s", filename);
        } else {
                filename = "[stdin]";
                input = stdin;
        }
+       if (ofilename == NULL) {
+               ofilename = "[stdout]";
+               output = stdout;
+       } else {
+               struct stat ist, ost;
+               if (stat(ofilename, &ost) == 0 &&
+                   fstat(fileno(input), &ist) == 0)
+                       overwriting = (ist.st_dev == ost.st_dev
+                                   && ist.st_ino == ost.st_ino);
+               if (overwriting) {
+                       const char *dirsep;
+                       int ofd;
+
+                       dirsep = strrchr(ofilename, '/');
+                       if (dirsep != NULL)
+                               snprintf(tempname, sizeof(tempname),
+                                   "%.*s/" TEMPLATE,
+                                   (int)(dirsep - ofilename), ofilename);
+                       else
+                               snprintf(tempname, sizeof(tempname),
+                                   TEMPLATE);
+                       ofd = mkstemp(tempname);
+                       if (ofd != -1)
+                               output = fdopen(ofd, "wb+");
+                       if (output == NULL)
+                               err(2, "can't create temporary file");
+                       fchmod(ofd, ist.st_mode & (S_IRWXU|S_IRWXG|S_IRWXO));
+               } else {
+                       output = fopen(ofilename, "wb");
+                       if (output == NULL)
+                               err(2, "can't open %s", ofilename);
+               }
+       }
        process();
        abort(); /* bug */
 }
 
 static void
+version(void)
+{
+       const char *c = copyright;
+       for (;;) {
+               while (*++c != '$')
+                       if (*c == '\0')
+                               exit(0);
+               while (*++c != '$')
+                       putc(*c, stderr);
+               putc('\n', stderr);
+       }
+}
+
+static void
 usage(void)
 {
-       fprintf(stderr, "usage: unifdef [-cdeklnst] [-Ipath]"
+       fprintf(stderr, "usage: unifdef [-bBcdeKknsStV] [-Ipath]"
            " [-Dsym[=val]] [-Usym] [-iDsym[=val]] [-iUsym] ... [file]\n");
        exit(2);
 }
@@ -320,7 +395,8 @@ usage(void)
  * When we have processed a group that starts off with a known-false
  * #if/#elif sequence (which has therefore been deleted) followed by a
  * #elif that we don't understand and therefore must keep, we edit the
- * latter into a #if to keep the nesting correct.
+ * latter into a #if to keep the nesting correct. We use strncpy() to
+ * overwrite the 4 byte token "elif" with "if  " without a '\0' byte.
  *
  * When we find a true #elif in a group, the following block will
  * always be kept and the rest of the sequence after the next #elif or
@@ -373,69 +449,62 @@ static void Oelif (void) { if (!iocccok) Eioccc(); Pelif(); }
 static void Idrop (void) { Fdrop();  ignoreon(); }
 static void Itrue (void) { Ftrue();  ignoreon(); }
 static void Ifalse(void) { Ffalse(); ignoreon(); }
-/* edit this line */
+/* modify this line */
 static void Mpass (void) { strncpy(keyword, "if  ", 4); Pelif(); }
-static void Mtrue (void) { keywordedit("else\n");  state(IS_TRUE_MIDDLE); }
-static void Melif (void) { keywordedit("endif\n"); state(IS_FALSE_TRAILER); }
-static void Melse (void) { keywordedit("endif\n"); state(IS_FALSE_ELSE); }
+static void Mtrue (void) { keywordedit("else");  state(IS_TRUE_MIDDLE); }
+static void Melif (void) { keywordedit("endif"); state(IS_FALSE_TRAILER); }
+static void Melse (void) { keywordedit("endif"); state(IS_FALSE_ELSE); }
 
 static state_fn * const trans_table[IS_COUNT][LT_COUNT] = {
 /* IS_OUTSIDE */
 { Itrue, Ifalse,Fpass, Ftrue, Ffalse,Eelif, Eelif, Eelif, Eelse, Eendif,
   Oiffy, Oiffy, Fpass, Oif,   Oif,   Eelif, Eelif, Eelif, Eelse, Eendif,
-  print, done },
+  print, done,  abort },
 /* IS_FALSE_PREFIX */
 { Idrop, Idrop, Fdrop, Fdrop, Fdrop, Mpass, Strue, Sfalse,Selse, Dendif,
   Idrop, Idrop, Fdrop, Fdrop, Fdrop, Mpass, Eioccc,Eioccc,Eioccc,Eioccc,
-  drop,  Eeof },
+  drop,  Eeof,  abort },
 /* IS_TRUE_PREFIX */
 { Itrue, Ifalse,Fpass, Ftrue, Ffalse,Dfalse,Dfalse,Dfalse,Delse, Dendif,
   Oiffy, Oiffy, Fpass, Oif,   Oif,   Eioccc,Eioccc,Eioccc,Eioccc,Eioccc,
-  print, Eeof },
+  print, Eeof,  abort },
 /* IS_PASS_MIDDLE */
 { Itrue, Ifalse,Fpass, Ftrue, Ffalse,Pelif, Mtrue, Delif, Pelse, Pendif,
   Oiffy, Oiffy, Fpass, Oif,   Oif,   Pelif, Oelif, Oelif, Pelse, Pendif,
-  print, Eeof },
+  print, Eeof,  abort },
 /* IS_FALSE_MIDDLE */
 { Idrop, Idrop, Fdrop, Fdrop, Fdrop, Pelif, Mtrue, Delif, Pelse, Pendif,
   Idrop, Idrop, Fdrop, Fdrop, Fdrop, Eioccc,Eioccc,Eioccc,Eioccc,Eioccc,
-  drop,  Eeof },
+  drop,  Eeof,  abort },
 /* IS_TRUE_MIDDLE */
 { Itrue, Ifalse,Fpass, Ftrue, Ffalse,Melif, Melif, Melif, Melse, Pendif,
   Oiffy, Oiffy, Fpass, Oif,   Oif,   Eioccc,Eioccc,Eioccc,Eioccc,Pendif,
-  print, Eeof },
+  print, Eeof,  abort },
 /* IS_PASS_ELSE */
 { Itrue, Ifalse,Fpass, Ftrue, Ffalse,Eelif, Eelif, Eelif, Eelse, Pendif,
   Oiffy, Oiffy, Fpass, Oif,   Oif,   Eelif, Eelif, Eelif, Eelse, Pendif,
-  print, Eeof },
+  print, Eeof,  abort },
 /* IS_FALSE_ELSE */
 { Idrop, Idrop, Fdrop, Fdrop, Fdrop, Eelif, Eelif, Eelif, Eelse, Dendif,
   Idrop, Idrop, Fdrop, Fdrop, Fdrop, Eelif, Eelif, Eelif, Eelse, Eioccc,
-  drop,  Eeof },
+  drop,  Eeof,  abort },
 /* IS_TRUE_ELSE */
 { Itrue, Ifalse,Fpass, Ftrue, Ffalse,Eelif, Eelif, Eelif, Eelse, Dendif,
   Oiffy, Oiffy, Fpass, Oif,   Oif,   Eelif, Eelif, Eelif, Eelse, Eioccc,
-  print, Eeof },
+  print, Eeof,  abort },
 /* IS_FALSE_TRAILER */
 { Idrop, Idrop, Fdrop, Fdrop, Fdrop, Dfalse,Dfalse,Dfalse,Delse, Dendif,
   Idrop, Idrop, Fdrop, Fdrop, Fdrop, Dfalse,Dfalse,Dfalse,Delse, Eioccc,
-  drop,  Eeof }
+  drop,  Eeof,  abort }
 /*TRUEI  FALSEI IF     TRUE   FALSE  ELIF   ELTRUE ELFALSE ELSE  ENDIF
   TRUEI  FALSEI IF     TRUE   FALSE  ELIF   ELTRUE ELFALSE ELSE  ENDIF (DODGY)
-  PLAIN  EOF */
+  PLAIN  EOF    ERROR */
 };
 
 /*
  * State machine utility functions
  */
 static void
-done(void)
-{
-       if (incomment)
-               error("EOF in comment");
-       exit(exitstat);
-}
-static void
 ignoreoff(void)
 {
        if (depth == 0)
@@ -450,15 +519,18 @@ ignoreon(void)
 static void
 keywordedit(const char *replacement)
 {
-       strlcpy(keyword, replacement, tline + sizeof(tline) - keyword);
+       snprintf(keyword, tline + sizeof(tline) - keyword,
+           "%s%s", replacement, newline);
        print();
 }
 static void
 nest(void)
 {
-       depth += 1;
-       if (depth >= MAXDEPTH)
+       if (depth > MAXDEPTH-1)
+               abort(); /* bug */
+       if (depth == MAXDEPTH-1)
                error("Too many levels of nesting");
+       depth += 1;
        stifline[depth] = linenum;
 }
 static void
@@ -483,16 +555,26 @@ flushline(bool keep)
        if (symlist)
                return;
        if (keep ^ complement) {
-               if (lnnum && delcount > 0)
-                       printf("#line %d\n", linenum);
-               fputs(tline, stdout);
-               delcount = 0;
+               bool blankline = tline[strspn(tline, " \t\r\n")] == '\0';
+               if (blankline && compblank && blankcount != blankmax) {
+                       delcount += 1;
+                       blankcount += 1;
+               } else {
+                       if (lnnum && delcount > 0)
+                               printf("#line %d%s", linenum, newline);
+                       fputs(tline, output);
+                       delcount = 0;
+                       blankmax = blankcount = blankline ? blankcount + 1 : 0;
+               }
        } else {
                if (lnblank)
-                       putc('\n', stdout);
+                       fputs(newline, output);
                exitstat = 1;
                delcount += 1;
+               blankcount = 0;
        }
+       if (debugging)
+               fflush(output);
 }
 
 /*
@@ -501,25 +583,61 @@ flushline(bool keep)
 static void
 process(void)
 {
-       Linetype lineval;
-
+       /* When compressing blank lines, act as if the file
+          is preceded by a large number of blank lines. */
+       blankmax = blankcount = 1000;
        for (;;) {
-               linenum++;
-               lineval = getline();
+               Linetype lineval = parseline();
                trans_table[ifstate[depth]][lineval]();
-               debug("process %s -> %s depth %d",
-                   linetype_name[lineval],
+               debug("process line %d %s -> %s depth %d",
+                   linenum, linetype_name[lineval],
                    ifstate_name[ifstate[depth]], depth);
        }
 }
 
 /*
+ * Flush the output and handle errors.
+ */
+static void
+closeout(void)
+{
+       if (symdepth && !zerosyms)
+               printf("\n");
+       if (fclose(output) == EOF) {
+               warn("couldn't write to %s", ofilename);
+               if (overwriting) {
+                       unlink(tempname);
+                       errx(2, "%s unchanged", filename);
+               } else {
+                       exit(2);
+               }
+       }
+}
+
+/*
+ * Clean up and exit.
+ */
+static void
+done(void)
+{
+       if (incomment)
+               error("EOF in comment");
+       closeout();
+       if (overwriting && rename(tempname, ofilename) == -1) {
+               warn("couldn't rename temporary file");
+               unlink(tempname);
+               errx(2, "%s unchanged", ofilename);
+       }
+       exit(exitstat);
+}
+
+/*
  * Parse a line and determine its type. We keep the preprocessor line
  * parser state between calls in the global variable linestate, with
  * help from skipcomment().
  */
 static Linetype
-getline(void)
+parseline(void)
 {
        const char *cp;
        int cursym;
@@ -527,14 +645,22 @@ getline(void)
        Linetype retval;
        Comment_state wascomment;
 
+       linenum++;
        if (fgets(tline, MAXLINE, input) == NULL)
                return (LT_EOF);
+       if (newline == NULL) {
+               if (strrchr(tline, '\n') == strrchr(tline, '\r') + 1)
+                       newline = newline_crlf;
+               else
+                       newline = newline_unix;
+       }
        retval = LT_PLAIN;
        wascomment = incomment;
        cp = skipcomment(tline);
        if (linestate == LS_START) {
                if (*cp == '#') {
                        linestate = LS_HASH;
+                       firstsym = true;
                        cp = skipcomment(cp + 1);
                } else if (*cp != '\0')
                        linestate = LS_DIRTY;
@@ -544,7 +670,8 @@ getline(void)
                cp = skipsym(cp);
                kwlen = cp - keyword;
                /* no way can we deal with a continuation inside a keyword */
-               if (strncmp(cp, "\\\n", 2) == 0)
+               if (strncmp(cp, "\\\r\n", 3) == 0 ||
+                   strncmp(cp, "\\\n", 2) == 0)
                        Eioccc();
                if (strlcmp("ifdef", keyword, kwlen) == 0 ||
                    strlcmp("ifndef", keyword, kwlen) == 0) {
@@ -588,32 +715,66 @@ getline(void)
                        if (incomment)
                                linestate = LS_DIRTY;
                }
-               /* skipcomment should have changed the state */
-               if (linestate == LS_HASH)
-                       abort(); /* bug */
+               /* skipcomment normally changes the state, except
+                  if the last line of the file lacks a newline, or
+                  if there is too much whitespace in a directive */
+               if (linestate == LS_HASH) {
+                       size_t len = cp - tline;
+                       if (fgets(tline + len, MAXLINE - len, input) == NULL) {
+                               /* append the missing newline */
+                               strcpy(tline + len, newline);
+                               cp += strlen(newline);
+                               linestate = LS_START;
+                       } else {
+                               linestate = LS_DIRTY;
+                       }
+               }
        }
        if (linestate == LS_DIRTY) {
                while (*cp != '\0')
                        cp = skipcomment(cp + 1);
        }
-       debug("parser %s comment %s line",
+       debug("parser line %d state %s comment %s line", linenum,
            comment_name[incomment], linestate_name[linestate]);
        return (retval);
 }
 
 /*
  * These are the binary operators that are supported by the expression
- * evaluator. Note that if support for division is added then we also
- * need short-circuiting booleans because of divide-by-zero.
+ * evaluator.
  */
-static int op_lt(int a, int b) { return (a < b); }
-static int op_gt(int a, int b) { return (a > b); }
-static int op_le(int a, int b) { return (a <= b); }
-static int op_ge(int a, int b) { return (a >= b); }
-static int op_eq(int a, int b) { return (a == b); }
-static int op_ne(int a, int b) { return (a != b); }
-static int op_or(int a, int b) { return (a || b); }
-static int op_and(int a, int b) { return (a && b); }
+static Linetype op_strict(int *p, int v, Linetype at, Linetype bt) {
+       if(at == LT_IF || bt == LT_IF) return (LT_IF);
+       return (*p = v, v ? LT_TRUE : LT_FALSE);
+}
+static Linetype op_lt(int *p, Linetype at, int a, Linetype bt, int b) {
+       return op_strict(p, a < b, at, bt);
+}
+static Linetype op_gt(int *p, Linetype at, int a, Linetype bt, int b) {
+       return op_strict(p, a > b, at, bt);
+}
+static Linetype op_le(int *p, Linetype at, int a, Linetype bt, int b) {
+       return op_strict(p, a <= b, at, bt);
+}
+static Linetype op_ge(int *p, Linetype at, int a, Linetype bt, int b) {
+       return op_strict(p, a >= b, at, bt);
+}
+static Linetype op_eq(int *p, Linetype at, int a, Linetype bt, int b) {
+       return op_strict(p, a == b, at, bt);
+}
+static Linetype op_ne(int *p, Linetype at, int a, Linetype bt, int b) {
+       return op_strict(p, a != b, at, bt);
+}
+static Linetype op_or(int *p, Linetype at, int a, Linetype bt, int b) {
+       if (!strictlogic && (at == LT_TRUE || bt == LT_TRUE))
+               return (*p = 1, LT_TRUE);
+       return op_strict(p, a || b, at, bt);
+}
+static Linetype op_and(int *p, Linetype at, int a, Linetype bt, int b) {
+       if (!strictlogic && (at == LT_FALSE || bt == LT_FALSE))
+               return (*p = 0, LT_FALSE);
+       return op_strict(p, a && b, at, bt);
+}
 
 /*
  * An evaluation function takes three arguments, as follows: (1) a pointer to
@@ -622,8 +783,8 @@ static int op_and(int a, int b) { return (a && b); }
  * value of the expression; and (3) a pointer to a char* that points to the
  * expression to be evaluated and that is updated to the end of the expression
  * when evaluation is complete. The function returns LT_FALSE if the value of
- * the expression is zero, LT_TRUE if it is non-zero, or LT_IF if the
- * expression could not be evaluated.
+ * the expression is zero, LT_TRUE if it is non-zero, LT_IF if the expression
+ * depends on an unknown symbol, or LT_ERROR if there is a parse failure.
  */
 struct ops;
 
@@ -642,7 +803,7 @@ static const struct ops {
        eval_fn *inner;
        struct op {
                const char *str;
-               int (*fn)(int, int);
+               Linetype (*fn)(int *, Linetype, int, Linetype, int);
        } op[5];
 } eval_ops[] = {
        { eval_table, { { "||", op_or } } },
@@ -657,8 +818,8 @@ static const struct ops {
 
 /*
  * Function for evaluating the innermost parts of expressions,
- * viz. !expr (expr) defined(symbol) symbol number
- * We reset the keepthis flag when we find a non-constant subexpression.
+ * viz. !expr (expr) number defined(symbol) symbol
+ * We reset the constexpr flag in the last two cases.
  */
 static Linetype
 eval_unary(const struct ops *ops, int *valp, const char **cpp)
@@ -666,63 +827,83 @@ eval_unary(const struct ops *ops, int *valp, const char **cpp)
        const char *cp;
        char *ep;
        int sym;
+       bool defparen;
+       Linetype lt;
 
        cp = skipcomment(*cpp);
        if (*cp == '!') {
                debug("eval%d !", ops - eval_ops);
                cp++;
-               if (eval_unary(ops, valp, &cp) == LT_IF)
-                       return (LT_IF);
-               *valp = !*valp;
+               lt = eval_unary(ops, valp, &cp);
+               if (lt == LT_ERROR)
+                       return (LT_ERROR);
+               if (lt != LT_IF) {
+                       *valp = !*valp;
+                       lt = *valp ? LT_TRUE : LT_FALSE;
+               }
        } else if (*cp == '(') {
                cp++;
                debug("eval%d (", ops - eval_ops);
-               if (eval_table(eval_ops, valp, &cp) == LT_IF)
-                       return (LT_IF);
+               lt = eval_table(eval_ops, valp, &cp);
+               if (lt == LT_ERROR)
+                       return (LT_ERROR);
                cp = skipcomment(cp);
                if (*cp++ != ')')
-                       return (LT_IF);
+                       return (LT_ERROR);
        } else if (isdigit((unsigned char)*cp)) {
                debug("eval%d number", ops - eval_ops);
                *valp = strtol(cp, &ep, 0);
+               if (ep == cp)
+                       return (LT_ERROR);
+               lt = *valp ? LT_TRUE : LT_FALSE;
                cp = skipsym(cp);
        } else if (strncmp(cp, "defined", 7) == 0 && endsym(cp[7])) {
                cp = skipcomment(cp+7);
                debug("eval%d defined", ops - eval_ops);
-               if (*cp++ != '(')
-                       return (LT_IF);
-               cp = skipcomment(cp);
+               if (*cp == '(') {
+                       cp = skipcomment(cp+1);
+                       defparen = true;
+               } else {
+                       defparen = false;
+               }
                sym = findsym(cp);
-               if (sym < 0)
-                       return (LT_IF);
-               *valp = (value[sym] != NULL);
+               if (sym < 0) {
+                       lt = LT_IF;
+               } else {
+                       *valp = (value[sym] != NULL);
+                       lt = *valp ? LT_TRUE : LT_FALSE;
+               }
                cp = skipsym(cp);
                cp = skipcomment(cp);
-               if (*cp++ != ')')
-                       return (LT_IF);
-               keepthis = false;
+               if (defparen && *cp++ != ')')
+                       return (LT_ERROR);
+               constexpr = false;
        } else if (!endsym(*cp)) {
                debug("eval%d symbol", ops - eval_ops);
                sym = findsym(cp);
-               if (sym < 0)
-                       return (LT_IF);
-               if (value[sym] == NULL)
+               cp = skipsym(cp);
+               if (sym < 0) {
+                       lt = LT_IF;
+                       cp = skipargs(cp);
+               } else if (value[sym] == NULL) {
                        *valp = 0;
-               else {
+                       lt = LT_FALSE;
+               } else {
                        *valp = strtol(value[sym], &ep, 0);
                        if (*ep != '\0' || ep == value[sym])
-                               return (LT_IF);
+                               return (LT_ERROR);
+                       lt = *valp ? LT_TRUE : LT_FALSE;
+                       cp = skipargs(cp);
                }
-               cp = skipsym(cp);
-               keepthis = false;
+               constexpr = false;
        } else {
                debug("eval%d bad expr", ops - eval_ops);
-               return (LT_IF);
+               return (LT_ERROR);
        }
 
        *cpp = cp;
        debug("eval%d = %d", ops - eval_ops, *valp);
-       return (*valp ? LT_TRUE : LT_FALSE);
+       return (lt);
 }
 
 /*
@@ -734,11 +915,13 @@ eval_table(const struct ops *ops, int *valp, const char **cpp)
        const struct op *op;
        const char *cp;
        int val;
+       Linetype lt, rt;
 
        debug("eval%d", ops - eval_ops);
        cp = *cpp;
-       if (ops->inner(ops+1, valp, &cp) == LT_IF)
-               return (LT_IF);
+       lt = ops->inner(ops+1, valp, &cp);
+       if (lt == LT_ERROR)
+               return (LT_ERROR);
        for (;;) {
                cp = skipcomment(cp);
                for (op = ops->op; op->str != NULL; op++)
@@ -748,14 +931,16 @@ eval_table(const struct ops *ops, int *valp, const char **cpp)
                        break;
                cp += strlen(op->str);
                debug("eval%d %s", ops - eval_ops, op->str);
-               if (ops->inner(ops+1, &val, &cp) == LT_IF)
-                       return (LT_IF);
-               *valp = op->fn(*valp, val);
+               rt = ops->inner(ops+1, &val, &cp);
+               if (rt == LT_ERROR)
+                       return (LT_ERROR);
+               lt = op->fn(valp, lt, *valp, rt, val);
        }
 
        *cpp = cp;
        debug("eval%d = %d", ops - eval_ops, *valp);
-       return (*valp ? LT_TRUE : LT_FALSE);
+       debug("eval%d lt = %s", ops - eval_ops, linetype_name[lt]);
+       return (lt);
 }
 
 /*
@@ -767,13 +952,13 @@ static Linetype
 ifeval(const char **cpp)
 {
        int ret;
-       int val;
+       int val = 0;
 
        debug("eval %s", *cpp);
-       keepthis = killconsts ? false : true;
+       constexpr = killconsts ? false : true;
        ret = eval_table(eval_ops, &val, cpp);
        debug("eval = %d", val);
-       return (keepthis ? LT_IF : ret);
+       return (constexpr ? LT_IF : ret == LT_ERROR ? LT_IF : ret);
 }
 
 /*
@@ -794,11 +979,16 @@ skipcomment(const char *cp)
        }
        while (*cp != '\0')
                /* don't reset to LS_START after a line continuation */
-               if (strncmp(cp, "\\\n", 2) == 0)
+               if (strncmp(cp, "\\\r\n", 3) == 0)
+                       cp += 3;
+               else if (strncmp(cp, "\\\n", 2) == 0)
                        cp += 2;
                else switch (incomment) {
                case NO_COMMENT:
-                       if (strncmp(cp, "/\\\n", 3) == 0) {
+                       if (strncmp(cp, "/\\\r\n", 4) == 0) {
+                               incomment = STARTING_COMMENT;
+                               cp += 4;
+                       } else if (strncmp(cp, "/\\\n", 3) == 0) {
                                incomment = STARTING_COMMENT;
                                cp += 3;
                        } else if (strncmp(cp, "/*", 2) == 0) {
@@ -818,7 +1008,7 @@ skipcomment(const char *cp)
                        } else if (strncmp(cp, "\n", 1) == 0) {
                                linestate = LS_START;
                                cp += 1;
-                       } else if (strchr(" \t", *cp) != NULL) {
+                       } else if (strchr(" \r\t", *cp) != NULL) {
                                cp += 1;
                        } else
                                return (cp);
@@ -850,7 +1040,10 @@ skipcomment(const char *cp)
                                cp += 1;
                        continue;
                case C_COMMENT:
-                       if (strncmp(cp, "*\\\n", 3) == 0) {
+                       if (strncmp(cp, "*\\\r\n", 4) == 0) {
+                               incomment = FINISHING_COMMENT;
+                               cp += 4;
+                       } else if (strncmp(cp, "*\\\n", 3) == 0) {
                                incomment = FINISHING_COMMENT;
                                cp += 3;
                        } else if (strncmp(cp, "*/", 2) == 0) {
@@ -885,6 +1078,31 @@ skipcomment(const char *cp)
 }
 
 /*
+ * Skip macro arguments.
+ */
+static const char *
+skipargs(const char *cp)
+{
+       const char *ocp = cp;
+       int level = 0;
+       cp = skipcomment(cp);
+       if (*cp != '(')
+               return (cp);
+       do {
+               if (*cp == '(')
+                       level++;
+               if (*cp == ')')
+                       level--;
+               cp = skipcomment(cp+1);
+       } while (level != 0 && *cp != '\0');
+       if (level == 0)
+               return (cp);
+       else
+       /* Rewind and re-detect the syntax error later. */
+               return (ocp);
+}
+
+/*
  * Skip over an identifier.
  */
 static const char *
@@ -896,7 +1114,7 @@ skipsym(const char *cp)
 }
 
 /*
- * Look for the symbol in the symbol table. If is is found, we return
+ * Look for the symbol in the symbol table. If it is found, we return
  * the symbol table index, else we return -1.
  */
 static int
@@ -909,7 +1127,13 @@ findsym(const char *str)
        if (cp == str)
                return (-1);
        if (symlist) {
-               printf("%.*s\n", (int)(cp-str), str);
+               if (symdepth && firstsym)
+                       printf("%s%3d", zerosyms ? "" : "\n", depth);
+               firstsym = zerosyms = false;
+               printf("%s%.*s%s",
+                   symdepth ? " " : "",
+                   (int)(cp-str), str,
+                   symdepth ? "" : "\n");
                /* we don't care about the value of the symbol */
                return (0);
        }
@@ -946,7 +1170,7 @@ addsym(bool ignorethis, bool definethis, char *sym)
                        value[symind] = val+1;
                        *val = '\0';
                } else if (*val == '\0')
-                       value[symind] = "";
+                       value[symind] = "1";
                else
                        usage();
        } else {
@@ -954,6 +1178,8 @@ addsym(bool ignorethis, bool definethis, char *sym)
                        usage();
                value[symind] = NULL;
        }
+       debug("addsym %s=%s", symname[symind],
+           value[symind] ? value[symind] : "undef");
 }
 
 /*
@@ -994,5 +1220,6 @@ error(const char *msg)
        else
                warnx("%s: %d: %s (#if line %d depth %d)",
                    filename, linenum, msg, stifline[depth], depth);
+       closeout();
        errx(2, "output may be truncated");
 }