/*
**      code-sim.c - PCode execution routines.  The machine simulator
**
**
** Copyright (c) 1995  Hughes Technologies Pty Ltd
**
** Permission to use, copy, and distribute for non-commercial purposes,
** is hereby granted without fee, providing that the above copyright
** notice appear in all copies and that both the copyright notice and this
** permission notice appear in supporting documentation.
**
** This software is provided "as is" without any expressed or implied warranty.
**
**
*/


#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <ctype.h>
#include <msql/msql.h>


#ifdef HAVE_UNISTD_H
#  include <unistd.h>
#endif
#ifdef HAVE_MALLOC_H
#  include <malloc.h>
#endif
#ifdef HAVE_STRING_H
#  include <string.h>
#else
#  ifdef HAVE_STRINGS_H
#    include <strings.h>
#  endif
#endif


#include "y.tab.h"
#include "lite.h"
#include "regexp/regexp.h"

extern	char	*scriptBuf;
extern	code_t	*codeHead;
extern	funct_t	*functions;
extern	sym_t	**symHead;

static	sstack_t *stackHead = NULL;
static	int	curBlock = 0;
static  code_t	*curCode;
static	sym_t	*retVal;

sym_t	*externReturn;

static char	errBuf[240];


#define	REG	register

int simGetLineNum()
{
	if (curCode)
		return(curCode->line);
	else
		return(-1);
}


char *simGetFileName()
{
	extern	char *libNames[];

	if (!curCode)
		return(NULL);
	if (curCode->file == 0)
		return(NULL);
	return(libNames[(int)curCode->file]);
}





/*
** Push / Pop
**
** The basis of the VM is a normal stack based machine.  To help
** performance, we don't free the stack frames as they are popped.
** We end up holding X frames in reserve ready for re-use (where X is
** set by MAX_FREE_FRAMES
*/

static int stackFreeFrames = 0;
static sstack_t *spareStackFrames = NULL;


static void push(sym)
	sym_t	*sym;
{
	sstack_t	*new;
	static sym_t	blank = {"","","",TYPE_CHAR,SCALAR,0};

	if (stackFreeFrames == 0)
	{
		new = (sstack_t *)malloc(sizeof(sstack_t));
	}
	else
	{
		new = spareStackFrames;
		spareStackFrames = spareStackFrames->next;
		stackFreeFrames--;
	}
	if (!sym)
	{
		new->sym = symdup(&blank);
	}
	else
	{
		if (!sym->val)
			new->sym = symdup(&blank);
		else
			new->sym = symdup(sym);
	}
	new->next = stackHead;
	new->block = curBlock;
	stackHead = new;
#ifdef TRACE
	printf("** PUSH '%s'\n",sym->name);
#endif
}



#define	MAX_FREE_FRAMES		10

static sym_t *pop()
{
	sstack_t *tmp;
	sym_t	*sym;

	if (!stackHead)
	{
		runError("Stack Underflow!!\n");
		exit(1);
	}
	tmp = stackHead;
	stackHead = stackHead->next;
	sym = tmp->sym;
	if (stackFreeFrames < MAX_FREE_FRAMES)
	{
		tmp->next = spareStackFrames ;
		spareStackFrames = tmp;
		stackFreeFrames ++;	
	}
	else
	{
		free(tmp);
	}
#ifdef TRACE
	printf("** POP '%s'\n",sym->name);
#endif
	return(sym);
}



void simPush(cur)
	code_t	*cur;
{
	sym_t	*sym;
	char	*tmp,
		*tmp2;
	int	oldLen;

	sym = symGetSymbol(cur->arg);
	if (sym)
	{
		if (*(sym->name))
		{
			if ((*sym->name != '$' && *sym->name != '@')
			     && sym->type == TYPE_CHAR 
			     && sym->array == SCALAR)
			{
				tmp = expandText(sym->val);
				tmp2 = sym->val;
				oldLen = sym->length;
				sym->val = tmp;
				sym->length = strlen(tmp) + 1;
				push(sym);
				sym->val = tmp2;
				sym->length = oldLen;
			}
			else
				push(sym);
		}
		else
			push(NULL);
	}
	else
		push(NULL);
}




void simBlockIn()
{
	curBlock++;
}




void simBlockOut()
{
	if (curBlock == 0)
	{
		runError("Code Block Underflow");
		exit(1);
	}
	curBlock--;
}


static char *typeCodes[] = {
        "REAL",
        "INT",
        "CHAR",
        "UINT",
        "????"};



