unifdef: update to upstream revision 1.190
authorTony Finch <dot@dotat.at>
Fri, 27 Nov 2009 15:50:30 +0000 (15:50 +0000)
committerMichal Marek <mmarek@suse.cz>
Sat, 12 Dec 2009 12:08:16 +0000 (13:08 +0100)
Fix handling of input files (e.g. with no newline at EOF) that could
make unifdef get into an unexpected state and call abort().

The new -B option compresses blank lines around a deleted section
so that blank lines around "paragraphs" of code don't get doubled.

The evaluator can now handle macros with arguments, and unbracketed
arguments to the "defined" operator.

Add myself to MAINTAINERS for unifdef.

Signed-off-by: Tony Finch <dot@dotat.at>
Acked-by: Sam Ravnborg <sam@ravnborg.org>
Signed-off-by: Michal Marek <mmarek@suse.cz>
MAINTAINERS
scripts/unifdef.c

index d58fa703ec16bedcb919fc314bf8466daa755e8b..94381eddccfa707c3198476feb6a86fb00ac07c5 100644 (file)
@@ -5407,6 +5407,12 @@ F:       drivers/uwb/*
 F:     include/linux/uwb.h
 F:     include/linux/uwb/
 
+UNIFDEF
+M:     Tony Finch <dot@dotat.at>
+W:     http://dotat.at/prog/unifdef
+S:     Maintained
+F:     scripts/unifdef.c
+
 UNIFORM CDROM DRIVER
 M:     Jens Axboe <axboe@kernel.dk>
 W:     http://www.kernel.dk
index 30d459fb0709a70a97d6ddd5f8d635e8bb467490..44d39785e50da4a67bdde65a86e55b6a795e55b3 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 - 2009 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>
+/*
+ * 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.
+ *
+ * The latest version is available from http://dotat.at/prog/unifdef
+ */
 
