/*
 * java script executor
 */

jsexecutor_abstract_type = function() {}
jsexecutor_abstract_type.prototype.value = function(target_type) {
	var target_type;
	var rv;

	if(!target_type)
		target_type = "int";
		
	switch(target_type) {
		case "int":
			rv = parseInt(""+this.val,10);
			rv = rv|0; // force internal cast to int32
//			if(isNaN(rv))
//				rv = 0;
			return rv;
		case "float":
			rv = parseFloat(""+this.val);
			if(isNaN(rv))
				rv = 0;
			return rv;
		case "string":
			return (""+this.val);
		default:
			throw "jsexecutor_type_unknown"+target_type;
	}
}
jsexecutor_abstract_type.prototype.set_value = function(_val) {}
jsexecutor_abstract_type.prototype.clone = function() {}

jsexecutor_abstract_type.prototype.as_text = function() { return ""+this.d_type+"("+this.val+")"; }

jsexecutor_abstract_type.prototype.is_zero = function() { return this.val?false:true; }

jsexecutor_abstract_type.prototype.op_not = function() {}
jsexecutor_abstract_type.prototype.op_neg = function() {}
jsexecutor_abstract_type.prototype.op_bool = function() { return new jsexecutor_type_int(this.is_zero()?0:1); }

jsexecutor_abstract_type.prototype.op_and = function(opr) { this.set_value(this.value("int") & opr.value("int")); }
jsexecutor_abstract_type.prototype.op_or  = function(opr) { this.set_value(this.value("int") | opr.value("int")); }
jsexecutor_abstract_type.prototype.op_xor = function(opr) { this.set_value(this.value("int") ^ opr.value("int")); }

jsexecutor_abstract_type.prototype.op_add = function(opr) { this.set_value(this.value("int") + opr.value("int")); }
jsexecutor_abstract_type.prototype.op_sub = function(opr) { this.set_value(this.value("int") - opr.value("int")); }
jsexecutor_abstract_type.prototype.op_mul = function(opr) { this.set_value(this.value("int") * opr.value("int")); }
jsexecutor_abstract_type.prototype.op_div = function(opr) { 
	if(!opr.value("int"))
		return false;
	this.set_value(this.value("int") / opr.value("int")); 
	return true;
}
jsexecutor_abstract_type.prototype.op_mod = function(opr) { 
	if(!opr.value("int"))
		return false;
	this.set_value(this.value("int") % opr.value("int")); 
	return true;
}

jsexecutor_abstract_type.prototype.op_equal = function(opr) {}
jsexecutor_abstract_type.prototype.op_lt = function(opr) {}
jsexecutor_abstract_type.prototype.op_gt = function(opr) {}


/*
 * type_int
 */

jsexecutor_type_int = function(_val) {
	this.d_type = "int";
	this.set_value(_val);
}
jsexecutor_type_int.prototype = new jsexecutor_abstract_type();
jsexecutor_type_int.prototype.set_value = function(_val) { this.val = parseInt(""+_val,10); }
jsexecutor_type_int.prototype.clone = function() { return new jsexecutor_type_int(this.val); }

jsexecutor_type_int.prototype.op_not = function() { this.val = this.val?0:1; }
jsexecutor_type_int.prototype.op_neg = function() { this.val = -this.val; }

jsexecutor_type_int.prototype.op_equal = function(opr) { return new jsexecutor_type_int((this.val == opr.value("int"))?1:0); }
jsexecutor_type_int.prototype.op_lt = function(opr) { return new jsexecutor_type_int((this.val < opr.value("int"))?1:0); }
jsexecutor_type_int.prototype.op_gt = function(opr) { return new jsexecutor_type_int((this.val > opr.value("int"))?1:0); }

/*
 * type_float
 */

jsexecutor_type_float = function(_val) {
	this.d_type = "float";
	this.set_value(_val);
}
jsexecutor_type_float.prototype = new jsexecutor_abstract_type();
jsexecutor_type_float.prototype.set_value = function(_val) { 
	this.val = parseFloat(""+_val); 
	if(isNaN(this.val))
		this.val = 0.0;
}
jsexecutor_type_float.prototype.clone = function() { return new jsexecutor_type_float(this.val); }

jsexecutor_type_float.prototype.op_not = function() { this.val = this.val?0.0:1.0; }
jsexecutor_type_float.prototype.op_neg = function() { this.val = -this.val; }

jsexecutor_type_float.prototype.op_add = function(opr) { this.val += opr.value("float"); }
jsexecutor_type_float.prototype.op_sub = function(opr) { this.val -= opr.value("float"); }
jsexecutor_type_float.prototype.op_mul = function(opr) { this.val *= opr.value("float"); }
jsexecutor_type_float.prototype.op_div = function(opr) { 
	if(!opr.value("float"))
		return false;
	this.val /= opr.value("float"); 
	return true;
}

