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

static char pop_array[MAX_TOKEN + 1];

void init_pop_array()
{
	register int i;

	DBUG_ENTER("init_pop_array");
	for(i = 0; i <= MAX_TOKEN; i++)
		pop_array[i] = 1;
	pop_array[T_STATEMENT] = pop_array[T_IF] = pop_array[T_DECLARE] = 0;
	pop_array[T_DECLARE] = pop_array[T_ARRAY_DECLARE] = 0;
	pop_array[T_BREAK] = 0;
	DBUG_VOID_RETURN;
}

int dopattern( root )
register EXPR_NODE *root;
{
	DBUG_ENTER("dopattern");
	Where = PATTERN;
	walk_tree(root);
	DBUG_RETURN(popint());
}

void doaction( root )
register EXPR_NODE *root;
{
	DBUG_ENTER("doaction");
	Where = ACTION;
	walk_tree(root);
	DBUG_VOID_RETURN;
}

void walk_tree(root)
register EXPR_NODE *root;
{
	register int ival;
	DATUM data;
	register VARIABLE *pvar;
	register VARDECL *pdecl;

	DBUG_ENTER("walk_tree");
	if(Saw_break || !root)
		DBUG_VOID_RETURN;
	switch(root->operator) {
	case T_ASSIGN:
		walk_tree(root->left);
		walk_tree(root->right);
		assignment();
		break;
	case T_LOR:
		walk_tree(root->left);
		if(popint())
			pushint(1);
		else {
			walk_tree(root->right);
			pushint(popint() != 0);
		}
		break;
	case T_LAND:
		walk_tree(root->left);
		if(!popint())
			pushint(0);
		else {
			walk_tree(root->right);
			pushint(popint() != 0);
		}
		break;
	case T_OR:
		walk_tree(root->left);
		walk_tree(root->right);
		pushint(popint() | popint());
		break;
	case T_AND:
		walk_tree(root->left);
		walk_tree(root->right);
		pushint(popint() & popint());
		break;
	case T_XOR:
		walk_tree(root->left);
		walk_tree(root->right);
		pushint(popint() ^ popint());
		break;
	case T_EQ:
		walk_tree(root->left);
		walk_tree(root->right);
		pushint(popint() == popint());
		break;
	case T_NE:
		walk_tree(root->left);
		walk_tree(root->right);
		pushint(popint() != popint());
		break;
	case T_LE:
		walk_tree(root->left);
		ival = popint();
		walk_tree(root->right);
		pushint(ival <= popint());
		break;
	case T_GE:
		walk_tree(root->left);
		ival = popint();
		walk_tree(root->right);
		pushint(ival >= popint());
		break;
	case T_LT:
		walk_tree(root->left);
		ival = popint();
		walk_tree(root->right);
		pushint(ival < popint());
		break;
	case T_GT:
		walk_tree(root->left);
		ival = popint();
		walk_tree(root->right);
		pushint(ival > popint());
		break;
	case T_SHL:
		walk_tree(root->left);
		ival = popint();
		walk_tree(root->right);
		pushint(ival << popint());
		break;
	case T_SHR:
		walk_tree(root->left);
		ival = popint();
		walk_tree(root->right);
		pushint(ival >> popint());
		break;
	case T_ADD:
		walk_tree(root->left);
		walk_tree(root->right);
		pushint(popint() + popint());
		break;
	case T_SUB:
		walk_tree(root->left);
		ival = popint();
		walk_tree(root->right);
		pushint(ival - popint());
		break;
	case T_MUL:
		walk_tree(root->left);
		ival = popint();
		walk_tree(root->right);
		pushint(ival * popint());
		break;
	case T_DIV:
		walk_tree(root->left);
		ival = popint();
		walk_tree(root->right);
		pushint(ival / popint());
		break;
	case T_MOD:
		walk_tree(root->left);
		ival = popint();
		walk_tree(root->right);
		pushint(ival % popint());
		break;
	case T_LNOT:
		walk_tree(root->left);
		pushint( ! popint() );
		break;
	case T_NOT:
		walk_tree(root->left);
		pushint( ~ popint() );
		break;
	case T_INCR:
		walk_tree(root->left);
		preincdec(1);
		break;
	case T_DECR:
		walk_tree(root->left);
		preincdec(-1);
		break;
	case T_DOLLAR:
		/*
		 * It's a reference to one (or all) of the words in Linebuf.
		 */
		walk_tree(root->left);
		if ( ival = popint() )
		{
			if ( ival > Fieldcount )
				ival = Fieldcount;
			else if ( ival < 1 )
				ival = 1;
			data.dptr = Fields[ ival-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( (char) 1, (char) ACTUAL, (char) BYTE, &data );
		break;
	case T_UMINUS:
		walk_tree(root->left);
		pushint( - popint() );
		break;
	case T_STAR:
		walk_tree(root->left);
		/*
		 * 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 = 
				(char **) *Stackptr->value.ptrptr;
		Stackptr->lvalue = 1;
		--Stackptr->class;
		break;
	case T_ADDROF:
		walk_tree(root->left);
		if ( Stackptr->lvalue )
			Stackptr->lvalue = 0;
		else
			error( "'&' operator needs an lvalue", ACT_ERROR );
		break;
	case T_CONSTANT:
		pushint(((DATUM *) (root->left))->ival);
		break;
	case T_FUNCTION:
		function(((DATUM *) (root->left))->ival, root->right);
		break;
	case T_REGEXP:
		/*
		 * Perform a match of the regular expression agains input
		 * line.
		 */
		unparse( Fields, Fieldcount, Linebuf, Fieldsep );
		pushint( match( Linebuf, (char *) root->left ) );
		break;
	case T_REGEXP_ARG:
		/*
		 * A regular expression that is to be passed as a function
		 * argument.
		 */
		data.dptr = (char *) root->left;
		push( (char) 1, (char) ACTUAL, (char) BYTE, &data );
		break;
	case T_STRING:
		data.dptr = (char *) root->left;
		push( (char) 1, (char) ACTUAL, (char) BYTE, &data );
		break;
	case T_NF:
		pushint( Fieldcount );
		break;
	case T_NR:
		pushint( Recordcount );
		break;
	case T_FS:
		data.dptr = Fieldsep;
		push( (char) 1, (char) ACTUAL, (char) BYTE, &data );
		break;
	case T_RS:
		data.dptr = Recordsep;
		push( (char) 1, (char) ACTUAL, (char) BYTE, &data );
		break;
	case T_FILENAME:
		data.dptr = Filename;
		push( (char) 1, (char) ACTUAL, (char) BYTE, &data );
		break;
	case T_VARIABLE:
		pvar = (VARIABLE *) root->left;
		/*
		 * 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 = (char *) &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, (char) !pvar->vlen, pvar->vsize, &data );
		break;
	case T_LBRACKET:
		walk_tree(root->left);
		if ( ! Stackptr->class )
			error( "'[]' needs an array or pointer", ACT_ERROR );
		/*
		 * compute the subscript
		 */
		walk_tree(root->right);
		ival = popint();
		/*
		 * compute the offset (subscript times WORD for int arrays)
		 * and then the effective address.
		 */
		ival *= 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+ival;
		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 += ival;

		/*
		 * The stack top item now becomes an LVALUE, but we've
		 * reduced the indirection level.
		 */
		Stackptr->lvalue = 1;
		--Stackptr->class;
		break;
	case T_POSTINCR:
		walk_tree(root->left);
		postincdec(1);
		break;
	case T_POSTDECR:
		walk_tree(root->left);
		postincdec(-1);
		break;
	case T_STATEMENT:
		if(root->left) {
			walk_tree(root->left);
			if(pop_array[root->left->operator])
				popint();
		}
		walk_tree(root->right);
		break;
	case T_DECLARE:
		pdecl = (VARDECL *) root->left;
		pvar = pdecl->variable;
		if(pdecl->vsize != ((pvar->vlen ? pvar->vlen : 1)*
				    pvar->vsize)) {
			/*
			 * The amount of storage needed for the variable has
			 * changed.
			 */
			free(pvar->vptr);
			pvar->vptr = get_clear_memory(pdecl->vsize);
		}
		pvar->vclass = pdecl->vclass;
		pvar->vsize = pdecl->vsize;
		pvar->vlen = 0;
		walk_tree(root->right);
		break;
	case T_ARRAY_DECLARE:
		/* Compute the dimension */
		walk_tree(root->left->right);
		ival = popint();
		pdecl = (VARDECL *) root->left->left;
		pvar = pdecl->variable;
		if((ival*pdecl->vsize) != ((pvar->vlen ? pvar->vlen : 1)*
				    pvar->vsize)) {
			free(pvar->vptr);
			pvar->vptr = get_clear_memory(ival*pdecl->vsize);
		}
		pvar->vclass = pdecl->vclass;
		pvar->vsize = pdecl->vsize;
		pvar->vlen = ival;
		walk_tree(root->right);
		break;
	case T_IF:
		walk_tree(root->left->left);
		if(popint())
			walk_tree(root->left->right);
		else
			walk_tree(root->right);
		break;
	case T_WHILE:
		while( !Saw_break )
		{
			walk_tree(root->left);
			if( ! popint() )
				break;
			walk_tree(root->right);
		}
		Saw_break = 0;
		break;
	case T_BREAK:
		Saw_break = 1;
		break;
	default:
		DBUG_PRINT("walk_tree",
			("decimal value of operator = %d",root->operator));
		error("internal error: parse tree node with unknown symbol",
		      ACT_ERROR);
	}
	DBUG_VOID_RETURN;
}

void preincdec(incr)
register int incr;
{
	/*
	 * Pre increment/decrement
	 */
	DBUG_ENTER("preincdec");
	if ( Stackptr->lvalue )
	{
		if ( Stackptr->class )
			incr *= Stackptr->size;
		*Stackptr->value.ptrptr += incr;
	}
	else
		error( "pre '++' or '--' needs an lvalue", ACT_ERROR );
	DBUG_VOID_RETURN;
}


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

	DBUG_ENTER("postincdec");
	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 = (char **) *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 = (char **) *Stackptr->value.dptr;
			else
				pp = (char **) *Stackptr->value.ptrptr;
			*Stackptr->value.ptrptr += incr;
			Stackptr->value.ptrptr = pp;
		}
		Stackptr->lvalue = 0;
	}
	else
		error( "post '++' or '--' needs an lvalue", ACT_ERROR );
	DBUG_VOID_RETURN;
}
