#include "rc.h"
#include "io.h"
#include "fns.h"

static tree*	body(int tok, int *ptok);
static tree*	brace(int tok);
static tree*	cmd(int tok, int *ptok);
static tree*	cmd2(int tok, int *ptok);
static tree*	cmd3(int tok, int *ptok);
static tree*	cmds(int tok, int *ptok, int nlok);
static tree*	epilog(int tok, int *ptok);
static int	iswordtok(int tok);
static tree*	line(int tok, int *ptok);
static tree*	paren(int tok);
static tree*	yyredir(int tok, int *ptok);
static tree*	yyword(int tok, int *ptok, int eqok);
static tree*	word1(int tok, int *ptok);
static tree*	words(int tok, int *ptok);

static jmp_buf yyjmp;

static int
dropnl(int tok)
{
	while(tok == ' ' || tok == '\n')
		tok = yylex();
	return tok;
}

static int
dropsp(int tok)
{
	while(tok == ' ')
		tok = yylex();
	return tok;
}

static void
syntax(int tok)
{
	USED(tok);
	yyerror("syntax error");
	longjmp(yyjmp, 1);
}

int
parse(void)
{
	tree *t;
	int tok;

	if(setjmp(yyjmp))
		return 1;

	// rc:				{ return 1;}
	// |	line '\n'		{return !compile($1);}

	tok = dropsp(yylex());
	if(tok == EOF)
		return 1;
	t = line(tok, &tok);
	if(tok != '\n')
		yyerror("missing newline at end of line");
	yylval.tree = t;
	return !compile(t);
}

static tree*
line(int tok, int *ptok)
{
	return cmds(tok, ptok, 0);
}

static tree*
body(int tok, int *ptok)
{
	return cmds(tok, ptok, 1);
}

static tree*
cmds(int tok, int *ptok, int nlok)
{
	tree *t, **last, *t2;

	// line:	cmd
	// |	cmdsa line		{$$=tree2(';', $1, $2);}
	// cmdsa:	cmd ';'
	// |	cmd '&'			{$$=tree1('&', $1);}

	// body:	cmd
	// |	cmdsan body		{$$=tree2(';', $1, $2);}
	// cmdsan:	cmdsa
	// |	cmd '\n'

	t = nil;
	last = nil;
	for(;;) {
		t2 = cmd(tok, &tok);
		if(tok == '&')
			t2 = tree1('&', t2);
		if(t2 != nil) {
			// slot into list t
			if(last == nil) {
				t = t2;
				last = &t;
			} else {
				*last = tree2(';', *last, t2);
				last = &(*last)->child[1];
			}
		}
		if(tok != ';' && tok != '&' && (!nlok || tok != '\n'))
			break;
		tok = yylex();
	}
	*ptok = tok;
	return t;
}

static tree*
brace(int tok)
{
	tree *t;

	// brace:	'{' body '}'		{$$=tree1(BRACE, $2);}

	tok = dropsp(tok);
	if(tok != '{')
		syntax(tok);
	t = body(yylex(), &tok);
	if(tok != '}')
		syntax(tok);
	return tree1(BRACE, t);
}

static tree*
paren(int tok)
{
	tree *t;

	// paren:	'(' body ')'		{$$=tree1(PCMD, $2);}

	tok = dropsp(tok);
	if(tok != '(')
		syntax(tok);
	t = body(yylex(), &tok);
	if(tok != ')')
		syntax(tok);
	return tree1(PCMD, t);
}

static tree*
epilog(int tok, int *ptok)
{
	tree *t, *r;

	// epilog:				{$$=0;}
	// |	redir epilog		{$$=mung2($1, $1->child[0], $2);}

	if(tok != REDIR && tok != DUP) {
		*ptok = tok;
		return nil;
	}

	r = yyredir(tok, &tok);
	t = epilog(tok, &tok);
	*ptok = tok;
	return mung2(r, r->child[0], t);
}