jsexecutor_type_float.prototype.op_equal = function(opr) { return new jsexecutor_type_int((this.val == opr.value("float"))?1:0); }
jsexecutor_type_float.prototype.op_lt = function(opr) { return new jsexecutor_type_int((this.val < opr.value("float"))?1:0); }
jsexecutor_type_float.prototype.op_gt = function(opr) { return new jsexecutor_type_int((this.val > opr.value("float"))?1:0); }

/*
 * type_string
 */

jsexecutor_type_string = function(_val) {
	this.d_type = "string";
	this.set_value(_val);
}
jsexecutor_type_string.prototype = new jsexecutor_abstract_type();
jsexecutor_type_string.prototype.set_value = function(_val) { this.val = ""+_val; }
jsexecutor_type_string.prototype.clone = function() { return new jsexecutor_type_string(this.val); }

jsexecutor_type_string.prototype.op_not = function() { this.val = this.val?"":"1"; }
jsexecutor_type_string.prototype.op_neg = function() { 
	if(this.val[0] == "-")
		this.val = String(this.val.substr(1,this.val.length));
	else
		this.val = "-"+this.val; 
}

jsexecutor_type_string.prototype.op_add = function(opr) { this.val += opr.value("string"); }

jsexecutor_type_string.prototype.op_equal = function(opr) { return new jsexecutor_type_int((this.val == opr.value("string"))?1:0); }
jsexecutor_type_string.prototype.op_lt = function(opr) { return new jsexecutor_type_int((this.val < opr.value("string"))?1:0); }
jsexecutor_type_string.prototype.op_gt = function(opr) { return new jsexecutor_type_int((this.val > opr.value("string"))?1:0); }


/*
 * executor
 */

jsexecutor = function(callbacks) {
	this.callback = callbacks;
}
	
jsexecutor.prototype.set_acc = 	function(val) { 
	this.acc = val;	
}
jsexecutor.prototype.stack_push = function(val) { 
	this.stack.push(val); 
}
jsexecutor.prototype.stack_pop = function() {
	if(!this.stack.length)
		throw "jsexecutor::stack_pop: empty stack";
		
	return this.stack.pop();
}

jsexecutor.prototype.get_id = function(name) {
	if(typeof this.idtable[name] == 'object')
		return this.idtable[name];
	else
		throw "use of uninitialized variable \""+name+"\"";
}
	
jsexecutor.prototype.set_id = function(name, val) { this.idtable[name] = val; }
jsexecutor.prototype.builtin_func_print = function(nparm) {
	var arg1;

	if(nparm != 1)
		throw "wrong parameter count nparm!=1 for print()";
			
	arg1 = this.stack_pop();
	document.write(arg1.value("string"));
	return arg1.op_bool();
}

jsexecutor.prototype.builtin_func_int = function(nparm) {
	var arg1;

	if(nparm != 1)
		throw "wrong parameter count nparm!=1 for int()";
			
	arg1 = this.stack_pop();
	return new jsexecutor_type_int(arg1.value("int"));
}

jsexecutor.prototype.builtin_func_float = function(nparm) {
	var arg1;

	if(nparm != 1)
		throw "wrong parameter count nparm!=1 for float()";
			
	arg1 = this.stack_pop();
	return new jsexecutor_type_float(arg1.value("float"));
}

jsexecutor.prototype.builtin_func_string = function (nparm) {
	var arg1;

	if(nparm != 1)
		throw "wrong parameter count nparm!=1 for string()";
			
	arg1 = this.stack_pop();
	return new jsexecutor_type_string(arg1.value("string"));
}
	
jsexecutor.prototype.do_funcall = function(nparm, fname) {
	var stack_before = this.stack.length;
	var rv;
		
	// do function call
	switch(fname) {
		case "int":
			rv = this.builtin_func_int(nparm);
			break;
		case "float":
			rv = this.builtin_func_float(nparm);
			break;
		case "string":
			rv = this.builtin_func_string(nparm);
			break;
		case "print":
			rv = this.builtin_func_print(nparm);
			break;
		default:
			/* check for user provided function */
			if(typeof this.callback[fname] == 'function')
				rv = this.callback[fname](nparm,this);
			else
				throw "call to unknown function '"+fname+"'";
	}
		
	while(this.stack.length > stack_before - nparm)
		this.stack_pop();

	return rv;
}

jsexecutor.prototype.is_running = function() {
	return this.running;
}

