#include "rc.h"
#include "io.h"
#include "exec.h"
#include "fns.h"
#include "getflags.h"
#define	c0	t->child[0]
#define	c1	t->child[1]
#define	c2	t->child[2]
int codep, ncode;
#define	emitf(x) ((void)(codep!=ncode || morecode()), codebuf[codep].f = (x), codep++)
#define	emiti(x) ((void)(codep!=ncode || morecode()), codebuf[codep].i = (x), codep++)
#define	emits(x) ((void)(codep!=ncode || morecode()), codebuf[codep].s = (x), codep++)
void stuffdot(int);
char *fnstr(tree*);
void outcode(tree*, int);
void codeswitch(tree*, int);
int iscase(tree*);
code *codecopy(code*);
void codefree(code*);

int
morecode(void)
{
	ncode+=100;
	codebuf = (code *)realloc((char *)codebuf, ncode*sizeof codebuf[0]);
	if(codebuf==0)
		panic("Can't realloc %d bytes in morecode!",
				ncode*sizeof codebuf[0]);
	memset(codebuf+ncode-100, 0, 100*sizeof codebuf[0]);
	return 0;
}

void
stuffdot(int a)
{
	if(a<0 || codep<=a)
		panic("Bad address %d in stuffdot", a);
	codebuf[a].i = codep;
}

int
compile(tree *t)
{
	ncode = 100;
	codebuf = (code *)emalloc(ncode*sizeof codebuf[0]);
	codep = 0;
	emiti(0);			/* reference count */
	outcode(t, flag['e']?1:0);
	if(nerror){
		efree((char *)codebuf);
		return 0;
	}
	readhere();
	emitf(Xreturn);
	emitf(0);
	return 1;
}

void
cleanhere(char *f)
{
	emitf(Xdelhere);
	emits(strdup(f));
}

char*
fnstr(tree *t)
{
	io *f = openstr();
	char *v;
	extern char nl;
	char svnl = nl;
	nl=';';
	pfmt(f, "%t", t);
	nl = svnl;
	v = f->strp;
	f->strp = 0;
	closeio(f);
	return v;
}

