package org.dynjs.ir;
import org.dynjs.exception.ThrowException;
import org.dynjs.ir.instructions.Add;
import org.dynjs.ir.instructions.BEQ;
import org.dynjs.ir.instructions.Call;
import org.dynjs.ir.instructions.Constructor;
import org.dynjs.ir.instructions.Copy;
import org.dynjs.ir.instructions.DefineFunction;
import org.dynjs.ir.instructions.Instanceof;
import org.dynjs.ir.instructions.Jump;
import org.dynjs.ir.instructions.LE;
import org.dynjs.ir.instructions.LT;
import org.dynjs.ir.instructions.Raise;
import org.dynjs.ir.instructions.ReceiveFunctionParameter;
import org.dynjs.ir.instructions.ResultInstruction;
import org.dynjs.ir.instructions.Return;
import org.dynjs.ir.instructions.Sub;
import org.dynjs.ir.operands.LocalVariable;
import org.dynjs.ir.operands.OffsetVariable;
import org.dynjs.ir.operands.Variable;
import org.dynjs.runtime.EnvironmentRecord;
import org.dynjs.runtime.ExecutionContext;
import org.dynjs.runtime.JSFunction;
import org.dynjs.runtime.JSObject;
import org.dynjs.runtime.ObjectEnvironmentRecord;
import org.dynjs.runtime.PropertyDescriptor;
import org.dynjs.runtime.Reference;
import org.dynjs.runtime.Types;
/**
* Created by enebo on 4/11/14.
*/
public class Interpreter {
public static Object execute(ExecutionContext context, Scope scope, Instruction[] instructions) {
//System.out.println("Executing: " + context.getFileName() + ":" + context.getLineNumber());
Object result = Types.UNDEFINED;
Object[] temps = new Object[scope.getTemporaryVariableSize()];
int size = instructions.length;
Object value = Types.UNDEFINED;
int ipc = 0;
while (ipc < size) {
Instruction instr = instructions[ipc];
ipc++;
//System.out.println("EX: " + instr);
switch(instr.getOperation()) {
case ADD:
value = add(context,
((Add) instr).getLHS().retrieve(context, temps),
((Add) instr).getRHS().retrieve(context, temps));
break;
case SUB:
value = sub(context,
((Sub) instr).getLHS().retrieve(context, temps),
((Sub) instr).getRHS().retrieve(context, temps));
break;
case RECEIVE_FUNCTION_PARAM:
value = context.getFunctionParameters()[((ReceiveFunctionParameter) instr).getIndex()];
break;
case COPY:
value = ((Copy) instr).getValue().retrieve(context, temps);
break;
case JUMP:
ipc = ((Jump) instr).getTarget().getTargetIPC();
break;
case CALL: {
Call call = (Call) instr;
Object ref = call.getIdentifier().retrieve(context, temps);
Object function = Types.getValue(context, ref);
Operand[] opers = call.getArgs();
Object[] args = new Object[opers.length];
if (!(function instanceof JSFunction)) {
throw new ThrowException(context, context.createTypeError(ref + " is not callable"));
}
for (int i = 0; i < args.length; i++) {
args[i] = opers[i].retrieve(context, temps);
}
Object thisValue = getThis(ref);
value = context.call(ref, (JSFunction) function, thisValue, args);
}
break;
case CONSTRUCTOR: {
Constructor constructor = (Constructor) instr;
Object ref = constructor.getIdentifier().retrieve(context, temps);
Object function = Types.getValue(context, ref);
Operand[] opers = constructor.getArgs();
Object[] args = new Object[opers.length];
if (!(function instanceof JSFunction)) {
throw new ThrowException(context, context.createTypeError(ref + " is not callable"));
}
for (int i = 0; i < args.length; i++) {
args[i] = opers[i].retrieve(context, temps);
}
value = context.construct(ref, (JSFunction) function, args);
}
break;
case LT: {
Object arg1 = ((LT) instr).getArg1().retrieve(context, temps);
Object arg2 = ((LT) instr).getArg2().retrieve(context, temps);
Object r = Types.compareRelational(context, arg1, arg2, true);
value = r == Types.UNDEFINED ? false : r;
break;
}
case LE: {
Object arg1 = ((LE) instr).getArg1().retrieve(context, temps);
Object arg2 = ((LE) instr).getArg2().retrieve(context, temps);
Object r = Types.compareRelational(context, arg1, arg2, true);
value = r == Boolean.TRUE || r == Types.UNDEFINED ? false : r;
break;
}
case BEQ: {
BEQ beq = (BEQ) instr;
Object arg1 = beq.getArg1().retrieve(context, temps);
Object arg2 = beq.getArg2().retrieve(context, temps);
if (arg1.equals(arg2)) {
ipc = beq.getTarget().getTargetIPC();
}
break;
}
case RETURN:
return ((Return) instr).getValue().retrieve(context, temps);
case DEFINE_FUNCTION: {
// FIXME: configurableBindings is false here but I think a define_function in an eval they should be true
FunctionScope functionScope = ((DefineFunction) instr).getScope();
value = new IRJSFunction(functionScope, context.getVars(),
context.getLexicalEnvironment(), context.getGlobalContext());
if (functionScope.getName() != null) {
EnvironmentRecord env = context.getVariableEnvironment().getRecord();
String identifier = functionScope.getName();
if (!env.hasBinding(context, identifier)) {
env.createMutableBinding(context, identifier, false);
} else if (env.isGlobal()) {
JSObject globalObject = ((ObjectEnvironmentRecord) env).getBindingObject();
PropertyDescriptor existingProp = (PropertyDescriptor) globalObject.getProperty(context, identifier, false);
if (existingProp.isConfigurable()) {
globalObject.defineOwnProperty(context, identifier,
PropertyDescriptor.newDataPropertyDescriptor(Types.UNDEFINED, true, false, true), true);
} else if (existingProp.isAccessorDescriptor() || (!existingProp.isWritable() && !existingProp.isEnumerable())) {
throw new ThrowException(context, context.createTypeError("unable to bind function '" + identifier + "'"));
}
}
((JSFunction) value).setDebugContext(identifier);
env.setMutableBinding(context, identifier, value, functionScope.isStrict());
}
break;
}
case RAISE:
throw new ThrowException(context,
context.createError(((Raise) instr).getType(), ((Raise) instr).getMessage()));
case INSTANCEOF: {
Object lhs = ((Instanceof) instr).getLHS().retrieve(context, temps);
Object rhs = ((Instanceof) instr).getRHS().retrieve(context, temps);
if (rhs instanceof JSObject) {
if (!(rhs instanceof JSFunction)) {
// FIXME: Might have to pass in original rhs to instr for proper string...
throw new ThrowException(context, context.createTypeError(rhs + " is not a function"));
}
value = ((JSFunction) rhs).hasInstance(context, lhs);
} else if (rhs instanceof Class) {
Class clazz = (Class) rhs;
value = lhs.getClass().getName().equals(clazz.getName());
} else {
// FIXME: Review against master...
throw new ThrowException(context, context.createTypeError(rhs + " is not a function"));
}
}
}
if (instr instanceof ResultInstruction) {
Variable variable = ((ResultInstruction) instr).getResult();
if (variable instanceof OffsetVariable) {
int offset = ((OffsetVariable) variable).getOffset();
if (variable instanceof LocalVariable) {
context.getVars().setVar(offset, ((LocalVariable) variable).getDepth(), value);
} else {
temps[offset] = value;
}
} else {
// FIXME: Lookup dynamicvariable
}
}
}
System.out.println("RESULT is " + result);
return result;
}
// FIXME: move to helper class
public static Object getThis(Object ref) {
Object thisValue = null;
// FIXME: We can probably remove this check for IRJSFunctions since we will not be using references
if (ref instanceof Reference) {
if (((Reference) ref).isPropertyReference()) {
thisValue = ((Reference) ref).getBase();
} else {
thisValue = ((EnvironmentRecord) ((Reference) ref).getBase()).implicitThisValue();
}
}
return thisValue;
}
// FIXME: This breaks for non-numeric uses if isSubtraction
private static Object add(ExecutionContext context, Object lhs, Object rhs) {
if (lhs instanceof String || rhs instanceof String) {
return(Types.toString(context, lhs) + Types.toString(context, rhs));
}
Number lhsNum = Types.toNumber(context, lhs);
Number rhsNum = Types.toNumber(context, rhs);
if (Double.isNaN(lhsNum.doubleValue()) || Double.isNaN(rhsNum.doubleValue())) {
return(Double.NaN);
}
if (lhsNum instanceof Double || rhsNum instanceof Double) {
if (lhsNum.doubleValue() == 0.0 && rhsNum.doubleValue() == 0.0) {
if (Double.compare(lhsNum.doubleValue(), 0.0) < 0 && Double.compare(rhsNum.doubleValue(), 0.0) < 0) {
return(-0.0);
} else {
return(0.0);
}
}
return(lhsNum.doubleValue() - rhsNum.doubleValue());
}
return(lhsNum.longValue() + rhsNum.longValue());
}
private static Object sub(ExecutionContext context, Object lhs, Object rhs) {
if (lhs instanceof String || rhs instanceof String) {
return(Double.NaN);
}
Number lhsNum = Types.toNumber(context, lhs);
Number rhsNum = Types.toNumber(context, rhs);
if (Double.isNaN(lhsNum.doubleValue()) || Double.isNaN(rhsNum.doubleValue())) {
return(Double.NaN);
}
if (lhsNum instanceof Double || rhsNum instanceof Double) {
if (lhsNum.doubleValue() == 0.0 && rhsNum.doubleValue() == 0.0) {
if (Double.compare(lhsNum.doubleValue(), 0.0) < 0 && Double.compare(rhsNum.doubleValue(), 0.0) < 0) {
return(-0.0);
} else {
return(0.0);
}
}
return(lhsNum.doubleValue() - rhsNum.doubleValue());
}
return(lhsNum.longValue() - rhsNum.longValue());
}
}