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