void
outcode(tree *t, int eflag)
{
	int p, q;
	tree *tt;
	if(t==0)
		return;
	if(t->type!=NOT && t->type!=';')
		runq->iflast = 0;
	switch(t->type){
	default:
		pfmt(err, "bad type %d in outcode\n", t->type);
		break;
	case '$':
		emitf(Xmark);
		outcode(c0, eflag);
		emitf(Xdol);
		break;
	case '"':
		emitf(Xmark);
		outcode(c0, eflag);
		emitf(Xqdol);
		break;
	case SUB:
		emitf(Xmark);
		outcode(c0, eflag);
		emitf(Xmark);
		outcode(c1, eflag);
		emitf(Xsub);
		break;
	case '&':
		emitf(Xasync);
		if(havefork){
			p = emiti(0);
			outcode(c0, eflag);
			emitf(Xexit);
			stuffdot(p);
		} else
			emits(fnstr(c0));
		break;
	case ';':
		outcode(c0, eflag);
		outcode(c1, eflag);
		break;
	case '^':
		emitf(Xmark);
		outcode(c1, eflag);
		emitf(Xmark);
		outcode(c0, eflag);
		emitf(Xconc);
		break;
	case '`':
		emitf(Xbackq);
		if(havefork){
			p = emiti(0);
			outcode(c0, 0);
			emitf(Xexit);
			stuffdot(p);
		} else
			emits(fnstr(c0));
		break;
	case ANDAND:
		outcode(c0, 0);
		emitf(Xtrue);
		p = emiti(0);
		outcode(c1, eflag);
		stuffdot(p);
		break;
	case ARGLIST:
		outcode(c1, eflag);
		outcode(c0, eflag);
		break;
	case BANG:
		outcode(c0, eflag);
		emitf(Xbang);
		break;
	case PCMD:
	case BRACE:
		outcode(c0, eflag);
		break;
	case COUNT:
		emitf(Xmark);
		outcode(c0, eflag);
		emitf(Xcount);
		break;
	case FN:
		emitf(Xmark);
		outcode(c0, eflag);
		if(c1){
			emitf(Xfn);
			p = emiti(0);
			emits(fnstr(c1));
			outcode(c1, eflag);
			emitf(Xunlocal);	/* get rid of $* */
			emitf(Xreturn);
			stuffdot(p);
		}
		else
			emitf(Xdelfn);
		break;
	case IF:
		outcode(c0, 0);
		emitf(Xif);
		p = emiti(0);
		outcode(c1, eflag);
		emitf(Xwastrue);
		stuffdot(p);
		break;
	case NOT:
		if(!runq->iflast)
			yyerror("`if not' does not follow `if(...)'");
		emitf(Xifnot);
		p = emiti(0);
		outcode(c0, eflag);
		stuffdot(p);
		break;
	case OROR:
		outcode(c0, 0);
		emitf(Xfalse);
		p = emiti(0);
		outcode(c1, eflag);
		stuffdot(p);
		break;
	case PAREN:
		outcode(c0, eflag);
		break;
	case SIMPLE:
		emitf(Xmark);
		outcode(c0, eflag);
		emitf(Xsimple);
		if(eflag)
			emitf(Xeflag);
		break;
	case SUBSHELL:
		emitf(Xsubshell);
		if(havefork){
			p = emiti(0);
			outcode(c0, eflag);
			emitf(Xexit);
			stuffdot(p);
		} else
			emits(fnstr(c0));
		if(eflag)
			emitf(Xeflag);
		break;
	case SWITCH:
		codeswitch(t, eflag);
		break;
	case TWIDDLE:
		emitf(Xmark);
		outcode(c1, eflag);
		emitf(Xmark);
		outcode(c0, eflag);
		emitf(Xmatch);
		if(eflag)
			emitf(Xeflag);
		break;
	case WHILE:
		q = codep;
		outcode(c0, 0);
		if(q==codep)
			emitf(Xsettrue);	/* empty condition == while(true) */
		emitf(Xtrue);
		p = emiti(0);
		outcode(c1, eflag);
		emitf(Xjump);
		emiti(q);
		stuffdot(p);
		break;
	case WORDS:
		outcode(c1, eflag);
		outcode(c0, eflag);
		break;
	case FOR:
		emitf(Xmark);
		if(c1){
			outcode(c1, eflag);
			emitf(Xglob);
		}
		else{
			emitf(Xmark);
			emitf(Xword);
			emits(strdup("*"));
			emitf(Xdol);
		}
		emitf(Xmark);		/* dummy value for Xlocal */
		emitf(Xmark);
		outcode(c0, eflag);
		emitf(Xlocal);
		p = emitf(Xfor);
		q = emiti(0);
		outcode(c2, eflag);
		emitf(Xjump);
		emiti(p);
		stuffdot(q);
		emitf(Xunlocal);
		break;
	case WORD:
		emitf(Xword);
		emits(strdup(t->str));
		break;
	case DUP:
		if(t->rtype==DUPFD){
			emitf(Xdup);
			emiti(t->fd0);
			emiti(t->fd1);
		}
		else{
			emitf(Xclose);
			emiti(t->fd0);
		}
		outcode(c1, eflag);
		emitf(Xpopredir);
		break;
	case PIPEFD:
		emitf(Xpipefd);
		emiti(t->rtype);
		if(havefork){
			p = emiti(0);
			outcode(c0, eflag);
			emitf(Xexit);
			stuffdot(p);
		} else {
			emits(fnstr(c0));
		}
		break;
	case REDIR:
		emitf(Xmark);
		outcode(c0, eflag);
		emitf(Xglob);
		switch(t->rtype){
		case APPEND:
			emitf(Xappend);
			break;
		case WRITE:
			emitf(Xwrite);
			break;
		case READ:
		case HERE:
			emitf(Xread);
			break;
		case RDWR:
			emitf(Xrdwr);
			break;
		}
		emiti(t->fd0);
		outcode(c1, eflag);
		emitf(Xpopredir);
		break;
	case '=':
		tt = t;
		for(;t && t->type=='=';t = c2);
		if(t){
			for(t = tt;t->type=='=';t = c2){
				emitf(Xmark);
				outcode(c1, eflag);
				emitf(Xmark);
				outcode(c0, eflag);
				emitf(Xlocal);
			}
			outcode(t, eflag);
			for(t = tt; t->type=='='; t = c2)
				emitf(Xunlocal);
		}
		else{
			for(t = tt;t;t = c2){
				emitf(Xmark);
				outcode(c1, eflag);
				emitf(Xmark);
				outcode(c0, eflag);
				emitf(Xassign);
			}
		}
		t = tt;	/* so tests below will work */
		break;
	case PIPE:
		emitf(Xpipe);
		emiti(t->fd0);
		emiti(t->fd1);
		if(havefork){
			p = emiti(0);
			q = emiti(0);
			outcode(c0, eflag);
			emitf(Xexit);
			stuffdot(p);
		} else {
			emits(fnstr(c0));
			q = emiti(0);
		}
		outcode(c1, eflag);
		emitf(Xreturn);
		stuffdot(q);
		emitf(Xpipewait);
		break;
	}
	if(t->type!=NOT && t->type!=';')
		runq->iflast = t->type==IF;
	else if(c0) runq->iflast = c0->type==IF;
}
/*
 * switch code looks like this:
 *	Xmark
 *	(get switch value)
 *	Xjump	1f
 * out:	Xjump	leave
 * 1:	Xmark
 *	(get case values)
 *	Xcase	1f
 *	(commands)
 *	Xjump	out
 * 1:	Xmark
 *	(get case values)
 *	Xcase	1f
 *	(commands)
 *	Xjump	out
 * 1:
 * leave:
 *	Xpopm
 */

