#include <u.h>
#include <libc.h>
#include <stdio.h>
#include "cpp.h"

#define	OUTS	16384
char	outbuf[OUTS];
char	*outp = outbuf;
Source	*cursource;
int	nerrs;
struct	token nltoken = { NL, 0, 0, 0, 1, (uchar*)"\n" };
char	*curtime;
int	incdepth;
int	ifdepth;
int	ifsatisfied[NIF];
int	skipping;

int
main(int argc, char **argv)
{
	Tokenrow tr;
	long t;
	char ebuf[BUFSIZ];

	setbuf(stderr, ebuf);
	t = time(NULL);
	curtime = ctime(t);
	maketokenrow(3, &tr);
	expandlex();
	setup(argc, argv);
	fixlex();
	iniths();
	genline();
	process(&tr);
	flushout();
	fflush(stderr);
	exits(nerrs? "errors" : 0);
	return 0;
}

void
process(Tokenrow *trp)
{
	int anymacros = 0;

	for (;;) {
		if (trp->tp >= trp->lp) {
			trp->tp = trp->lp = trp->bp;
			outp = outbuf;
			anymacros |= gettokens(trp, 1);
			trp->tp = trp->bp;
		}
		if (trp->tp->type == END) {
			if (--incdepth>=0) {
				if (cursource->ifdepth)
					error(ERROR,
					 "Unterminated conditional in #include");
				unsetsource();
				cursource->line += cursource->lineinc;
				trp->tp = trp->lp;
				genline();
				continue;
			}
			if (ifdepth)
				error(ERROR, "Unterminated #if/#ifdef/#ifndef");
			break;
		}
		if (trp->tp->type==SHARP) {
			trp->tp += 1;
			control(trp);
		} else if (!skipping && anymacros)
			expandrow(trp, NULL, Notinmacro);
		if (skipping)
			setempty(trp);
		puttokens(trp);
		anymacros = 0;
		cursource->line += cursource->lineinc;
		if (cursource->lineinc>1) {
			genline();
		}
	}
}
	
