/*
 * Bawk C actions interpreter
 */
#include <stdio.h>
#include "bawk.h"

dopattern( pat )
char *pat;
{
	Where = PATTERN;
	Actptr = pat;
	getoken();
	expression();
	return popint();
}

doaction( act )
char *act;
{
	Where = ACTION;
	Actptr = act;
	getoken();
	while ( Token!=T_EOF )
		statement();
}

expression()
{
	expr1();

	if ( Token==T_ASSIGN )
	{
		getoken();
		assignment( expression() );
	}
}

expr1()
{
	int ival;

	expr2();
	for ( ;; )
	{
		if ( Token==T_LIOR )
		{
			getoken();
			ival = popint();
			expr2();
			pushint( popint() || ival );
		}
		else
			return;
	}
}

expr2()
{
	int ival;

	expr3();
	for ( ;; )
	{
		if ( Token==T_LAND )
		{
			getoken();
			ival = popint();
			expr3();
			pushint( popint() && ival );
		}
		else
			return;
	}
}

expr3()
{
	int ival;

	expr4();
	for ( ;; )
	{
		if ( Token==T_IOR )
		{
			getoken();
			ival = popint();
			expr4();
			pushint( popint() | ival );
		}
		else
			return;
	}
}


expr4()
{
	int ival;

	expr5();
	for ( ;; )
	{
		if ( Token==T_AND )
		{
			getoken();
			ival = popint();
			expr5();
			pushint( popint() & ival );
		}
		else
			return;
	}
}

expr5()
{
	int ival;

	expr6();
	for ( ;; )
	{
		if ( Token==T_XOR )
		{
			getoken();
			ival = popint();
			expr6();
			pushint( popint() ^ ival );
		}
		else
			return;
	}
}

expr6()
{
	int ival;

	expr7();
	for ( ;; )
	{
		if ( Token==T_EQ )
		{
			getoken();
			ival = popint();
			expr7();
			pushint( ival == popint() );
		}
		else if ( Token==T_NE )
		{
			getoken();
			ival = popint();
			expr7();
			pushint( ival != popint() );
		}
		else
			return;
	}
}

expr7()
{
	int ival;

	expr8();
	for ( ;; )
	{
		if ( Token==T_LE )
		{
			getoken();
			ival = popint();
			expr8();
			pushint( ival <= popint() );
		}
		else if ( Token==T_GE )
		{
			getoken();
			ival = popint();
			expr8();
			pushint( ival >= popint() );
		}
		else if ( Token==T_LT )
		{
			getoken();
			ival = popint();
			expr8();
			pushint( ival < popint() );
		}
		else if ( Token==T_GT )
		{
			getoken();
			ival = popint();
			expr8();
			pushint( ival > popint() );
		}
		else
			return;
	}
}

expr8()
{
	int ival;

	expr9();
	for ( ;; )
	{
		if ( Token==T_SHL )
		{
			getoken();
			ival = popint();
			expr9();
			pushint( ival << popint() );
		}
		else if ( Token==T_SHR )
		{
			getoken();
			ival = popint();
			expr9();
			pushint( ival >> popint() );
		}
		else
			return;
	}
}

expr9()
{
	int ival;

	expr10();
	for ( ;; )
	{
		if ( Token==T_ADD )
		{
			getoken();
			ival = popint();
			expr10();
			pushint( ival + popint() );
		}
		else if ( Token==T_SUB )
		{
			getoken();
			ival = popint();
			expr10();
			pushint( ival - popint() );
		}
		else
			return;
	}
}

expr10()
{
	int ival;

	primary();
	for ( ;; )
	{
		if ( Token==T_MUL )
		{
			getoken();
			ival = popint();
			primary();
			pushint( ival * popint() );
		}
		else if ( Token==T_DIV )
		{
			getoken();
			ival = popint();
			primary();
			pushint( ival / popint() );
		}
		else if ( Token==T_MOD )
		{
			getoken();
			ival = popint();
			primary();
			pushint( ival % popint() );
		}
		else
			return;
	}
}

