j2s/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java

598 lines
22 KiB
Java

package me.topchetoeu.jscript.runtime;
import java.util.ArrayList;
import java.util.Collections;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Operation;
import me.topchetoeu.jscript.common.environment.Environment;
import me.topchetoeu.jscript.runtime.exceptions.EngineException;
import me.topchetoeu.jscript.runtime.values.Member.FieldMember;
import me.topchetoeu.jscript.runtime.values.Member.PropertyMember;
import me.topchetoeu.jscript.runtime.values.Value;
import me.topchetoeu.jscript.runtime.values.functions.CodeFunction;
import me.topchetoeu.jscript.runtime.values.functions.FunctionValue;
import me.topchetoeu.jscript.runtime.values.objects.ArrayValue;
import me.topchetoeu.jscript.runtime.values.objects.ObjectValue;
import me.topchetoeu.jscript.runtime.values.primitives.BoolValue;
import me.topchetoeu.jscript.runtime.values.primitives.NumberValue;
import me.topchetoeu.jscript.runtime.values.primitives.StringValue;
public class InstructionRunner {
private static Value execReturn(Environment env, Instruction instr, Frame frame) {
return frame.pop();
}
private static Value execThrow(Environment env, Instruction instr, Frame frame) {
throw new EngineException(frame.pop());
}
private static Value execThrowSyntax(Environment env, Instruction instr, Frame frame) {
throw EngineException.ofSyntax((String)instr.get(0));
}
private static Value execCall(Environment env, Instruction instr, Frame frame) {
var callArgs = frame.take(instr.get(0));
var func = frame.pop();
frame.push(func.call(env, false, instr.get(1), Value.UNDEFINED, callArgs));
frame.codePtr++;
return null;
}
private static Value execCallMember(Environment env, Instruction instr, Frame frame) {
var callArgs = frame.take(instr.get(0));
var index = frame.pop();
var obj = frame.pop();
frame.push(obj.getMember(env, index).call(env, false, instr.get(1), obj, callArgs));
frame.codePtr++;
return null;
}
private static Value execCallNew(Environment env, Instruction instr, Frame frame) {
var callArgs = frame.take(instr.get(0));
var funcObj = frame.pop();
frame.push(funcObj.callNew(env, instr.get(1), callArgs));
frame.codePtr++;
return null;
}
private static Value execDefProp(Environment env, Instruction instr, Frame frame) {
var setterVal = frame.pop();
var getterVal = frame.pop();
var key = frame.pop();
var obj = frame.pop();
FunctionValue getter, setter;
if (getterVal == Value.UNDEFINED) getter = null;
else if (getterVal instanceof FunctionValue) getter = (FunctionValue)getterVal;
else throw EngineException.ofType("Getter must be a function or undefined.");
if (setterVal == Value.UNDEFINED) setter = null;
else if (setterVal instanceof FunctionValue) setter = (FunctionValue)setterVal;
else throw EngineException.ofType("Setter must be a function or undefined.");
obj.defineOwnMember(env, key, new PropertyMember(getter, setter, true, true));
frame.push(obj);
frame.codePtr++;
return null;
}
private static Value execKeys(Environment env, Instruction instr, Frame frame) {
var val = frame.pop();
var members = new ArrayList<>(val.getMembers(env, false, true).keySet());
Collections.reverse(members);
frame.push(null);
for (var el : members) {
var obj = new ObjectValue();
obj.defineOwnMember(env, "value", FieldMember.of(new StringValue(el)));
frame.push(obj);
}
frame.codePtr++;
return null;
}
private static Value execTryStart(Environment env, Instruction instr, Frame frame) {
int start = frame.codePtr + 1;
int catchStart = (int)instr.get(0);
int finallyStart = (int)instr.get(1);
if (finallyStart >= 0) finallyStart += start;
if (catchStart >= 0) catchStart += start;
int end = (int)instr.get(2) + start;
frame.addTry(start, end, catchStart, finallyStart);
frame.codePtr++;
return null;
}
private static Value execTryEnd(Environment env, Instruction instr, Frame frame) {
frame.popTryFlag = true;
return null;
}
private static Value execDup(Environment env, Instruction instr, Frame frame) {
int count = instr.get(0);
for (var i = 0; i < count; i++) {
frame.push(frame.peek());
}
frame.codePtr++;
return null;
}
private static Value execLoadValue(Environment env, Instruction instr, Frame frame) {
switch (instr.type) {
case PUSH_UNDEFINED: frame.push(Value.UNDEFINED); break;
case PUSH_NULL: frame.push(Value.NULL); break;
case PUSH_BOOL: frame.push(BoolValue.of(instr.get(0))); break;
case PUSH_NUMBER: frame.push(new NumberValue(instr.get(0))); break;
case PUSH_STRING: frame.push(new StringValue(instr.get(0))); break;
default:
}
frame.codePtr++;
return null;
}
private static Value execLoadVar(Environment env, Instruction instr, Frame frame) {
int i = instr.get(0);
frame.push(frame.getVar(i));
frame.codePtr++;
return null;
}
private static Value execLoadObj(Environment env, Instruction instr, Frame frame) {
var obj = new ObjectValue();
obj.setPrototype(Value.OBJECT_PROTO);
frame.push(obj);
frame.codePtr++;
return null;
}
private static Value execLoadGlob(Environment env, Instruction instr, Frame frame) {
frame.push(Value.global(env));
frame.codePtr++;
return null;
}
private static Value execLoadIntrinsics(Environment env, Instruction instr, Frame frame) {
frame.push(Value.intrinsics(env).get((String)instr.get(0)));
frame.codePtr++;
return null;
}
private static Value execLoadArr(Environment env, Instruction instr, Frame frame) {
var res = new ArrayValue();
res.setSize(instr.get(0));
frame.push(res);
frame.codePtr++;
return null;
}
private static Value execLoadFunc(Environment env, Instruction instr, Frame frame) {
int id = instr.get(0);
String name = instr.get(1);
boolean callable = instr.get(2);
boolean constructible = instr.get(3);
boolean captureThis = instr.get(4);
var captures = new Value[instr.params.length - 5][];
for (var i = 5; i < instr.params.length; i++) {
captures[i - 5] = frame.captureVar(instr.get(i));
}
var func = new CodeFunction(env, name, frame.function.body.children[id], captures);
if (!callable) func.enableCall = false;
if (!constructible) func.enableNew = false;
if (captureThis) {
func.self = frame.self;
func.argsVal = frame.argsVal;
}
frame.push(func);
frame.codePtr++;
return null;
}
private static Value execLoadMember(Environment env, Instruction instr, Frame frame) {
var key = frame.pop();
try {
var top = frame.stackPtr - 1;
frame.stack[top] = frame.stack[top].getMember(env, key);
}
catch (IllegalArgumentException e) {
throw EngineException.ofType(e.getMessage());
}
frame.codePtr++;
return null;
}
private static Value execLoadMemberInt(Environment env, Instruction instr, Frame frame) {
try {
var top = frame.stackPtr - 1;
frame.stack[top] = frame.stack[top].getMember(env, (int)instr.get(0));
}
catch (IllegalArgumentException e) {
throw EngineException.ofType(e.getMessage());
}
frame.codePtr++;
return null;
}
private static Value execLoadMemberStr(Environment env, Instruction instr, Frame frame) {
try {
var top = frame.stackPtr - 1;
frame.stack[top] = frame.stack[top].getMember(env, (String)instr.get(0));
}
catch (IllegalArgumentException e) {
throw EngineException.ofType(e.getMessage());
}
frame.codePtr++;
return null;
}
private static Value execLoadRegEx(Environment env, Instruction instr, Frame frame) {
if (env.hasNotNull(Value.REGEX_CONSTR)) {
frame.push(env.get(Value.REGEX_CONSTR).callNew(env, instr.get(0), instr.get(1)));
}
else {
throw EngineException.ofSyntax("Regex is not supported.");
}
frame.codePtr++;
return null;
}
private static Value execDiscard(Environment env, Instruction instr, Frame frame) {
frame.pop();
frame.codePtr++;
return null;
}
private static Value execStoreMember(Environment env, Instruction instr, Frame frame) {
var val = frame.pop();
var key = frame.pop();
var obj = frame.pop();
if (!obj.setMember(env, key, val)) throw EngineException.ofSyntax("Can't set member '" + key.toReadable(env) + "'.");
if ((boolean)instr.get(0)) frame.push(val);
frame.codePtr++;
return null;
}
private static Value execStoreMemberStr(Environment env, Instruction instr, Frame frame) {
var val = frame.pop();
var obj = frame.pop();
if (!obj.setMember(env, (String)instr.get(0), val)) throw EngineException.ofSyntax("Can't set member '" + instr.get(0) + "'.");
if ((boolean)instr.get(1)) frame.push(val);
frame.codePtr++;
return null;
}
private static Value execStoreMemberInt(Environment env, Instruction instr, Frame frame) {
var val = frame.pop();
var obj = frame.pop();
if (!obj.setMember(env, (int)instr.get(0), val)) throw EngineException.ofSyntax("Can't set member '" + instr.get(0) + "'.");
if ((boolean)instr.get(1)) frame.push(val);
frame.codePtr++;
return null;
}
private static Value execStoreVar(Environment env, Instruction instr, Frame frame) {
var val = (boolean)instr.get(1) ? frame.peek() : frame.pop();
int i = instr.get(0);
frame.setVar(i, val);
frame.codePtr++;
return null;
}
private static Value execJmp(Environment env, Instruction instr, Frame frame) {
frame.codePtr += (int)instr.get(0);
frame.jumpFlag = true;
return null;
}
private static Value execJmpIf(Environment env, Instruction instr, Frame frame) {
if (frame.pop().toBoolean()) {
frame.codePtr += (int)instr.get(0);
frame.jumpFlag = true;
}
else frame.codePtr ++;
return null;
}
private static Value execJmpIfNot(Environment env, Instruction instr, Frame frame) {
if (!frame.pop().toBoolean()) {
frame.codePtr += (int)instr.get(0);
frame.jumpFlag = true;
}
else frame.codePtr ++;
return null;
}
private static Value execTypeof(Environment env, Instruction instr, Frame frame) {
String name = instr.get(0);
Value obj;
if (name != null) obj = Value.global(env).getMember(env, name);
else obj = frame.pop();
frame.push(obj.type());
frame.codePtr++;
return null;
}
private static Value execNop(Environment env, Instruction instr, Frame frame) {
frame.codePtr++;
return null;
}
private static Value execDelete(Environment env, Instruction instr, Frame frame) {
var key = frame.pop();
var val = frame.pop();
if (!val.deleteMember(env, key)) throw EngineException.ofSyntax("Can't delete member '" + key.toReadable(env) + "'.");
frame.codePtr++;
return null;
}
private static Value execOperation(Environment env, Instruction instr, Frame frame) {
Operation op = instr.get(0);
Value res;
var stack = frame.stack;
frame.stackPtr -= 1;
var ptr = frame.stackPtr;
switch (op) {
case ADD:
res = Value.add(env, stack[ptr - 1], stack[ptr]);
break;
case SUBTRACT:
res = Value.subtract(env, stack[ptr - 1], stack[ptr]);
break;
case DIVIDE:
res = Value.divide(env, stack[ptr - 1], stack[ptr]);
break;
case MULTIPLY:
res = Value.multiply(env, stack[ptr - 1], stack[ptr]);
break;
case MODULO:
res = Value.modulo(env, stack[ptr - 1], stack[ptr]);
break;
case AND:
res = Value.and(env, stack[ptr - 1], stack[ptr]);
break;
case OR:
res = Value.or(env, stack[ptr - 1], stack[ptr]);
break;
case XOR:
res = Value.xor(env, stack[ptr - 1], stack[ptr]);
break;
case EQUALS:
res = BoolValue.of(stack[ptr - 1].equals(stack[ptr]));
break;
case NOT_EQUALS:
res = BoolValue.of(!stack[ptr - 1].equals(stack[ptr]));
break;
case LOOSE_EQUALS:
res = BoolValue.of(Value.looseEqual(env, stack[ptr - 1], stack[ptr]));
break;
case LOOSE_NOT_EQUALS:
res = BoolValue.of(!Value.looseEqual(env, stack[ptr - 1], stack[ptr]));
break;
case GREATER:
res = BoolValue.of(Value.greater(env, stack[ptr - 1], stack[ptr]));
break;
case GREATER_EQUALS:
res = BoolValue.of(Value.greaterOrEqual(env, stack[ptr - 1], stack[ptr]));
break;
case LESS:
res = BoolValue.of(Value.less(env, stack[ptr - 1], stack[ptr]));
break;
case LESS_EQUALS:
res = BoolValue.of(Value.lessOrEqual(env, stack[ptr - 1], stack[ptr]));
break;
case INVERSE:
res = Value.bitwiseNot(env, stack[ptr++]);
frame.stackPtr++;
break;
case NOT:
res = BoolValue.of(!stack[ptr++].toBoolean());
frame.stackPtr++;
break;
case POS:
res = stack[ptr++].toNumber(env);
frame.stackPtr++;
break;
case NEG:
res = Value.negative(env, stack[ptr++]);
frame.stackPtr++;
break;
case SHIFT_LEFT:
res = Value.shiftLeft(env, stack[ptr], stack[ptr]);
break;
case SHIFT_RIGHT:
res = Value.shiftRight(env, stack[ptr], stack[ptr]);
break;
case USHIFT_RIGHT:
res = Value.unsignedShiftRight(env, stack[ptr], stack[ptr]);
break;
case IN:
res = BoolValue.of(stack[ptr - 1].hasMember(env, stack[ptr], false));
break;
case INSTANCEOF:
res = BoolValue.of(stack[ptr - 1].isInstanceOf(env, stack[ptr].getMember(env, new StringValue("prototype"))));
break;
default: return null;
}
stack[ptr - 1] = res;
frame.codePtr++;
return null;
}
private static Value exexGlobDef(Environment env, Instruction instr, Frame frame) {
var name = (String)instr.get(0);
if (!Value.global(env).hasMember(env, name, false)) {
if (!Value.global(env).defineOwnMember(env, name, Value.UNDEFINED)) throw EngineException.ofError("Couldn't define variable " + name);
}
frame.codePtr++;
return null;
}
private static Value exexGlobGet(Environment env, Instruction instr, Frame frame) {
var name = (String)instr.get(0);
var res = Value.global(env).getMemberOrNull(env, name);
if (res == null) throw EngineException.ofSyntax(name + " is not defined");
else frame.push(res);
frame.codePtr++;
return null;
}
private static Value exexGlobSet(Environment env, Instruction instr, Frame frame) {
var name = (String)instr.get(0);
var keep = (boolean)instr.get(1);
var define = (boolean)instr.get(2);
var val = keep ? frame.peek() : frame.pop();
var res = false;
if (define) res = Value.global(env).setMember(env, name, val);
else res = Value.global(env).setMemberIfExists(env, name, val);
if (!res) throw EngineException.ofError("Couldn't set variable " + name);
frame.codePtr++;
return null;
}
private static Value execLoadArgs(Environment env, Instruction instr, Frame frame) {
if ((boolean)instr.get(0) || frame.fakeArgs == null) frame.push(frame.argsVal);
else frame.push(frame.fakeArgs);
frame.codePtr++;
return null;
}
private static Value execLoadRestArgs(Environment env, Instruction instr, Frame frame) {
int offset = instr.get(0);
var res = new ArrayValue();
if (offset < frame.args.length) res.copyFrom(frame.args, instr.get(0), 0, frame.args.length - offset);
frame.push(res);
frame.codePtr++;
return null;
}
private static Value execLoadCallee(Environment env, Instruction instr, Frame frame) {
frame.push(frame.function);
frame.codePtr++;
return null;
}
private static Value execLoadThis(Environment env, Instruction instr, Frame frame) {
frame.push(frame.self);
frame.codePtr++;
return null;
}
private static Value execLoadError(Environment env, Instruction instr, Frame frame) {
frame.push(frame.tryStack.peek().error.value);
frame.codePtr++;
return null;
}
private static Value execStackAlloc(Environment env, Instruction instr, Frame frame) {
int offset = instr.get(0);
int n = instr.get(1);
for (var i = offset; i < n; i++) frame.capturables[i] = new Value[] { Value.UNDEFINED };
frame.codePtr++;
return null;
}
private static Value execStackRealloc(Environment env, Instruction instr, Frame frame) {
int offset = instr.get(0);
int n = instr.get(1);
for (var i = offset; i < n; i++) frame.capturables[i] = new Value[] { frame.capturables[i][0] };
frame.codePtr++;
return null;
}
private static Value execStackFree(Environment env, Instruction instr, Frame frame) {
// int n = instr.get(0);
// TODO: Remove if safe to do so
frame.codePtr++;
return null;
}
public static Value exec(Environment env, Instruction instr, Frame frame) {
switch (instr.type) {
case NOP: return execNop(env, instr, frame);
case RETURN: return execReturn(env, instr, frame);
case THROW: return execThrow(env, instr, frame);
case THROW_SYNTAX: return execThrowSyntax(env, instr, frame);
case CALL: return execCall(env, instr, frame);
case CALL_NEW: return execCallNew(env, instr, frame);
case CALL_MEMBER: return execCallMember(env, instr, frame);
case TRY_START: return execTryStart(env, instr, frame);
case TRY_END: return execTryEnd(env, instr, frame);
case DUP: return execDup(env, instr, frame);
case PUSH_UNDEFINED:
case PUSH_NULL:
case PUSH_STRING:
case PUSH_NUMBER:
case PUSH_BOOL:
return execLoadValue(env, instr, frame);
case LOAD_VAR: return execLoadVar(env, instr, frame);
case LOAD_OBJ: return execLoadObj(env, instr, frame);
case LOAD_ARR: return execLoadArr(env, instr, frame);
case LOAD_FUNC: return execLoadFunc(env, instr, frame);
case LOAD_MEMBER: return execLoadMember(env, instr, frame);
case LOAD_MEMBER_INT: return execLoadMemberInt(env, instr, frame);
case LOAD_MEMBER_STR: return execLoadMemberStr(env, instr, frame);
case LOAD_REGEX: return execLoadRegEx(env, instr, frame);
case LOAD_GLOB: return execLoadGlob(env, instr, frame);
case LOAD_INTRINSICS: return execLoadIntrinsics(env, instr, frame);
case LOAD_ARGS: return execLoadArgs(env, instr, frame);
case LOAD_REST_ARGS: return execLoadRestArgs(env, instr, frame);
case LOAD_CALLEE: return execLoadCallee(env, instr, frame);
case LOAD_THIS: return execLoadThis(env, instr, frame);
case LOAD_ERROR: return execLoadError(env, instr, frame);
case DISCARD: return execDiscard(env, instr, frame);
case STORE_MEMBER: return execStoreMember(env, instr, frame);
case STORE_MEMBER_STR: return execStoreMemberStr(env, instr, frame);
case STORE_MEMBER_INT: return execStoreMemberInt(env, instr, frame);
case STORE_VAR: return execStoreVar(env, instr, frame);
case KEYS: return execKeys(env, instr, frame);
case DEF_PROP: return execDefProp(env, instr, frame);
case TYPEOF: return execTypeof(env, instr, frame);
case DELETE: return execDelete(env, instr, frame);
case JMP: return execJmp(env, instr, frame);
case JMP_IF: return execJmpIf(env, instr, frame);
case JMP_IFN: return execJmpIfNot(env, instr, frame);
case OPERATION: return execOperation(env, instr, frame);
case GLOB_DEF: return exexGlobDef(env, instr, frame);
case GLOB_GET: return exexGlobGet(env, instr, frame);
case GLOB_SET: return exexGlobSet(env, instr, frame);
case STACK_ALLOC: return execStackAlloc(env, instr, frame);
case STACK_REALLOC: return execStackRealloc(env, instr, frame);
case STACK_FREE: return execStackFree(env, instr, frame);
default: throw EngineException.ofSyntax("Invalid instruction " + instr.type.name() + ".");
}
}
}