static tree*
yyredir(int tok, int *ptok)
{
	tree *r, *w;

	// redir:	REDIR word		{$$=mung1($1, $1->rtype==HERE?heredoc($2):$2);}
	// |	DUP

	switch(tok) {
	default:
		syntax(tok);
	case DUP:
		r = yylval.tree;
		*ptok = dropsp(yylex());
		break;
	case REDIR:
		r = yylval.tree;
		w = yyword(yylex(), &tok, 1);
		*ptok = dropsp(tok);
		r = mung1(r, r->rtype==HERE?heredoc(w):w);
		break;
	}
	return r;
}

static tree*
cmd(int tok, int *ptok)
{
	int op;
	tree *t1, *t2;

	// |	cmd ANDAND cmd		{$$=tree2(ANDAND, $1, $3);}
	// |	cmd OROR cmd		{$$=tree2(OROR, $1, $3);}

	tok = dropsp(tok);
	t1 = cmd2(tok, &tok);
	while(tok == ANDAND || tok == OROR) {
		op = tok;
		t2 = cmd2(dropnl(yylex()), &tok);
		t1 = tree2(op, t1, t2);
	}
	*ptok = tok;
	return t1;
}

static tree*
cmd2(int tok, int *ptok)
{
	tree *t1, *t2, *t3;

	// |	cmd PIPE cmd		{$$=mung2($2, $1, $3);}
	t1 = cmd3(tok, &tok);
	while(tok == PIPE) {
		t2 = yylval.tree;
		t3 = cmd3(dropnl(yylex()), &tok);
		t1 = mung2(t2, t1, t3);
	}
	*ptok = tok;
	return t1;
}

