/* Copyright (c) 1993 Stephen F. White */

#include <stdio.h>
#ifdef SYSV
#include <string.h>
#else
#include <strings.h>
#endif
#include <ctype.h>
#include <sys/time.h>

#include "config.h"
#include "cool.h"
#include "proto.h"
#include "sys_proto.h"
#include "netio.h"
#include "execute.h"
#include "x.tab.h"

void
op_add(void)
{
    Var		right, left, ret;

    right = pop(); left = pop();
    if (left.type != right.type && left.type != LIST) {
	var_free(left);  var_free(right);
	raise(E_TYPE);
    } else if (left.type == NUM) {
	left.v.num = left.v.num + right.v.num;
	push(left);
    } else if (left.type == STR) {
	ret.type = STR;
	if (left.v.str->ref == 1) {
	    ret.v.str = string_cat(left.v.str, right.v.str->str);
	} else {
	    ret.v.str = string_new(left.v.str->len + right.v.str->len + 1);
	    strcpy(ret.v.str->str, left.v.str->str);
	    strcpy(ret.v.str->str + left.v.str->len, right.v.str->str);
	    ret.v.str->len = left.v.str->len + right.v.str->len;
	    var_free(left);
	}
	var_free(right);
	push(ret);
    } else if (left.type == LIST) {
	ret.type = LIST;
	ret.v.list = list_setadd(left.v.list, right);
	push(ret);
    } else {
	var_free(left);  var_free(right);
	raise(E_TYPE);
    }
}

void
op_sub(void)
{
    Var		right, left;

    right = pop(); left = pop();
    if (left.type != right.type && left.type != LIST) {
	var_free(left);  var_free(right);
	raise(E_TYPE);
    } else if (left.type == NUM) {
	left.v.num -= right.v.num;
	push(left);
    } else if (left.type == LIST) {
	left.v.list = list_setremove(left.v.list, right);
	var_free(right);
	push(left);
    } else {
	raise(E_ARGTYPE);
    }
}

void
op_mul(void)
{
    Var		right, left;

    right = pop(); left = pop();
    if (left.type != NUM || right.type != NUM) {
	var_free(left);  var_free(right);
	raise(E_TYPE);
    } else {
	left.v.num *= right.v.num;
	push(left);
    }
}

void
op_div(void)
{
    Var		right, left;

    right = pop(); left = pop();

    if (left.type != NUM || right.type != NUM) {
	var_free(left);  var_free(right);
	raise(E_TYPE);
    } else if (!right.v.num) {
	raise(E_DIV);
    } else {
	left.v.num /= right.v.num;
	push(left);
    }
}

void
op_mod(void)
{
    Var		right, left;

    right = pop(); left = pop();

    if (left.type != NUM || right.type != NUM) {
	var_free(left);  var_free(right);
	raise(E_TYPE);
    } else if (!right.v.num) {
	raise(E_DIV);
    } else {
	left.v.num %= right.v.num;
	push(left);
    }
}

void
op_negate(void)
{
    Var		arg;

    arg = pop();
    if (arg.type != NUM) {
	var_free(arg);
	raise(E_TYPE);
    } else {
	arg.v.num = -arg.v.num;
	push(arg);
    }
}

void
op_and(void)
{
    Var		left;
    Var		ret;

    left = pop();
    if (ISTRUE(left)) {	/* LHS true, evaluate RHS */
	frame.pc++;	/* skip over breakout goto */
    } else {		/* LHS false, abort */
	ret.type = NUM;
	ret.v.num = 0;
	push(ret);
	frame.pc = frame.m->code[frame.pc];	 /* skip to breakout */
    }
    var_free(left);
}

void
op_or(void)
{
    Var		left;
    Var		ret;

    left = pop();
    if (!ISTRUE(left)) {	/* LHS false, evaluate RHS */
	frame.pc++;		/* skip over breakout goto */
    } else {			/* LHS true, skip to end */
	ret.type = NUM;
	ret.v.num = 1;
	push(ret);
	frame.pc = frame.m->code[frame.pc];	/* skip to breakout */
    }
    var_free(left);
}

void
op_not(void)
{
    Var		ret, arg;

    arg = pop();
    ret.type = NUM;
    ret.v.num = !ISTRUE(arg);
    var_free(arg);
    push(ret);
}