-#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
+static const char * const copyright[] = {
+    "@(#) Copyright (c) 2002 - 2009 Tony Finch <dot@dotat.at>\n",
+    "$dotat: unifdef/unifdef.c,v 1.190 2009/11/27 17:21:26 fanf2 Exp $",
+};
 
 /*
  * unifdef - remove ifdef'ed lines
@@ -72,8 +61,6 @@ __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);
-
 /* types of input lines: */
 typedef enum {
        LT_TRUEI,               /* a true #if with ignore flag */
@@ -90,6 +77,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 +88,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 */
@@ -168,11 +156,13 @@ static char const * const linestate_name[] = {
  * 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             text;                  /* -t: this is a text file */
@@ -196,7 +186,9 @@ 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 int              exitstat;              /* program exit status */
 
@@ -206,13 +198,14 @@ static void             done(void);
 static void             error(const char *);
 static int              findsym(const char *);
 static void             flushline(bool);
-static Linetype         get_line(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);
@@ -220,7 +213,7 @@ static int              strlcmp(const char *, const char *, size_t);
 static void             unnest(void);
 static void             usage(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 +223,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:BbcdeKklnst")) != -1)
                switch (opt) {
                case 'i': /* treat stuff controlled by these symbols as text */
                        /*
@@ -255,6 +248,13 @@ main(int argc, char *argv[])
                case 'I':
                        /* no-op for compatibility with cpp */
                        break;
+               case 'B': /* compress blank lines around removed section */
+                       compblank = true;
+                       break;
+               case 'b': /* blank deleted lines instead of omitting them */
+               case 'l': /* backwards compatibility */
+                       lnblank = true;
+                       break;
                case 'c': /* treat -D as -U and vice versa */
                        complement = true;
                        break;
@@ -264,12 +264,12 @@ 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;
@@ -284,6 +284,8 @@ main(int argc, char *argv[])
                }
        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) {
@@ -302,7 +304,7 @@ main(int argc, char *argv[])
 static void
 usage(void)
 {
-       fprintf(stderr, "usage: unifdef [-cdeklnst] [-Ipath]"
+       fprintf(stderr, "usage: unifdef [-BbcdeKknst] [-Ipath]"
            " [-Dsym[=val]] [-Usym] [-iDsym[=val]] [-iUsym] ... [file]\n");
        exit(2);
 }
@@ -383,46 +385,46 @@ 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 */
 };
 
 /*
@@ -463,9 +465,11 @@ keywordedit(const char *replacement)
 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
@@ -490,15 +494,23 @@ 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\n")] == '\0';
+               if (blankline && compblank && blankcount != blankmax) {
+                       delcount += 1;
+                       blankcount += 1;
+               } else {
+                       if (lnnum && delcount > 0)
+                               printf("#line %d\n", linenum);
+                       fputs(tline, stdout);
+                       delcount = 0;
+                       blankmax = blankcount = blankline ? blankcount + 1 : 0;
+               }
        } else {
                if (lnblank)
                        putc('\n', stdout);
                exitstat = 1;
                delcount += 1;
+               blankcount = 0;
        }
 }
 
@@ -510,9 +522,12 @@ 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 = get_line();
+               lineval = parseline();
                trans_table[ifstate[depth]][lineval]();
                debug("process %s -> %s depth %d",
                    linetype_name[lineval],
@@ -526,7 +541,7 @@ process(void)
  * help from skipcomment().
  */
 static Linetype
-get_line(void)
+parseline(void)
 {
        const char *cp;
        int cursym;
@@ -595,9 +610,21 @@ get_line(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 */
+                               tline[len+0] = '\n';
+                               tline[len+1] = '\0';
+                               cp++;
+                               linestate = LS_START;
+                       } else {
+                               linestate = LS_DIRTY;
+                       }
+               }
        }
        if (linestate == LS_DIRTY) {
                while (*cp != '\0')
@@ -610,17 +637,40 @@ get_line(void)
 
 /*
  * 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
@@ -629,8 +679,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;
 
@@ -649,7 +699,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 } } },
@@ -664,8 +714,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)
@@ -673,68 +723,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) {
-                       *cpp = cp;
-                       return (LT_IF);
+               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;
                }
-               *valp = !*valp;
        } 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);
-               cp = skipsym(cp);
-               cp = skipcomment(cp);
-               if (*cp++ != ')')
-                       return (LT_IF);
-               if (sym >= 0)
+               if (sym < 0) {
+                       lt = LT_IF;
+               } else {
                        *valp = (value[sym] != NULL);
-               else {
-                       *cpp = cp;
-                       return (LT_IF);
+                       lt = *valp ? LT_TRUE : LT_FALSE;
                }
-               keepthis = false;
+               cp = skipsym(cp);
+               cp = skipcomment(cp);
+               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);
 }
 
 /*
@@ -746,11 +811,13 @@ eval_table(const struct ops *ops, int *valp, const char **cpp)
        const struct op *op;
        const char *cp;
        int val;
-       Linetype lhs, rhs;
+       Linetype lt, rt;
 
        debug("eval%d", ops - eval_ops);
        cp = *cpp;
-       lhs = ops->inner(ops+1, valp, &cp);
+       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++)
@@ -760,32 +827,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);
-               rhs = ops->inner(ops+1, &val, &cp);
-               if (op->fn == op_and && (lhs == LT_FALSE || rhs == LT_FALSE)) {
-                       debug("eval%d: and always false", ops - eval_ops);
-                       if (lhs == LT_IF)
-                               *valp = val;
-                       lhs = LT_FALSE;
-                       continue;
-               }
-               if (op->fn == op_or && (lhs == LT_TRUE || rhs == LT_TRUE)) {
-                       debug("eval%d: or always true", ops - eval_ops);
-                       if (lhs == LT_IF)
-                               *valp = val;
-                       lhs = LT_TRUE;
-                       continue;
-               }
-               if (rhs == LT_IF)
-                       lhs = LT_IF;
-               if (lhs != 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);
-       if (lhs != LT_IF)
-               lhs = (*valp ? LT_TRUE : LT_FALSE);
-       return lhs;
+       debug("eval%d lt = %s", ops - eval_ops, linetype_name[lt]);
+       return (lt);
 }
 
 /*
@@ -796,17 +847,14 @@ eval_table(const struct ops *ops, int *valp, const char **cpp)
 static Linetype
 ifeval(const char **cpp)
 {
-       const char *cp = *cpp;
        int ret;
-       int val;
+       int val = 0;
 
        debug("eval %s", *cpp);
-       keepthis = killconsts ? false : true;
-       ret = eval_table(eval_ops, &val, &cp);
-       if (ret != LT_IF)
-               *cpp = cp;
+       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);
 }
 
 /*
@@ -917,6 +965,31 @@ skipcomment(const char *cp)
        return (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.
  */
@@ -929,7 +1002,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