/*
 * Bawk C actions builtin functions, variable declaration, and
 * stack management routines.
 */
#include <stdio.h>
#include "bawk.h"

#define MAXARGS		10	/* max # of arguments to a builtin func */
#define F_PRINTF	1
#define F_GETLINE	2
#define F_STRLEN	3
#define F_STRCPY	4
#define F_STRCMP	5
#define F_TOUPPER	6
#define F_TOLOWER	7
#define F_MATCH		8
#define F_NEXTFILE	9

int isfunction( s )
register char *s;
{
	/*
	 * Compare the string "s" to a list of builtin functions
	 * and return its (non-zero) token number.
	 * Return zero if "s" is not a function.
	 */
	DBUG_ENTER("isfunction");
	switch(*s) {
	    case 'g':
		if ( !strcmp( s, "getline" ) )
			DBUG_RETURN(F_GETLINE);
		break;
	    case 'm':
		if ( !strcmp( s, "match" ) )
			DBUG_RETURN(F_MATCH);
		break;
	    case 'n':
		if ( !strcmp( s, "nextfile" ) )
			DBUG_RETURN(F_NEXTFILE);
		break;
	    case 'p':
		if ( !strcmp( s, "printf" ) )
			DBUG_RETURN(F_PRINTF);
		break;
	    case 's':
		if ( !strcmp( s, "strlen" ) )
			DBUG_RETURN(F_STRLEN);
		if ( !strcmp( s, "strcpy" ) )
			DBUG_RETURN(F_STRCPY);
		if ( !strcmp( s, "strcmp" ) )
			DBUG_RETURN(F_STRCMP);
		break;
	    case 't':
		if ( !strcmp( s, "toupper" ) )
			DBUG_RETURN(F_TOUPPER);
		if ( !strcmp( s, "tolower" ) )
			DBUG_RETURN(F_TOLOWER);
		break;
	    default:;
	}
	DBUG_RETURN(0);
}

int iskeyword( s )
register char *s;
{
	/*
	 * Compare the string "s" to a list of keywords and return its
	 * (non-zero) token number.  Return zero if "s" is not a keyword.
	 */
	DBUG_ENTER("iskeyword");
	switch(*s) {
	    case 'b':
		if ( !strcmp( s, "break" ) )
			DBUG_RETURN(T_BREAK);
		break;
	    case 'c':
		if ( !strcmp( s, "char" ) )
			DBUG_RETURN(T_CHAR);
		break;
	    case 'e':
		if ( !strcmp( s, "else" ) )
			DBUG_RETURN(T_ELSE);
		break;
	    case 'i':
		if ( !strcmp( s, "int" ) )
			DBUG_RETURN(T_INT);
		if ( !strcmp( s, "if" ) )
			DBUG_RETURN(T_IF);
		break;
	    case 'w':
		if ( !strcmp( s, "while" ) )
			DBUG_RETURN(T_WHILE);
		break;
	    case 'B':
		if ( !strcmp( s, "BEGIN" ) )
			DBUG_RETURN(T_BEGIN);
		break;
	    case 'E':
		if ( !strcmp( s, "END" ) )
			DBUG_RETURN(T_END);
		break;
	    case 'F':
		if ( !strcmp( s, "FS" ) )
			DBUG_RETURN(T_FS);
		if ( !strcmp( s, "FILENAME" ) )
			DBUG_RETURN(T_FILENAME);
		break;
	    case 'N':
		if ( !strcmp( s, "NF" ) )
			DBUG_RETURN(T_NF);
		if ( !strcmp( s, "NR" ) )
			DBUG_RETURN(T_NR);
		break;
	    case 'R':
		if ( !strcmp( s, "RS" ) )
			DBUG_RETURN(T_RS);
		break;
	    default:;
	}
	DBUG_RETURN(0);
}