#define REL(NAME, OP) \
void NAME(void) \
{ \
    Var		ret, right, left; \
\
    right = pop(); left = pop(); \
    if (left.type != right.type) { \
	raise(E_TYPE); \
    } else if (left.type != STR && left.type != NUM) { \
	raise(E_TYPE); \
    } else { \
	ret.type = NUM; ret.v.num = var_compare(left, right) OP 0; \
	push(ret); \
    } \
    var_free(left);  var_free(right); \
}

REL(op_lt, <)
REL(op_gt, >)
REL(op_le, <=)
REL(op_ge, >=)

void
op_eq(void)
{
    Var		ret, right, left;

    right = pop(); left = pop();

    ret.type = NUM; ret.v.num = var_eq(left, right);
    push (ret);
    var_free(left);  var_free(right);
}

void
op_ne(void)
{
    Var		ret, right, left;

    right = pop(); left = pop();
    ret.type = NUM; ret.v.num = !var_eq(left, right);
    push (ret);
    var_free(left);  var_free(right);
}

void
op_index(void)
{
    Var		ret, i, base;

    i = pop(); base = pop();
    if (i.type != NUM) {
	raise(E_TYPE);
    } else switch (base.type) {
      case STR:
	if (i.v.num < 1 || i.v.num > base.v.str->len) {
	    raise(E_RANGE);
	} else {
	    ret.type = STR;
	    ret.v.str = string_new(2);
	    ret.v.str->str[0] = base.v.str->str[i.v.num - 1];
	    ret.v.str->str[1] = '\0';
	    ret.v.str->len = 1;
	    push(ret);
	}
	break;
      case LIST:
	if (i.v.num < 1 || i.v.num > base.v.list->len) {
	    raise(E_RANGE);
	} else {
	    ret = var_dup(base.v.list->el[i.v.num - 1]);
	    push(ret);
	}
	break;
      default:
	raise(E_TYPE);
    }
    var_free(base); var_free(i);
}

void
op_subset(void)
{
    Var		base, left, right, ret;
    int		len;

    right = pop(); left = pop();
    base = pop();

    ret.type = base.type;

    if (base.type == STR) {
	len = base.v.str->len;
    } else if (base.type == LIST) {
	len = base.v.list->len;
    }
    if (left.type != NUM || right.type != NUM) {
	var_free(left);  var_free(right);
	var_free(base); raise(E_TYPE); return;
    }
    if (right.v.num < 1) {
	right.v.num = len;
    }
    if (left.v.num > right.v.num
     || left.v.num < 1 || left.v.num > len
     || right.v.num < 1 || right.v.num > len) {
	raise(E_RANGE);
    } else if (base.type == STR) {
	len = right.v.num - left.v.num + 1;	/* substring length */
	ret.type = STR;
	ret.v.str = string_new(len);
	strncpy(ret.v.str->str, base.v.str->str + left.v.num - 1, len);
	ret.v.str->str[len] = '\0';
	ret.v.str->len = len;
	push(ret);
    } else if (base.type == LIST) {
	ret.v.list = list_subset(base.v.list, left.v.num, right.v.num);
	push(ret);
    } else {
	raise(E_TYPE);
    }
    var_free(base);
    var_free(left); var_free(right);
}

void
op_lsubset(void)
{
    Var		left, right;

    right = pop();
    left.type = NUM;
    left.v.num = 1;
    push(left);
    push(right);
    op_subset();
}

void
op_rsubset(void)
{
    Var		left, right;

    left = pop();
    right.type = NUM;
    right.v.num = -1;
    push(left);
    push(right);
    op_subset();
}

void
op_return(void)
{
    ex_retval = pop();
    ex_state = STOPPED;
}

void
op_pop(void)
{
    Var		v;
     
    v = pop();
    var_free(v);
}

void
op_numpush(void)
{
    Var		n;
    
    n.type = NUM;
    n.v.num = frame.m->code[frame.pc++];
    push(n);
}

void
op_strpush(void)
{
    Var		s;
    int		symno;

    symno = frame.m->code[frame.pc++];
    s.type = STR;
    s.v.str = string_dup(sym_get(frame.on, symno));
    push(s);
}

void
op_objpush(void)
{
    Var		o;

    o.type = OBJ;
    o.v.obj.id = frame.m->code[frame.pc++];
    o.v.obj.server = frame.m->code[frame.pc++];
    push(o);
}

void
op_listpush(void)
{
    Var		list;

    list = pop_args(frame.m->code[frame.pc++]);
    push(list);
}