void simCall(code)
	code_t	*code;
{
	plist_t	*head,
		*tail,
		*cur;
	efunct_t *funct;
	int	numParams,
		curParam,
		res;
		

#ifdef TRACE
	printf("** CALL '%s'\n",code->arg);
#endif

	/*
	** Is it a user defined function?
	*/
	res = runCode(code->arg);
	if (res == 0)
	{
		return;
	}
	if (res == -2)
	{
		exit(1);
	}


	/*
	** Build param list
	*/
	head = tail = NULL;
	if (stackHead)
	{
		while(stackHead->block == curBlock + 1)
		{
			cur = (plist_t*)malloc(sizeof(plist_t));
			cur->sym = stackHead->sym;
			cur->next = head;
			head = cur;
			pop();
			if (!stackHead)
			{
				break;
			}
		}
	}

	/*
	** Find the function and check the args
	*/
	funct = (efunct_t *)findExtern(code->arg);
	if (!funct)
	{
		sprintf(errBuf,"Call to unknown function %s()",code->arg);
		runError(errBuf);
		exit(1);
	}
	numParams = (int) funct->numParams;
	curParam = 0;
	cur = head;
	while(cur)
	{
		if (funct->paramTypes[curParam] == 0)
			break;
		if ((cur->sym->array == ARRAY && 
			funct->paramTypes[curParam] != P_ARRAY) ||
		    (cur->sym->array != ARRAY && 
			funct->paramTypes[curParam] == P_ARRAY))
		{
			sprintf(errBuf,
				"Array passed as scalar parameter to %s()",
				code->arg);
			runError(errBuf);
			exit(1);
		}

		switch(funct->paramTypes[curParam])
		{
			case 0:
				if (numParams != -1)
				{
					sprintf(errBuf,
					"Too many params in call to %s()",
					code->arg);
					runError(errBuf);
					exit(1);
				}
				break;
		}
		cur = cur->next;
		curParam++;
	}
	if (curParam < numParams && numParams > -1)
	{
		sprintf(errBuf,"Not enough parameters in call to %s()",
			code->arg);
		runError(errBuf);
		exit(1);
	}

	/*
	** Call the function
	*/
	externReturn = NULL;
	(*funct->funct)(head);
	retVal = externReturn;
	cur = head;
	while(cur)
	{
		head = cur;
		cur = cur->next;
		symFreeSymbol(head->sym);
		free(head);
	}
}



void simStore(cur)
	code_t	*cur;
{
	sym_t	*sym,
		*var;

	sym = pop();
	var = symGetSymbol(cur->arg);
	if (!var)
	{
		var = symCreateSymbol(cur->arg, sym->type, sym->array);
	}
	if (sym->array == SCALAR)
	{
		symSetValue(var,sym);
	}
	if (sym->array == ARRAY)
	{
		if (var->val)
		{
			symFreeSymbolData(var);
		}
		var->val = (char *)symArrayDup(sym);
	}
	var->type = sym->type;
	var->array = sym->array;
	symFreeSymbol(sym);
#ifdef TRACE
	printf("** STORE '%s'\n",cur->arg);
#endif
}



void simArrayStore(cur)
	code_t	*cur;
{
	sym_t	*sym,
		*var,
		*tmp;
	int	*intptr,
		offset;

#ifdef TRACE
	printf("** A_STORE '%s'\n",cur->arg);
#endif
	sym = pop();
	tmp = pop();
	symTypeCast(tmp,TYPE_INT);
	intptr = (int *)tmp->val;
	offset = (int) *intptr;

	if (offset < 0)
	{
		sprintf(errBuf,"Invalid array index %d on %s",offset,
			cur->arg);
		runError(errBuf);
		exit(1);
	}

	var = symGetSymbol(cur->arg);
	if (!var)
		var = symCreateSymbol(cur->arg,sym->type, ARRAY);
	symSetArrayElement(var,offset, sym);
	symFreeSymbol(tmp);
}


void simDeref(cur)
	code_t	*cur;
{
	sym_t	*sym,
		*var;
	char	dummy[2];
	int	index;

#ifdef TRACE
	printf("** DEREF '%s'\n",cur->arg);
#endif
	sym = pop();
	var = symGetSymbol(cur->arg);
	if (!var)
	{
		sprintf(errBuf,"Unknown variable '%s'",cur->arg);
		runError(errBuf);
		exit(1);
	}
	if (var->array == SCALAR && var->type != TYPE_CHAR)
	{
		runError("Bad type for dereference!");
		exit(1);
	}
	symTypeCast(sym, TYPE_INT);
	index = (int) * (int *)sym->val;
	symFreeSymbol(sym);
	if (var->array == ARRAY)
	{
		sym = symGetArrayElement(var,index);
	}
	else
	{
		
		if (index < strlen(var->val))
			dummy[0] = *(var->val + index);
		else
			dummy[0] = 0;
		dummy[1] = 0;
		sym = createCharSymbol(dummy);
	}
	push(sym);
}