static tree*
cmd3(int tok, int *ptok)
{
	tree *t1, *t2, *t3, *t4;

	tok = dropsp(tok);
	switch(tok) {
	case ';':
	case '&':
	case '\n':
		*ptok = tok;
		return nil;

	case IF:
		// |	IF paren {skipnl();} cmd	{$$=mung2($1, $2, $4);}
		// |	IF NOT {skipnl();} cmd	{$$=mung1($2, $4);}
		t1 = yylval.tree;
		tok = dropsp(yylex());
		if(tok == NOT) {
			t1 = yylval.tree;
			t2 = cmd(dropnl(yylex()), ptok);
			return mung1(t1, t2);
		}
		t2 = paren(tok);
		t3 = cmd(dropnl(yylex()), ptok);
		return mung2(t1, t2, t3);

	case FOR:
		// |	FOR '(' word IN words ')' {skipnl();} cmd
		//		{$$=mung3($1, $3, $5 ? $5 : tree1(PAREN, $5), $8);}
		// |	FOR '(' word ')' {skipnl();} cmd
		//		{$$=mung3($1, $3, (tree *)0, $6);}
		t1 = yylval.tree;
		tok = dropsp(yylex());
		if(tok != '(')
			syntax(tok);
		t2 = yyword(yylex(), &tok, 1);
		switch(tok) {
		default:
			syntax(tok);
		case ')':
			t3 = nil;
			break;
		case IN:
			t3 = words(yylex(), &tok);
			if(t3 == nil)
				t3 = tree1(PAREN, nil);
			if(tok != ')')
				syntax(tok);
			break;
		}
		t4 = cmd(dropnl(yylex()), ptok);
		return mung3(t1, t2, t3, t4);

	case WHILE:
		// |	WHILE paren {skipnl();} cmd
		//		{$$=mung2($1, $2, $4);}
		t1 = yylval.tree;
		t2 = paren(yylex());
		t3 = cmd(dropnl(yylex()), ptok);
		return mung2(t1, t2, t3);

	case SWITCH:
		// |	SWITCH word {skipnl();} brace
		//		{$$=tree2(SWITCH, $2, $4);}
		t1 = yyword(yylex(), &tok, 1);
		tok = dropnl(tok); // doesn't work in yacc grammar but works here!
		t2 = brace(tok);
		*ptok = dropsp(yylex());
		return tree2(SWITCH, t1, t2);

	case FN:
		// |	FN words brace		{$$=tree2(FN, $2, $3);}
		// |	FN words		{$$=tree1(FN, $2);}
		t1 = words(yylex(), &tok);
		if(tok != '{') {
			*ptok = tok;
			return tree1(FN, t1);
		}
		t2 = brace(tok);
		*ptok = dropsp(yylex());
		return tree2(FN, t1, t2);

	case TWIDDLE:
		// |	TWIDDLE word words	{$$=mung2($1, $2, $3);}
		t1 = yylval.tree;
		t2 = yyword(yylex(), &tok, 1);
		t3 = words(tok, ptok);
		return mung2(t1, t2, t3);

	case BANG:
	case SUBSHELL:
		// |	BANG cmd		{$$=mung1($1, $2);}
		// |	SUBSHELL cmd		{$$=mung1($1, $2);}
		// Note: cmd2: ! x | y is !{x | y} not {!x} | y.
		t1 = yylval.tree;
		return mung1(t1, cmd2(yylex(), ptok));

	case REDIR:
	case DUP:
		// |	redir cmd  %prec BANG	{$$=mung2($1, $1->child[0], $2);}
		// Note: cmd2: {>x echo a | tr a-z A-Z} writes A to x.
		t1 = yyredir(tok, &tok);
		t2 = cmd2(tok, ptok);
		return mung2(t1, t1->child[0], t2);

	case '{':
		// |	brace epilog		{$$=epimung($1, $2);}
		t1 = brace(tok);
		tok = dropsp(yylex());
		t2 = epilog(tok, ptok);
		return epimung(t1, t2);
	}

	if(!iswordtok(tok)) {
		*ptok = tok;
		return nil;
	}

	// cmd: ...
	// |	simple			{$$=simplemung($1);}
	// |	assign cmd %prec BANG	{$$=mung3($1, $1->child[0], $1->child[1], $2);}
	// assign:	first '=' word		{$$=tree2('=', $1, $3);}
	// Note: first is same as word except for disallowing all the leading keywords,
	// but all those keywords have been picked off in the switch above.
	// Except NOT, but disallowing that in yacc was likely a mistake anyway:
	// there's no ambiguity in not=1 or not x y z.
	t1 = yyword(tok, &tok, 0);
	if(tok == '=') {
		// assignment
		// Note: cmd2: {x=1 true | echo $x} echoes 1.
		t1 = tree2('=', t1, yyword(yylex(), &tok, 1));
		t2 = cmd2(tok, ptok);
		return mung3(t1, t1->child[0], t1->child[1], t2);
	}

	// simple:	first
	// |	simple word		{$$=tree2(ARGLIST, $1, $2);}
	// |	simple redir		{$$=tree2(ARGLIST, $1, $2);}
	for(;;) {
		if(tok == REDIR || tok == DUP) {
			t1 = tree2(ARGLIST, t1, yyredir(tok, &tok));
		} else if(iswordtok(tok)) {
			t1 = tree2(ARGLIST, t1, yyword(tok, &tok, 1));
		} else {
			break;
		}
	}
	*ptok = tok;
	return simplemung(t1);
}

static tree*
words(int tok, int *ptok)
{
	tree *t;

	// words:				{$$=(tree*)0;}
	// |	words word		{$$=tree2(WORDS, $1, $2);}

	t = nil;
	tok = dropsp(tok);
	while(iswordtok(tok))
		t = tree2(WORDS, t, yyword(tok, &tok, 1));
	*ptok = tok;
	return t;
}