void
control(Tokenrow *trp)
{
	Nlist *np;
	Token *tp;

	tp = trp->tp;
	if (tp->type!=NAME) {
		if (tp->type==NUMBER)
			goto kline;
		if (tp->type != NL)
			error(ERROR, "Unidentifiable control line");
		return;			/* else empty line */
	}
	if ((np = lookup(tp, 0))==NULL || (np->flag&ISKW)==0 && !skipping) {
		error(WARNING, "Unknown preprocessor control %t", tp);
		return;
	}
	if (skipping) {
		if ((np->flag&ISKW)==0)
			return;
		switch (np->val) {
		case KENDIF:
			if (--ifdepth<skipping)
				skipping = 0;
			--cursource->ifdepth;
			setempty(trp);
			return;

		case KIFDEF:
		case KIFNDEF:
		case KIF:
			if (++ifdepth >= NIF)
				error(FATAL, "#if too deeply nested");
			++cursource->ifdepth;
			return;

		case KELIF:
		case KELSE:
			if (ifdepth<=skipping)
				break;
			return;

		default:
			return;
		}
	}
	switch (np->val) {
	case KDEFINE:
		dodefine(trp);
		break;

	case KUNDEF:
		tp += 1;
		if (tp->type!=NAME || trp->lp - trp->bp != 4) {
			error(ERROR, "Syntax error in #undef");
			break;
		}
		if ((np = lookup(tp, 0))) {
			if (np->flag&ISUNCHANGE) {
				error(ERROR, "#defined token %t can't be undefined", tp);
				return;
			}
			np->flag &= ~ISDEFINED;
		}
		break;

	case KPRAGMA:
		return;

	case KIFDEF:
	case KIFNDEF:
	case KIF:
		if (++ifdepth >= NIF)
			error(FATAL, "#if too deeply nested");
		++cursource->ifdepth;
		ifsatisfied[ifdepth] = 0;
		if (eval(trp, np->val))
			ifsatisfied[ifdepth] = 1;
		else
			skipping = ifdepth;
		break;

	case KELIF:
		if (ifdepth==0) {
			error(ERROR, "#elif with no #if");
			return;
		}
		if (ifsatisfied[ifdepth]==2)
			error(ERROR, "#elif after #else");
		if (eval(trp, np->val)) {
			if (ifsatisfied[ifdepth])
				skipping = ifdepth;
			else {
				skipping = 0;
				ifsatisfied[ifdepth] = 1;
			}
		} else
			skipping = ifdepth;
		break;

	case KELSE:
		if (ifdepth==0 || cursource->ifdepth==0) {
			error(ERROR, "#else with no #if");
			return;
		}
		if (ifsatisfied[ifdepth]==2)
			error(ERROR, "#else after #else");
		if (trp->lp - trp->bp != 3)
			error(ERROR, "Syntax error in #else");
		skipping = ifsatisfied[ifdepth]? ifdepth: 0;
		ifsatisfied[ifdepth] = 2;
		break;

	case KENDIF:
		if (ifdepth==0 || cursource->ifdepth==0) {
			error(ERROR, "#endif with no #if");
			return;
		}
		--ifdepth;
		--cursource->ifdepth;
		if (trp->lp - trp->bp != 3)
			error(WARNING, "Syntax error in #endif");
		break;

	case KERROR:
		trp->tp = tp+1;
		error(ERROR, "#error directive: %r", trp);
		break;

	case KWARNING:
		trp->tp = tp+1;
		error(WARNING, "#warning directive: %r", trp);
		break;

	case KLINE:
		trp->tp = tp+1;
		expandrow(trp, "<line>", Notinmacro);
		tp = trp->bp+2;
	kline:
		if (tp+1>=trp->lp || tp->type!=NUMBER || tp+3<trp->lp
		 || (tp+3==trp->lp && ((tp+1)->type!=STRING)||*(tp+1)->t=='L')){
			error(ERROR, "Syntax error in #line");
			return;
		}
		cursource->line = atol((char*)tp->t)-1;
		if (cursource->line<0 || cursource->line>=32768)
			error(WARNING, "#line specifies number out of range");
		tp = tp+1;
		if (tp+1<trp->lp)
			cursource->filename=(char*)newstring(tp->t+1,tp->len-2,0);
		return;

	case KDEFINED:
		error(ERROR, "Bad syntax for control line");
		break;

	case KINCLUDE:
		doinclude(trp);
		trp->lp = trp->bp;
		return;

	case KEVAL:
		eval(trp, np->val);
		break;

	default:
		error(ERROR, "Preprocessor control `%t' not yet implemented", tp);
		break;
	}
	setempty(trp);
	return;
}

void *
dorealloc(void *ptr, int size)
{
	void *p = realloc(ptr, size);

	if (p==NULL)
		error(FATAL, "Out of memory from realloc");
	return p;
}

void *
domalloc(int size)
{
	void *p = malloc(size);

	if (p==NULL)
		error(FATAL, "Out of memory from malloc");
	return p;
}

void
dofree(void *p)
{
	free(p);
}

void
error(enum errtype type, char *string, ...)
{
	va_list ap;
	char *cp, *ep;
	Token *tp;
	Tokenrow *trp;
	Source *s;
	int i;
	void *p;

	fprintf(stderr, "cpp: ");
	for (s=cursource; s; s=s->next)
		if (*s->filename)
			fprintf(stderr, "%s:%d ", s->filename, s->line);
	va_start(ap, string);
	for (ep=string; *ep; ep++) {
		if (*ep=='%') {
			switch (*++ep) {

			case 's':
				cp = va_arg(ap, char *);
				fprintf(stderr, "%s", cp);
				break;
			case 'd':
				i = va_arg(ap, int);
				fprintf(stderr, "%d", i);
				break;
			case 'p':
				p = va_arg(ap, void *);
				fprintf(stderr, "%p", p);
				break;
			case 't':
				tp = va_arg(ap, Token *);
				fprintf(stderr, "%.*s", tp->len, tp->t);
				break;

			case 'r':
				trp = va_arg(ap, Tokenrow *);
				for (tp=trp->tp; tp<trp->lp&&tp->type!=NL; tp++) {
					if (tp>trp->tp && tp->wslen)
						fputc(' ', stderr);
					fprintf(stderr, "%.*s", tp->len, tp->t);
				}
				break;

			default:
				fputc(*ep, stderr);
				break;
			}
		} else
			fputc(*ep, stderr);
	}
	va_end(ap);
	fputc('\n', stderr);
	if (type==FATAL)
		exits("error");
	if (type!=WARNING)
		nerrs = 1;
	fflush(stderr);
}