#define	TEXT_BUF_LEN	20480

char *expandText(str)
	char	*str;
{
	static	char *buf = NULL;
	char	*cp1,*cp2,
		*val,
		*var1,*var2,varBuf[80],
		*id1, *id2, indexBuf[80];
	int	index;
	sym_t	*sym,
		*tmp;

	if (!buf)
		buf = (char *)malloc(TEXT_BUF_LEN);
	cp1 = str;
	cp2 = buf;
	while (*cp1)
	{
		switch(*cp1)
		{
			case '$':
			case '@':
				var1 = cp1;
				var2 = cp1+1;
				while(isalnum(*var2) || *var2 == '_')
				{
					var2++;
				}
				strncpy(varBuf,var1,var2-var1);
				varBuf[var2 - var1] = 0;
				if (*var2 == '[')
				{
					id1 = var2+1;
					while (iswhite(*id1))
						id1++;
					if (*id1 == '$' || *id1 == '@')
					{
						id2 = id1+1;
						while(isalnum(*id2) ||
						 	*id2 == '_')
						{
							id2++;
						}
					}
					else
					{
						id2 = id1+1;
						while(isdigit(*id2))
						{
							id2++;
						}
					}
					strncpy(indexBuf,id1,id2-id1);
					indexBuf[id2 - id1] = 0;
					while (iswhite(*id2))
						id2++;
					if (*id2 != ']')
					{
						sprintf(errBuf,
						    "Bad array index for '%s'",
						     varBuf);
						runError(errBuf);
						exit(1);
					}
					if (*indexBuf!='$' && *indexBuf!='@')
					{
						index = atoi(indexBuf);
					}
					else
					{
						sym = symGetSymbol(indexBuf);
						if (!sym)
						{
						    sprintf(errBuf,
						       "Unknown variable '%s'",
						       indexBuf);
						    runError(errBuf);
						    exit(1);
						}
						if (sym->type != TYPE_INT&&
						    sym->type != TYPE_UINT&&
						    sym->type != TYPE_REAL)
						{
						    sprintf(errBuf,
						       "'%s' invalid type",
						       indexBuf);
						    runError(errBuf);
						    exit(1);
						}
					
						tmp = symdup(sym);
                				symTypeCast(tmp, TYPE_INT);
						index = (int)*(int*)tmp->val;
					}
					sym = symGetSymbol(varBuf);
					sym = symGetArrayElement(sym,index);
					val = symUnpackSymbol(sym);
					cp1 = id2+1;
				}
				else
				{
					sym = symGetSymbol(varBuf);
					val = symUnpackSymbol(sym);
					cp1 = var2;
				}
				if (symCheckSymbol(varBuf))
				{
					if (sym && val)
					{
						strcpy(cp2,val);
						cp2 += strlen(val);
						free(val);
					}
					else
					{
						strcpy(cp2,"(NULL)");
						cp2 += 6;
					}
				}
				else
				{
					strcpy(cp2,varBuf);
					cp2 += strlen(varBuf);
				}
				break;

			case '\\':
				cp1++;
				if (*cp1)
				{
					switch(*cp1)
					{
						case 'n':
							*cp2++ = '\n';
							break;
						case 't':
							*cp2++ = '\t';
							break;
						case 'r':
							*cp2++ = '\r';
							break;
						default:
							*cp2++ = *cp1;
							break;
					}
					cp1++;
				}
				else
				{
					*cp2 = '\\';
				}
				break;

			default:
				*cp2++ = *cp1++;
				break;
		}
	}
	*cp2 = 0;
	return(buf);
}