void
op_errpush(void)
{
    Var		e;

    e.type = ERR;
    e.v.err = frame.m->code[frame.pc++];
    push(e);
}

void
op_message(void)
{
    Var		args;
    Var		dest;
    String	*msg;

    args = pop_args(frame.m->code[frame.pc++]);
    dest = pop();
    if (dest.type != OBJ) {
	var_free(args); var_free(dest);
	frame.pc++;
	raise(E_INVIND);
    } else {
	msg = string_dup(sym_get(frame.on, frame.m->code[frame.pc]));
	frame.pc++;
        send_message_and_block(frame.this, dest.v.obj, msg,
			       args.v.list, dest.v.obj);
    }
}

void
op_message_expr(void)
{
    Var		args;
    Var		msg, dest;

    args = pop_args(frame.m->code[frame.pc++]);
    msg = pop(); dest = pop();

    if (dest.type != OBJ) {
	var_free(args); var_free(dest); var_free(msg);
	raise(E_INVIND);
    } else if (msg.type != STR) {
	var_free(args); var_free(msg);
	raise(E_TYPE);
    } else {
	send_message_and_block(frame.this, dest.v.obj, msg.v.str,
			       args.v.list, dest.v.obj);
    }
}

void
op_if(void)
{
    Var		cond;

    cond = pop();
    if(ISTRUE(cond)) {
	pushn(frame.m->code[frame.pc + 1]);	/* push end address */
	frame.pc += 2;				/* skip to code */
    } else if (frame.m->code[frame.pc]) {	/* if there's an elseif */
	pushn(frame.m->code[frame.pc + 1]);	/* push end address */
	frame.pc = frame.m->code[frame.pc];	/* go to elseif code */
    } else {
	frame.pc = frame.m->code[frame.pc + 1];	/* go to end address */
    }
    var_free(cond);
}

void
op_null(void)		/* null function */
{ }

void
op_elseif(void)
{
    Var		cond;

    cond = pop();
    if (ISTRUE(cond)) {
	frame.pc++;
    } else {
	frame.pc = frame.m->code[frame.pc];
    }
    var_free(cond);
}

void
op_for(void)
{
    Var		idx, list;
    
    idx = pop();
    list = pop();
    
    if (list.type != LIST) {
	var_free(list);
	raise(E_FOR);
    } else if (idx.v.num >= list.v.list->len) {	/* loop is complete */
	var_free(list);
	frame.pc = frame.m->code[frame.pc + 1];	/* skip to end */
    } else {
	var_assign_local(frame.stack, frame.m->code[frame.pc],
		   var_dup(list.v.list->el[idx.v.num]));
	idx.v.num++;
	push(list);		/* push list */
	push(idx);		/* push new index */
	pushpc(frame.pc - 1);	/* push address of FOR statement */
	frame.pc += 2;		/* go to first instruction in loop */
    }
}

void
op_forrng(void)
{
    Var		v, upper;

    upper = pop();
    v = pop();
    if (v.type != NUM || upper.type != NUM) {
	var_free(upper);  var_free(v);
	raise(E_TYPE);
    } else if (v.v.num > upper.v.num) {
	frame.pc = frame.m->code[frame.pc + 1];
    } else {
	var_assign_local(frame.stack, frame.m->code[frame.pc], v);
	v.v.num++;
	push(v);
	push(upper);
	pushpc(frame.pc - 1);
	frame.pc += 2;
    }
}

void
op_pushpc(void)
{
    pushpc(frame.pc - 1);
}

void
op_while(void)
{
    Var		cond;

    cond = pop();
    if (!ISTRUE(cond)) {
	(void) pop();			/* take pc off stack */
	frame.pc = frame.m->code[frame.pc];
    } else {
	frame.pc++;		/* go to first instruction in loop */
    }
    var_free(cond);
}

void
op_do(void)
{
    pushpc(frame.m->code[frame.pc++]);
}

void
op_dowhile(void)
{
    Var		cond;

    cond = pop();
    if (ISTRUE(cond)) {				/* keep looping */
	frame.pc = frame.m->code[frame.pc];
    } else {					/* loop is finished */
	(void) pop();				/* remove sentinel */
	frame.pc += 2;				/* skip to end */
    }
    var_free(cond);
}