void function( funcnum, arg_root )
register int funcnum;
register EXPR_NODE *arg_root;
{
	register int argc, args[ MAXARGS ];

	DBUG_ENTER("function");
	argc = 0;
	/*
	 * If there are any arguments, evaluate them and copy their values
	 * to a local array.
	 */
	for(; argc < MAXARGS && arg_root; arg_root = arg_root->right)
	{
		walk_tree(arg_root->left);
		args[ argc++ ] = popint();
	}
	switch ( funcnum )
	{
	case F_PRINTF:	/* just like the real printf() function */
		pushint( printf( (char *) args[0], args[1], args[2], args[3],
			 args[4], args[5], args[6], args[7], args[8],
			 args[9] ) );
		break;
	case F_GETLINE:
		/*
		 * Get the next line of input from the current input file
		 * and parse according to the current field seperator.
		 * Don't forget to free up the previous line's words first...
		 */
		while ( Fieldcount )
			free( Fields[ --Fieldcount ] );
		pushint( getline() );
		Fieldcount = parse( Linebuf, Fields, Fieldsep );
		break;
	case F_STRLEN:	/* calculate length of string argument */
		pushint( strlen( args[0] ) );
		break;
	case F_STRCPY:	/* copy second string argument to first string */
		pushint( strcpy( args[0], args[1] ) );
		break;
	case F_STRCMP:	/* compare two strings */
		pushint( strcmp( args[0], args[1] ) );
		break;
	case F_TOUPPER:	/* convert the character argument to upper case */
		pushint( toupper( args[0] ) );
		break;
	case F_TOLOWER:	/* convert the character argument to lower case */
		pushint( tolower( args[0] ) );
		break;
	case F_MATCH:	/* match a string argument to a regular expression */
		pushint( match( (char *) args[0], (char *) args[1] ) );
		break;
	case F_NEXTFILE:/* close current input file and process next file */
		endfile();
		pushint( 1 ); /* is this a correct value? jw */
		break;
	default:	/* oops! */
		error( "bad function call", ACT_ERROR );
	}
	DBUG_VOID_RETURN;
}

VARIABLE *
findvar( s )
register char *s;
{
	/*
	 * Search the symbol table for a variable whose name is "s".
	 */
	register VARIABLE *pvar;
	register int i;
	register char name[ MAXVARLEN ];

	DBUG_ENTER("findvar");
	i = 0;
	while ( i < MAXVARLEN && (isalnum( *s ) || (*s == '_')))
		name[i++] = *s++;
	if ( i<MAXVARLEN )
		name[i] = 0;

	for ( pvar = Vartab; pvar<Nextvar; ++pvar )
	{
		if ( !strncmp( pvar->vname, name, MAXVARLEN ) )
			DBUG_RETURN(pvar);
	}
	DBUG_RETURN(NULL);
}

VARIABLE *
addvar( name )
register char *name;
{
	/*
	 * Add a new variable to symbol table and assign it default
	 * attributes (int name;)
	 */
	register int i;

	DBUG_ENTER("addvar");
	if ( Nextvar <= Vartab + MAXVARTABSZ )
	{
		i = 0;
		while ( i<MAXVARLEN && (isalnum( *name ) || (*name == '_')))
			Nextvar->vname[i++] = *name++;
		if ( i<MAXVARLEN )
			Nextvar->vname[i] = 0;

		Nextvar->vclass = 0;
		Nextvar->vsize = WORD;
		Nextvar->vlen = 0;
		/*
		 * Allocate some new room
		 */
		Nextvar->vptr = get_clear_memory( WORD );
	}
	else
		error( "symbol table overflow", MEM_ERROR );

	DBUG_RETURN(Nextvar++);
}

EXPR_NODE *declist_parse()
{
	/*
	 * Parse a "char" or "int" statement.
	 */
	register char type;
	register EXPR_NODE *root, *end_pointer;

	DBUG_ENTER("declist_parse");
	type = Token;
	getoken();
	root = end_pointer = decl_parse( type );
	while ( Token==T_COMMA )
	{
		getoken();
		end_pointer->right = decl_parse( type );
		end_pointer = end_pointer->right;
	}
	if ( Token==T_SEMICOLON )
		getoken();
	DBUG_RETURN(root);
}

