/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */
/*
 * Copyright 2005 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */

/*	Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T	*/
/*	  All Rights Reserved  	*/

/* Copyright (c) 1982 Regents of the University of California */

#pragma ident	"%Z%%M%	%I%	%E% SMI"

/*
 *    unifdef - remove ifdef'ed lines
 */

#include <stdio.h>
#include <ctype.h>
#include <locale.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

FILE *input;
#ifndef YES
#define	YES 1
#define	NO  0
#endif

char *progname;
char *filename;
char text;		/* -t option in effect: this is a text file */
char lnblank;		/* -l option in effect: blank deleted lines */
char complement;	/* -c option in effect: complement the operation */
#define	MAXSYMS 100
char true[MAXSYMS];
char ignore[MAXSYMS];
char *sym[MAXSYMS];
signed char insym[MAXSYMS];
#define	KWSIZE 8
char buf[KWSIZE];
char nsyms;
char incomment;
#define	QUOTE1 0
#define	QUOTE2 1
char inquote[2];
int exitstat;

static char *skipcomment(char *cp);
static char *skipquote(char *cp, int type);
static char *nextsym(char *p);
static int doif(int thissym, int inif, int prevreject, int depth);
static void pfile(void);
static int getlin(char *line, int maxline, FILE *inp, int expandtabs);
static void prname(void);
static void flushline(int keep);
static int checkline(int *cursym);
static int error(int err, int line, int depth);
static void putlin(char *line, FILE *fio);

static void
usage(void)
{
	(void) fprintf(stderr, gettext(
	    "Usage: %s [-l] [-t] [-c] [[-Dsym] [-Usym] [-idsym] "
	    "[-iusym]]... [file]\n"
	    "    At least one arg from [-D -U -id -iu] is required\n"),
	    progname);
	exit(2);
}

int
main(int argc, char **argv)
{
	char **curarg;
	char *cp;
	char *cp1;
	char ignorethis;

	(void) setlocale(LC_ALL, "");

#if !defined(TEXT_DOMAIN)
#define	TEXT_DOMAIN "SYS_TEST"
#endif
	(void) textdomain(TEXT_DOMAIN);

	progname = argv[0][0] ? argv[0] : "unifdef";

	for (curarg = &argv[1]; --argc > 0; curarg++) {
		if (*(cp1 = cp = *curarg) != '-')
			break;
		if (*++cp1 == 'i') {
			ignorethis = YES;
			cp1++;
		} else
			ignorethis = NO;
		if ((*cp1 == 'D' || *cp1 == 'U') &&
		    cp1[1] != '\0') {
			if (nsyms >= MAXSYMS) {
				prname();
				(void) fprintf(stderr,
				    gettext("too many symbols.\n"));
				exit(2);
			}
			ignore[nsyms] = ignorethis;
			true[nsyms] = *cp1 == 'D' ? YES : NO;
			sym[nsyms++] = &cp1[1];
		} else if (ignorethis)
			goto unrec;
		else if (strcmp(&cp[1], "t") == 0)
			text = YES;
		else if (strcmp(&cp[1], "l") == 0)
			lnblank = YES;
		else if (strcmp(&cp[1], "c") == 0)
			complement = YES;
		else {
unrec:
			prname();
			(void) fprintf(stderr,
			    gettext("unrecognized option: %s\n"), cp);
			usage();
		}
	}
	if (nsyms == 0) {
		usage();
	}

	if (argc > 1) {
		prname();
		(void) fprintf(stderr, gettext("can only do one file.\n"));
	} else if (argc == 1) {
		filename = *curarg;
		if ((input = fopen(filename, "r")) != NULL) {
			pfile();
			(void) fclose(input);
		} else {
			prname();
			perror(*curarg);
		}
	} else {
		filename = "[stdin]";
		input = stdin;
		pfile();
	}

	(void) fflush(stdout);
	return (exitstat);
}

/* types of input lines: */
#define	PLAIN	0   /* ordinary line */
#define	TRUE	1   /* a true  #ifdef of a symbol known to us */
#define	FALSE	2   /* a false #ifdef of a symbol known to us */
#define	OTHER	3   /* an #ifdef of a symbol not known to us */
#define	ELSE	4   /* #else */
#define	ENDIF	5   /* #endif */
#define	LEOF	6   /* end of file */

/* should be int declaration, was char */
int reject;    /* 0 or 1: pass thru; 1 or 2: ignore comments */
int linenum;    /* current line number */
int stqcline;   /* start of current comment or quote */

char *errs[] = {
#define	NO_ERR		0
			"",
#define	END_ERR		1
			"",
#define	ELSE_ERR	2
			"Inappropriate else",
#define	ENDIF_ERR	3
			"Inappropriate endif",
#define	IEOF_ERR	4
			"Premature EOF in ifdef",
#define	CEOF_ERR	5
			"Premature EOF in comment",
#define	Q1EOF_ERR	6
			"Premature EOF in quoted character",
#define	Q2EOF_ERR	7
			"Premature EOF in quoted string"
};