void simALU(cur)
	code_t	*cur;
{
	sym_t	*sym1,
		*sym2,
		res;
	int	op;
	type_t	*type;

	sym2 = pop();
	sym1 = pop();
	if (sym1->array == ARRAY || sym2->array == ARRAY)
	{
		runError("Bad array usage");
		exit(1);
	}
	if ((sym1->type == TYPE_INT && sym2->type == TYPE_UINT) ||
	    (sym2->type == TYPE_INT && sym1->type == TYPE_UINT))
	{
		sym1->type = sym2->type = TYPE_INT;
	}
	if ( (sym1->type == TYPE_INT || sym1->type == TYPE_UINT) && 
	     sym2->type == TYPE_REAL)
	{
		symTypeCast(sym1, TYPE_REAL);
	}
	if ( (sym2->type == TYPE_INT || sym2->type == TYPE_UINT) && 
	     sym1->type == TYPE_REAL)
	{
		symTypeCast(sym2, TYPE_REAL);
	}
	if (sym1->type != sym2->type)
	{
		runError("Type mismatch in maths.");
		exit(1);
	}
	op = cur->type;
	type = typeGetType(sym1->type);
	res.length = (*type->math)(sym1->val,sym2->val, &(res.val), op,
		sym1->length, sym2->length);
	if (res.length < 0)
	{
		runError("Invalid math operation");
		exit(1);
	}
	res.type = sym1->type;
	res.array = sym1->array;
	*(res.name) = 0;
	push(&res);
	free(res.val);
	symFreeSymbol(sym1);
	symFreeSymbol(sym2);
}




void simCast(cur)
	code_t	*cur;
{
	sym_t	*sym;
	int	type;

	sym = pop();
	type = cur->type;
	symTypeCast(sym, type);
	push(sym);
	symFreeSymbol(sym);
}


void simCount()
{
	sym_t	*sym,
		tmp;
	int	count;
	array_t	*cur;

	count = 0;
	sym = pop();
	if (sym->array == ARRAY)
	{
		cur = (array_t *)sym->val;
		while(cur)
		{
			count++;
			cur = cur->next;
		}
	}
	else
	{
		if (sym->type == TYPE_CHAR)
		{
			count = sym->length - 1;
			if (count < 0)
				count = 0;
		}
		else
		{
			runError("Bad type passed to count");
			exit(1);
		}
	}
	tmp.type = TYPE_INT;
	tmp.length = 4;
	tmp.array = SCALAR;
	*(tmp.name) = 0;
	tmp.val = (char *)&count;
	push(&tmp);
	symFreeSymbol(sym);
}



void simCmp(cur)
	code_t	*cur;
{
	sym_t	*sym1,
		*sym2,
		sym;
	int	op,
		res;
	type_t	*type;

	op = cur->type;
	sym2 = pop();
	sym1 = pop();
        if (sym1->array == ARRAY || sym2->array == ARRAY)
        {
                runError("Bad array usage");
                exit(1);
        }
	if ((sym1->type == TYPE_INT && sym2->type == TYPE_UINT) ||
	    (sym2->type == TYPE_INT && sym1->type == TYPE_UINT))
	{
		sym1->type = sym2->type = TYPE_UINT;
	}
        if (sym1->type == TYPE_INT && sym2->type == TYPE_REAL)
        {
                symTypeCast(sym1, TYPE_REAL);
        }
        if (sym2->type == TYPE_INT && sym1->type == TYPE_REAL)
        {
                symTypeCast(sym2, TYPE_REAL);
        }
        if (sym1->type != sym2->type)
        {
		type_t	*type1,
			*type2;

		type1 = typeGetType(sym1->type);
		type2 = typeGetType(sym2->type);
		sprintf(errBuf,"Type mismatch in comparison. %s is %s, %s is %s"
			,sym1->name, type1->name, sym2->name, type2->name);
                runError(errBuf);
                exit(1);
        }

	type = typeGetType(sym1->type);
	if (!type)
	{
		runError("Bad type");
		exit(1);
	}
	if((*type->compare) (sym1->val, sym2->val, op, sym1->length,
		sym2->length, &res) < 0)
	{
		runError("Invalid comparison for this type");
		exit(1);
	}
        sym.type = TYPE_INT;
        *(sym.name) = 0;
	sym.array = SCALAR;
	sym.val = (char *)&res;
	sym.length = 4;
	push(&sym);
	symFreeSymbol(sym1);
	symFreeSymbol(sym2);
}



void simEcho(cur)
	code_t	*cur;
{
	sym_t	*sym;
	char	*val;

	checkContentType();
	sym = symGetSymbol(cur->arg);
	if (!sym)
	{
		runError("Missing value for echo!  (internal error)");
		exit(1);
	}
	val = symUnpackSymbol(sym);
	printf("%s",expandText(val));
	free(val);
}



void simStoreRetVal()
{
	sym_t	*tmp;

	tmp = pop();
	if (retVal != NULL)
	{
		symFreeSymbol(retVal);
	}
	retVal = tmp;
}



void simPushRetVal()
{
	push(retVal);
	if(retVal != NULL)
	{
		symFreeSymbol(retVal);
		retVal = NULL;
	}
}