void
codeswitch(tree *t, int eflag)
{
	int leave;		/* patch jump address to leave switch */
	int out;		/* jump here to leave switch */
	int nextcase;	/* patch jump address to next case */
	tree *tt;
	if(c1->child[0]==nil
	|| c1->child[0]->type!=';'
	|| !iscase(c1->child[0]->child[0])){
		yyerror("case missing in switch");
		return;
	}
	emitf(Xmark);
	outcode(c0, eflag);
	emitf(Xjump);
	nextcase = emiti(0);
	out = emitf(Xjump);
	leave = emiti(0);
	stuffdot(nextcase);
	t = c1->child[0];
	while(t->type==';'){
		tt = c1;
		emitf(Xmark);
		for(t = c0->child[0];t->type==ARGLIST;t = c0) outcode(c1, eflag);
		emitf(Xcase);
		nextcase = emiti(0);
		t = tt;
		for(;;){
			if(t->type==';'){
				if(iscase(c0)) break;
				outcode(c0, eflag);
				t = c1;
			}
			else{
				if(!iscase(t)) outcode(t, eflag);
				break;
			}
		}
		emitf(Xjump);
		emiti(out);
		stuffdot(nextcase);
	}
	stuffdot(leave);
	emitf(Xpopm);
}

int
iscase(tree *t)
{
	if(t->type!=SIMPLE)
		return 0;
	do t = c0; while(t->type==ARGLIST);
	return t->type==WORD && !t->quoted && strcmp(t->str, "case")==0;
}

code*
codecopy(code *cp)
{
	cp[0].i++;
	return cp;
}

void
codefree(code *cp)
{
	code *p;
	if(--cp[0].i!=0)
		return;
	for(p = cp+1;p->f;p++){
		if(p->f==Xappend || p->f==Xclose || p->f==Xread || p->f==Xwrite
		|| p->f==Xrdwr
		|| p->f==Xasync || p->f==Xbackq || p->f==Xcase || p->f==Xfalse
		|| p->f==Xfor || p->f==Xjump
		|| p->f==Xsubshell || p->f==Xtrue) p++;
		else if(p->f==Xdup || p->f==Xpipefd) p+=2;
		else if(p->f==Xpipe) p+=4;
		else if(p->f==Xword || p->f==Xdelhere) efree((++p)->s);
		else if(p->f==Xfn){
			efree(p[2].s);
			p+=2;
		}
	}
	efree((char *)cp);
}