static void
pfile(void)
{
	reject = 0;
	(void) doif(-1, NO, reject, 0);
}

static int
doif(
    int thissym,	/* index of the symbol who was last ifdef'ed */
    int inif,		/* YES or NO we are inside an ifdef */
    int prevreject,	/* previous value of reject */
    int depth		/* depth of ifdef's */
)
{
	int lineval;
	int thisreject;
	int doret;	/* tmp return value of doif */
	int cursym;	/* index of the symbol returned by checkline */
	int stline;	/* line number when called this time */
	int err;

	stline = linenum;
	for (;;) {
		switch (lineval = checkline(&cursym)) {
		case PLAIN:
			flushline(YES);
			break;

		case TRUE:
		case FALSE:
			thisreject = reject;
			if (lineval == TRUE)
				insym[cursym] = 1;
			else {
				if (reject < 2)
					reject = ignore[cursym] ? 1 : 2;
				insym[cursym] = -1;
			}
			if (ignore[cursym])
				flushline(YES);
			else {
				exitstat = 0;
				flushline(NO);
			}
			if ((doret = doif(cursym, YES,
			    thisreject, depth + 1)) != NO_ERR)
				return (error(doret, stline, depth));
			break;

		case OTHER:
			flushline(YES);
			if ((doret = doif(-1, YES,
			    reject, depth + 1)) != NO_ERR)
				return (error(doret, stline, depth));
			break;

		case ELSE:
			if (inif != 1)
				return (error(ELSE_ERR, linenum, depth));
			inif = 2;
			if (thissym >= 0) {
				if ((insym[thissym] = -insym[thissym]) < 0)
					reject = ignore[thissym] ? 1 : 2;
				else
					reject = prevreject;
				if (!ignore[thissym]) {
					flushline(NO);
					break;
				}
			}
			flushline(YES);
			break;

		case ENDIF:
			if (inif == 0)
				return (error(ENDIF_ERR, linenum, depth));
			if (thissym >= 0) {
				insym[thissym] = 0;
				reject = prevreject;
				if (!ignore[thissym]) {
					flushline(NO);
					return (NO_ERR);
				}
			}
			flushline(YES);
			return (NO_ERR);

		case LEOF:
			err = incomment
			    ? CEOF_ERR
			    : inquote[QUOTE1]
			    ? Q1EOF_ERR
			    : inquote[QUOTE2]
			    ? Q2EOF_ERR
			    : NO_ERR;
			if (inif) {
				if (err != NO_ERR)
					(void) error(err, stqcline, depth);
				return (error(IEOF_ERR, stline, depth));
			} else if (err != NO_ERR)
				return (error(err, stqcline, depth));
			else
				return (NO_ERR);
		}
	}
}

#define	endsym(c) (!isalpha(c) && !isdigit(c) && c != '_')

#define	MAXLINE 256
char tline[MAXLINE];

static int
checkline(int *cursym)
{
	char *cp;
	char *symp;
	char chr;
	char *scp;
	int retval;
	int symind;
	char keyword[KWSIZE];

	linenum++;
	if (getlin(tline, sizeof (tline), input, NO) == EOF)
		return (LEOF);

	retval = PLAIN;
	if (*(cp = tline) != '#' || incomment ||
	    inquote[QUOTE1] || inquote[QUOTE2])
		goto eol;

	cp = skipcomment(++cp);
	symp = keyword;
	while (!endsym (*cp)) {
		*symp = *cp++;
		if (++symp >= &keyword[KWSIZE])
			goto eol;
	}
	*symp = '\0';

	if (strcmp(keyword, "ifdef") == 0) {
		retval = YES;
		goto ifdef;
	} else if (strcmp(keyword, "if") == 0) {
		cp = skipcomment(++cp);
		if (strcmp(nextsym(cp), "defined") == 0) {
			cp += strlen("defined") + 1;
			/* skip to identifier */
			while (endsym(*cp))
				++cp;
			retval = YES;
			goto ifdef;
		} else {
			retval = OTHER;
			goto eol;
		}
	} else if (strcmp(keyword, "ifndef") == 0) {
		retval = NO;
ifdef:
		scp = cp = skipcomment(cp);
		if (incomment) {
			retval = PLAIN;
			goto eol;
		}
		symind = 0;
		for (;;) {
			if (insym[symind] == 0) {
				for (symp = sym[symind], cp = scp;
				*symp && *cp == *symp; cp++, symp++) {
					/* NULL */
				}
				chr = *cp;
				if (*symp == '\0' && endsym(chr)) {
					*cursym = symind;
					retval = (retval ^ true[symind]) ?
					    FALSE : TRUE;
					break;
				}
			}
			if (++symind >= nsyms) {
				retval = OTHER;
				break;
			}
		}
	} else if (strcmp(keyword, "else") == 0)
		retval = ELSE;
	else if (strcmp(keyword, "endif") == 0)
		retval = ENDIF;

eol:
	if (!text && !reject)
		while (*cp) {
			if (incomment)
				cp = skipcomment(cp);
			else if (inquote[QUOTE1])
				cp = skipquote(cp, QUOTE1);
			else if (inquote[QUOTE2])
				cp = skipquote(cp, QUOTE2);
			else if (*cp == '/' && cp[1] == '*')
				cp = skipcomment(cp);
			else if (*cp == '\'')
				cp = skipquote(cp, QUOTE1);
			else if (*cp == '"')
				cp = skipquote(cp, QUOTE2);
			else
				cp++;
		}
	return (retval);
}