primary()
{
	int index;
	DATUM data;
	VARIABLE *pvar;

	switch ( Token )
	{
	case T_LPAREN:
		/*
		 * it's a parenthesized expression
		 */
		getoken();
		expression();
		if ( Token!=T_RPAREN )
			error( "missing ')'", ACT_ERROR );
		getoken();
		break;
	case T_LNOT:
		getoken();
		primary();
		pushint( ! popint() );
		break;
	case T_NOT:
		getoken();
		primary();
		pushint( ~ popint() );
		break;
	case T_ADD:
		getoken();
		primary();
		break;
	case T_SUB:
		getoken();
		primary();
		pushint( - popint() );
		break;
	case T_INCR:
	case T_DECR:
		preincdec();
		break;
	case T_MUL:
		getoken();
		primary();
		/*
		 * If item on stack is an LVALUE, do an extra level of
		 * indirection before changing it to an LVALUE.
		 */
		if ( Stackptr->lvalue )
			Stackptr->value.ptrptr = *Stackptr->value.ptrptr;
		Stackptr->lvalue = 1;
		--Stackptr->class;
		break;
	case T_AND:
		getoken();
		primary();
		if ( Stackptr->lvalue )
			Stackptr->lvalue = 0;
		else
			error( "'&' operator needs an lvalue", ACT_ERROR );
		break;
	case T_CONSTANT:
		pushint( Value.ival );
		getoken();
		break;
	case T_REGEXP:
		/*
		 * It's a regular expression - parse it and compile it.
		 */
		if ( Where == PATTERN )
		{
			/*
			 * We're processing a pattern right now - perform a
			 * match of the regular expression agains input line.
			 */
			unparse( Fields, Fieldcount, Linebuf, Fieldsep );
			pushint( match( Linebuf, Value.dptr ) );
		}
		else
			push( 1, ACTUAL, BYTE, &Value );
		getoken();
		break;
	case T_NF:
		pushint( Fieldcount );
		getoken();
		break;
	case T_NR:
		pushint( Recordcount );
		getoken();
		break;
	case T_FS:
		Fieldsep[1] = 0;
		data.dptr = Fieldsep;
		push( 0, LVALUE, BYTE, &data );
		getoken();
		break;
	case T_RS:
		Recordsep[1] = 0;
		data.dptr = Recordsep;
		push( 0, LVALUE, BYTE, &data );
		getoken();
		break;
	case T_FILENAME:
		data.dptr = Filename;
		push( 1, ACTUAL, BYTE, &data );
		getoken();
		break;
	case T_DOLLAR:
		/*
		 * It's a reference to one (or all) of the words in Linebuf.
		 */
		getoken();
		primary();
		if ( index = popint() )
		{
			if ( index > Fieldcount )
				index = Fieldcount;
			else if ( index < 1 )
				index = 1;
			data.dptr = Fields[ index-1 ];
		}
		else
		{
			/*
			 * Reconstitute the line buffer in case any of the
			 * fields have been changed.
			 */
			unparse( Fields, Fieldcount, Linebuf, Fieldsep );
			data.dptr = Linebuf;
		}
		/*
		 * $<expr>'s are treated the same as string constants:
		 */
		push( 1, ACTUAL, BYTE, &data );
		break;
	case T_STRING:
		push( 1, ACTUAL, BYTE, &Value );
		getoken();
		break;
	case T_FUNCTION:
		/*
		 * Do a built-in function call
		 */
		index = Value.ival;
		getoken();
		function( index );
		break;
	case T_VARIABLE:
		pvar = Value.dptr;
		getoken();
		/*
		 * it's a plain variable. The way a variable is
		 * represented on the stack depends on its type:
		 *      lvalue class value.dptr
		 * vars:  1      0   address of var
		 * ptrs:  1      1   ptr to address of ptr
		 * array: 0      1   address of var
		 */
		if ( pvar->vclass && !pvar->vlen )
			/* it's a pointer */
			data.dptr = &pvar->vptr;
		else
			/* an array or simple variable */
			data.dptr = pvar->vptr;
		/*
		 * If it's an array it can't be used as an LVALUE.
		 */
		push( pvar->vclass, !pvar->vlen, pvar->vsize, &data );
		break;
	case T_EOF:
		break;
	default:
		syntaxerror();
	}
	/*
	 * a "[" means it's an array reference
	 */
	if ( Token==T_LBRACKET )
	{
		getoken();
		if ( ! Stackptr->class )
			error( "'[]' needs an array or pointer", ACT_ERROR );
		/*
		 * compute the subscript
		 */
		expression();
		if ( Token!=T_RBRACKET )
			error( "missing ']'", ACT_ERROR );
		getoken();
		index = popint();
		/*
		 * compute the offset (subscript times two for int arrays)
		 * and then the effective address.
		 */
		index *= Stackptr->size;
		if ( Stackptr->lvalue )
			/*
			 * It's a pointer - don't forget that the stack top
			 * item's value is the address of the pointer so we
			 * must do another level of indirection.
			 */
			Stackptr->value.dptr = *Stackptr->value.ptrptr+index;
		else
			/*
			 * It's a plain array - the stack top item's value is
			 * the address of the first element in the array.
			 */
			Stackptr->value.dptr += index;

		/*
		 * The stack top item now becomes an LVALUE, but we've
		 * reduced the indirection level.
		 */
		Stackptr->lvalue = 1;
		--Stackptr->class;
	}

	if ( Token==T_INCR || Token==T_DECR )
		postincdec();
}

preincdec()
{
	/*
	 * Pre increment/decrement
	 */
	int incr;

	incr = Token==T_INCR ? 1 : -1;
	getoken();
	primary();
	if ( Stackptr->lvalue )
	{
		if ( Stackptr->class )
			incr *= Stackptr->size;
		*Stackptr->value.ptrptr += incr;
	}
	else
		error( "pre '++' or '--' needs an lvalue", ACT_ERROR );
}

