unifdef.c revision 87698
1/*
2 * Copyright (c) 1985, 1993
3 *	The Regents of the University of California.  All rights reserved.
4 *
5 * This code is derived from software contributed to Berkeley by
6 * Dave Yost.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 *    notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 *    notice, this list of conditions and the following disclaimer in the
15 *    documentation and/or other materials provided with the distribution.
16 * 3. All advertising materials mentioning features or use of this software
17 *    must display the following acknowledgement:
18 *	This product includes software developed by the University of
19 *	California, Berkeley and its contributors.
20 * 4. Neither the name of the University nor the names of its contributors
21 *    may be used to endorse or promote products derived from this software
22 *    without specific prior written permission.
23 *
24 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
25 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
28 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
29 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
30 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
31 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
33 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34 * SUCH DAMAGE.
35 */
36
37#include <sys/cdefs.h>
38
39__FBSDID("$FreeBSD: head/usr.bin/unifdef/unifdef.c 87698 2001-12-11 23:20:23Z markm $");
40
41#ifndef lint
42static const char copyright[] =
43"@(#) Copyright (c) 1985, 1993\n\
44	The Regents of the University of California.  All rights reserved.\n";
45#endif
46
47#ifndef lint
48static const char sccsid[] = "@(#)unifdef.c	8.1 (Berkeley) 6/6/93";
49#endif
50
51/*
52 * unifdef - remove ifdef'ed lines
53 *
54 *  Warning: will not work correctly if input contains null characters.
55 *
56 *  Wishlist:
57 *      provide an option which will append the name of the
58 *        appropriate symbol after #else's and #endif's
59 *      provide an option which will check symbols after
60 *        #else's and #endif's to see that they match their
61 *        corresponding #ifdef or #ifndef
62 */
63
64#include <ctype.h>
65#include <err.h>
66#include <stdio.h>
67#include <stdlib.h>
68#include <string.h>
69
70typedef int Reject_level;
71
72#define BSS
73FILE *input;
74#ifndef YES
75#define YES 1
76#define NO  0
77#endif/*YES */
78typedef int Bool;
79typedef int Linetype;
80
81const char *filename BSS;
82char text BSS;          /* -t option in effect: this is a text file */
83char lnblank BSS;       /* -l option in effect: blank deleted lines */
84char complement BSS;    /* -c option in effect: complement the operation */
85
86#define MAXSYMS 100
87char *symname[MAXSYMS] BSS; /* symbol name */
88char true[MAXSYMS] BSS;     /* -Dsym */
89char ignore[MAXSYMS] BSS;   /* -iDsym or -iUsym */
90char insym[MAXSYMS] BSS;    /* state: false, inactive, true */
91#define SYM_INACTIVE 0      /* symbol is currently inactive */
92#define SYM_FALSE    1      /* symbol is currently false */
93#define SYM_TRUE     2      /* symbol is currently true  */
94
95char nsyms BSS;
96char incomment BSS;         /* inside C comment */
97
98#define QUOTE_NONE   0
99#define QUOTE_SINGLE 1
100#define QUOTE_DOUBLE 2
101char inquote BSS;           /* inside single or double quotes */
102
103int exitstat BSS;
104char *skipcomment __P((char *));
105char *skipquote __P((char *, int));
106static void usage __P((void));
107void flushline __P((Bool));
108int getlin __P((char *, int, FILE *, int));
109int error __P((int, int, int));
110void pfile __P((void));
111int doif __P((int, int, Reject_level, int));
112int findsym __P((char *));
113Linetype checkline __P((int *));
114
115int
116main (argc, argv)
117int argc;
118char **argv;
119{
120    char **curarg;
121    char *cp;
122    char *cp1;
123    char ignorethis;
124
125    for (curarg = &argv[1]; --argc > 0; curarg++) {
126	if (*(cp1 = cp = *curarg) != '-')
127	    break;
128	if (*++cp1 == 'i') {
129	    ignorethis = YES;
130	    cp1++;
131	} else
132	    ignorethis = NO;
133	if (   (   *cp1 == 'D'
134		|| *cp1 == 'U'
135	       )
136	    && cp1[1] != '\0'
137	   ) {
138	    int symind;
139
140	    if ((symind = findsym (&cp1[1])) < 0) {
141		if (nsyms >= MAXSYMS)
142		    errx(2, "too many symbols");
143		symind = nsyms++;
144		symname[symind] = &cp1[1];
145		insym[symind] = SYM_INACTIVE;
146	    }
147	    ignore[symind] = ignorethis;
148	    true[symind] = *cp1 == 'D' ? YES : NO;
149	} else if (ignorethis)
150	    goto unrec;
151	else if (strcmp (&cp[1], "t") == 0)
152	    text = YES;
153	else if (strcmp (&cp[1], "l") == 0)
154	    lnblank = YES;
155	else if (strcmp (&cp[1], "c") == 0)
156	    complement = YES;
157	else {
158 unrec:
159	    warnx("unrecognized option: %s", cp);
160	    usage();
161	}
162    }
163    if (nsyms == 0)
164		usage();
165
166    if (argc > 1) {
167	warnx("can only do one file");
168    } else if (argc == 1) {
169	filename = *curarg;
170	if ((input = fopen (filename, "r")) != NULL) {
171	    pfile();
172	    (void) fclose (input);
173	} else {
174	    warn("can't open %s", *curarg);
175	}
176    } else {
177	filename = "[stdin]";
178	input = stdin;
179	pfile();
180    }
181
182    (void) fflush (stdout);
183    exit (exitstat);
184}
185
186static void
187usage()
188{
189	fprintf (stderr, "usage: %s",
190"unifdef [-l] [-t] [-c] [[-Dsym] [-Usym] [-iDsym] [-iUsym]] ... [file]\n");
191	exit (2);
192}
193
194/* types of input lines: */
195#define LT_PLAIN       0   /* ordinary line */
196#define LT_TRUE        1   /* a true  #ifdef of a symbol known to us */
197#define LT_FALSE       2   /* a false #ifdef of a symbol known to us */
198#define LT_OTHER       3   /* an #ifdef of a symbol not known to us */
199#define LT_IF          4   /* an #ifdef of a symbol not known to us */
200#define LT_ELSE        5   /* #else */
201#define LT_ENDIF       6   /* #endif */
202#define LT_LEOF        7   /* end of file */
203
204Reject_level reject BSS;    /* 0 or 1: pass thru; 1 or 2: ignore comments */
205#define REJ_NO          0
206#define REJ_IGNORE      1
207#define REJ_YES         2
208
209int linenum BSS;    /* current line number */
210int stqcline BSS;   /* start of current coment or quote */
211const char *errs[] = {
212#define NO_ERR      0
213			"",
214#define END_ERR     1
215			"",
216#define ELSE_ERR    2
217			"Inappropriate else",
218#define ENDIF_ERR   3
219			"Inappropriate endif",
220#define IEOF_ERR    4
221			"Premature EOF in ifdef",
222#define CEOF_ERR    5
223			"Premature EOF in comment",
224#define Q1EOF_ERR   6
225			"Premature EOF in quoted character",
226#define Q2EOF_ERR   7
227			"Premature EOF in quoted string"
228};
229
230/* States for inif arg to doif */
231#define IN_NONE 0
232#define IN_IF   1
233#define IN_ELSE 2
234
235void
236pfile ()
237{
238    reject = REJ_NO;
239    (void) doif (-1, IN_NONE, reject, 0);
240}
241
242int
243doif (thissym, inif, prevreject, depth)
244int thissym;   /* index of the symbol who was last ifdef'ed */
245int inif;               /* YES or NO we are inside an ifdef */
246Reject_level prevreject;/* previous value of reject */
247int depth;              /* depth of ifdef's */
248{
249    Linetype lineval;
250    Reject_level thisreject;
251    int doret;          /* tmp return value of doif */
252    int cursym;         /* index of the symbol returned by checkline */
253    int stline;         /* line number when called this time */
254
255    stline = linenum;
256    for (;;) {
257	switch (lineval = checkline (&cursym)) {
258	case LT_PLAIN:
259	    flushline (YES);
260	    break;
261
262	case LT_TRUE:
263	case LT_FALSE:
264	    thisreject = reject;
265	    if (lineval == LT_TRUE)
266		insym[cursym] = SYM_TRUE;
267	    else {
268		if (reject != REJ_YES)
269		    reject = ignore[cursym] ? REJ_IGNORE : REJ_YES;
270		insym[cursym] = SYM_FALSE;
271	    }
272	    if (ignore[cursym])
273		flushline (YES);
274	    else {
275		exitstat = 1;
276		flushline (NO);
277	    }
278	    if ((doret = doif (cursym, IN_IF, thisreject, depth + 1)) != NO_ERR)
279		return error (doret, stline, depth);
280	    break;
281
282	case LT_IF:
283	case LT_OTHER:
284	    flushline (YES);
285	    if ((doret = doif (-1, IN_IF, reject, depth + 1)) != NO_ERR)
286		return error (doret, stline, depth);
287	    break;
288
289	case LT_ELSE:
290	    if (inif != IN_IF)
291		return error (ELSE_ERR, linenum, depth);
292	    inif = IN_ELSE;
293	    if (thissym >= 0) {
294		if (insym[thissym] == SYM_TRUE) {
295		    reject = ignore[thissym] ? REJ_IGNORE : REJ_YES;
296		    insym[thissym] = SYM_FALSE;
297		} else { /* (insym[thissym] == SYM_FALSE) */
298		    reject = prevreject;
299		    insym[thissym] = SYM_TRUE;
300		}
301		if (!ignore[thissym]) {
302		    flushline (NO);
303		    break;
304		}
305	    }
306	    flushline (YES);
307	    break;
308
309	case LT_ENDIF:
310	    if (inif == IN_NONE)
311		return error (ENDIF_ERR, linenum, depth);
312	    if (thissym >= 0) {
313		insym[thissym] = SYM_INACTIVE;
314		reject = prevreject;
315		if (!ignore[thissym]) {
316		    flushline (NO);
317		    return NO_ERR;
318		}
319	    }
320	    flushline (YES);
321	    return NO_ERR;
322
323	case LT_LEOF: {
324	    int lerr;
325	    lerr =   incomment
326		  ? CEOF_ERR
327		  : inquote == QUOTE_SINGLE
328		  ? Q1EOF_ERR
329		  : inquote == QUOTE_DOUBLE
330		  ? Q2EOF_ERR
331		  : NO_ERR;
332	    if (inif != IN_NONE) {
333		if (lerr != NO_ERR)
334		    (void) error (lerr, stqcline, depth);
335		return error (IEOF_ERR, stline, depth);
336	    } else if (lerr != NO_ERR)
337		return error (lerr, stqcline, depth);
338	    else
339		return NO_ERR;
340	    }
341	}
342    }
343}
344
345#define endsym(c) (!isalpha (c) && !isdigit (c) && c != '_')
346
347#define MAXLINE 256
348char tline[MAXLINE] BSS;
349
350Linetype
351checkline (cursym)
352int *cursym;    /* if LT_TRUE or LT_FALSE returned, set this to sym index */
353{
354    char *cp;
355    char *symp;
356    char *scp;
357    Linetype retval;
358#   define KWSIZE 8
359    char keyword[KWSIZE];
360
361    linenum++;
362    if (getlin (tline, sizeof tline, input, NO) == EOF)
363	return LT_LEOF;
364
365    retval = LT_PLAIN;
366    if (   *(cp = tline) != '#'
367	|| incomment
368	|| inquote == QUOTE_SINGLE
369	|| inquote == QUOTE_DOUBLE
370       )
371	goto eol;
372
373    cp = skipcomment (++cp);
374    symp = keyword;
375    while (!endsym (*cp)) {
376	*symp = *cp++;
377	if (++symp >= &keyword[KWSIZE])
378	    goto eol;
379    }
380    *symp = '\0';
381
382    if (strcmp (keyword, "ifdef") == 0) {
383	retval = YES;
384	goto ifdef;
385    } else if (strcmp (keyword, "ifndef") == 0) {
386	retval = NO;
387 ifdef:
388	scp = cp = skipcomment (++cp);
389	if (incomment) {
390	    retval = LT_PLAIN;
391	    goto eol;
392	}
393	{
394	    int symind;
395
396	    if ((symind = findsym (scp)) >= 0)
397		retval = (retval ^ true[*cursym = symind])
398			 ? LT_FALSE : LT_TRUE;
399	    else
400		retval = LT_OTHER;
401	}
402    } else if (strcmp (keyword, "if") == 0)
403	retval = LT_IF;
404    else if (strcmp (keyword, "else") == 0)
405	retval = LT_ELSE;
406    else if (strcmp (keyword, "endif") == 0)
407	retval = LT_ENDIF;
408
409 eol:
410    if (!text && reject != REJ_IGNORE)
411	for (; *cp; ) {
412	    if (incomment)
413		cp = skipcomment (cp);
414	    else if (inquote == QUOTE_SINGLE)
415		cp = skipquote (cp, QUOTE_SINGLE);
416	    else if (inquote == QUOTE_DOUBLE)
417		cp = skipquote (cp, QUOTE_DOUBLE);
418	    else if (*cp == '/' && cp[1] == '*')
419		cp = skipcomment (cp);
420	    else if (*cp == '\'')
421		cp = skipquote (cp, QUOTE_SINGLE);
422	    else if (*cp == '"')
423		cp = skipquote (cp, QUOTE_DOUBLE);
424	    else
425		cp++;
426	}
427    return retval;
428}
429
430/*
431 *  Skip over comments and stop at the next charaacter
432 *  position that is not whitespace.
433 */
434char *
435skipcomment (cp)
436char *cp;
437{
438    if (incomment)
439	goto inside;
440    for (;; cp++) {
441	while (*cp == ' ' || *cp == '\t')
442	    cp++;
443	if (text)
444	    return cp;
445	if (   cp[0] != '/'
446	    || cp[1] != '*'
447	   )
448	    return cp;
449	cp += 2;
450	if (!incomment) {
451	    incomment = YES;
452	    stqcline = linenum;
453	}
454 inside:
455	for (;;) {
456	    for (; *cp != '*'; cp++)
457		if (*cp == '\0')
458		    return cp;
459	    if (*++cp == '/') {
460		incomment = NO;
461		break;
462	    }
463	}
464    }
465}
466
467/*
468 *  Skip over a quoted string or character and stop at the next charaacter
469 *  position that is not whitespace.
470 */
471char *
472skipquote (cp, type)
473char *cp;
474int type;
475{
476    char qchar;
477
478    qchar = type == QUOTE_SINGLE ? '\'' : '"';
479
480    if (inquote == type)
481	goto inside;
482    for (;; cp++) {
483	if (*cp != qchar)
484	    return cp;
485	cp++;
486	inquote = type;
487	stqcline = linenum;
488 inside:
489	for (; ; cp++) {
490	    if (*cp == qchar)
491		break;
492	    if (   *cp == '\0'
493		|| (*cp == '\\' && *++cp == '\0')
494	       )
495		return cp;
496	}
497	inquote = QUOTE_NONE;
498    }
499}
500
501/*
502 *  findsym - look for the symbol in the symbol table.
503 *            if found, return symbol table index,
504 *            else return -1.
505 */
506int
507findsym (str)
508char *str;
509{
510    char *cp;
511    char *symp;
512    int symind;
513    char chr;
514
515    for (symind = 0; symind < nsyms; ++symind) {
516	if (insym[symind] == SYM_INACTIVE) {
517	    for ( symp = symname[symind], cp = str
518		; *symp && *cp == *symp
519		; cp++, symp++
520		)
521		continue;
522	    chr = *cp;
523	    if (*symp == '\0' && endsym (chr))
524		return symind;
525	}
526    }
527    return -1;
528}
529
530/*
531 *   getlin - expands tabs if asked for
532 *            and (if compiled in) treats form-feed as an end-of-line
533 */
534int
535getlin (line, maxline, inp, expandtabs)
536char *line;
537int maxline;
538FILE *inp;
539int expandtabs;
540{
541    int tmp;
542    int num;
543    int chr;
544#ifdef  FFSPECIAL
545    static char havechar = NO;  /* have leftover char from last time */
546    static char svchar BSS;
547#endif/*FFSPECIAL */
548
549    num = 0;
550#ifdef  FFSPECIAL
551    if (havechar) {
552	havechar = NO;
553	chr = svchar;
554	goto ent;
555    }
556#endif/*FFSPECIAL */
557    while (num + 8 < maxline) {   /* leave room for tab */
558	chr = getc (inp);
559	if (isprint (chr)) {
560#ifdef  FFSPECIAL
561 ent:
562#endif/*FFSPECIAL */
563	    *line++ = chr;
564	    num++;
565	} else
566	    switch (chr) {
567	    case EOF:
568		return EOF;
569
570	    case '\t':
571		if (expandtabs) {
572		    num += tmp = 8 - (num & 7);
573		    do
574			*line++ = ' ';
575		    while (--tmp);
576		    break;
577		}
578	    default:
579		*line++ = chr;
580		num++;
581		break;
582
583	    case '\n':
584		*line = '\n';
585		num++;
586		goto end;
587
588#ifdef  FFSPECIAL
589	    case '\f':
590		if (++num == 1)
591		    *line = '\f';
592		else {
593		    *line = '\n';
594		    havechar = YES;
595		    svchar = chr;
596		}
597		goto end;
598#endif/*FFSPECIAL */
599	    }
600    }
601 end:
602    *++line = '\0';
603    return num;
604}
605
606void
607flushline (keep)
608Bool keep;
609{
610    if ((keep && reject != REJ_YES) ^ complement) {
611	char *line = tline;
612	FILE *out = stdout;
613	char chr;
614
615	while ((chr = *line++))
616	    putc (chr, out);
617    } else if (lnblank)
618	putc ('\n', stdout);
619}
620
621int
622error (lerr, line, depth)
623int lerr;        /* type of error & index into error string array */
624int line;       /* line number */
625int depth;      /* how many ifdefs we are inside */
626{
627    if (lerr == END_ERR)
628	return lerr;
629
630#ifndef TESTING
631    warnx("error in %s line %d: %s", filename, line, errs[lerr]);
632#else/* TESTING */
633    warnx("error in %s line %d: %s. ifdef depth: %d",
634			filename, line, errs[lerr], depth);
635#endif/*TESTING */
636
637    exitstat = 2;
638    return depth > 1 ? IEOF_ERR : END_ERR;
639}
640