void
op_break(void)
{
    Var		newpc;
    int		break_lvl = frame.m->code[frame.pc++];

    while (break_lvl--) {
	do {
	    newpc = pop();
	    var_free(newpc);
	} while (newpc.type != PC);
    }
    if (frame.m->code[newpc.v.num] == FOR) {
	(void) pop();			/* pop index */
	newpc = pop();  var_free(newpc);/* pop list */
	frame.pc = frame.m->code[newpc.v.num + 2];
    } else if (frame.m->code[newpc.v.num] == FORRNG) {
	(void) pop();			/* pop upper range */
	(void) pop();			/* pop current value */
	frame.pc = frame.m->code[newpc.v.num + 2];
    } else if (frame.m->code[newpc.v.num] == DOWHILE) {
	frame.pc = newpc.v.num + 3;
    } else if (frame.m->code[newpc.v.num] == PUSHPC) {	/* WHILE, actually */
	while (frame.m->code[newpc.v.num] != WHILE) {
	    newpc.v.num++;
	}
	frame.pc = frame.m->code[newpc.v.num + 1];
    }
}

void
op_continue(void)
{
    Var		newpc;
    int		break_lvl = frame.m->code[frame.pc++];

    while (break_lvl--) {
	do {
	    newpc = pop();
	    var_free(newpc);
	} while (newpc.type != PC);
    }
    if (frame.m->code[newpc.v.num] == DOWHILE) {
	pushpc(newpc.v.num);
	frame.pc = frame.m->code[newpc.v.num + 2];
    } else {
	frame.pc = newpc.v.num;		/* all the others push their own PC */
    }
}

void
op_asgnlvar(void)
{
    Var		v;

    v = pop();
    var_assign_local(frame.stack, frame.m->code[frame.pc++], v);
}

void
op_asgngvar(void)
{
    Var		v;
    int		varno = frame.m->code[frame.pc++];

    v = pop();
    raise(var_assign_global(this, sym_get(frame.on, varno), v));
    (void) pop();
}

void
op_getlvar(void)
{
    Var		v;

    var_get_local(frame.stack, frame.m->code[frame.pc++], &v);
    push (var_dup(v));
}

void
op_getgvar(void)
{
    Var		ret;
    Error	r;
    int		varname;

    varname = frame.m->code[frame.pc++];
    r = var_get_global(this, sym_get(frame.on, varname)->str, &ret);
    if (r != E_NONE) {
	raise(r);
	(void) pop();
    } else {
	push (var_dup(ret));
    }
}

void
op_getgvarexpr(void)
{
    Var		arg, ret;
    Error	r;

    frame.pc++;		/* skip nargs */
    arg = pop();
    if (arg.type != STR) {
	raise(E_ARGTYPE);
    } else {
	r = var_get_global(this, arg.v.str->str, &ret);
	if (r != E_NONE) {
	    raise(r);
	} else {
	    push (var_dup(ret));
	}
    }
    var_free(arg);
}

void
op_asgngvarexpr(void)
{
    Var		arg1, arg2;
    Error	r;

    frame.pc++;		/* skip nargs */
    arg2 = pop();  arg1 = pop();
    if (arg1.type != STR) {
	raise(E_ARGTYPE);
    } else if (!valid_ident(arg1.v.str->str)) {
	raise(E_RANGE);
    } else {
	r = var_assign_global(this, arg1.v.str, arg2);
	if (r != E_NONE) {
	    raise(r);
	} else {
	    push (var_dup(arg2));
	}
    }
    var_free(arg1);
}
	
void
op_stop(void)
{
    Var		newpc;

    newpc = pop();
    if (newpc.type != PC && newpc.type != NUM) {
	writelog();
	fprintf(stderr, "EXECUTION ERROR:  STOP encountered non-num PC\n");
	raise(E_INTERNAL);
	var_free(newpc);
    } else {
	if (newpc.v.num < 0) {
	    ex_state = STOPPED;
	} else {
	    frame.pc = newpc.v.num;
	}
    }
}

void
op_in(void)
{
    Var		left, right, r;

    right = pop();
    left = pop();
    r.type = NUM;

    if (right.type == LIST) {
	r.v.num = list_ismember(left, right.v.list);
	push(r);
    } else if (right.type == STR) {
	if (left.type != STR) {
	    raise(E_TYPE);
	} else {
	    r.v.num = str_in(left.v.str->str, right.v.str->str);
	    push(r);
	}
    } else {
	raise(E_TYPE);
    }
    var_free(left);
    var_free(right);
}