int runCode(funct)
	char	*funct;
{
	REG	code_t	*cur;
	sym_t	*sym,
		*param,
		*tmp;
	sstack_t *prev;
	int	label,
		zero = 0,
		curLine,
		count;
	funct_t	*curFunct;


	/*
	** Is this a valid Lite function?
	*/
	curFunct = functions;
	while(curFunct)
	{
		if (strcmp(funct,curFunct->name) == 0)
			break;
		curFunct = curFunct->next;
	}
	if (!curFunct)
	{
		return(-1);
	}

	/*
	** Yup.  Setup the params
	*/
	if (strcmp(funct,"main") != 0)
	{
		symStackIn();
	}
	param = curFunct->params;
	count = 0;
	while(stackHead && param)
	{
		tmp = stackHead->sym;
		if (stackHead->block != curBlock + 1)
			break;
		if (tmp->type == TYPE_INT && param->type == TYPE_UINT)
			tmp->type = TYPE_UINT;
		if (param->type == TYPE_INT && tmp->type == TYPE_UINT)
			param->type = TYPE_UINT;
		if (tmp->type != param->type)
		{
			sprintf(errBuf,
				"Param '%s' calling %s() was %s, expected %s\n",
				param->name, curFunct->name, 
				typeCodes[tmp->type], typeCodes[param->type]);
			runError(errBuf);
			return(-2);
		}
		strcpy(tmp->name,param->name);
		symStoreSymbol(symHead, tmp);
		prev = stackHead;
		stackHead = stackHead->next;
		free(prev);
		param = param->next;
	}
	if (param)
	{
		sprintf(errBuf,"Not enough params in call to %s()",
			curFunct->name);
		runError(errBuf);
		return(-2);
	}
	if (stackHead)
	{
		if (stackHead->block == curBlock + 1)
		{
			sprintf(errBuf,"Too many params in call to %s()",
				curFunct->name);
			runError(errBuf);
			return(-2);
		}
	}



	/*
	** Jump in and run the pcode
	*/
	cur = curFunct->code;
	while(cur)
	{
		curCode = cur;
		curLine = cur->line;
		switch(cur->op)
		{

			case OP_ECHO:
				simEcho(cur);
				break;

			case OP_PUSH:
				simPush(cur);
				break;

			case OP_STORE:
				simStore(cur);
				break;

			case OP_CALL:
				simCall(cur);
				break;

			case OP_COUNT:
				simCount();
				break;

			case OP_A_STORE:
				simArrayStore(cur);
				break;

			case OP_DEREF:
				simDeref(cur);
				break;

			case OP_ALU:
				simALU(cur);
				break;

			case OP_CAST:
				simCast(cur);
				break;


			case OP_BLOCK_IN:
				simBlockIn();
				break;

			case OP_BLOCK_OUT:
				simBlockOut();
				break;

			case OP_RETURN:
				simStoreRetVal();
				if (strcmp(funct,"main")!=0)
					symStackOut();
				return(0);

			case OP_PUSHRET:
				simPushRetVal();
				break;

			case OP_CMP:
				simCmp(cur);
				break;

			case OP_JMP:
				label = cur->label;
				cur=cur->next;
				while(cur)
				{
					if (cur->op == OP_LABEL &&
					    cur->label == label)
					{
						break;
					}
					cur=cur->next;
				}
				break;

			case OP_JMP_BACK:
				label = cur->label;
				cur=cur->prev;
				while(cur)
				{
					if (cur->op == OP_LABEL &&
					    cur->label == label)
					{
						break;
					}
					cur=cur->prev;
				}
				break;

			case OP_JMP_FALSE:
				label = cur->label;
				sym = pop();
				if (sym->type != 1)
				{
					runError("Bad type on stack!");
					exit(1);
				}
				if (bcmp(sym->val,&zero,4) == 0)
				{
					cur=cur->next;
					while(cur)
					{
						if (cur->op == OP_LABEL &&
						    cur->label == label)
						{
							break;
						}
						cur=cur->next;
					}
				}
				symFreeSymbol(sym);
		}
		cur = cur->next;
	}

	/*
	** Reset the symbol table and return
	*/
	if (strcmp(funct,"main")!=0)
		symStackOut();
	return(0);
}



void freeCode()
{
	code_t	*cur,
		*prev;

        cur = codeHead;
	while(cur)
	{
		prev = cur;
		cur = cur->next;
		if (prev->arg)
			free(prev->arg);
		free(prev);
	}
}