EXPR_NODE *decl_parse( type )
register int type;
{
	/*
	 * Parse an element of a "char" or "int" declaration list.
	 * The function stmt_compile() has already entered the variable
	 * into the symbol table as an integer, this routine simply changes
	 * the symbol's class, size or length according to the declaraction.
	 * WARNING: The interpreter depends on the fact that pointers are
	 * the same length as int's.  If your machine uses long's for
	 * pointers either change the code or #define int long (or whatever).
	 */
	register char class, size;
	register VARIABLE *pvar;
	register VARDECL *pdecl;
	register EXPR_NODE *node;
	EXPR_NODE *action;

	DBUG_ENTER("decl_parse");
	if ( Token==T_MUL )
	{
		/*
		 * it's a pointer
		 */
		getoken();
		node = decl_parse( type );
		if(node->operator == T_DECLARE)
			((VARDECL *) (node->right))->vclass += 1;
		else
			((VARDECL *) (node->right->right))->vclass += 1;
	}
	else if ( Token==T_VARIABLE )
	{
		/*
		 * Simple variable so far.  The token value (in the global
		 * "Value" variable) is a pointer to the variable's symbol
		 * table entry.
		 */
		pdecl = (VARDECL *) getmemory(sizeof(VARDECL));
		pvar = (VARIABLE *) Value.dptr;
		getoken();
		class = 0;
		/*
		 * Compute its length
		 */
		if ( Token==T_LBRACKET )
		{
			/*
			 * It's an array.
			 */
			node = get_expr_node((char) T_ARRAY_DECLARE);
			node->left = action =
				get_expr_node((char) T_ARRAY_DECLARE);
			action->left = (EXPR_NODE *) pdecl;
			getoken();
			++class;
			/*
			 * Parse the dimension expression
			 */
			action->right = expr_parse();
			if ( Token!=T_RBRACKET )
				error( "missing ']'", ACT_ERROR );
			getoken();
		}
		else
		{
			/*
			 * It's a simple variable.
			 */
			node = get_expr_node((char) T_DECLARE);
			node->left = (EXPR_NODE *) pdecl;
		}
		size = (type==T_CHAR) ? BYTE : WORD;
		pdecl->variable = pvar;
		pdecl->vclass = class;
		pdecl->vsize = size;
	}
	else
		syntaxerror();

	DBUG_RETURN(node);
}

void assignment()
{
	/*
	 * Perform an assignment
	 */
	int ival;

	DBUG_ENTER("assignment");
	ival = popint();
	/*
	 * make sure we've got an lvalue
	 */
	if ( Stackptr->lvalue )
	{
		if ( Stackptr->class )
			movmem((char *) &ival, Stackptr->value.dptr, WORD );
		else
			movmem((char *) &ival, Stackptr->value.dptr,
			       Stackptr->size);
		pop();
		pushint( ival );
	}
	else
		error( "'=' needs an lvalue", ACT_ERROR );
	DBUG_VOID_RETURN;
}

int pop()
{
	/*
	 * Pop the stack and return the integer value
	 */
	DBUG_ENTER("pop");
	if ( Stackptr >= Stackbtm )
		DBUG_RETURN((Stackptr--)->value.ival);
	DBUG_RETURN(error( "stack underflow", ACT_ERROR ));
}

void push( pclass, plvalue, psize, pdatum )
register char pclass, plvalue, psize;
register DATUM *pdatum;
{
	/*
	 * Push item parts onto the stack
	 */
	DBUG_ENTER("push");
	if ( ++Stackptr <= Stacktop )
	{
		Stackptr->lvalue = plvalue;
		Stackptr->size = psize;
		if ( !(Stackptr->class = pclass)  &&  !plvalue )
			Stackptr->value.ival = pdatum->ival;
		else
			Stackptr->value.dptr = pdatum->dptr;
	}
	else
		error( "stack overflow", MEM_ERROR );
	DBUG_VOID_RETURN;
}

void pushint( intvalue )
register int intvalue;
{
	/*
	 * push an integer onto the stack
	 */
	DBUG_ENTER("pushint");
	if ( ++Stackptr <= Stacktop )
	{
		Stackptr->lvalue =
		Stackptr->class = 0;
		Stackptr->size = WORD;
		Stackptr->value.ival = intvalue;
	}
	else
		error( "stack overflow", MEM_ERROR );
	DBUG_VOID_RETURN;
}

int popint()
{
	/*
	 * Resolve the item on the top of the stack and return it
	 */
	register int intvalue;

	DBUG_ENTER("popint");
	if ( Stackptr->lvalue )
	{
		/*
		 * if it's a byte indirect, sign extend it
		 */
		if ( Stackptr->size == BYTE && !Stackptr->class )
			intvalue = *Stackptr->value.dptr;
		else
		{
			/*
			 * otherwise, it's an unsigned int
			 */
			intvalue = (int) (*Stackptr->value.ptrptr);
		}
		pop();
		DBUG_RETURN(intvalue);
	}
	else
	{
		/*
		 * else it's an ACTUAL, just pop it
		 */
		DBUG_RETURN(pop());
	}
}