postincdec()
{
	/*
	 * Post increment/decrement
	 */
	char **pp;
	int incr;

	incr = Token==T_INCR ? 1 : -1;
	getoken();
	if ( Stackptr->lvalue )
	{
		if ( Stackptr->class )
		{
			/*
			 * It's a pointer - save its old value then
			 * increment/decrement the pointer.  This makes the
			 * item on top of the stack look like an array, which
			 * means it can no longer be used as an LVALUE. This
			 * doesn't really hurt, since it doesn't make much
			 * sense to say:
			 *   char *cp;
			 *   cp++ = value;
			 */
			pp = *Stackptr->value.ptrptr;
			*Stackptr->value.ptrptr += incr * Stackptr->size;
			Stackptr->value.ptrptr = pp;
		}
		else
		{
			/*
			 * It's a simple variable - save its old value then
			 * increment/decrement the variable.  This makes the
			 * item on top of the stack look like a constant,
			 * which means it can no longer be used as an LVALUE.
			 * Same reasoning as above.
			 */
			if ( Stackptr->size == BYTE )
				pp = *Stackptr->value.dptr;
			else
				pp = *Stackptr->value.ptrptr;
			*Stackptr->value.ptrptr += incr;
			Stackptr->value.ival = pp;
		}
		Stackptr->lvalue = 0;
	}
	else
		error( "post '++' or '--' needs an lvalue", ACT_ERROR );
}

statement()
{
	/*
	 * Evaluate a statement
	 */
	char *repeat, *body;

	switch ( Token )
	{
	case T_EOF:
		break;
	case T_CHAR:
	case T_INT:
		declist();
		break;
	case T_LBRACE:
		/*
		 * parse a compound statement
		 */
		getoken();
		while ( !Saw_break && Token!=T_RBRACE )
			statement();

		if ( Token==T_RBRACE )
			getoken();
		break;
	case T_IF:
		/*
		 * parse an "if-else" statement
		 */
		if ( getoken() != T_LPAREN )
			syntaxerror();
		getoken();
		expression();
		if ( Token!=T_RPAREN )
			syntaxerror();
		getoken();
		if ( popint() )
		{
			statement();
			if ( Token==T_ELSE )
			{
				getoken();
				skipstatement();
			}
		}
		else
		{
			skipstatement();
			if ( Token==T_ELSE )
			{
				getoken();
				statement();
			}
		}
		break;
	case T_WHILE:
		/*
		 * parse a "while" statement
		 */
		repeat = Actptr;
		for ( ;; )
		{
			if ( getoken() != T_LPAREN )
				syntaxerror();

			getoken();
			expression();
			if ( Token!=T_RPAREN )
				syntaxerror();

			if ( popint() )
			{
				body = Actptr;
				getoken();
				statement();
				if ( Saw_break )
				{
					Actptr = body;
					Saw_break = 0;
					break;
				}
				Actptr = repeat;
			}
			else
				break;
		}
		getoken();
		skipstatement();
		break;
	case T_BREAK:
		/*
		 * parse a "break" statement
		 */
		getoken();
		Saw_break = 1;
		break;
	case T_SEMICOLON:
		break;
	default:
		expression();
		popint();
	}

	if ( Token==T_SEMICOLON )
		getoken();
}	

skipstatement()
{
	/*
	 * Skip a statement
	 */

	switch ( Token )
	{
	case T_LBRACE:
		/*
		 * skip a compound statement
		 */
		skip( T_LBRACE, T_RBRACE );
		break;
	case T_IF:
		/*
		 * skip an "if-else" statement
		 */
		getoken();	/* skip 'if' */
		skip( T_LPAREN, T_RPAREN );
		skipstatement();
		if ( Token==T_ELSE )
		{
			getoken();	/* skip 'else' */
			skipstatement();
		}
		break;
	case T_WHILE:
		/*
		 * skip a "while" statement
		 */
		getoken();	/* skip 'while' */
		skip( T_LPAREN, T_RPAREN );
		skipstatement();
		break;
	default:
		/*
		 * skip a one-liner
		 */
		while (Token!=T_SEMICOLON && Token!=T_RBRACE && Token!=T_EOF)
			getoken();
		if ( Token==T_EOF )
			error( "unexpected end", ACT_ERROR );
		if ( Token==T_SEMICOLON )
			getoken();
	}
}

skip( left, right )
char left, right;
{
	/*
	 * Skip matched left and right delimiters and everything in between
	 */
	int parity;
	char *save, errmsg[ 80 ];

	parity = 1;
	save = Actptr;
	while ( getoken() != T_EOF )
	{
		if ( Token == left )
		{
			save = Actptr;
			++parity;
		}
		else if ( Token == right )
			--parity;
		if ( !parity )
		{
			getoken();
			return;
		}
	}
	Actptr = save;

	sprintf( errmsg, "mismatched '%c' and '%c'", left, right );
	error( errmsg, ACT_ERROR );
}

syntaxerror()
{
	error( "syntax error", ACT_ERROR );
}