/*
 * Skip over comments and stop at the next character
 *  position that is not whitespace.
 */
static char *
skipcomment(char *cp)
{
	if (incomment)
		goto inside;
	for (;;) {
		while (*cp == ' ' || *cp == '\t')
			cp++;
		if (text)
			return (cp);
		if (cp[0] != '/' || cp[1] != '*')
			return (cp);
		cp += 2;
		if (!incomment) {
			incomment = YES;
			stqcline = linenum;
		}
inside:
		for (;;) {
			for (; *cp != '*'; cp++)
				if (*cp == '\0')
					return (cp);
			if (*++cp == '/')
				break;
		}
		incomment = NO;
		cp++;
	}
}

/*
 * Skip over a quoted string or character and stop at the next charaacter
 *  position that is not whitespace.
 */
static char *
skipquote(char *cp, int type)
{
	char qchar;

	qchar = type == QUOTE1 ? '\'' : '"';

	if (inquote[type])
		goto inside;
	for (;;) {
		if (*cp != qchar)
			return (cp);
		cp++;
		if (!inquote[type]) {
			inquote[type] = YES;
			stqcline = linenum;
		}
inside:
		for (; ; cp++) {
			if (*cp == qchar)
				break;
			if (*cp == '\0' || *cp == '\\' && *++cp == '\0')
				return (cp);
		}
		inquote[type] = NO;
		cp++;
	}
}

/*
 *   special getlin - treats form-feed as an end-of-line
 *                    and expands tabs if asked for
 */
static int
getlin(char *line, int maxline, FILE *inp, int expandtabs)
{
	int tmp;
	int num;
	int chr;
#ifdef FFSPECIAL
	static char havechar = NO;  /* have leftover char from last time */
	static char svchar;
#endif

	num = 0;
#ifdef FFSPECIAL
	if (havechar) {
		havechar = NO;
		chr = svchar;
		goto ent;
	}
#endif
	while (num + 8 < maxline) {   /* leave room for tab */
		chr = getc(inp);
		if (isprint(chr)) {
#ifdef FFSPECIAL
ent:
#endif
			*line++ = chr;
			num++;
		} else
			switch (chr) {
			case EOF:
				return (EOF);

			case '\t':
				if (expandtabs) {
					num += tmp = 8 - (num & 7);
					do
						*line++ = ' ';
					while (--tmp);
					break;
				}
			default:
				*line++ = chr;
				num++;
				break;

			case '\n':
				*line = '\n';
				num++;
				goto end;

#ifdef FFSPECIAL
			case '\f':
				if (++num == 1)
					*line = '\f';
				else {
					*line = '\n';
					havechar = YES;
					svchar = chr;
				}
				goto end;
#endif
			}
	}
end:
	*++line = '\0';
	return (num);
}

static void
flushline(int keep)
{
	if ((keep && reject < 2) ^ complement)
		putlin(tline, stdout);
	else if (lnblank)
		putlin("\n", stdout);
}

/*
 *  putlin - for tools
 */
static void
putlin(char *line, FILE *fio)
{
	char chr;

	while (chr = *line++)
		(void) putc(chr, fio);
}

static void
prname(void)
{
	(void) fprintf(stderr, "%s: ", progname);
}


static int
error(int err, int line, int depth)
{
	if (err == END_ERR)
		return (err);

	prname();

#ifndef TESTING
	(void) fprintf(stderr, gettext("Error in %s line %d: %s.\n"),
	    filename, line, gettext(errs[err]));
#endif

#ifdef TESTING
	(void) fprintf(stderr, gettext("Error in %s line %d: %s. "),
	    filename, line, errs[err]);
	(void) fprintf(stderr, gettext("ifdef depth: %d\n"), depth);
#endif

	exitstat = 1;
	return (depth > 1 ? IEOF_ERR : END_ERR);
}

/* return the next token in the line buffer */
char *
nextsym(char *p)
{
	char *key;
	int i = KWSIZE;

	key = buf;
	while (!endsym(*p) && --i)
		*key++ = *p++;
	*key = '\0';

	return (buf);
}