Try-catch fix for interruptible code #2

Merged
TopchetoEU merged 5 commits from TopchetoEU/fix-try-catch into master 2023-08-25 14:07:32 +00:00
36 changed files with 691 additions and 596 deletions

View File

@ -297,6 +297,14 @@ setProps(Array.prototype, {
return -1;
},
lastIndexOf(el, start) {
start = start! | 0;
for (var i = this.length; i >= start; i--) {
if (i in this && this[i] == el) return i;
}
return -1;
},
includes(el, start) {
return this.indexOf(el, start) >= 0;
},

View File

@ -14,7 +14,7 @@ interface FunctionConstructor extends Function {
(...args: string[]): (...args: any[]) => any;
new (...args: string[]): (...args: any[]) => any;
prototype: Function;
async<ArgsT extends any[], RetT>(func: (await: <T>(val: T) => Awaited<T>, args: ArgsT) => RetT): Promise<RetT>;
async<ArgsT extends any[], RetT>(func: (await: <T>(val: T) => Awaited<T>) => (...args: ArgsT) => RetT): (...args: ArgsT) => Promise<RetT>;
}
interface CallableFunction extends Function {

View File

@ -14,6 +14,7 @@ import me.topchetoeu.jscript.events.Observer;
import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.SyntaxException;
import me.topchetoeu.jscript.polyfills.PolyfillEngine;
import me.topchetoeu.jscript.polyfills.TypescriptEngine;
public class Main {
static Thread task;
@ -53,7 +54,7 @@ public class Main {
public static void main(String args[]) {
var in = new BufferedReader(new InputStreamReader(System.in));
engine = new PolyfillEngine(new File("."));
engine = new TypescriptEngine(new File("."));
var scope = engine.global().globalChild();
var exited = new boolean[1];

View File

@ -0,0 +1,19 @@
package me.topchetoeu.jscript.compilation;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public abstract class AssignStatement extends Statement {
public abstract void compile(List<Instruction> target, ScopeRecord scope, boolean retPrevValue);
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
compile(target, scope, false);
}
protected AssignStatement(Location loc) {
super(loc);
}
}

View File

@ -1,10 +1,10 @@
package me.topchetoeu.jscript.compilation;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction.Type;
import me.topchetoeu.jscript.engine.Operation;
public abstract class AssignableStatement extends Statement {
public abstract Statement toAssign(Statement val, Type operation);
public abstract AssignStatement toAssign(Statement val, Operation operation);
protected AssignableStatement(Location loc) {
super(loc);

View File

@ -1,6 +1,7 @@
package me.topchetoeu.jscript.compilation;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.exceptions.SyntaxException;
public class Instruction {
@ -33,6 +34,7 @@ public class Instruction {
LOAD_REGEX,
DUP,
MOVE,
STORE_VAR,
STORE_MEMBER,
@ -43,45 +45,47 @@ public class Instruction {
KEYS,
TYPEOF,
INSTANCEOF(true),
IN(true),
OPERATION;
// TYPEOF,
// INSTANCEOF(true),
// IN(true),
MULTIPLY(true),
DIVIDE(true),
MODULO(true),
ADD(true),
SUBTRACT(true),
// MULTIPLY(true),
// DIVIDE(true),
// MODULO(true),
// ADD(true),
// SUBTRACT(true),
USHIFT_RIGHT(true),
SHIFT_RIGHT(true),
SHIFT_LEFT(true),
// USHIFT_RIGHT(true),
// SHIFT_RIGHT(true),
// SHIFT_LEFT(true),
GREATER(true),
LESS(true),
GREATER_EQUALS(true),
LESS_EQUALS(true),
LOOSE_EQUALS(true),
LOOSE_NOT_EQUALS(true),
EQUALS(true),
NOT_EQUALS(true),
// GREATER(true),
// LESS(true),
// GREATER_EQUALS(true),
// LESS_EQUALS(true),
// LOOSE_EQUALS(true),
// LOOSE_NOT_EQUALS(true),
// EQUALS(true),
// NOT_EQUALS(true),
AND(true),
OR(true),
XOR(true),
// AND(true),
// OR(true),
// XOR(true),
NEG(true),
POS(true),
NOT(true),
INVERSE(true);
// NEG(true),
// POS(true),
// NOT(true),
// INVERSE(true);
final boolean isOperation;
// final boolean isOperation;
private Type(boolean isOperation) {
this.isOperation = isOperation;
}
private Type() {
this(false);
}
// private Type(boolean isOperation) {
// this.isOperation = isOperation;
// }
// private Type() {
// this(false);
// }
}
public final Type type;
@ -103,6 +107,11 @@ public class Instruction {
if (i >= params.length || i < 0) return null;
return (T)params[i];
}
@SuppressWarnings("unchecked")
public <T> T get(int i, T defaultVal) {
if (i >= params.length || i < 0) return defaultVal;
return (T)params[i];
}
public boolean match(Object ...args) {
if (args.length != params.length) return false;
for (int i = 0; i < args.length; i++) {
@ -126,8 +135,8 @@ public class Instruction {
this.params = params;
}
public static Instruction tryInstr(boolean hasCatch, boolean hasFinally) {
return new Instruction(null, Type.TRY, hasCatch, hasFinally);
public static Instruction tryInstr(int n, int catchN, int finallyN) {
return new Instruction(null, Type.TRY, n, catchN, finallyN);
}
public static Instruction throwInstr() {
return new Instruction(null, Type.THROW);
@ -226,11 +235,14 @@ public class Instruction {
public static Instruction loadArr(int count) {
return new Instruction(null, Type.LOAD_ARR, count);
}
public static Instruction dup(int count) {
return new Instruction(null, Type.DUP, count, 0);
public static Instruction dup() {
return new Instruction(null, Type.DUP, 0, 1);
}
public static Instruction dup(int count, int offset) {
return new Instruction(null, Type.DUP, count, offset);
return new Instruction(null, Type.DUP, offset, count);
}
public static Instruction move(int count, int offset) {
return new Instruction(null, Type.MOVE, offset, count);
}
public static Instruction storeSelfFunc(int i) {
@ -255,7 +267,7 @@ public class Instruction {
public static Instruction typeof() {
return new Instruction(null, Type.TYPEOF);
}
public static Instruction typeof(String varName) {
public static Instruction typeof(Object varName) {
return new Instruction(null, Type.TYPEOF, varName);
}
@ -267,9 +279,8 @@ public class Instruction {
return new Instruction(null, Type.DEF_PROP);
}
public static Instruction operation(Type op) {
if (!op.isOperation) throw new IllegalArgumentException("The instruction type %s is not an operation.".formatted(op));
return new Instruction(null, op);
public static Instruction operation(Operation op) {
return new Instruction(null, Type.OPERATION, op);
}
@Override

View File

@ -5,7 +5,7 @@ import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.Instruction.Type;
import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class ForInStatement extends Statement {
@ -37,17 +37,18 @@ public class ForInStatement extends Statement {
target.add(Instruction.keys());
int start = target.size();
target.add(Instruction.dup(1));
target.add(Instruction.dup());
target.add(Instruction.loadMember("length"));
target.add(Instruction.loadValue(0));
target.add(Instruction.operation(Type.LESS_EQUALS));
target.add(Instruction.operation(Operation.LESS_EQUALS));
int mid = target.size();
target.add(Instruction.nop());
target.add(Instruction.dup(2));
target.add(Instruction.dup());
target.add(Instruction.dup());
target.add(Instruction.loadMember("length"));
target.add(Instruction.loadValue(1));
target.add(Instruction.operation(Type.SUBTRACT));
target.add(Instruction.operation(Operation.SUBTRACT));
target.add(Instruction.dup(1, 2));
target.add(Instruction.loadValue("length"));
target.add(Instruction.dup(1, 2));

View File

@ -7,6 +7,7 @@ import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.Instruction.Type;
import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class SwitchStatement extends Statement {
@ -33,9 +34,9 @@ public class SwitchStatement extends Statement {
value.compile(target, scope);
for (var ccase : cases) {
target.add(Instruction.dup(1).locate(loc()));
target.add(Instruction.dup().locate(loc()));
ccase.value.compileWithPollution(target, scope);
target.add(Instruction.operation(Type.EQUALS).locate(loc()));
target.add(Instruction.operation(Operation.EQUALS).locate(loc()));
caseMap.put(target.size(), ccase.statementI);
target.add(Instruction.nop());
}

View File

@ -5,7 +5,8 @@ import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.Instruction.Type;
import me.topchetoeu.jscript.engine.scope.GlobalScope;
import me.topchetoeu.jscript.engine.scope.LocalScopeRecord;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class TryStatement extends Statement {
@ -24,57 +25,43 @@ public class TryStatement extends Statement {
if (finallyBody != null) finallyBody.declare(globScope);
}
private void compileBody(List<Instruction> target, ScopeRecord scope, Statement body, String arg) {
var subscope = scope.child();
int start = target.size();
target.add(Instruction.nop());
subscope.define("this");
var argsVar = subscope.define("<catchargs>");
if (arg != null) {
target.add(Instruction.loadVar(argsVar));
target.add(Instruction.loadMember(0));
target.add(Instruction.storeVar(subscope.define(arg)));
}
int bodyStart = target.size();
body.compile(target, subscope);
target.add(Instruction.signal("no_return"));
target.get(bodyStart).locate(body.loc());
target.set(start, Instruction.loadFunc(target.size() - start, subscope.localsCount(), 0, subscope.getCaptures()));
}
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
int start = target.size();
target.add(Instruction.nop());
compileBody(target, scope, tryBody, null);
int start = target.size(), tryN, catchN = -1, finN = -1;
tryBody.compileNoPollution(target, scope);
tryN = target.size() - start;
if (catchBody != null) {
compileBody(target, scope, catchBody, name);
int tmp = target.size();
var local = scope instanceof GlobalScope ? scope.child() : (LocalScopeRecord)scope;
local.define(name, true);
catchBody.compileNoPollution(target, scope);
local.undefine();
catchN = target.size() - tmp;
}
if (finallyBody != null) {
compileBody(target, scope, finallyBody, null);
int tmp = target.size();
finallyBody.compileNoPollution(target, scope);
finN = target.size() - tmp;
}
for (int i = start; i < target.size(); i++) {
if (target.get(i).type == Type.NOP) {
var instr = target.get(i);
if (instr.is(0, "break")) {
target.set(i, Instruction.nop("try_break", instr.get(1), target.size()).locate(instr.location));
}
else if (instr.is(0, "cont")) {
target.set(i, Instruction.nop("try_cont", instr.get(1), target.size()).locate(instr.location));
}
}
}
// for (int i = start; i < target.size(); i++) {
// if (target.get(i).type == Type.NOP) {
// var instr = target.get(i);
// if (instr.is(0, "break")) {
// target.set(i, Instruction.nop("try_break", instr.get(1), target.size()).locate(instr.location));
// }
// else if (instr.is(0, "cont")) {
// target.set(i, Instruction.nop("try_cont", instr.get(1), target.size()).locate(instr.location));
// }
// }
// }
target.add(Instruction.tryInstr(catchBody != null, finallyBody != null).locate(loc()));
target.set(start - 1, Instruction.tryInstr(tryN, catchN, finN).locate(loc()));
}
public TryStatement(Location loc, Statement tryBody, Statement catchBody, Statement finallyBody, String name) {

View File

@ -21,7 +21,7 @@ public class ArrayStatement extends Statement {
var i = 0;
for (var el : statements) {
if (el != null) {
target.add(Instruction.dup(1).locate(loc()));
target.add(Instruction.dup().locate(loc()));
target.add(Instruction.loadValue(i).locate(loc()));
el.compileWithPollution(target, scope);
target.add(Instruction.storeMember().locate(loc()));

View File

@ -6,7 +6,7 @@ import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.AssignableStatement;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.Instruction.Type;
import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class ChangeStatement extends Statement {
@ -19,11 +19,7 @@ public class ChangeStatement extends Statement {
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
value.toAssign(new ConstantStatement(loc(), -addAmount), Type.SUBTRACT).compileWithPollution(target, scope);
if (postfix) {
target.add(Instruction.loadValue(addAmount).locate(loc()));
target.add(Instruction.operation(Type.SUBTRACT).locate(loc()));
}
value.toAssign(new ConstantStatement(loc(), -addAmount), Operation.SUBTRACT).compile(target, scope, postfix);
}
public ChangeStatement(Location loc, AssignableStatement value, double addAmount, boolean postfix) {

View File

@ -53,13 +53,11 @@ public class FunctionStatement extends Statement {
target.add(Instruction.nop());
subscope.define("this");
var argsVar = subscope.define("arguments");
if (args.length > 0) {
target.add(Instruction.loadVar(argsVar).locate(loc()));
if (args.length != 1) target.add(Instruction.dup(args.length - 1).locate(loc()));
if (args.length > 0) {
for (var i = 0; i < args.length; i++) {
target.add(Instruction.loadVar(argsVar).locate(loc()));
target.add(Instruction.loadMember(i).locate(loc()));
target.add(Instruction.storeVar(subscope.define(args[i])).locate(loc()));
}
@ -82,7 +80,7 @@ public class FunctionStatement extends Statement {
if (name == null) name = this.name;
if (name != null) {
target.add(Instruction.dup(1).locate(loc()));
target.add(Instruction.dup().locate(loc()));
target.add(Instruction.loadValue("name").locate(loc()));
target.add(Instruction.loadValue(name).locate(loc()));
target.add(Instruction.storeMember().locate(loc()));

View File

@ -3,45 +3,52 @@ package me.topchetoeu.jscript.compilation.values;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.AssignStatement;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.Instruction.Type;
import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class IndexAssignStatement extends Statement {
public class IndexAssignStatement extends AssignStatement {
public final Statement object;
public final Statement index;
public final Statement value;
public final Type operation;
public final Operation operation;
@Override
public boolean pollutesStack() { return true; }
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
public void compile(List<Instruction> target, ScopeRecord scope, boolean retPrevValue) {
int start = 0;
if (operation != null) {
object.compileWithPollution(target, scope);
index.compileWithPollution(target, scope);
target.add(Instruction.dup(2, 0).locate(loc()));
target.add(Instruction.dup(1, 1).locate(loc()));
target.add(Instruction.dup(1, 1).locate(loc()));
target.add(Instruction.loadMember().locate(loc()));
if (retPrevValue) {
target.add(Instruction.dup().locate(loc()));
target.add(Instruction.move(3, 1).locate(loc()));
}
value.compileWithPollution(target, scope);
target.add(Instruction.operation(operation).locate(loc()));
target.add(Instruction.storeMember(true).locate(loc()));
target.add(Instruction.storeMember(!retPrevValue).locate(loc()).setDebug(true));
}
else {
object.compileWithPollution(target, scope);
if (retPrevValue) target.add(Instruction.dup().locate(loc()));
index.compileWithPollution(target, scope);
value.compileWithPollution(target, scope);
target.add(Instruction.storeMember(true).locate(loc()));
target.add(Instruction.storeMember(!retPrevValue).locate(loc()).setDebug(true));
}
target.get(start).setDebug(true);
target.get(start);
}
public IndexAssignStatement(Location loc, Statement object, Statement index, Statement value, Type operation) {
public IndexAssignStatement(Location loc, Statement object, Statement index, Statement value, Operation operation) {
super(loc);
this.object = object;
this.index = index;

View File

@ -3,10 +3,11 @@ package me.topchetoeu.jscript.compilation.values;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.AssignStatement;
import me.topchetoeu.jscript.compilation.AssignableStatement;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.Instruction.Type;
import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class IndexStatement extends AssignableStatement {
@ -19,13 +20,13 @@ public class IndexStatement extends AssignableStatement {
public boolean pure() { return true; }
@Override
public Statement toAssign(Statement val, Type operation) {
public AssignStatement toAssign(Statement val, Operation operation) {
return new IndexAssignStatement(loc(), object, index, val, operation);
}
public void compile(List<Instruction> target, ScopeRecord scope, boolean dupObj) {
int start = 0;
object.compileWithPollution(target, scope);
if (dupObj) target.add(Instruction.dup(1).locate(loc()));
if (dupObj) target.add(Instruction.dup().locate(loc()));
if (index instanceof ConstantStatement) {
target.add(Instruction.loadMember(((ConstantStatement)index).value).locate(loc()));
return;

View File

@ -29,7 +29,7 @@ public class LazyAndStatement extends Statement {
}
first.compileWithPollution(target, scope);
target.add(Instruction.dup(1).locate(loc()));
target.add(Instruction.dup().locate(loc()));
int start = target.size();
target.add(Instruction.nop());
target.add(Instruction.discard().locate(loc()));

View File

@ -29,7 +29,7 @@ public class LazyOrStatement extends Statement {
}
first.compileWithPollution(target, scope);
target.add(Instruction.dup(1).locate(loc()));
target.add(Instruction.dup().locate(loc()));
int start = target.size();
target.add(Instruction.nop());
target.add(Instruction.discard().locate(loc()));

View File

@ -20,9 +20,9 @@ public class ObjectStatement extends Statement {
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
target.add(Instruction.loadObj().locate(loc()));
if (!map.isEmpty()) target.add(Instruction.dup(map.size()).locate(loc()));
for (var el : map.entrySet()) {
target.add(Instruction.dup().locate(loc()));
target.add(Instruction.loadValue(el.getKey()).locate(loc()));
var val = el.getValue();
if (val instanceof FunctionStatement) ((FunctionStatement)val).compile(target, scope, el.getKey().toString(), false);

View File

@ -7,13 +7,14 @@ import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.control.ThrowStatement;
import me.topchetoeu.jscript.engine.CallContext;
import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException;
public class OperationStatement extends Statement {
public final Statement[] args;
public final Instruction.Type operation;
public final Operation operation;
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
@ -96,7 +97,7 @@ public class OperationStatement extends Statement {
}
public OperationStatement(Location loc, Instruction.Type operation, Statement... args) {
public OperationStatement(Location loc, Operation operation, Statement... args) {
super(loc);
this.operation = operation;
this.args = args;

View File

@ -6,8 +6,7 @@ import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.Symbol;
import me.topchetoeu.jscript.engine.values.Values;
public class TypeofStatement extends Statement {
public final Statement value;
@ -22,7 +21,7 @@ public class TypeofStatement extends Statement {
if (value instanceof VariableStatement) {
var i = scope.getKey(((VariableStatement)value).name);
if (i instanceof String) {
target.add(Instruction.typeof((String)i));
target.add(Instruction.typeof((String)i).locate(loc()));
return;
}
}
@ -35,15 +34,14 @@ public class TypeofStatement extends Statement {
var val = value.optimize();
if (val instanceof ConstantStatement) {
var cnst = (ConstantStatement)val;
if (cnst.value == null) return new ConstantStatement(loc(), "undefined");
if (cnst.value instanceof Number) return new ConstantStatement(loc(), "number");
if (cnst.value instanceof Boolean) return new ConstantStatement(loc(), "boolean");
if (cnst.value instanceof String) return new ConstantStatement(loc(), "string");
if (cnst.value instanceof Symbol) return new ConstantStatement(loc(), "symbol");
if (cnst.value instanceof FunctionValue) return new ConstantStatement(loc(), "function");
return new ConstantStatement(loc(), "object");
return new ConstantStatement(loc(), Values.type(((ConstantStatement)val).value));
}
else if (
val instanceof ObjectStatement ||
val instanceof ArrayStatement ||
val instanceof GlobalThisStatement
) return new ConstantStatement(loc(), "object");
else if(val instanceof FunctionStatement) return new ConstantStatement(loc(), "function");
return new TypeofStatement(loc(), val);
}

View File

@ -4,36 +4,39 @@ import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.AssignStatement;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Instruction.Type;
import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class VariableAssignStatement extends Statement {
public class VariableAssignStatement extends AssignStatement {
public final String name;
public final Statement value;
public final Type operation;
public final Operation operation;
@Override
public boolean pollutesStack() { return true; }
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
public void compile(List<Instruction> target, ScopeRecord scope, boolean retPrevValue) {
var i = scope.getKey(name);
if (operation != null) {
target.add(Instruction.loadVar(i).locate(loc()));
if (retPrevValue) target.add(Instruction.dup().locate(loc()));
if (value instanceof FunctionStatement) ((FunctionStatement)value).compile(target, scope, name, false);
else value.compileWithPollution(target, scope);
target.add(Instruction.operation(operation).locate(loc()));
target.add(Instruction.storeVar(i, !retPrevValue).locate(loc()));
}
else {
if (retPrevValue) target.add(Instruction.loadVar(i).locate(loc()));
if (value instanceof FunctionStatement) ((FunctionStatement)value).compile(target, scope, name, false);
else value.compileWithPollution(target, scope);
target.add(Instruction.storeVar(i, !retPrevValue).locate(loc()));
}
target.add(Instruction.storeVar(i, true).locate(loc()));
}
public VariableAssignStatement(Location loc, String name, Statement val, Type operation) {
public VariableAssignStatement(Location loc, String name, Statement val, Operation operation) {
super(loc);
this.name = name;
this.value = val;

View File

@ -3,10 +3,11 @@ package me.topchetoeu.jscript.compilation.values;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.AssignStatement;
import me.topchetoeu.jscript.compilation.AssignableStatement;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.Instruction.Type;
import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class VariableStatement extends AssignableStatement {
@ -18,7 +19,7 @@ public class VariableStatement extends AssignableStatement {
public boolean pure() { return true; }
@Override
public Statement toAssign(Statement val, Type operation) {
public AssignStatement toAssign(Statement val, Operation operation) {
return new VariableAssignStatement(loc(), name, val, operation);
}

View File

@ -0,0 +1,42 @@
package me.topchetoeu.jscript.engine;
public enum Operation {
INSTANCEOF(2, false),
IN(2, false),
MULTIPLY(2, true),
DIVIDE(2, true),
MODULO(2, true),
ADD(2, true),
SUBTRACT(2, true),
USHIFT_RIGHT(2, true),
SHIFT_RIGHT(2, true),
SHIFT_LEFT(2, true),
GREATER(2, true),
LESS(2, true),
GREATER_EQUALS(2, true),
LESS_EQUALS(2, true),
LOOSE_EQUALS(2, true),
LOOSE_NOT_EQUALS(2, true),
EQUALS(2, true),
NOT_EQUALS(2, true),
AND(2, true),
OR(2, true),
XOR(2, true),
NEG(1, true),
POS(1, true),
NOT(1, true),
INVERSE(1, true);
public final int operands;
public final boolean optimizable;
private Operation(int n, boolean opt) {
this.operands = n;
this.optimizable = opt;
}
}

View File

@ -4,19 +4,47 @@ import java.util.ArrayList;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction.Type;
import me.topchetoeu.jscript.engine.BreakpointData;
import me.topchetoeu.jscript.engine.CallContext;
import me.topchetoeu.jscript.engine.DebugCommand;
import me.topchetoeu.jscript.engine.Engine;
import me.topchetoeu.jscript.engine.CallContext.DataKey;
import me.topchetoeu.jscript.engine.scope.LocalScope;
import me.topchetoeu.jscript.engine.scope.ValueVariable;
import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.CodeFunction;
import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException;
public class CodeFrame {
private class TryCtx {
public static final int STATE_TRY = 0;
public static final int STATE_CATCH = 1;
public static final int STATE_FINALLY_THREW = 2;
public static final int STATE_FINALLY_RETURNED = 3;
public static final int STATE_FINALLY_JUMPED = 4;
public final boolean hasCatch, hasFinally;
public final int tryStart, catchStart, finallyStart, end;
public int state;
public Object retVal;
public int jumpPtr;
public EngineException err;
public TryCtx(int tryStart, int tryN, int catchN, int finallyN) {
hasCatch = catchN >= 0;
hasFinally = finallyN >= 0;
if (catchN < 0) catchN = 0;
if (finallyN < 0) finallyN = 0;
this.tryStart = tryStart;
this.catchStart = tryStart + tryN;
this.finallyStart = catchStart + catchN;
this.end = finallyStart + finallyN;
this.jumpPtr = end;
}
}
public static final DataKey<Integer> STACK_N_KEY = new DataKey<>();
public static final DataKey<Integer> MAX_STACK_KEY = new DataKey<>();
public static final DataKey<Boolean> STOP_AT_START_KEY = new DataKey<>();
@ -25,53 +53,70 @@ public class CodeFrame {
public final LocalScope scope;
public final Object thisArg;
public final Object[] args;
public final List<Object> stack = new ArrayList<>();
public final List<TryContext> tryCtxs = new ArrayList<>();
public final List<TryCtx> tryStack = new ArrayList<>();
public final CodeFunction function;
public Object[] stack = new Object[32];
public int stackPtr = 0;
public int codePtr = 0;
public boolean jumpFlag = false;
private DebugCommand debugCmd = null;
private Location prevLoc = null;
public void addTry(int n, int catchN, int finallyN) {
var res = new TryCtx(codePtr + 1, n, catchN, finallyN);
tryStack.add(res);
}
public Object peek() {
return peek(0);
}
public Object peek(int offset) {
if (stack.size() <= offset) return null;
else return stack.get(stack.size() - 1 - offset);
if (stackPtr <= offset) return null;
else return stack[stackPtr - 1 - offset];
}
public Object pop() {
if (stack.size() == 0) return null;
else return stack.remove(stack.size() - 1);
if (stackPtr == 0) return null;
return stack[--stackPtr];
}
public Object[] take(int n) {
int srcI = stackPtr - n;
if (srcI < 0) srcI = 0;
int dstI = n + srcI - stackPtr;
int copyN = stackPtr - srcI;
Object[] res = new Object[n];
System.arraycopy(stack, srcI, res, dstI, copyN);
stackPtr -= copyN;
return res;
}
public void push(Object val) {
stack.add(stack.size(), Values.normalize(val));
if (stack.length <= stackPtr) {
var newStack = new Object[stack.length * 2];
System.arraycopy(stack, 0, newStack, 0, stack.length);
stack = newStack;
}
stack[stackPtr++] = Values.normalize(val);
}
public void cleanup(CallContext ctx) {
stack.clear();
codePtr = 0;
debugCmd = null;
public void start(CallContext ctx) {
if (ctx.getData(STACK_N_KEY, 0) >= ctx.addData(MAX_STACK_KEY, 10000)) throw EngineException.ofRange("Stack overflow!");
ctx.changeData(STACK_N_KEY);
var debugState = ctx.getData(Engine.DEBUG_STATE_KEY);
if (debugState != null) debugState.pushFrame(this);
}
public void end(CallContext ctx) {
var debugState = ctx.getData(Engine.DEBUG_STATE_KEY);
if (debugState != null) debugState.popFrame();
ctx.changeData(STACK_N_KEY, -1);
}
public Object next(CallContext ctx) throws InterruptedException {
var debugState = ctx.getData(Engine.DEBUG_STATE_KEY);
if (debugCmd == null) {
if (ctx.getData(STACK_N_KEY, 0) >= ctx.addData(MAX_STACK_KEY, 100000))
throw EngineException.ofRange("Stack overflow!");
ctx.changeData(STACK_N_KEY);
if (ctx.getData(STOP_AT_START_KEY, false)) debugCmd = DebugCommand.STEP_OVER;
else debugCmd = DebugCommand.NORMAL;
if (debugState != null) debugState.pushFrame(this);
}
private Object nextNoTry(CallContext ctx) throws InterruptedException {
if (Thread.currentThread().isInterrupted()) throw new InterruptedException();
if (codePtr < 0 || codePtr >= function.body.length) return null;
@ -80,22 +125,31 @@ public class CodeFrame {
var loc = instr.location;
if (loc != null) prevLoc = loc;
if (debugState != null && loc != null) {
if (
instr.type == Type.NOP && instr.match("debug") || debugState.breakpoints.contains(loc) || (
ctx.getData(STEPPING_TROUGH_KEY, false) &&
(debugCmd == DebugCommand.STEP_INTO || debugCmd == DebugCommand.STEP_OVER)
)
) {
ctx.setData(STEPPING_TROUGH_KEY, true);
// var debugState = ctx.getData(Engine.DEBUG_STATE_KEY);
// if (debugCmd == null) {
// if (ctx.getData(STOP_AT_START_KEY, false)) debugCmd = DebugCommand.STEP_OVER;
// else debugCmd = DebugCommand.NORMAL;
debugState.breakpointNotifier.next(new BreakpointData(loc, ctx));
debugCmd = debugState.commandNotifier.toAwaitable().await();
if (debugCmd == DebugCommand.NORMAL) ctx.setData(STEPPING_TROUGH_KEY, false);
}
}
// if (debugState != null) debugState.pushFrame(this);
// }
// if (debugState != null && loc != null) {
// if (
// instr.type == Type.NOP && instr.match("debug") || debugState.breakpoints.contains(loc) || (
// ctx.getData(STEPPING_TROUGH_KEY, false) &&
// (debugCmd == DebugCommand.STEP_INTO || debugCmd == DebugCommand.STEP_OVER)
// )
// ) {
// ctx.setData(STEPPING_TROUGH_KEY, true);
// debugState.breakpointNotifier.next(new BreakpointData(loc, ctx));
// debugCmd = debugState.commandNotifier.toAwaitable().await();
// if (debugCmd == DebugCommand.NORMAL) ctx.setData(STEPPING_TROUGH_KEY, false);
// }
// }
try {
this.jumpFlag = false;
return Runners.exec(debugCmd, instr, this, ctx);
}
catch (EngineException e) {
@ -103,73 +157,143 @@ public class CodeFrame {
}
}
public Object next(CallContext ctx, EngineException prevError) throws InterruptedException {
TryCtx tryCtx = null;
var handled = prevError == null;
while (!tryStack.isEmpty()) {
var tmp = tryStack.get(tryStack.size() - 1);
var remove = false;
if (tmp.state == TryCtx.STATE_TRY) {
if (prevError != null) {
tmp.jumpPtr = tmp.end;
if (tmp.hasCatch) {
tmp.state = TryCtx.STATE_CATCH;
scope.catchVars.add(new ValueVariable(false, prevError));
codePtr = tmp.catchStart;
handled = true;
}
else if (tmp.hasFinally) {
tmp.state = TryCtx.STATE_FINALLY_THREW;
tmp.err = prevError;
codePtr = tmp.finallyStart;
handled = true;
}
}
else if (codePtr < tmp.tryStart || codePtr >= tmp.catchStart) {
if (jumpFlag) tmp.jumpPtr = codePtr;
else tmp.jumpPtr = tmp.end;
if (tmp.hasFinally) {
tmp.state = TryCtx.STATE_FINALLY_JUMPED;
codePtr = tmp.finallyStart;
}
else codePtr = tmp.jumpPtr;
remove = !tmp.hasFinally;
}
}
else if (tmp.state == TryCtx.STATE_CATCH) {
if (codePtr < tmp.catchStart || codePtr >= tmp.finallyStart) {
if (jumpFlag) tmp.jumpPtr = codePtr;
else tmp.jumpPtr = tmp.end;
scope.catchVars.remove(scope.catchVars.size() - 1);
if (tmp.hasFinally) {
tmp.state = TryCtx.STATE_FINALLY_JUMPED;
codePtr = tmp.finallyStart;
}
else codePtr = tmp.jumpPtr;
remove = !tmp.hasFinally;
}
}
else if (codePtr < tmp.finallyStart || codePtr >= tmp.end) {
if (!jumpFlag) {
if (tmp.state == TryCtx.STATE_FINALLY_THREW) throw tmp.err;
else if (tmp.state == TryCtx.STATE_FINALLY_RETURNED) return tmp.retVal;
else if (tmp.state == TryCtx.STATE_FINALLY_JUMPED) codePtr = tmp.jumpPtr;
}
else codePtr = tmp.jumpPtr;
remove = true;
}
if (!handled) throw prevError;
if (remove) tryStack.remove(tryStack.size() - 1);
else {
tryCtx = tmp;
break;
}
}
if (!handled) throw prevError;
if (tryCtx == null) return nextNoTry(ctx);
else if (tryCtx.state == TryCtx.STATE_TRY) {
try {
var res = nextNoTry(ctx);
if (res != Runners.NO_RETURN && tryCtx.hasFinally) {
tryCtx.retVal = res;
tryCtx.state = TryCtx.STATE_FINALLY_RETURNED;
}
else return res;
}
catch (EngineException e) {
if (tryCtx.hasCatch) {
tryCtx.state = TryCtx.STATE_CATCH;
codePtr = tryCtx.catchStart;
scope.catchVars.add(new ValueVariable(false, e.value));
return Runners.NO_RETURN;
}
else if (tryCtx.hasFinally) {
tryCtx.err = e;
tryCtx.state = TryCtx.STATE_FINALLY_THREW;
}
else throw e;
}
codePtr = tryCtx.finallyStart;
return Runners.NO_RETURN;
}
else if (tryCtx.state == TryCtx.STATE_CATCH) {
try {
var res = nextNoTry(ctx);
if (res != Runners.NO_RETURN && tryCtx.hasFinally) {
tryCtx.retVal = res;
tryCtx.state = TryCtx.STATE_FINALLY_RETURNED;
}
else return res;
}
catch (EngineException e) {
if (tryCtx.hasFinally) {
tryCtx.err = e;
tryCtx.state = TryCtx.STATE_FINALLY_THREW;
}
else throw e;
}
codePtr = tryCtx.finallyStart;
return Runners.NO_RETURN;
}
else return nextNoTry(ctx);
}
public void handleReturn(Object value) {
}
public Object run(CallContext ctx) throws InterruptedException {
try {
start(ctx);
while (true) {
var res = next(ctx);
var res = next(ctx, null);
if (res != Runners.NO_RETURN) return res;
}
}
finally {
cleanup(ctx);
end(ctx);
}
// var debugState = ctx.getData(Engine.DEBUG_STATE_KEY);
// DebugCommand command = ctx.getData(STOP_AT_START_KEY, false) ? DebugCommand.STEP_OVER : DebugCommand.NORMAL;
// if (ctx.getData(STACK_N_KEY, 0) >= ctx.addData(MAX_STACK_KEY, 200)) throw EngineException.ofRange("Stack overflow!");
// ctx.changeData(STACK_N_KEY);
// if (debugState != null) debugState.pushFrame(this);
// Location loc = null;
// Location loc = null;
// try {
// while (codePtr >= 0 && codePtr < function.body.length) {
// var _loc = function.body[codePtr].location;
// if (_loc != null) loc = _loc;
// if (Thread.currentThread().isInterrupted()) throw new InterruptedException();
// var instr = function.body[codePtr];
// if (debugState != null && loc != null) {
// if (
// instr.type == Type.NOP && instr.match("debug") ||
// (
// (command == DebugCommand.STEP_INTO || command == DebugCommand.STEP_OVER) &&
// ctx.getData(STEPPING_TROUGH_KEY, false)
// ) ||
// debugState.breakpoints.contains(loc)
// ) {
// ctx.setData(STEPPING_TROUGH_KEY, true);
// debugState.breakpointNotifier.next(new BreakpointData(loc, ctx));
// command = debugState.commandNotifier.toAwaitable().await();
// if (command == DebugCommand.NORMAL) ctx.setData(STEPPING_TROUGH_KEY, false);
// }
// }
// try {
// var res = Runners.exec(command, instr, this, ctx);
// if (res != Runners.NO_RETURN) return res;
// }
// catch (EngineException e) {
// throw e.add(function.name, instr.location);
// }
// }
// return null;
// }
// // catch (StackOverflowError e) {
// // e.printStackTrace();
// // throw EngineException.ofRange("Stack overflow!").add(function.name, loc);
// // }
// finally {
// ctx.changeData(STACK_N_KEY, -1);
// }
}
public CodeFrame(Object thisArg, Object[] args, CodeFunction func) {

View File

@ -5,6 +5,7 @@ import java.util.Collections;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.engine.CallContext;
import me.topchetoeu.jscript.engine.DebugCommand;
import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ValueVariable;
import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.CodeFunction;
@ -38,10 +39,7 @@ public class Runners {
}
public static Object execCall(DebugCommand state, Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
int n = instr.get(0);
var callArgs = new Object[n];
for (var i = n - 1; i >= 0; i--) callArgs[i] = frame.pop();
var callArgs = frame.take(instr.get(0));
var func = frame.pop();
var thisArg = frame.pop();
@ -51,10 +49,7 @@ public class Runners {
return NO_RETURN;
}
public static Object execCallNew(DebugCommand state, Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
int n = instr.get(0);
var callArgs = new Object[n];
for (var i = n - 1; i >= 0; i--) callArgs[i] = frame.pop();
var callArgs = frame.take(instr.get(0));
var funcObj = frame.pop();
if (Values.isFunction(funcObj) && Values.function(funcObj).special) {
@ -129,75 +124,30 @@ public class Runners {
}
public static Object execTry(DebugCommand state, Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
var finallyFunc = (boolean)instr.get(1) ? frame.pop() : null;
var catchFunc = (boolean)instr.get(0) ? frame.pop() : null;
var func = frame.pop();
if (
!Values.isFunction(func) ||
catchFunc != null && !Values.isFunction(catchFunc) ||
finallyFunc != null && !Values.isFunction(finallyFunc)
) throw EngineException.ofType("TRY instruction can be applied only upon functions.");
Object res = new SignalValue("no_return");
EngineException exception = null;
Values.function(func).name = frame.function.name + "::try";
if (catchFunc != null) Values.function(catchFunc).name = frame.function.name + "::catch";
if (finallyFunc != null) Values.function(finallyFunc).name = frame.function.name + "::finally";
try {
ctx.setData(CodeFrame.STOP_AT_START_KEY, state != DebugCommand.NORMAL);
res = Values.call(ctx, func, frame.thisArg);
}
catch (EngineException e) {
exception = e.setCause(exception);
}
if (exception != null && catchFunc != null) {
var exc = exception;
exception = null;
try {
ctx.setData(CodeFrame.STOP_AT_START_KEY, state != DebugCommand.NORMAL);
var _res = Values.call(ctx, catchFunc, frame.thisArg, exc);
if (!SignalValue.isSignal(_res, "no_return")) res = _res;
}
catch (EngineException e) {
exception = e.setCause(exc);
}
}
if (finallyFunc != null) {
try {
ctx.setData(CodeFrame.STOP_AT_START_KEY, state != DebugCommand.NORMAL);
var _res = Values.call(ctx, finallyFunc, frame.thisArg);
if (!SignalValue.isSignal(_res, "no_return")) {
res = _res;
exception = null;
}
}
catch (EngineException e) {
exception = e.setCause(exception);
}
}
if (exception != null) throw exception;
if (SignalValue.isSignal(res, "no_return")) {
frame.codePtr++;
return NO_RETURN;
}
else if (SignalValue.isSignal(res, "jmp_*")) {
frame.codePtr += Integer.parseInt(((SignalValue)res).data.substring(4));
return NO_RETURN;
}
else return res;
frame.addTry(instr.get(0), instr.get(1), instr.get(2));
frame.codePtr++;
return NO_RETURN;
}
public static Object execDup(Instruction instr, CodeFrame frame, CallContext ctx) {
var val = frame.peek(instr.get(1));
for (int i = 0; i < (int)instr.get(0); i++) {
frame.push(val);
int offset = instr.get(0), count = instr.get(1);
for (var i = 0; i < count; i++) {
frame.push(frame.peek(offset + count - 1));
}
frame.codePtr++;
return NO_RETURN;
}
public static Object execMove(Instruction instr, CodeFrame frame, CallContext ctx) {
int offset = instr.get(0), count = instr.get(1);
var tmp = frame.take(offset);
var res = frame.take(count);
for (var i = 0; i < offset; i++) frame.push(tmp[i]);
for (var i = 0; i < count; i++) frame.push(res[i]);
frame.codePtr++;
return NO_RETURN;
}
@ -316,14 +266,23 @@ public class Runners {
public static Object execJmp(Instruction instr, CodeFrame frame, CallContext ctx) {
frame.codePtr += (int)instr.get(0);
frame.jumpFlag = true;
return NO_RETURN;
}
public static Object execJmpIf(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
frame.codePtr += Values.toBoolean(frame.pop()) ? (int)instr.get(0) : 1;
if (Values.toBoolean(frame.pop())) {
frame.codePtr += (int)instr.get(0);
frame.jumpFlag = true;
}
else frame.codePtr ++;
return NO_RETURN;
}
public static Object execJmpIfNot(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
frame.codePtr += Values.not(frame.pop()) ? (int)instr.get(0) : 1;
if (Values.not(frame.pop())) {
frame.codePtr += (int)instr.get(0);
frame.jumpFlag = true;
}
else frame.codePtr ++;
return NO_RETURN;
}
@ -376,157 +335,13 @@ public class Runners {
return NO_RETURN;
}
public static Object execAdd(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
Object b = frame.pop(), a = frame.pop();
frame.push(Values.add(ctx, a, b));
frame.codePtr++;
return NO_RETURN;
}
public static Object execSubtract(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
Object b = frame.pop(), a = frame.pop();
frame.push(Values.subtract(ctx, a, b));
frame.codePtr++;
return NO_RETURN;
}
public static Object execMultiply(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
Object b = frame.pop(), a = frame.pop();
frame.push(Values.multiply(ctx, a, b));
frame.codePtr++;
return NO_RETURN;
}
public static Object execDivide(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
Object b = frame.pop(), a = frame.pop();
frame.push(Values.divide(ctx, a, b));
frame.codePtr++;
return NO_RETURN;
}
public static Object execModulo(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
Object b = frame.pop(), a = frame.pop();
frame.push(Values.modulo(ctx, a, b));
frame.codePtr++;
return NO_RETURN;
}
public static Object execOperation(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
Operation op = instr.get(0);
var args = new Object[op.operands];
public static Object execAnd(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
Object b = frame.pop(), a = frame.pop();
for (var i = op.operands - 1; i >= 0; i--) args[i] = frame.pop();
frame.push(Values.and(ctx, a, b));
frame.codePtr++;
return NO_RETURN;
}
public static Object execOr(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
Object b = frame.pop(), a = frame.pop();
frame.push(Values.or(ctx, a, b));
frame.codePtr++;
return NO_RETURN;
}
public static Object execXor(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
Object b = frame.pop(), a = frame.pop();
frame.push(Values.xor(ctx, a, b));
frame.codePtr++;
return NO_RETURN;
}
public static Object execLeftShift(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
Object b = frame.pop(), a = frame.pop();
frame.push(Values.shiftLeft(ctx, a, b));
frame.codePtr++;
return NO_RETURN;
}
public static Object execRightShift(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
Object b = frame.pop(), a = frame.pop();
frame.push(Values.shiftRight(ctx, a, b));
frame.codePtr++;
return NO_RETURN;
}
public static Object execUnsignedRightShift(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
Object b = frame.pop(), a = frame.pop();
frame.push(Values.unsignedShiftRight(ctx, a, b));
frame.codePtr++;
return NO_RETURN;
}
public static Object execNot(Instruction instr, CodeFrame frame, CallContext ctx) {
frame.push(Values.not(frame.pop()));
frame.codePtr++;
return NO_RETURN;
}
public static Object execNeg(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
frame.push(Values.negative(ctx, frame.pop()));
frame.codePtr++;
return NO_RETURN;
}
public static Object execPos(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
frame.push(Values.toNumber(ctx, frame.pop()));
frame.codePtr++;
return NO_RETURN;
}
public static Object execInverse(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
frame.push(Values.bitwiseNot(ctx, frame.pop()));
frame.codePtr++;
return NO_RETURN;
}
public static Object execGreaterThan(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
Object b = frame.pop(), a = frame.pop();
frame.push(Values.compare(ctx, a, b) > 0);
frame.codePtr++;
return NO_RETURN;
}
public static Object execLessThan(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
Object b = frame.pop(), a = frame.pop();
frame.push(Values.compare(ctx, a, b) < 0);
frame.codePtr++;
return NO_RETURN;
}
public static Object execGreaterThanEquals(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
Object b = frame.pop(), a = frame.pop();
frame.push(Values.compare(ctx, a, b) >= 0);
frame.codePtr++;
return NO_RETURN;
}
public static Object execLessThanEquals(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
Object b = frame.pop(), a = frame.pop();
frame.push(Values.compare(ctx, a, b) <= 0);
frame.codePtr++;
return NO_RETURN;
}
public static Object execLooseEquals(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
Object b = frame.pop(), a = frame.pop();
frame.push(Values.looseEqual(ctx, a, b));
frame.codePtr++;
return NO_RETURN;
}
public static Object execLooseNotEquals(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
Object b = frame.pop(), a = frame.pop();
frame.push(!Values.looseEqual(ctx, a, b));
frame.codePtr++;
return NO_RETURN;
}
public static Object execEquals(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
Object b = frame.pop(), a = frame.pop();
frame.push(Values.strictEquals(a, b));
frame.codePtr++;
return NO_RETURN;
}
public static Object execNotEquals(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
Object b = frame.pop(), a = frame.pop();
frame.push(!Values.strictEquals(a, b));
frame.push(Values.operation(ctx, op, args));
frame.codePtr++;
return NO_RETURN;
}
@ -544,6 +359,7 @@ public class Runners {
case TRY: return execTry(state, instr, frame, ctx);
case DUP: return execDup(instr, frame, ctx);
case MOVE: return execMove(instr, frame, ctx);
case LOAD_VALUE: return execLoadValue(instr, frame, ctx);
case LOAD_VAR: return execLoadVar(instr, frame, ctx);
case LOAD_OBJ: return execLoadObj(instr, frame, ctx);
@ -560,45 +376,16 @@ public class Runners {
case STORE_SELF_FUNC: return execStoreSelfFunc(instr, frame, ctx);
case MAKE_VAR: return execMakeVar(instr, frame, ctx);
case IN: return execIn(instr, frame, ctx);
case KEYS: return execKeys(instr, frame, ctx);
case DEF_PROP: return execDefProp(instr, frame, ctx);
case TYPEOF: return execTypeof(instr, frame, ctx);
case DELETE: return execDelete(instr, frame, ctx);
case INSTANCEOF: return execInstanceof(instr, frame, ctx);
case JMP: return execJmp(instr, frame, ctx);
case JMP_IF: return execJmpIf(instr, frame, ctx);
case JMP_IFN: return execJmpIfNot(instr, frame, ctx);
case ADD: return execAdd(instr, frame, ctx);
case SUBTRACT: return execSubtract(instr, frame, ctx);
case MULTIPLY: return execMultiply(instr, frame, ctx);
case DIVIDE: return execDivide(instr, frame, ctx);
case MODULO: return execModulo(instr, frame, ctx);
case AND: return execAnd(instr, frame, ctx);
case OR: return execOr(instr, frame, ctx);
case XOR: return execXor(instr, frame, ctx);
case SHIFT_LEFT: return execLeftShift(instr, frame, ctx);
case SHIFT_RIGHT: return execRightShift(instr, frame, ctx);
case USHIFT_RIGHT: return execUnsignedRightShift(instr, frame, ctx);
case NOT: return execNot(instr, frame, ctx);
case NEG: return execNeg(instr, frame, ctx);
case POS: return execPos(instr, frame, ctx);
case INVERSE: return execInverse(instr, frame, ctx);
case GREATER: return execGreaterThan(instr, frame, ctx);
case GREATER_EQUALS: return execGreaterThanEquals(instr, frame, ctx);
case LESS: return execLessThan(instr, frame, ctx);
case LESS_EQUALS: return execLessThanEquals(instr, frame, ctx);
case LOOSE_EQUALS: return execLooseEquals(instr, frame, ctx);
case LOOSE_NOT_EQUALS: return execLooseNotEquals(instr, frame, ctx);
case EQUALS: return execEquals(instr, frame, ctx);
case NOT_EQUALS: return execNotEquals(instr, frame, ctx);
case OPERATION: return execOperation(instr, frame, ctx);
default: throw EngineException.ofSyntax("Invalid instruction " + instr.type.name() + ".");
}

View File

@ -11,6 +11,10 @@ import me.topchetoeu.jscript.exceptions.EngineException;
public class GlobalScope implements ScopeRecord {
public final ObjectValue obj;
public final GlobalScope parent;
@Override
public GlobalScope parent() { return parent; }
public boolean has(CallContext ctx, String name) throws InterruptedException {
return obj.hasMember(ctx, name, false);
@ -74,9 +78,11 @@ public class GlobalScope implements ScopeRecord {
}
public GlobalScope() {
this.parent = null;
this.obj = new ObjectValue();
}
public GlobalScope(GlobalScope parent) {
this.parent = null;
this.obj = new ObjectValue();
this.obj.setPrototype(null, parent.obj);
}

View File

@ -1,12 +1,16 @@
package me.topchetoeu.jscript.engine.scope;
import java.util.ArrayList;
public class LocalScope {
private String[] names;
private LocalScope parent;
public final ValueVariable[] captures;
public final ValueVariable[] locals;
public final ArrayList<ValueVariable> catchVars = new ArrayList<>();
public ValueVariable get(int i) {
if (i >= locals.length) return catchVars.get(i - locals.length);
if (i >= 0) return locals[i];
else return captures[~i];
}

View File

@ -15,6 +15,9 @@ public class LocalScopeRecord implements ScopeRecord {
return locals.toArray(String[]::new);
}
@Override
public LocalScopeRecord parent() { return parent; }
public LocalScopeRecord child() {
return new LocalScopeRecord(this, global);
}
@ -43,7 +46,7 @@ public class LocalScopeRecord implements ScopeRecord {
public Object getKey(String name) {
var capI = captures.indexOf(name);
var locI = locals.indexOf(name);
var locI = locals.lastIndexOf(name);
if (locI >= 0) return locI;
if (capI >= 0) return ~capI;
if (parent != null) {
@ -62,11 +65,17 @@ public class LocalScopeRecord implements ScopeRecord {
locals.contains(name) ||
parent != null && parent.has(ctx, name);
}
public Object define(String name) {
if (locals.contains(name)) return locals.indexOf(name);
public Object define(String name, boolean force) {
if (!force && locals.contains(name)) return locals.indexOf(name);
locals.add(name);
return locals.size() - 1;
}
public Object define(String name) {
return define(name, false);
}
public void undefine() {
locals.remove(locals.size() - 1);
}
public LocalScopeRecord(GlobalScope global) {
this.parent = null;

View File

@ -3,5 +3,6 @@ package me.topchetoeu.jscript.engine.scope;
public interface ScopeRecord {
public Object getKey(String name);
public Object define(String name);
public ScopeRecord parent();
public LocalScopeRecord child();
}

View File

@ -11,6 +11,7 @@ import java.util.List;
import java.util.Map;
import me.topchetoeu.jscript.engine.CallContext;
import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.frame.ConvertHint;
import me.topchetoeu.jscript.exceptions.EngineException;
@ -218,6 +219,47 @@ public class Values {
return false;
}
public static Object operation(CallContext ctx, Operation op, Object... args) throws InterruptedException {
switch (op) {
case ADD: return add(ctx, args[0], args[1]);
case SUBTRACT: return subtract(ctx, args[0], args[1]);
case DIVIDE: return divide(ctx, args[0], args[1]);
case MULTIPLY: return multiply(ctx, args[0], args[1]);
case MODULO: return modulo(ctx, args[0], args[1]);
case AND: return and(ctx, args[0], args[1]);
case OR: return or(ctx, args[0], args[1]);
case XOR: return xor(ctx, args[0], args[1]);
case EQUALS: return strictEquals(args[0], args[1]);
case NOT_EQUALS: return !strictEquals(args[0], args[1]);
case LOOSE_EQUALS: return looseEqual(ctx, args[0], args[1]);
case LOOSE_NOT_EQUALS: return !looseEqual(ctx, args[0], args[1]);
case GREATER: return compare(ctx, args[0], args[1]) > 0;
case GREATER_EQUALS: return compare(ctx, args[0], args[1]) >= 0;
case LESS: return compare(ctx, args[0], args[1]) < 0;
case LESS_EQUALS: return compare(ctx, args[0], args[1]) <= 0;
case INVERSE: return bitwiseNot(ctx, args[0]);
case NOT: return not(args[0]);
case POS: return toNumber(ctx, args[0]);
case NEG: return negative(ctx, args[0]);
case SHIFT_LEFT: return shiftLeft(ctx, args[0], args[1]);
case SHIFT_RIGHT: return shiftRight(ctx, args[0], args[1]);
case USHIFT_RIGHT: return unsignedShiftRight(ctx, args[0], args[1]);
case IN: return hasMember(ctx, args[1], args[0], false);
case INSTANCEOF: {
var proto = getMember(ctx, args[1], "prototype");
return isInstanceOf(ctx, args[0], proto);
}
default: return null;
}
}
public static Object getMember(CallContext ctx, Object obj, Object key) throws InterruptedException {
obj = normalize(obj); key = normalize(key);
if (obj == null) throw new IllegalArgumentException("Tried to access member of undefined.");
@ -312,7 +354,8 @@ public class Values {
}
public static Object call(CallContext ctx, Object func, Object thisArg, Object ...args) throws InterruptedException {
if (!isFunction(func)) throw EngineException.ofType("Attempted to call a non-function value.");
if (!isFunction(func))
throw EngineException.ofType("Tried to call a non-function value.");
return function(func).call(ctx, thisArg, args);
}

View File

@ -2,7 +2,7 @@
var ts = require('./ts__');
log("Loaded typescript!");
var src = '', lib = libs.join(''), decls = '', version = 0;
var src = '', lib = libs.concat([ 'declare const exit: never;' ]).join(''), decls = '', version = 0;
var libSnapshot = ts.ScriptSnapshot.fromString(lib);
var settings = {

View File

@ -10,31 +10,17 @@ import me.topchetoeu.jscript.parsing.Token;
public class JSON {
public static ParseRes<String> parseIdentifier(List<Token> tokens, int i) {
try {
if (tokens.get(i).isIdentifier()) return ParseRes.res(tokens.get(i).identifier(), 1);
else return ParseRes.failed();
}
catch (IndexOutOfBoundsException e) {
return ParseRes.failed();
}
return Parsing.parseIdentifier(tokens, i);
}
public static ParseRes<String> parseString(String filename, List<Token> tokens, int i) {
try {
if (tokens.get(i).isString()) return ParseRes.res(tokens.get(i).string(), 1);
else return ParseRes.failed();
}
catch (IndexOutOfBoundsException e) {
return ParseRes.failed();
}
var res = Parsing.parseString(filename, tokens, i);
if (res.isSuccess()) return ParseRes.res((String)res.result.value, res.n);
else return res.transform();
}
public static ParseRes<Double> parseNumber(String filename, List<Token> tokens, int i) {
try {
if (tokens.get(i).isNumber()) return ParseRes.res(tokens.get(i).number(), 1);
else return ParseRes.failed();
}
catch (IndexOutOfBoundsException e) {
return ParseRes.failed();
}
var res = Parsing.parseNumber(filename, tokens, i);
if (res.isSuccess()) return ParseRes.res((Double)res.result.value, res.n);
else return res.transform();
}
public static ParseRes<Boolean> parseBool(String filename, List<Token> tokens, int i) {
var id = parseIdentifier(tokens, i);

View File

@ -10,7 +10,7 @@ public class JSONElement {
MAP,
}
public static JSONElement NULL = new JSONElement(Type.NULL, null);
public static final JSONElement NULL = new JSONElement(Type.NULL, null);
public static JSONElement map(JSONMap val) {
return new JSONElement(Type.MAP, val);

View File

@ -3,29 +3,28 @@ package me.topchetoeu.jscript.parsing;
import java.util.HashMap;
import java.util.Map;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Instruction.Type;
import me.topchetoeu.jscript.engine.Operation;
public enum Operator {
MULTIPLY("*", Type.MULTIPLY, 13),
DIVIDE("/", Type.DIVIDE, 12),
MODULO("%", Type.MODULO, 12),
SUBTRACT("-", Type.SUBTRACT, 11),
ADD("+", Type.ADD, 11),
SHIFT_RIGHT(">>", Type.SHIFT_RIGHT, 10),
SHIFT_LEFT("<<", Type.SHIFT_LEFT, 10),
USHIFT_RIGHT(">>>", Type.USHIFT_RIGHT, 10),
GREATER(">", Type.GREATER, 9),
LESS("<", Type.LESS, 9),
GREATER_EQUALS(">=", Type.GREATER_EQUALS, 9),
LESS_EQUALS("<=", Type.LESS_EQUALS, 9),
NOT_EQUALS("!=", Type.LOOSE_NOT_EQUALS, 8),
LOOSE_NOT_EQUALS("!==", Type.NOT_EQUALS, 8),
EQUALS("==", Type.LOOSE_EQUALS, 8),
LOOSE_EQUALS("===", Type.EQUALS, 8),
AND("&", Type.AND, 7),
XOR("^", Type.XOR, 6),
OR("|", Type.OR, 5),
MULTIPLY("*", Operation.MULTIPLY, 13),
DIVIDE("/", Operation.DIVIDE, 12),
MODULO("%", Operation.MODULO, 12),
SUBTRACT("-", Operation.SUBTRACT, 11),
ADD("+", Operation.ADD, 11),
SHIFT_RIGHT(">>", Operation.SHIFT_RIGHT, 10),
SHIFT_LEFT("<<", Operation.SHIFT_LEFT, 10),
USHIFT_RIGHT(">>>", Operation.USHIFT_RIGHT, 10),
GREATER(">", Operation.GREATER, 9),
LESS("<", Operation.LESS, 9),
GREATER_EQUALS(">=", Operation.GREATER_EQUALS, 9),
LESS_EQUALS("<=", Operation.LESS_EQUALS, 9),
NOT_EQUALS("!=", Operation.LOOSE_NOT_EQUALS, 8),
LOOSE_NOT_EQUALS("!==", Operation.NOT_EQUALS, 8),
EQUALS("==", Operation.LOOSE_EQUALS, 8),
LOOSE_EQUALS("===", Operation.EQUALS, 8),
AND("&", Operation.AND, 7),
XOR("^", Operation.XOR, 6),
OR("|", Operation.OR, 5),
LAZY_AND("&&", 4),
LAZY_OR("||", 3),
ASSIGN_SHIFT_LEFT("<<=", 2, true),
@ -57,7 +56,7 @@ public enum Operator {
DECREASE("--");
public final String value;
public final Instruction.Type operation;
public final Operation operation;
public final int precedence;
public final boolean reverse;
private static final Map<String, Operator> ops = new HashMap<>();
@ -99,13 +98,13 @@ public enum Operator {
this.reverse = reverse;
}
private Operator(String value, Instruction.Type funcName, int precedence) {
private Operator(String value, Operation funcName, int precedence) {
this. value = value;
this.operation = funcName;
this.precedence = precedence;
this.reverse = false;
}
private Operator(String value, Instruction.Type funcName, int precedence, boolean reverse) {
private Operator(String value, Operation funcName, int precedence, boolean reverse) {
this.value = value;
this.operation = funcName;
this.precedence = precedence;

View File

@ -13,6 +13,7 @@ import me.topchetoeu.jscript.compilation.VariableDeclareStatement.Pair;
import me.topchetoeu.jscript.compilation.control.*;
import me.topchetoeu.jscript.compilation.control.SwitchStatement.SwitchCase;
import me.topchetoeu.jscript.compilation.values.*;
import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.GlobalScope;
import me.topchetoeu.jscript.engine.scope.ValueVariable;
import me.topchetoeu.jscript.engine.values.CodeFunction;
@ -938,12 +939,12 @@ public class Parsing {
if (!opState.isSuccess()) return ParseRes.failed();
var op = opState.result;
Type operation = null;
Operation operation = null;
if (op == Operator.ADD) operation = Type.POS;
else if (op == Operator.SUBTRACT) operation = Type.NEG;
else if (op == Operator.INVERSE) operation = Type.INVERSE;
else if (op == Operator.NOT) operation = Type.NOT;
if (op == Operator.ADD) operation = Operation.POS;
else if (op == Operator.SUBTRACT) operation = Operation.NEG;
else if (op == Operator.INVERSE) operation = Operation.INVERSE;
else if (op == Operator.NOT) operation = Operation.NOT;
else return ParseRes.failed();
var res = parseValue(filename, tokens, n + i, 14);
@ -1103,19 +1104,19 @@ public class Parsing {
if (!res.isSuccess()) return ParseRes.error(loc, "Expected value after assignment operator '%s'.".formatted(op.value), res);
n += res.n;
Type operation = null;
Operation operation = null;
if (op == Operator.ASSIGN_ADD) operation = Type.ADD;
if (op == Operator.ASSIGN_SUBTRACT) operation = Type.SUBTRACT;
if (op == Operator.ASSIGN_MULTIPLY) operation = Type.MULTIPLY;
if (op == Operator.ASSIGN_DIVIDE) operation = Type.DIVIDE;
if (op == Operator.ASSIGN_MODULO) operation = Type.MODULO;
if (op == Operator.ASSIGN_OR) operation = Type.OR;
if (op == Operator.ASSIGN_XOR) operation = Type.XOR;
if (op == Operator.ASSIGN_AND) operation = Type.AND;
if (op == Operator.ASSIGN_SHIFT_LEFT) operation = Type.SHIFT_LEFT;
if (op == Operator.ASSIGN_SHIFT_RIGHT) operation = Type.SHIFT_RIGHT;
if (op == Operator.ASSIGN_USHIFT_RIGHT) operation = Type.USHIFT_RIGHT;
if (op == Operator.ASSIGN_ADD) operation = Operation.ADD;
if (op == Operator.ASSIGN_SUBTRACT) operation = Operation.SUBTRACT;
if (op == Operator.ASSIGN_MULTIPLY) operation = Operation.MULTIPLY;
if (op == Operator.ASSIGN_DIVIDE) operation = Operation.DIVIDE;
if (op == Operator.ASSIGN_MODULO) operation = Operation.MODULO;
if (op == Operator.ASSIGN_OR) operation = Operation.OR;
if (op == Operator.ASSIGN_XOR) operation = Operation.XOR;
if (op == Operator.ASSIGN_AND) operation = Operation.AND;
if (op == Operator.ASSIGN_SHIFT_LEFT) operation = Operation.SHIFT_LEFT;
if (op == Operator.ASSIGN_SHIFT_RIGHT) operation = Operation.SHIFT_RIGHT;
if (op == Operator.ASSIGN_USHIFT_RIGHT) operation = Operation.USHIFT_RIGHT;
return ParseRes.res(((AssignableStatement)prev).toAssign(res.result, operation), n);
}
@ -1180,7 +1181,7 @@ public class Parsing {
if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'instanceof'.", valRes);
n += valRes.n;
return ParseRes.res(new OperationStatement(loc, Type.INSTANCEOF, prev, valRes.result), n);
return ParseRes.res(new OperationStatement(loc, Operation.INSTANCEOF, prev, valRes.result), n);
}
public static ParseRes<OperationStatement> parseIn(String filename, List<Token> tokens, int i, Statement prev, int precedence) {
var loc = getLoc(filename, tokens, i);
@ -1193,7 +1194,7 @@ public class Parsing {
if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'in'.", valRes);
n += valRes.n;
return ParseRes.res(new OperationStatement(loc, Type.IN, prev, valRes.result), n);
return ParseRes.res(new OperationStatement(loc, Operation.IN, prev, valRes.result), n);
}
public static ParseRes<CommaStatement> parseComma(String filename, List<Token> tokens, int i, Statement prev, int precedence) {
var loc = getLoc(filename, tokens, i);

View File

@ -3,7 +3,6 @@ package me.topchetoeu.jscript.polyfills;
import me.topchetoeu.jscript.engine.CallContext;
import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.engine.frame.Runners;
import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.CodeFunction;
import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.NativeFunction;
@ -18,30 +17,38 @@ public class AsyncFunction extends FunctionValue {
private Object awaited = null;
public final Promise promise = new Promise();
public CodeFrame frame;
private final NativeFunction fulfillFunc = new NativeFunction("", this::fulfill);
private final NativeFunction rejectFunc = new NativeFunction("", this::reject);
private final NativeFunction fulfillFunc = new NativeFunction("", (ctx, thisArg, args) -> {
if (args.length == 0) exec(ctx, null, Runners.NO_RETURN);
else exec(ctx, args[0], Runners.NO_RETURN);
private Object reject(CallContext ctx, Object thisArg, Object[] args) throws InterruptedException {
if (args.length > 0) promise.reject(ctx, args[0]);
return null;
}
public Object fulfill(CallContext ctx, Object thisArg, Object[] args) throws InterruptedException {
if (args.length == 1) frame.push(args[0]);
});
private final NativeFunction rejectFunc = new NativeFunction("", (ctx, thisArg, args) -> {
if (args.length == 0) exec(ctx, Runners.NO_RETURN, null);
else exec(ctx, Runners.NO_RETURN, args[0]);
return null;
});
public Object exec(CallContext ctx, Object val, Object err) throws InterruptedException {
if (val != Runners.NO_RETURN) frame.push(val);
frame.start(ctx);
while (true) {
awaiting = false;
awaited = null;
try {
var res = frame.next(ctx);
var res = frame.next(ctx, err == Runners.NO_RETURN ? null : new EngineException(err));
if (res != Runners.NO_RETURN) {
promise.fulfill(ctx, res);
return null;
break;
}
}
catch (EngineException e) {
promise.reject(e);
return null;
promise.reject(e.value);
break;
}
if (!awaiting) continue;
@ -50,19 +57,24 @@ public class AsyncFunction extends FunctionValue {
if (awaited instanceof Promise) ((Promise)awaited).then(ctx, fulfillFunc, rejectFunc);
else if (Values.isPrimitive(awaited)) frame.push(awaited);
try {
var res = Values.getMember(ctx, awaited, "then");
if (res instanceof FunctionValue) {
Values.function(res).call(ctx, awaited, fulfillFunc, rejectFunc);
return null;
else {
try {
var res = Values.getMember(ctx, awaited, "then");
if (res instanceof FunctionValue) {
Values.function(res).call(ctx, awaited, fulfillFunc, rejectFunc);
break;
}
else frame.push(awaited);
}
catch (EngineException e) {
promise.reject(e);
break;
}
else frame.push(awaited);
}
catch (EngineException e) {
promise.reject(e);
return null;
}
}
frame.end(ctx);
return null;
}
public Object await(CallContext ctx, Object thisArg, Object[] args) {
@ -75,8 +87,10 @@ public class AsyncFunction extends FunctionValue {
@Override
public Object call(CallContext _ctx, Object thisArg, Object... args) throws InterruptedException {
var handler = new CallHandler();
handler.frame = new CodeFrame(thisArg, new Object[] { new NativeFunction("await", handler::await), new ArrayValue(args) }, body);
handler.fulfill(_ctx, null, new Object[0]);
var func = body.call(_ctx, thisArg, new NativeFunction("await", handler::await));
if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function.");
handler.frame = new CodeFrame(thisArg, args, (CodeFunction)func);
handler.exec(_ctx, Runners.NO_RETURN, Runners.NO_RETURN);
return handler.promise;
}

View File

@ -1,5 +1,7 @@
package me.topchetoeu.jscript.polyfills;
import java.util.HashSet;
import me.topchetoeu.jscript.engine.CallContext;
import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.ObjectValue;
@ -8,31 +10,75 @@ import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.SyntaxException;
import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.json.JSONElement;
import me.topchetoeu.jscript.json.JSONList;
import me.topchetoeu.jscript.json.JSONMap;
public class JSON {
private static Object convert(JSONElement val) {
private static Object toJS(JSONElement val) {
if (val.isBoolean()) return val.bool();
if (val.isString()) return val.string();
if (val.isNumber()) return val.number();
if (val.isList()) return ArrayValue.of(val.list().stream().map(JSON::convert).toList());
if (val.isList()) return ArrayValue.of(val.list().stream().map(JSON::toJS).toList());
if (val.isMap()) {
var res = new ObjectValue();
for (var el : val.map().entrySet()) {
res.defineProperty(el.getKey(), convert(el.getValue()));
res.defineProperty(el.getKey(), toJS(el.getValue()));
}
return res;
}
if (val.isNull()) return Values.NULL;
return null;
}
private static JSONElement toJSON(CallContext ctx, Object val, HashSet<Object> prev) throws InterruptedException {
if (val instanceof Boolean) return JSONElement.bool((boolean)val);
if (val instanceof Number) return JSONElement.number(((Number)val).doubleValue());
if (val instanceof String) return JSONElement.string((String)val);
if (val == Values.NULL) return JSONElement.NULL;
if (val instanceof ObjectValue) {
if (prev.contains(val)) throw new EngineException("Circular dependency in JSON.");
prev.add(val);
var res = new JSONMap();
for (var el : ((ObjectValue)val).keys(false)) {
var jsonEl = toJSON(ctx, ((ObjectValue)val).getMember(ctx, el), prev);
if (jsonEl == null) continue;
if (el instanceof String || el instanceof Number) res.put(el.toString(), jsonEl);
}
prev.remove(val);
return JSONElement.of(res);
}
if (val instanceof ArrayValue) {
if (prev.contains(val)) throw new EngineException("Circular dependency in JSON.");
prev.add(val);
var res = new JSONList();
for (var el : ((ArrayValue)val).toArray()) {
var jsonEl = toJSON(ctx, el, prev);
if (jsonEl == null) jsonEl = JSONElement.NULL;
res.add(jsonEl);
}
prev.remove(val);
return JSONElement.of(res);
}
if (val == null) return null;
return null;
}
@Native
public static Object parse(CallContext ctx, String val) throws InterruptedException {
try {
return convert(me.topchetoeu.jscript.json.JSON.parse("<value>", val));
return toJS(me.topchetoeu.jscript.json.JSON.parse("<value>", val));
}
catch (SyntaxException e) {
throw EngineException.ofSyntax(e.msg);
}
}
@Native
public static String stringify(CallContext ctx, Object val) throws InterruptedException {
return me.topchetoeu.jscript.json.JSON.stringify(toJSON(ctx, val, new HashSet<>()));
}
}