jsexecutor.prototype.execute = function(oa) {
	var last;
	var target;
	var splitres;
	var op0;
	var op1;

	if(this.running)
		throw "executor already running";

	this.running = true;
	this.error_text = "";
	this.stack = new Array();
	this.idtable = {};
	this.acc = new jsexecutor_type_int(0);
	this.ip = 0;
		
	last = oa.length;
	while(this.ip < last) {
		switch(oa[this.ip]) {
			case 1:
				this.acc.op_not();
				break;
			case 2:
				this.acc.op_neg();
				break;
			case 5:
				op1 = this.acc.op_bool();
				this.set_acc(op1);
				break;
			case 6:
				op1 = this.stack_pop();
				op1.op_add(this.acc);
				this.set_acc(op1);
				break;
			case 7:
				op1 = this.stack_pop();
				op1.op_sub(this.acc);
				this.set_acc(op1);
				break;
			case 8:
				op1 = this.stack_pop();
				op1.op_mul(this.acc);
				this.set_acc(op1);
				break;
			case 9:
				op1 = this.stack_pop();
				if(!op1.op_div(this.acc))
					throw "division by zero";
				this.set_acc(op1);
				break;
			case 10:
				op1 = this.stack_pop();
				if(!op1.op_mod(this.acc))
					throw "division by zero";
				this.set_acc(op1);
				break;
			case 11:
				op1 = this.stack_pop();
				op1.op_and(this.acc);
				this.set_acc(op1);
				break;
			case 12:
				op1 = this.stack_pop();
				op1.op_or(this.acc);
				this.set_acc(op1);
				break;
			case 13:
				op1 = this.stack_pop();
				op1.op_xor(this.acc);
				this.set_acc(op1);
				break;
			case 14:
				op1 = this.stack_pop();
				this.set_acc(op1.op_equal(this.acc));
				break;
			case 15:
				op1 = this.stack_pop();
				this.set_acc(op1.op_lt(this.acc));
				break;
			case 16:
				op1 = this.stack_pop();
				this.set_acc(op1.op_gt(this.acc));
				break;
			case 17:
				target = parseInt(oa[this.ip+1]);
				if(target < 0 || target > last)
					throw "jsexecutor::execute_jmp: destination address out of bounds";
				this.ip = 2*(target-1);
				break;
			case 18:
				if(!this.acc.is_zero())
					break;
				target = parseInt(oa[this.ip+1]);
				if(target < 0 || target > last)
					throw "jsexecutor::execute_jz: destination address out of bounds";
				this.ip = 2*(target-1);
				break;
			case 19:
				if(this.acc.is_zero())
					break;
				target = parseInt(oa[this.ip+1]);
				if(target < 0 || target > last)
					throw "jsexecutor::execute_jnz: destination address out of bounds";
				this.ip = 2*(target-1);
				break;
			case 20:
				splitres = oa[this.ip+1].split(":");
				op1 = this.do_funcall(splitres[0]|0, splitres[1]);
				this.set_acc(op1);
				break;
			case 21:
				op1 = new jsexecutor_type_string(oa[this.ip+1]);
				this.set_acc(op1);
				break;
			case 22:
				op1 = new jsexecutor_type_int(oa[this.ip+1]);
				this.set_acc(op1);
				break;
			case 23:
				op1 = new jsexecutor_type_float(oa[this.ip+1]);
				this.set_acc(op1);
				break;
			case 24:
				/* hier keine referenz */
				op0 = this.get_id(oa[this.ip+1]);
				op1 = op0.clone();
				this.set_acc(op1);
				break;
			case 25:
				/* hier keine referenz */
				op1 = this.acc.clone();
				this.set_id(oa[this.ip+1], op1);
				break;
			case 26:
				/* hier keine referenz */
				op1 = this.acc.clone();
				this.stack_push(op1);
				break;
			case 27:
				op1 = this.stack_pop();
				this.set_acc(op1);
				break;
			case 28:
				break;
			case 29:
				this.ip = last;
				break;
			default:
				throw "jsexecutor::execute: unknown opcode";
		}
		this.ip+=2;
		if(this.error_text != "") {
			throw this.error_text;
			break;
		}
	}
	// done
	this.running = false;
	return this.acc;
}

jsexecutor.prototype.error = function(text) {
	this.error_text = text;
	return new jsexecutor_type_int(0); 
}
	
/*
	function dump_state() {
		echo "ip="+this.ip;
		echo " acc="+this.acc.as_text();
		echo " stack=[";
		foreach(this.stack as val)
			echo val.as_text()+" ";
		echo "] idtable=[";
		foreach(this.idtable as name=>val)
			echo name+"="+val.as_text()+" ";
		echo "]\n";
	}
*/