static tree*
yyword(int tok, int *ptok, int eqok)
{
	tree *t;

	// word:	keyword			{lastword=1; $1->type=WORD;}
	// |	comword
	// |	word '^' word		{$$=tree2('^', $1, $3);}
	// comword: '$' word		{$$=tree1('$', $2);}
	// |	'$' word SUB words ')'	{$$=tree2(SUB, $2, $4);}
	// |	'"' word		{$$=tree1('"', $2);}
	// |	COUNT word		{$$=tree1(COUNT, $2);}
	// |	WORD
	// |	'`' brace		{$$=tree1('`', $2);}
	// |	'(' words ')'		{$$=tree1(PAREN, $2);}
	// |	REDIR brace		{$$=mung1($1, $2); $$->type=PIPEFD;}
	// keyword: FOR|IN|WHILE|IF|NOT|TWIDDLE|BANG|SUBSHELL|SWITCH|FN
	//
	// factored into:
	//
	// word: word1
	// | word '^' word1
	//
	// word1: keyword | comword

	t = word1(tok, &tok);
	if(tok == '=' && !eqok)
		goto out;
	for(;;) {
		if(iswordtok(tok)) {
			// No free carats around parens.
			if(t->type == PAREN || tok == '(')
				syntax(tok);
			t = tree2('^', t, word1(tok, &tok));
			continue;
		}
		tok = dropsp(tok);
		if(tok == '^') {
			t = tree2('^', t, word1(yylex(), &tok));
			continue;
		}
		break;
	}
out:
	*ptok = dropsp(tok);
	return t;
}

static tree*
word1(int tok, int *ptok)
{
	tree *w, *sub, *t;

	tok = dropsp(tok);
	switch(tok) {
	default:
		syntax(tok);

	case WORD:
	case FOR:
	case IN:
	case WHILE:
	case IF:
	case NOT:
	case TWIDDLE:
	case BANG:
	case SUBSHELL:
	case SWITCH:
	case FN:
		// |	WORD
		// keyword: FOR|IN|WHILE|IF|NOT|TWIDDLE|BANG|SUBSHELL|SWITCH|FN
		t = yylval.tree;
		t->type = WORD;
		*ptok = yylex();
		return t;

	case '=':
		*ptok = yylex();
		return token("=", WORD);

	case '$':
		// comword: '$' word1		{$$=tree1('$', $2);}
		// |	'$' word1 SUB words ')'	{$$=tree2(SUB, $2, $4);}
		w = word1(yylex(), &tok);
		if(tok == '(') {
			sub = words(yylex(), &tok);
			if(tok != ')')
				syntax(tok);
			*ptok = yylex();
			return tree2(SUB, w, sub);
		}
		*ptok = tok;
		return tree1('$', w);

	case '"':
		// |	'"' word1		{$$=tree1('"', $2);}
		return tree1('"', word1(yylex(), ptok));

	case COUNT:
		// |	COUNT word1		{$$=tree1(COUNT, $2);}
		return tree1(COUNT, word1(yylex(), ptok));

	case '`':
		// |	'`' brace		{$$=tree1('`', $2);}
		t = tree1('`', brace(yylex()));
		*ptok = yylex();
		return t;

	case '(':
		// |	'(' words ')'		{$$=tree1(PAREN, $2);}
		t = tree1(PAREN, words(yylex(), &tok));
		if(tok != ')')
			syntax(tok);
		*ptok = yylex();
		return t;

	case REDIRW:
		// |	REDIRW brace		{$$=mung1($1, $2); $$->type=PIPEFD;}
		t = yylval.tree;
		t = mung1(t, brace(yylex()));
		t->type = PIPEFD;
		*ptok = yylex();
		return t;
	}
}

static int
iswordtok(int tok)
{
	switch(tok) {
	case FOR:
	case IN:
	case WHILE:
	case IF:
	case NOT:
	case TWIDDLE:
	case BANG:
	case SUBSHELL:
	case SWITCH:
	case FN:
	case '$':
	case '"':
	case COUNT:
	case WORD:
	case '`':
	case '(':
	case REDIRW:
	case '=':
		return 1;
	}
	return 0;
}
