Try-catch fix for interruptible code #2
@ -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;
|
||||
},
|
||||
|
@ -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 {
|
||||
|
@ -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];
|
||||
|
||||
|
19
src/me/topchetoeu/jscript/compilation/AssignStatement.java
Normal file
19
src/me/topchetoeu/jscript/compilation/AssignStatement.java
Normal 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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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,55 +34,58 @@ public class Instruction {
|
||||
LOAD_REGEX,
|
||||
|
||||
DUP,
|
||||
MOVE,
|
||||
|
||||
STORE_VAR,
|
||||
STORE_MEMBER,
|
||||
DISCARD,
|
||||
|
||||
|
||||
MAKE_VAR,
|
||||
DEF_PROP,
|
||||
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
|
||||
|
@ -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));
|
||||
|
@ -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());
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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()));
|
||||
|
@ -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) {
|
||||
|
@ -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()));
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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()));
|
||||
|
@ -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()));
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
42
src/me/topchetoeu/jscript/engine/Operation.java
Normal file
42
src/me/topchetoeu/jscript/engine/Operation.java
Normal 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;
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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() + ".");
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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];
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
2
src/me/topchetoeu/jscript/js/bootstrap.js
vendored
2
src/me/topchetoeu/jscript/js/bootstrap.js
vendored
@ -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 = {
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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<>()));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user