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