diff options
author | hselasky <hselasky@FreeBSD.org> | 2015-03-05 09:39:29 +0000 |
---|---|---|
committer | hselasky <hselasky@FreeBSD.org> | 2015-03-05 09:39:29 +0000 |
commit | cce776ae0744f38846584229b374afb9d7fe8c11 (patch) | |
tree | 849fb6ba87edd4704332cd329d0d105d219bc896 /usr.bin | |
parent | f4065a3aafdcb030be51284b3e3456650378f3d4 (diff) | |
download | FreeBSD-src-cce776ae0744f38846584229b374afb9d7fe8c11.zip FreeBSD-src-cce776ae0744f38846584229b374afb9d7fe8c11.tar.gz |
MFC r279297:
Update to upstream version 2.10
The most notable new feature is support for definition files.
Obtained from: http://dotat.at/prog/unifdef
Diffstat (limited to 'usr.bin')
-rw-r--r-- | usr.bin/unifdef/unifdef.1 | 129 | ||||
-rw-r--r-- | usr.bin/unifdef/unifdef.c | 560 | ||||
-rw-r--r-- | usr.bin/unifdef/unifdef.h | 4 | ||||
-rw-r--r-- | usr.bin/unifdef/unifdefall.sh | 34 |
4 files changed, 486 insertions, 241 deletions
diff --git a/usr.bin/unifdef/unifdef.1 b/usr.bin/unifdef/unifdef.1 index d362eaf..7b63694 100644 --- a/usr.bin/unifdef/unifdef.1 +++ b/usr.bin/unifdef/unifdef.1 @@ -31,7 +31,7 @@ .\" .\" $FreeBSD$ .\" -.Dd February 21, 2012 +.Dd January 7, 2014 .Dt UNIFDEF 1 PRM .Os " " .Sh NAME @@ -44,6 +44,7 @@ .Op Fl [i]D Ns Ar sym Ns Op = Ns Ar val .Op Fl [i]U Ns Ar sym .Ar ... +.Op Fl f Ar defile .Op Fl x Bro Ar 012 Brc .Op Fl M Ar backext .Op Fl o Ar outfile @@ -70,11 +71,17 @@ utility acts on .Ic #elif , #else , and .Ic #endif -lines. -A directive is only processed -if the symbols specified on the command line are sufficient to allow -.Nm -to get a definite value for its control expression. +lines, +using macros specified in +.Fl D +and +.Fl U +command line options or in +.Fl f +definitions files. +A directive is processed +if the macro specifications are sufficient to provide +a definite value for its control expression. If the result is false, the directive and the following lines under its control are removed. If the result is true, @@ -84,7 +91,7 @@ An or .Ic #ifndef directive is passed through unchanged -if its controlling symbol is not specified on the command line. +if its controlling macro is not specified. Any .Ic #if or @@ -110,7 +117,7 @@ and .Ic #elif lines: integer constants, -integer values of symbols defined on the command line, +integer values of macros defined on the command line, the .Fn defined operator, @@ -131,16 +138,42 @@ if either operand of .Ic || is definitely true then the result is true. .Pp -In most cases, the +When evaluating an expression, .Nm -utility does not distinguish between object-like macros -(without arguments) and function-like arguments (with arguments). -If a macro is not explicitly defined, or is defined with the +does not expand macros first. +The value of a macro must be a simple number, +not an expression. +A limited form of indirection is allowed, +where one macro's value is the name of another. +.Pp +In most cases, +.Nm +does not distinguish between object-like macros +(without arguments) and function-like macros (with arguments). +A function-like macro invocation can appear in +.Ic #if +and +.Ic #elif +control expressions. +If the macro is not explicitly defined, +or is defined with the .Fl D -flag on the command-line, its arguments are ignored. +flag on the command-line, +or with +.Ic #define +in a +.Fl f +definitions file, +its arguments are ignored. If a macro is explicitly undefined on the command line with the .Fl U -flag, it may not have any arguments since this leads to a syntax error. +flag, +or with +.Ic #undef +in a +.Fl f +definitions file, +it may not have any arguments since this leads to a syntax error. .Pp The .Nm @@ -161,7 +194,7 @@ It uses .Nm Fl s and .Nm cpp Fl dM -to get lists of all the controlling symbols +to get lists of all the controlling macros and their definitions (or lack thereof), then invokes .Nm @@ -169,19 +202,15 @@ with appropriate arguments to process the file. .Sh OPTIONS .Bl -tag -width indent -compact .It Fl D Ns Ar sym Ns = Ns Ar val -Specify that a symbol is defined to a given value -which is used when evaluating -.Ic #if -and -.Ic #elif -control expressions. +Specify that a macro is defined to a given value. .Pp .It Fl D Ns Ar sym -Specify that a symbol is defined to the value 1. +Specify that a macro is defined to the value 1. .Pp .It Fl U Ns Ar sym -Specify that a symbol is undefined. -If the same symbol appears in more than one argument, +Specify that a macro is undefined. +.Pp +If the same macro appears in more than one argument, the last occurrence dominates. .Pp .It Fl iD Ns Ar sym Ns Op = Ns Ar val @@ -193,9 +222,37 @@ are ignored within and .Ic #ifndef blocks -controlled by symbols +controlled by macros specified with these options. .Pp +.It Fl f Ar defile +The file +.Ar defile +contains +.Ic #define +and +.Ic #undef +preprocessor directives, +which have the same effect as the corresponding +.Fl D +and +.Fl U +command-line arguments. +You can have multiple +.Fl f +arguments and mix them with +.Fl D +and +.Fl U +arguments; +later options override earlier ones. +.Pp +Each directive must be on a single line. +Object-like macro definitions (without arguments) +are set to the given value. +Function-like macro definitions (with arguments) +are treated as if they are set to 1. +.Pp .It Fl b Replace removed lines with blank lines instead of deleting them. @@ -291,24 +348,15 @@ instead of the standard output when processing a single file. Instead of processing an input file as usual, this option causes .Nm -to produce a list of symbols that appear in expressions -that -.Nm -understands. -It is useful in conjunction with the -.Fl dM -option of -.Xr cpp 1 -for creating -.Nm -command lines. +to produce a list of macros that are used in +preprocessor directive controlling expressions. .Pp .It Fl S Like the .Fl s -option, but the nesting depth of each symbol is also printed. +option, but the nesting depth of each macro is also printed. This is useful for working out the number of possible combinations -of interdependent defined/undefined symbols. +of interdependent defined/undefined macros. .Pp .It Fl t Disables parsing for C strings, comments, @@ -406,6 +454,9 @@ in comment. .Sh SEE ALSO .Xr cpp 1 , .Xr diff 1 +.Pp +The unifdef home page is +.Pa http://dotat.at/prog/unifdef .Sh HISTORY The .Nm @@ -430,7 +481,7 @@ cannot be handled in every situation. .Pp Trigraphs are not recognized. .Pp -There is no support for symbols with different definitions at +There is no support for macros with different definitions at different points in the source file. .Pp The text-mode and ignore functionality does not correspond to modern diff --git a/usr.bin/unifdef/unifdef.c b/usr.bin/unifdef/unifdef.c index 22f81ef..fd378a1 100644 --- a/usr.bin/unifdef/unifdef.c +++ b/usr.bin/unifdef/unifdef.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2002 - 2013 Tony Finch <dot@dotat.at> + * Copyright (c) 2002 - 2014 Tony Finch <dot@dotat.at> * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -46,7 +46,7 @@ #include "unifdef.h" static const char copyright[] = - "@(#) $Version: unifdef-2.7 $\n" + "@(#) $Version: unifdef-2.10 $\n" "@(#) $FreeBSD$\n" "@(#) $Author: Tony Finch (dot@dotat.at) $\n" "@(#) $URL: http://dotat.at/prog/unifdef $\n" @@ -138,7 +138,7 @@ static char const * const linestate_name[] = { */ #define MAXDEPTH 64 /* maximum #if nesting */ #define MAXLINE 4096 /* maximum length of line */ -#define MAXSYMS 4096 /* maximum number of symbols */ +#define MAXSYMS 16384 /* maximum number of symbols */ /* * Sometimes when editing a keyword the replacement text is longer, so @@ -180,6 +180,15 @@ static char *tempname; /* avoid splatting input */ static char tline[MAXLINE+EDITSLOP];/* input buffer plus space */ static char *keyword; /* used for editing #elif's */ +/* + * When processing a file, the output's newline style will match the + * input's, and unifdef correctly handles CRLF or LF endings whatever + * the platform's native style. The stdio streams are opened in binary + * mode to accommodate platforms whose native newline style is CRLF. + * When the output isn't a processed input file (when it is error / + * debug / diagnostic messages) then unifdef uses native line endings. + */ + static const char *newline; /* input file format */ static const char newline_unix[] = "\n"; static const char newline_crlf[] = "\r\n"; @@ -200,33 +209,41 @@ static bool firstsym; /* ditto */ static int exitmode; /* exit status mode */ static int exitstat; /* program exit status */ -static void addsym(bool, bool, char *); +static void addsym1(bool, bool, char *); +static void addsym2(bool, const char *, const char *); static char *astrcat(const char *, const char *); static void cleantemp(void); -static void closeout(void); +static void closeio(void); static void debug(const char *, ...); +static void debugsym(const char *, int); +static bool defundef(void); +static void defundefile(const char *); static void done(void); static void error(const char *); -static int findsym(const char *); +static int findsym(const char **); static void flushline(bool); static void hashline(void); static void help(void); static Linetype ifeval(const char **); static void ignoreoff(void); static void ignoreon(void); +static void indirectsym(void); static void keywordedit(const char *); +static const char *matchsym(const char *, const char *); static void nest(void); static Linetype parseline(void); static void process(void); static void processinout(const char *, const char *); static const char *skipargs(const char *); static const char *skipcomment(const char *); +static const char *skiphash(void); +static const char *skipline(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); +static const char *xstrdup(const char *, const char *); #define endsym(c) (!isalnum((unsigned char)c) && c != '_') @@ -238,7 +255,7 @@ main(int argc, char *argv[]) { int opt; - while ((opt = getopt(argc, argv, "i:D:U:I:M:o:x:bBcdehKklmnsStV")) != -1) + while ((opt = getopt(argc, argv, "i:D:U:f:I:M:o:x:bBcdehKklmnsStV")) != -1) switch (opt) { case 'i': /* treat stuff controlled by these symbols as text */ /* @@ -248,17 +265,17 @@ main(int argc, char *argv[]) */ opt = *optarg++; if (opt == 'D') - addsym(true, true, optarg); + addsym1(true, true, optarg); else if (opt == 'U') - addsym(true, false, optarg); + addsym1(true, false, optarg); else usage(); break; case 'D': /* define a symbol */ - addsym(false, true, optarg); + addsym1(false, true, optarg); break; case 'U': /* undef a symbol */ - addsym(false, false, optarg); + addsym1(false, false, optarg); break; case 'I': /* no-op for compatibility with cpp */ break; @@ -278,6 +295,9 @@ main(int argc, char *argv[]) case 'e': /* fewer errors from dodgy lines */ iocccok = true; break; + case 'f': /* definitions file */ + defundefile(optarg); + break; case 'h': help(); break; @@ -334,6 +354,7 @@ main(int argc, char *argv[]) argc = 1; if (argc == 1 && !inplace && ofilename == NULL) ofilename = "-"; + indirectsym(); atexit(cleantemp); if (ofilename != NULL) @@ -392,10 +413,10 @@ processinout(const char *ifn, const char *ofn) if (backext != NULL) { char *backname = astrcat(ofn, backext); if (rename(ofn, backname) < 0) - err(2, "can't rename \"%s\" to \"%s%s\"", ofn, ofn, backext); + err(2, "can't rename \"%s\" to \"%s\"", ofn, backname); free(backname); } - if (rename(tempname, ofn) < 0) + if (replace(tempname, ofn) < 0) err(2, "can't rename \"%s\" to \"%s\"", tempname, ofn); free(tempname); tempname = NULL; @@ -434,7 +455,7 @@ synopsis(FILE *fp) { fprintf(fp, "usage: unifdef [-bBcdehKkmnsStV] [-x{012}] [-Mext] [-opath] \\\n" - " [-[i]Dsym[=val]] [-[i]Usym] ... [file] ...\n"); + " [-[i]Dsym[=val]] [-[i]Usym] [-fpath] ... [file] ...\n"); } static void @@ -455,6 +476,7 @@ help(void) " -iDsym=val \\ ignore C strings and comments\n" " -iDsym ) in sections controlled by these\n" " -iUsym / preprocessor symbols\n" + " -fpath file containing #define and #undef directives\n" " -b blank lines instead of deleting them\n" " -B compress blank lines around deleted section\n" " -c complement (invert) keep vs. delete\n" @@ -650,12 +672,12 @@ done(void) { if (incomment) error("EOF in comment"); - closeout(); + closeio(); } /* * Write a line to the output or not, according to command line options. - * If writing fails, closeout() will print the error and exit. + * If writing fails, closeio() will print the error and exit. */ static void flushline(bool keep) @@ -671,19 +693,19 @@ flushline(bool keep) if (lnnum && delcount > 0) hashline(); if (fputs(tline, output) == EOF) - closeout(); + closeio(); delcount = 0; blankmax = blankcount = blankline ? blankcount + 1 : 0; } } else { if (lnblank && fputs(newline, output) == EOF) - closeout(); + closeio(); exitstat = 1; delcount += 1; blankcount = 0; } if (debugging && fflush(output) == EOF) - closeout(); + closeio(); } /* @@ -700,20 +722,21 @@ hashline(void) e = fprintf(output, "#line %d \"%s\"%s", linenum, linefile, newline); if (e < 0) - closeout(); + closeio(); } /* * Flush the output and handle errors. */ static void -closeout(void) +closeio(void) { /* Tidy up after findsym(). */ if (symdepth && !zerosyms) printf("\n"); - if (ferror(output) || fclose(output) == EOF) - err(2, "%s: can't write to output", filename); + if (output != NULL && (ferror(output) || fclose(output) == EOF)) + err(2, "%s: can't write to output", filename); + fclose(input); } /* @@ -727,6 +750,8 @@ process(void) is preceded by a large number of blank lines. */ blankmax = blankcount = 1000; zerosyms = true; + newline = NULL; + linenum = 0; while (lineval != LT_EOF) { lineval = parseline(); trans_table[ifstate[depth]][lineval](); @@ -746,105 +771,86 @@ parseline(void) { const char *cp; int cursym; - int kwlen; Linetype retval; Comment_state wascomment; - linenum++; - if (fgets(tline, MAXLINE, input) == NULL) { - if (ferror(input)) - err(2, "can't read %s", filename); - else - return (LT_EOF); - } + wascomment = incomment; + cp = skiphash(); + if (cp == 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; + if (*cp == '\0') { + retval = LT_PLAIN; + goto done; } - if (!incomment && linestate == LS_HASH) { - keyword = tline + (cp - tline); - cp = skipsym(cp); - kwlen = cp - keyword; + keyword = tline + (cp - tline); + if ((cp = matchsym("ifdef", keyword)) != NULL || + (cp = matchsym("ifndef", keyword)) != NULL) { + cp = skipcomment(cp); + if ((cursym = findsym(&cp)) < 0) + retval = LT_IF; + else { + retval = (keyword[2] == 'n') + ? LT_FALSE : LT_TRUE; + if (value[cursym] == NULL) + retval = (retval == LT_TRUE) + ? LT_FALSE : LT_TRUE; + if (ignore[cursym]) + retval = (retval == LT_TRUE) + ? LT_TRUEI : LT_FALSEI; + } + } else if ((cp = matchsym("if", keyword)) != NULL) + retval = ifeval(&cp); + else if ((cp = matchsym("elif", keyword)) != NULL) + retval = linetype_if2elif(ifeval(&cp)); + else if ((cp = matchsym("else", keyword)) != NULL) + retval = LT_ELSE; + else if ((cp = matchsym("endif", keyword)) != NULL) + retval = LT_ENDIF; + else { + cp = skipsym(keyword); /* no way can we deal with a continuation inside a keyword */ if (strncmp(cp, "\\\r\n", 3) == 0 || strncmp(cp, "\\\n", 2) == 0) Eioccc(); - if (strlcmp("ifdef", keyword, kwlen) == 0 || - strlcmp("ifndef", keyword, kwlen) == 0) { - cp = skipcomment(cp); - if ((cursym = findsym(cp)) < 0) - retval = LT_IF; - else { - retval = (keyword[2] == 'n') - ? LT_FALSE : LT_TRUE; - if (value[cursym] == NULL) - retval = (retval == LT_TRUE) - ? LT_FALSE : LT_TRUE; - if (ignore[cursym]) - retval = (retval == LT_TRUE) - ? LT_TRUEI : LT_FALSEI; - } - cp = skipsym(cp); - } else if (strlcmp("if", keyword, kwlen) == 0) - retval = ifeval(&cp); - else if (strlcmp("elif", keyword, kwlen) == 0) - retval = linetype_if2elif(ifeval(&cp)); - else if (strlcmp("else", keyword, kwlen) == 0) - retval = LT_ELSE; - else if (strlcmp("endif", keyword, kwlen) == 0) - retval = LT_ENDIF; - else { - linestate = LS_DIRTY; - retval = LT_PLAIN; - } - cp = skipcomment(cp); - if (*cp != '\0') { + cp = skipline(cp); + retval = LT_PLAIN; + goto done; + } + cp = skipcomment(cp); + if (*cp != '\0') { + cp = skipline(cp); + if (retval == LT_TRUE || retval == LT_FALSE || + retval == LT_TRUEI || retval == LT_FALSEI) + retval = LT_IF; + if (retval == LT_ELTRUE || retval == LT_ELFALSE) + retval = LT_ELIF; + } + /* the following can happen if the last line of the file lacks a + newline or if there is too much whitespace in a directive */ + if (linestate == LS_HASH) { + long len = cp - tline; + if (fgets(tline + len, MAXLINE - len, input) == NULL) { + if (ferror(input)) + err(2, "can't read %s", filename); + /* append the missing newline at eof */ + strcpy(tline + len, newline); + cp += strlen(newline); + linestate = LS_START; + } else { linestate = LS_DIRTY; - if (retval == LT_TRUE || retval == LT_FALSE || - retval == LT_TRUEI || retval == LT_FALSEI) - retval = LT_IF; - if (retval == LT_ELTRUE || retval == LT_ELFALSE) - retval = LT_ELIF; - } - if (retval != LT_PLAIN && (wascomment || incomment)) { - retval = linetype_2dodgy(retval); - if (incomment) - linestate = LS_DIRTY; - } - /* 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) { - if (ferror(input)) - err(2, "can't read %s", filename); - /* append the missing newline at eof */ - 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); + if (retval != LT_PLAIN && (wascomment || linestate != LS_START)) { + retval = linetype_2dodgy(retval); + linestate = LS_DIRTY; } +done: debug("parser line %d state %s comment %s line", linenum, comment_name[incomment], linestate_name[linestate]); return (retval); @@ -854,34 +860,34 @@ parseline(void) * These are the binary operators that are supported by the expression * evaluator. */ -static Linetype op_strict(int *p, int v, Linetype at, Linetype bt) { +static Linetype op_strict(long *p, long 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) { +static Linetype op_lt(long *p, Linetype at, long a, Linetype bt, long b) { return op_strict(p, a < b, at, bt); } -static Linetype op_gt(int *p, Linetype at, int a, Linetype bt, int b) { +static Linetype op_gt(long *p, Linetype at, long a, Linetype bt, long b) { return op_strict(p, a > b, at, bt); } -static Linetype op_le(int *p, Linetype at, int a, Linetype bt, int b) { +static Linetype op_le(long *p, Linetype at, long a, Linetype bt, long b) { return op_strict(p, a <= b, at, bt); } -static Linetype op_ge(int *p, Linetype at, int a, Linetype bt, int b) { +static Linetype op_ge(long *p, Linetype at, long a, Linetype bt, long b) { return op_strict(p, a >= b, at, bt); } -static Linetype op_eq(int *p, Linetype at, int a, Linetype bt, int b) { +static Linetype op_eq(long *p, Linetype at, long a, Linetype bt, long b) { return op_strict(p, a == b, at, bt); } -static Linetype op_ne(int *p, Linetype at, int a, Linetype bt, int b) { +static Linetype op_ne(long *p, Linetype at, long a, Linetype bt, long b) { return op_strict(p, a != b, at, bt); } -static Linetype op_or(int *p, Linetype at, int a, Linetype bt, int b) { +static Linetype op_or(long *p, Linetype at, long a, Linetype bt, long 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) { +static Linetype op_and(long *p, Linetype at, long a, Linetype bt, long b) { if (!strictlogic && (at == LT_FALSE || bt == LT_FALSE)) return (*p = 0, LT_FALSE); return op_strict(p, a && b, at, bt); @@ -899,7 +905,7 @@ static Linetype op_and(int *p, Linetype at, int a, Linetype bt, int b) { */ struct ops; -typedef Linetype eval_fn(const struct ops *, int *, const char **); +typedef Linetype eval_fn(const struct ops *, long *, const char **); static eval_fn eval_table, eval_unary; @@ -912,7 +918,7 @@ static eval_fn eval_table, eval_unary; */ struct op { const char *str; - Linetype (*fn)(int *, Linetype, int, Linetype, int); + Linetype (*fn)(long *, Linetype, long, Linetype, long); }; struct ops { eval_fn *inner; @@ -930,7 +936,7 @@ static const struct ops eval_ops[] = { }; /* Current operator precedence level */ -static int prec(const struct ops *ops) +static long prec(const struct ops *ops) { return (ops - eval_ops); } @@ -941,7 +947,7 @@ static int prec(const struct ops *ops) * We reset the constexpr flag in the last two cases. */ static Linetype -eval_unary(const struct ops *ops, int *valp, const char **cpp) +eval_unary(const struct ops *ops, long *valp, const char **cpp) { const char *cp; char *ep; @@ -975,32 +981,33 @@ eval_unary(const struct ops *ops, int *valp, const char **cpp) 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 = ep; + } else if (matchsym("defined", cp) != NULL) { cp = skipcomment(cp+7); - debug("eval%d defined", prec(ops)); if (*cp == '(') { cp = skipcomment(cp+1); defparen = true; } else { defparen = false; } - sym = findsym(cp); + sym = findsym(&cp); + cp = skipcomment(cp); + if (defparen && *cp++ != ')') { + debug("eval%d defined missing ')'", prec(ops)); + return (LT_ERROR); + } if (sym < 0) { + debug("eval%d defined unknown", prec(ops)); lt = LT_IF; } else { + debug("eval%d defined %s", prec(ops), symname[sym]); *valp = (value[sym] != NULL); lt = *valp ? LT_TRUE : LT_FALSE; } - cp = skipsym(cp); - cp = skipcomment(cp); - if (defparen && *cp++ != ')') - return (LT_ERROR); constexpr = false; } else if (!endsym(*cp)) { debug("eval%d symbol", prec(ops)); - sym = findsym(cp); - cp = skipsym(cp); + sym = findsym(&cp); if (sym < 0) { lt = LT_IF; cp = skipargs(cp); @@ -1029,11 +1036,11 @@ eval_unary(const struct ops *ops, int *valp, const char **cpp) * Table-driven evaluation of binary operators. */ static Linetype -eval_table(const struct ops *ops, int *valp, const char **cpp) +eval_table(const struct ops *ops, long *valp, const char **cpp) { const struct op *op; const char *cp; - int val; + long val; Linetype lt, rt; debug("eval%d", prec(ops)); @@ -1071,7 +1078,7 @@ static Linetype ifeval(const char **cpp) { Linetype ret; - int val = 0; + long val = 0; debug("eval %s", *cpp); constexpr = killconsts ? false : true; @@ -1081,6 +1088,49 @@ ifeval(const char **cpp) } /* + * Read a line and examine its initial part to determine if it is a + * preprocessor directive. Returns NULL on EOF, or a pointer to a + * preprocessor directive name, or a pointer to the zero byte at the + * end of the line. + */ +static const char * +skiphash(void) +{ + const char *cp; + + linenum++; + if (fgets(tline, MAXLINE, input) == NULL) { + if (ferror(input)) + err(2, "can't read %s", filename); + else + return (NULL); + } + cp = skipcomment(tline); + if (linestate == LS_START && *cp == '#') { + linestate = LS_HASH; + return (skipcomment(cp + 1)); + } else if (*cp == '\0') { + return (cp); + } else { + return (skipline(cp)); + } +} + +/* + * Mark a line dirty and consume the rest of it, keeping track of the + * lexical state. + */ +static const char * +skipline(const char *cp) +{ + if (*cp != '\0') + linestate = LS_DIRTY; + while (*cp != '\0') + cp = skipcomment(cp + 1); + return (cp); +} + +/* * Skip over comments, strings, and character literals and stop at the * next character position that is not whitespace. Between calls we keep * the comment state in the global variable incomment, and we also adjust @@ -1233,33 +1283,69 @@ skipsym(const char *cp) } /* + * Skip whitespace and take a copy of any following identifier. + */ +static const char * +getsym(const char **cpp) +{ + const char *cp = *cpp, *sym; + + cp = skipcomment(cp); + cp = skipsym(sym = cp); + if (cp == sym) + return NULL; + *cpp = cp; + return (xstrdup(sym, cp)); +} + +/* + * Check that s (a symbol) matches the start of t, and that the + * following character in t is not a symbol character. Returns a + * pointer to the following character in t if there is a match, + * otherwise NULL. + */ +static const char * +matchsym(const char *s, const char *t) +{ + while (*s != '\0' && *t != '\0') + if (*s != *t) + return (NULL); + else + ++s, ++t; + if (*s == '\0' && endsym(*t)) + return(t); + else + return(NULL); +} + +/* * Look for the symbol in the symbol table. If it is found, we return * the symbol table index, else we return -1. */ static int -findsym(const char *str) +findsym(const char **strp) { - const char *cp; + const char *str; int symind; - cp = skipsym(str); - if (cp == str) - return (-1); + str = *strp; + *strp = skipsym(str); if (symlist) { + if (*strp == str) + return (-1); if (symdepth && firstsym) printf("%s%3d", zerosyms ? "" : "\n", depth); firstsym = zerosyms = false; printf("%s%.*s%s", - symdepth ? " " : "", - (int)(cp-str), str, - symdepth ? "" : "\n"); + symdepth ? " " : "", + (int)(*strp-str), str, + symdepth ? "" : "\n"); /* we don't care about the value of the symbol */ return (0); } for (symind = 0; symind < nsyms; ++symind) { - if (strlcmp(symname[symind], str, cp-str) == 0) { - debug("findsym %s %s", symname[symind], - value[symind] ? value[symind] : ""); + if (matchsym(symname[symind], str) != NULL) { + debugsym("findsym", symind); return (symind); } } @@ -1267,53 +1353,155 @@ findsym(const char *str) } /* + * Resolve indirect symbol values to their final definitions. + */ +static void +indirectsym(void) +{ + const char *cp; + int changed, sym, ind; + + do { + changed = 0; + for (sym = 0; sym < nsyms; ++sym) { + if (value[sym] == NULL) + continue; + cp = value[sym]; + ind = findsym(&cp); + if (ind == -1 || ind == sym || + *cp != '\0' || + value[ind] == NULL || + value[ind] == value[sym]) + continue; + debugsym("indir...", sym); + value[sym] = value[ind]; + debugsym("...ectsym", sym); + changed++; + } + } while (changed); +} + +/* + * Add a symbol to the symbol table, specified with the format sym=val + */ +static void +addsym1(bool ignorethis, bool definethis, char *symval) +{ + const char *sym, *val; + + sym = symval; + val = skipsym(sym); + if (definethis && *val == '=') { + symval[val - sym] = '\0'; + val = val + 1; + } else if (*val == '\0') { + val = definethis ? "1" : NULL; + } else { + usage(); + } + addsym2(ignorethis, sym, val); +} + +/* * Add a symbol to the symbol table. */ static void -addsym(bool ignorethis, bool definethis, char *sym) +addsym2(bool ignorethis, const char *sym, const char *val) { + const char *cp = sym; int symind; - char *val; - symind = findsym(sym); + symind = findsym(&cp); if (symind < 0) { if (nsyms >= MAXSYMS) errx(2, "too many symbols"); symind = nsyms++; } - symname[symind] = sym; ignore[symind] = ignorethis; - val = sym + (skipsym(sym) - sym); - if (definethis) { - if (*val == '=') { - value[symind] = val+1; - *val = '\0'; - } else if (*val == '\0') - value[symind] = "1"; - else - usage(); - } else { - if (*val != '\0') - usage(); - value[symind] = NULL; - } - debug("addsym %s=%s", symname[symind], + symname[symind] = sym; + value[symind] = val; + debugsym("addsym", symind); +} + +static void +debugsym(const char *why, int symind) +{ + debug("%s %s%c%s", why, symname[symind], + value[symind] ? '=' : ' ', value[symind] ? value[symind] : "undef"); } /* - * Compare s with n characters of t. - * The same as strncmp() except that it checks that s[n] == '\0'. + * Add symbols to the symbol table from a file containing + * #define and #undef preprocessor directives. */ -static int -strlcmp(const char *s, const char *t, size_t n) +static void +defundefile(const char *fn) { - while (n-- && *t != '\0') - if (*s != *t) - return ((unsigned char)*s - (unsigned char)*t); - else - ++s, ++t; - return ((unsigned char)*s); + filename = fn; + input = fopen(fn, "rb"); + if (input == NULL) + err(2, "can't open %s", fn); + linenum = 0; + while (defundef()) + ; + if (ferror(input)) + err(2, "can't read %s", filename); + else + fclose(input); + if (incomment) + error("EOF in comment"); +} + +/* + * Read and process one #define or #undef directive + */ +static bool +defundef(void) +{ + const char *cp, *kw, *sym, *val, *end; + + cp = skiphash(); + if (cp == NULL) + return (false); + if (*cp == '\0') + goto done; + /* strip trailing whitespace, and do a fairly rough check to + avoid unsupported multi-line preprocessor directives */ + end = cp + strlen(cp); + while (end > tline && strchr(" \t\n\r", end[-1]) != NULL) + --end; + if (end > tline && end[-1] == '\\') + Eioccc(); + + kw = cp; + if ((cp = matchsym("define", kw)) != NULL) { + sym = getsym(&cp); + if (sym == NULL) + error("missing macro name in #define"); + if (*cp == '(') { + val = "1"; + } else { + cp = skipcomment(cp); + val = (cp < end) ? xstrdup(cp, end) : ""; + } + debug("#define"); + addsym2(false, sym, val); + } else if ((cp = matchsym("undef", kw)) != NULL) { + sym = getsym(&cp); + if (sym == NULL) + error("missing macro name in #undef"); + cp = skipcomment(cp); + debug("#undef"); + addsym2(false, sym, NULL); + } else { + error("unrecognized preprocessor directive"); + } + skipline(cp); +done: + debug("parser line %d state %s comment %s line", linenum, + comment_name[incomment], linestate_name[linestate]); + return (true); } /* @@ -1324,12 +1512,34 @@ astrcat(const char *s1, const char *s2) { char *s; int len; + size_t size; + + len = snprintf(NULL, 0, "%s%s", s1, s2); + if (len < 0) + err(2, "snprintf"); + size = (size_t)len + 1; + s = (char *)malloc(size); + if (s == NULL) + err(2, "malloc"); + snprintf(s, size, "%s%s", s1, s2); + return (s); +} + +/* + * Duplicate a segment of a string, checking for failure. + */ +static const char * +xstrdup(const char *start, const char *end) +{ + size_t n; + char *s; - len = 1 + snprintf(NULL, 0, "%s%s", s1, s2); - s = (char *)malloc(len); + if (end < start) abort(); /* bug */ + n = (size_t)(end - start) + 1; + s = malloc(n); if (s == NULL) err(2, "malloc"); - snprintf(s, len, "%s%s", s1, s2); + snprintf(s, n, "%s", start); return (s); } @@ -1356,6 +1566,6 @@ error(const char *msg) else warnx("%s: %d: %s (#if line %d depth %d)", filename, linenum, msg, stifline[depth], depth); - closeout(); + closeio(); errx(2, "output may be truncated"); } diff --git a/usr.bin/unifdef/unifdef.h b/usr.bin/unifdef/unifdef.h index 8837b68..e2e0bd8 100644 --- a/usr.bin/unifdef/unifdef.h +++ b/usr.bin/unifdef/unifdef.h @@ -1,5 +1,5 @@ /* - * Copyright (c) 2012 Tony Finch <dot@dotat.at> + * Copyright (c) 2012 - 2013 Tony Finch <dot@dotat.at> * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -40,6 +40,8 @@ #define fbinmode(fp) (fp) +#define replace(old,new) rename(old,new) + static FILE * mktempmode(char *tmp, int mode) { diff --git a/usr.bin/unifdef/unifdefall.sh b/usr.bin/unifdef/unifdefall.sh index 6c8614b..6abf60b 100644 --- a/usr.bin/unifdef/unifdefall.sh +++ b/usr.bin/unifdef/unifdefall.sh @@ -47,32 +47,14 @@ trap 'rm -r "$tmp" || exit 2' EXIT export LC_ALL=C -# list of all controlling macros -"$unifdef" $debug -s "$@" | sort | uniq >"$tmp/ctrl" +# list of all controlling macros; assume these are undefined +"$unifdef" $debug -s "$@" | sort -u | sed 's/^/#undef /' >"$tmp/undefs" # list of all macro definitions -cpp -dM "$@" | sort | sed 's/^#define //' >"$tmp/hashdefs" -# list of defined macro names -sed 's/[^A-Za-z0-9_].*$//' <"$tmp/hashdefs" >"$tmp/alldef" -# list of undefined and defined controlling macros -comm -23 "$tmp/ctrl" "$tmp/alldef" >"$tmp/undef" -comm -12 "$tmp/ctrl" "$tmp/alldef" >"$tmp/def" -# create a sed script that extracts the controlling macro definitions -# and converts them to unifdef command-line arguments -sed 's|.*|s/^&\\(([^)]*)\\)\\{0,1\\} /-D&=/p|' <"$tmp/def" >"$tmp/script" -# create the final unifdef command -{ echo "$unifdef" $debug -k '\' - # convert the controlling undefined macros to -U arguments - sed 's/.*/-U& \\/' <"$tmp/undef" - # convert the controlling defined macros to quoted -D arguments - sed -nf "$tmp/script" <"$tmp/hashdefs" | - sed "s/'/'\\\\''/g;s/.*/'&' \\\\/" - echo '"$@"' -} >"$tmp/cmd" +cc -E -dM "$@" | sort >"$tmp/defs" + case $debug in --d) for i in ctrl hashdefs alldef undef def script cmd - do echo ==== $i - cat "$tmp/$i" - done 1>&2 +-d) cat "$tmp/undefs" "$tmp/defs" 1>&2 esac -# run the command we just created -sh "$tmp/cmd" "$@" + +# order of -f arguments means definitions override undefs +"$unifdef" $debug -k -f "$tmp/undefs" -f "$tmp/defs" "$@" |