/**************************************************************
 * A simple cpp preprocessor processing simple tokens.
 * It support the binary syntax @if token, @else, @endif
 * as well as @include (can be recursive) and @comment. Space
 * between if and token are facultative.
 * Conditions can be nested up to 32 levels.
 * Will compile to support 32 tokens. Easily expandable.
 *
 * Usage:
 *
 * simplecpp [-v] -Dtoken1 [-Dtoken2 ...] [input [output]]
 *
 * Simple is beautiful! Conserve true line number. Discard false.
 *
 * Martin-D. Lacasse 1994
 *
 **************************************************************/

#include <stdio.h>
#include <string.h>

char *gettok(char *);

#define MAXTOKEN 	32
#define MAXIFS 		32
#define MAXLVL 		2  			/* Keep it simple! */
#define KEYCHAR 	'@'
#define USAGE fprintf(stderr, "Usage: %s [-v] -Ddef [-Ddef2...] [infile [outfile]]\n", argv[0]); exit(1)
#define SWALLOW_SPACE(c)	while (*c && (*c == ' ' || *c == '\t') ) c++;


int main(int argc, char **argv)
{

	FILE *fpin[MAXLVL], *fpout;
	char bufline[1024], token[MAXTOKEN][256];
	int lineno[MAXLVL];
	int truestate[MAXIFS];
	int falsestate;
	int verbose;
	int iflvl, fplvl, total, toptoken;
	int i;
	char *cp, *infile, *cptok;

	fpin[0] = stdin; infile = "stdin";
	fpout = stdout;
	fplvl = total = toptoken = 0;
	falsestate = iflvl = -1;
	verbose = 0;

	for (i=1;i<argc;i++) {		/* PARSE ARGUMENTS */
		if (strncmp(argv[i], "-v", 2) == 0) {
			verbose++;
			continue;
		}
		if (strncmp(argv[i], "-D", 2) == 0) {
			if (toptoken >= MAXTOKEN) {
				fprintf(stderr,
				"%s: Too many tokens for present capacity.\n", argv[0]);
				exit(1);
			}
			strncpy(token[toptoken], argv[i]+2, 255);
			if (strlen(token[toptoken]))
				toptoken++;
		}
		else if (fpin[0] == stdin) {
			if ((fpin[fplvl] = fopen(argv[i], "r")) == NULL) {
				fprintf(stderr, "%s: %s: File not found\n", argv[0], argv[i]);
				exit(1);
			}
			infile = argv[i];
		}
		else if (fpout == stdout) {
			if ((fpout = fopen(argv[i], "w")) == NULL) {
				fprintf(stderr,
				"%s: %s: Permission denied.\n", argv[0], argv[i]);
				exit(1);
			}
		}
		else {
			USAGE;
		}
	}			/* END OF PARSING ARGUMENTS */

	lineno[fplvl] = 0;
	while (fplvl >= 0) {		/* HERE WE START */
		if (fgets(bufline, 1023, fpin[fplvl]) == NULL) {
			fclose(fpin[fplvl]);
			if (verbose) {
				if (fplvl) {
					fprintf(stderr, "%d lines.\n", lineno[fplvl]);
					total += lineno[fplvl];
				}
				else {
					fprintf(stderr, "%s: %d lines.\n", infile, lineno[0]);
					total += lineno[fplvl];
					fprintf(stderr, "Total: %d lines.\n", total);
				}
			}
			fplvl--;
			continue;
		}
		lineno[fplvl]++;
		if (bufline[0] == KEYCHAR) {
			cp = bufline+1;
			SWALLOW_SPACE(cp);
			if ((cptok = gettok(cp)) == NULL) {
				fprintf(stderr, "%s: Error: Line %d: %s",
				argv[0], lineno[fplvl], bufline);
				fprintf(stderr, "String too long for compiled capacity.\n");
				exit(1);
			}
			if (strncmp(cptok, "if", 2) == 0) {
				if (++iflvl > MAXIFS) {
					fprintf(stderr, "%s: Error: Line %d: %s",
					argv[0], lineno[fplvl], bufline);
					fprintf(stderr, "If too nested for compiled capacity.\n");
					exit(1);
				}
				cp += 2;
				SWALLOW_SPACE(cp);
				if ((cptok = gettok(cp)) == NULL) {
					fprintf(stderr, "%s: Error: Line %d: %s",
					argv[0], lineno[fplvl], bufline);
					fprintf(stderr, "String too long for compiled capacity.\n");
					exit(1);
				}
				for (i=0;i<toptoken;i++) {
					if (strcmp(cptok, token[i]) == 0) {
						truestate[iflvl] = 1;
						break;
					}
				}
				if (falsestate < 0 && !truestate[iflvl])
					falsestate = iflvl;
			}
			else if (strncmp(cptok, "else", 4) == 0) {
				if (iflvl < 0) {
					fprintf(stderr, "%s: Error: Line %d: %s",
					argv[0], lineno[fplvl], bufline);
					fprintf(stderr, "Found else without if!\n");
					exit(1);
				}
				truestate[iflvl] = !truestate[iflvl];
				if (falsestate == iflvl)
					falsestate = -1;
				else if (falsestate < 0 && !truestate[iflvl])
					falsestate = iflvl;
			}
			else if (strncmp(cptok, "endif", 5) == 0) {
				if (iflvl < 0) {
					fprintf(stderr, "%s: Error: Line %d: %s",
					argv[0], lineno[fplvl], bufline);
					fprintf(stderr, "Found endif without if.\n");
					exit(1);
				}
				truestate[iflvl] = 0;  /* RESET TO FALSE */
				if (falsestate == iflvl)
					falsestate = -1;
				iflvl--;
			}
			else if (strncmp(cptok, "include", 7) == 0) {
				if (falsestate < 0) {
					if (++fplvl > MAXLVL-1) {
						fprintf(stderr, "%s: Error: Line %d: %s",
						argv[0], lineno[fplvl-1], bufline);
						fprintf(stderr, "Too many @include's!\n");
						exit(1);
					}
					lineno[fplvl] = 0;
					cp += 7;
					SWALLOW_SPACE(cp);
					if ((cptok = gettok(cp)) == NULL) {
						fprintf(stderr, "%s: Error: Line %d: %s",
						argv[0], lineno[fplvl], bufline);
						fprintf(stderr,
						"String too long for compiled capacity.\n");
						exit(1);
					}
					if ((fpin[fplvl] = fopen(cptok, "r")) == NULL) {
						fprintf(stderr, "%s: Error: Line %d: %s",
						argv[0], lineno[fplvl], bufline);
						fprintf(stderr,
						"%s: File not found\n", cptok);
						exit(1);
					}
					if (verbose)
						fprintf(stderr, "%s: ", cptok);
				}
				continue;
			}
			else if (strncmp(cptok, "comment", 7) == 0) {
				continue;
			}
			else {
				fprintf(stderr, "%s: Error: Line %d: %s",
				argv[0], lineno[fplvl], bufline);
				fprintf(stderr, "Unknown command.\n");
				exit(1);
			}
			continue;
		}
		if (falsestate < 0)
			fputs(bufline, fpout);
	}
	if (iflvl >= 0) {
		fprintf(stderr, "%s: Error: Exiting with an open if statement.\n",
		argv[0]);
		exit(1);
	}
	exit(0);
}

char *gettok(char *cpin)
{
	/* It is safe to use static but be aware of recursive limits ;-( */
	static char string[256];
	int i = 0;

	SWALLOW_SPACE(cpin);
	while (*cpin && i < 256) {
		if (*cpin == ' ' || *cpin == '\t' || *cpin == '\n')
			break;
		string[i++] = *cpin++;
	}
	string[i] = '\0';

	if (i == 256)
		return (0);

	return(string);
}
