initial commit
This commit is contained in:
63
src/me/topchetoeu/jscript/Location.java
Normal file
63
src/me/topchetoeu/jscript/Location.java
Normal file
@@ -0,0 +1,63 @@
|
||||
package me.topchetoeu.jscript;
|
||||
|
||||
public class Location {
|
||||
public static final Location INTERNAL = new Location(0, 0, "<internal>");
|
||||
private int line;
|
||||
private int start;
|
||||
private String filename;
|
||||
|
||||
public int line() { return line; }
|
||||
public int start() { return start; }
|
||||
public String filename() { return filename; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return filename + ":" + line + ":" + start;
|
||||
}
|
||||
|
||||
public Location add(int n, boolean clone) {
|
||||
if (clone) return new Location(line, start + n, filename);
|
||||
this.start += n;
|
||||
return this;
|
||||
}
|
||||
public Location add(int n) {
|
||||
return add(n, false);
|
||||
}
|
||||
public Location nextLine() {
|
||||
line++;
|
||||
start = 0;
|
||||
return this;
|
||||
}
|
||||
public Location clone() {
|
||||
return new Location(line, start, filename);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
int result = 1;
|
||||
result = prime * result + line;
|
||||
result = prime * result + start;
|
||||
result = prime * result + ((filename == null) ? 0 : filename.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (this == obj) return true;
|
||||
if (obj == null) return false;
|
||||
if (getClass() != obj.getClass()) return false;
|
||||
Location other = (Location) obj;
|
||||
if (line != other.line) return false;
|
||||
if (start != other.start) return false;
|
||||
if (filename == null && other.filename != null) return false;
|
||||
else if (!filename.equals(other.filename)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
public Location(int line, int start, String filename) {
|
||||
this.line = line;
|
||||
this.start = start;
|
||||
this.filename = filename;
|
||||
}
|
||||
}
|
||||
104
src/me/topchetoeu/jscript/Main.java
Normal file
104
src/me/topchetoeu/jscript/Main.java
Normal file
@@ -0,0 +1,104 @@
|
||||
package me.topchetoeu.jscript;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.Map;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Engine;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.events.Observer;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.exceptions.SyntaxException;
|
||||
import me.topchetoeu.jscript.polyfills.PolyfillEngine;
|
||||
|
||||
public class Main {
|
||||
static Thread task;
|
||||
static Engine engine;
|
||||
|
||||
private static Observer<Object> valuePrinter = new Observer<Object>() {
|
||||
public void next(Object data) {
|
||||
try {
|
||||
Values.printValue(engine.context(), data);
|
||||
}
|
||||
catch (InterruptedException e) { }
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
public void error(RuntimeException err) {
|
||||
try {
|
||||
if (err instanceof EngineException) {
|
||||
System.out.println("Uncaught " + ((EngineException)err).toString(engine.context()));
|
||||
}
|
||||
else if (err instanceof SyntaxException) {
|
||||
System.out.println("Syntax error:" + ((SyntaxException)err).msg);
|
||||
}
|
||||
else if (err.getCause() instanceof InterruptedException) return;
|
||||
else {
|
||||
System.out.println("Internal error ocurred:");
|
||||
err.printStackTrace();
|
||||
}
|
||||
}
|
||||
catch (EngineException ex) {
|
||||
System.out.println("Uncaught [error while converting to string]");
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public static void main(String args[]) {
|
||||
var in = new BufferedReader(new InputStreamReader(System.in));
|
||||
engine = new PolyfillEngine(new File("."));
|
||||
var scope = engine.global().globalChild();
|
||||
var exited = new boolean[1];
|
||||
|
||||
scope.define("exit", ctx -> {
|
||||
exited[0] = true;
|
||||
task.interrupt();
|
||||
throw new InterruptedException();
|
||||
});
|
||||
|
||||
task = engine.start();
|
||||
var reader = new Thread(() -> {
|
||||
try {
|
||||
while (true) {
|
||||
try {
|
||||
var raw = in.readLine();
|
||||
|
||||
if (raw == null) break;
|
||||
engine.pushMsg(false, scope, Map.of(), "<stdio>", raw, null).toObservable().once(valuePrinter);
|
||||
}
|
||||
catch (EngineException e) {
|
||||
try {
|
||||
System.out.println("Uncaught " + e.toString(engine.context()));
|
||||
}
|
||||
catch (EngineException ex) {
|
||||
System.out.println("Uncaught [error while converting to string]");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
return;
|
||||
}
|
||||
catch (SyntaxException ex) {
|
||||
if (exited[0]) return;
|
||||
System.out.println("Syntax error:" + ex.msg);
|
||||
}
|
||||
catch (RuntimeException ex) {
|
||||
if (exited[0]) return;
|
||||
System.out.println("Internal error ocurred:");
|
||||
ex.printStackTrace();
|
||||
}
|
||||
catch (InterruptedException e) { return; }
|
||||
if (exited[0]) return;
|
||||
});
|
||||
reader.setDaemon(true);
|
||||
reader.setName("STD Reader");
|
||||
reader.start();
|
||||
}
|
||||
}
|
||||
6
src/me/topchetoeu/jscript/MessageReceiver.java
Normal file
6
src/me/topchetoeu/jscript/MessageReceiver.java
Normal file
@@ -0,0 +1,6 @@
|
||||
package me.topchetoeu.jscript;
|
||||
|
||||
public interface MessageReceiver {
|
||||
void sendMessage(String msg);
|
||||
void sendError(String msg);
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Instruction.Type;
|
||||
|
||||
public abstract class AssignableStatement extends Statement {
|
||||
public abstract Statement toAssign(Statement val, Type operation);
|
||||
|
||||
protected AssignableStatement(Location loc) {
|
||||
super(loc);
|
||||
}
|
||||
}
|
||||
11
src/me/topchetoeu/jscript/compilation/CompileOptions.java
Normal file
11
src/me/topchetoeu/jscript/compilation/CompileOptions.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
public class CompileOptions {
|
||||
public final boolean emitBpMap;
|
||||
public final boolean emitVarNames;
|
||||
|
||||
public CompileOptions(boolean emitBpMap, boolean emitVarNames) {
|
||||
this.emitBpMap = emitBpMap;
|
||||
this.emitVarNames = emitVarNames;
|
||||
}
|
||||
}
|
||||
77
src/me/topchetoeu/jscript/compilation/CompoundStatement.java
Normal file
77
src/me/topchetoeu/jscript/compilation/CompoundStatement.java
Normal file
@@ -0,0 +1,77 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.control.ContinueStatement;
|
||||
import me.topchetoeu.jscript.compilation.control.ReturnStatement;
|
||||
import me.topchetoeu.jscript.compilation.control.ThrowStatement;
|
||||
import me.topchetoeu.jscript.compilation.values.FunctionStatement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public class CompoundStatement extends Statement {
|
||||
public final Statement[] statements;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() {
|
||||
for (var stm : statements) {
|
||||
if (stm instanceof FunctionStatement) continue;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord varsScope) {
|
||||
for (var stm : statements) {
|
||||
stm.declare(varsScope);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
for (var stm : statements) {
|
||||
if (stm instanceof FunctionStatement) {
|
||||
int start = target.size();
|
||||
((FunctionStatement)stm).compile(target, scope, null, true);
|
||||
target.get(start).setDebug(true);
|
||||
target.add(Instruction.discard());
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < statements.length; i++) {
|
||||
var stm = statements[i];
|
||||
|
||||
if (stm instanceof FunctionStatement) continue;
|
||||
if (i != statements.length - 1) stm.compileNoPollution(target, scope, true);
|
||||
else stm.compileWithPollution(target, scope);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement optimize() {
|
||||
var res = new ArrayList<Statement>();
|
||||
|
||||
for (var i = 0; i < statements.length; i++) {
|
||||
var stm = statements[i].optimize();
|
||||
if (i < statements.length - 1 && stm.pure()) continue;
|
||||
res.add(stm);
|
||||
if (
|
||||
stm instanceof ContinueStatement ||
|
||||
stm instanceof ReturnStatement ||
|
||||
stm instanceof ThrowStatement ||
|
||||
stm instanceof ContinueStatement
|
||||
) break;
|
||||
}
|
||||
|
||||
if (res.size() == 1) return res.get(0);
|
||||
else return new CompoundStatement(loc(), res.toArray(Statement[]::new));
|
||||
}
|
||||
|
||||
public CompoundStatement(Location loc, Statement... statements) {
|
||||
super(loc);
|
||||
this.statements = statements;
|
||||
}
|
||||
}
|
||||
33
src/me/topchetoeu/jscript/compilation/DiscardStatement.java
Normal file
33
src/me/topchetoeu/jscript/compilation/DiscardStatement.java
Normal file
@@ -0,0 +1,33 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.values.ConstantStatement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public class DiscardStatement extends Statement {
|
||||
public final Statement value;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
if (value == null) return;
|
||||
value.compile(target, scope);
|
||||
if (value.pollutesStack()) target.add(Instruction.discard());
|
||||
}
|
||||
@Override
|
||||
public Statement optimize() {
|
||||
if (value == null) return this;
|
||||
var val = value.optimize();
|
||||
if (val.pure()) return new ConstantStatement(loc(), null);
|
||||
else return new DiscardStatement(loc(), val);
|
||||
}
|
||||
|
||||
public DiscardStatement(Location loc, Statement val) {
|
||||
super(loc);
|
||||
this.value = val;
|
||||
}
|
||||
}
|
||||
285
src/me/topchetoeu/jscript/compilation/Instruction.java
Normal file
285
src/me/topchetoeu/jscript/compilation/Instruction.java
Normal file
@@ -0,0 +1,285 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.exceptions.SyntaxException;
|
||||
|
||||
public class Instruction {
|
||||
public static enum Type {
|
||||
RETURN,
|
||||
SIGNAL,
|
||||
THROW,
|
||||
THROW_SYNTAX,
|
||||
DELETE,
|
||||
TRY,
|
||||
NOP,
|
||||
|
||||
CALL,
|
||||
CALL_NEW,
|
||||
JMP_IF,
|
||||
JMP_IFN,
|
||||
JMP,
|
||||
|
||||
LOAD_VALUE,
|
||||
|
||||
LOAD_VAR,
|
||||
LOAD_MEMBER,
|
||||
LOAD_VAL_MEMBER,
|
||||
LOAD_GLOB,
|
||||
|
||||
LOAD_FUNC,
|
||||
LOAD_ARR,
|
||||
LOAD_OBJ,
|
||||
STORE_SELF_FUNC,
|
||||
LOAD_REGEX,
|
||||
|
||||
DUP,
|
||||
|
||||
STORE_VAR,
|
||||
STORE_MEMBER,
|
||||
DISCARD,
|
||||
|
||||
MAKE_VAR,
|
||||
DEF_PROP,
|
||||
KEYS,
|
||||
|
||||
TYPEOF,
|
||||
INSTANCEOF(true),
|
||||
IN(true),
|
||||
|
||||
MULTIPLY(true),
|
||||
DIVIDE(true),
|
||||
MODULO(true),
|
||||
ADD(true),
|
||||
SUBTRACT(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),
|
||||
|
||||
AND(true),
|
||||
OR(true),
|
||||
XOR(true),
|
||||
|
||||
NEG(true),
|
||||
POS(true),
|
||||
NOT(true),
|
||||
INVERSE(true);
|
||||
|
||||
final boolean isOperation;
|
||||
|
||||
private Type(boolean isOperation) {
|
||||
this.isOperation = isOperation;
|
||||
}
|
||||
private Type() {
|
||||
this(false);
|
||||
}
|
||||
}
|
||||
|
||||
public final Type type;
|
||||
public final Object[] params;
|
||||
public Location location;
|
||||
public boolean debugged;
|
||||
|
||||
public Instruction locate(Location loc) {
|
||||
this.location = loc;
|
||||
return this;
|
||||
}
|
||||
public Instruction setDebug(boolean debug) {
|
||||
debugged = debug;
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T get(int i) {
|
||||
if (i >= params.length || i < 0) return null;
|
||||
return (T)params[i];
|
||||
}
|
||||
public boolean match(Object ...args) {
|
||||
if (args.length != params.length) return false;
|
||||
for (int i = 0; i < args.length; i++) {
|
||||
var a = params[i];
|
||||
var b = args[i];
|
||||
if (a == null || b == null) {
|
||||
if (!(a == null && b == null)) return false;
|
||||
}
|
||||
if (!a.equals(b)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
public boolean is(int i, Object arg) {
|
||||
if (params.length <= i) return false;
|
||||
return params[i].equals(arg);
|
||||
}
|
||||
|
||||
private Instruction(Location location, Type type, Object... params) {
|
||||
this.location = location;
|
||||
this.type = type;
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
public static Instruction tryInstr(boolean hasCatch, boolean hasFinally) {
|
||||
return new Instruction(null, Type.TRY, hasCatch, hasFinally);
|
||||
}
|
||||
public static Instruction throwInstr() {
|
||||
return new Instruction(null, Type.THROW);
|
||||
}
|
||||
public static Instruction throwSyntax(SyntaxException err) {
|
||||
return new Instruction(null, Type.THROW_SYNTAX, err.getMessage());
|
||||
}
|
||||
public static Instruction delete() {
|
||||
return new Instruction(null, Type.DELETE);
|
||||
}
|
||||
public static Instruction ret() {
|
||||
return new Instruction(null, Type.RETURN);
|
||||
}
|
||||
public static Instruction debug() {
|
||||
return new Instruction(null, Type.NOP, "debug");
|
||||
}
|
||||
public static Instruction debugVarNames(String[] names) {
|
||||
var args = new Object[names.length + 1];
|
||||
args[0] = "dbg_vars";
|
||||
|
||||
System.arraycopy(names, 0, args, 1, names.length);
|
||||
|
||||
return new Instruction(null, Type.NOP, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* ATTENTION: Usage outside of try/catch is broken af
|
||||
*/
|
||||
public static Instruction signal(String name) {
|
||||
return new Instruction(null, Type.SIGNAL, name);
|
||||
}
|
||||
public static Instruction nop(Object ...params) {
|
||||
for (var param : params) {
|
||||
if (param instanceof String) continue;
|
||||
if (param instanceof Boolean) continue;
|
||||
if (param instanceof Double) continue;
|
||||
if (param instanceof Integer) continue;
|
||||
if (param == null) continue;
|
||||
|
||||
throw new RuntimeException("NOP params may contain only strings, booleans, doubles, integers and nulls.");
|
||||
}
|
||||
return new Instruction(null, Type.NOP, params);
|
||||
}
|
||||
|
||||
public static Instruction call(int argn) {
|
||||
return new Instruction(null, Type.CALL, argn);
|
||||
}
|
||||
public static Instruction callNew(int argn) {
|
||||
return new Instruction(null, Type.CALL_NEW, argn);
|
||||
}
|
||||
public static Instruction jmp(int offset) {
|
||||
return new Instruction(null, Type.JMP, offset);
|
||||
}
|
||||
public static Instruction jmpIf(int offset) {
|
||||
return new Instruction(null, Type.JMP_IF, offset);
|
||||
}
|
||||
public static Instruction jmpIfNot(int offset) {
|
||||
return new Instruction(null, Type.JMP_IFN, offset);
|
||||
}
|
||||
|
||||
public static Instruction loadValue(Object val) {
|
||||
return new Instruction(null, Type.LOAD_VALUE, val);
|
||||
}
|
||||
|
||||
public static Instruction makeVar(String name) {
|
||||
return new Instruction(null, Type.MAKE_VAR, name);
|
||||
}
|
||||
public static Instruction loadVar(Object i) {
|
||||
return new Instruction(null, Type.LOAD_VAR, i);
|
||||
}
|
||||
public static Instruction loadGlob() {
|
||||
return new Instruction(null, Type.LOAD_GLOB);
|
||||
}
|
||||
public static Instruction loadMember() {
|
||||
return new Instruction(null, Type.LOAD_MEMBER);
|
||||
}
|
||||
public static Instruction loadMember(Object key) {
|
||||
if (key instanceof Number) key = ((Number)key).doubleValue();
|
||||
return new Instruction(null, Type.LOAD_VAL_MEMBER, key);
|
||||
}
|
||||
|
||||
public static Instruction loadRegex(String pattern, String flags) {
|
||||
return new Instruction(null, Type.LOAD_REGEX, pattern, flags);
|
||||
}
|
||||
public static Instruction loadFunc(int instrN, int varN, int len, int[] captures) {
|
||||
var args = new Object[3 + captures.length];
|
||||
args[0] = instrN;
|
||||
args[1] = varN;
|
||||
args[2] = len;
|
||||
for (var i = 0; i < captures.length; i++) args[i + 3] = captures[i];
|
||||
return new Instruction(null, Type.LOAD_FUNC, args);
|
||||
}
|
||||
public static Instruction loadObj() {
|
||||
return new Instruction(null, Type.LOAD_OBJ);
|
||||
}
|
||||
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(int count, int offset) {
|
||||
return new Instruction(null, Type.DUP, count, offset);
|
||||
}
|
||||
|
||||
public static Instruction storeSelfFunc(int i) {
|
||||
return new Instruction(null, Type.STORE_SELF_FUNC, i);
|
||||
}
|
||||
public static Instruction storeVar(Object i) {
|
||||
return new Instruction(null, Type.STORE_VAR, i, false);
|
||||
}
|
||||
public static Instruction storeVar(Object i, boolean keep) {
|
||||
return new Instruction(null, Type.STORE_VAR, i, keep);
|
||||
}
|
||||
public static Instruction storeMember() {
|
||||
return new Instruction(null, Type.STORE_MEMBER, false);
|
||||
}
|
||||
public static Instruction storeMember(boolean keep) {
|
||||
return new Instruction(null, Type.STORE_MEMBER, keep);
|
||||
}
|
||||
public static Instruction discard() {
|
||||
return new Instruction(null, Type.DISCARD);
|
||||
}
|
||||
|
||||
public static Instruction typeof() {
|
||||
return new Instruction(null, Type.TYPEOF);
|
||||
}
|
||||
public static Instruction typeof(String varName) {
|
||||
return new Instruction(null, Type.TYPEOF, varName);
|
||||
}
|
||||
|
||||
public static Instruction keys() {
|
||||
return new Instruction(null, Type.KEYS);
|
||||
}
|
||||
|
||||
public static Instruction defProp() {
|
||||
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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
var res = type.toString();
|
||||
|
||||
for (int i = 0; i < params.length; i++) {
|
||||
res += " " + params[i];
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
42
src/me/topchetoeu/jscript/compilation/Statement.java
Normal file
42
src/me/topchetoeu/jscript/compilation/Statement.java
Normal file
@@ -0,0 +1,42 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public abstract class Statement {
|
||||
private Location _loc;
|
||||
|
||||
public abstract boolean pollutesStack();
|
||||
public boolean pure() { return false; }
|
||||
public abstract void compile(List<Instruction> target, ScopeRecord scope);
|
||||
public void declare(ScopeRecord varsScope) { }
|
||||
public Statement optimize() { return this; }
|
||||
|
||||
public void compileNoPollution(List<Instruction> target, ScopeRecord scope, boolean debug) {
|
||||
int start = target.size();
|
||||
compile(target, scope);
|
||||
if (debug && target.size() != start) target.get(start).setDebug(true);
|
||||
if (pollutesStack()) target.add(Instruction.discard().locate(loc()));
|
||||
}
|
||||
public void compileWithPollution(List<Instruction> target, ScopeRecord scope, boolean debug) {
|
||||
int start = target.size();
|
||||
compile(target, scope);
|
||||
if (debug && target.size() != start) target.get(start).setDebug(true);
|
||||
if (!pollutesStack()) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
}
|
||||
public void compileNoPollution(List<Instruction> target, ScopeRecord scope) {
|
||||
compileNoPollution(target, scope, false);
|
||||
}
|
||||
public void compileWithPollution(List<Instruction> target, ScopeRecord scope) {
|
||||
compileWithPollution(target, scope, false);
|
||||
}
|
||||
|
||||
public Location loc() { return _loc; }
|
||||
public void setLoc(Location loc) { _loc = loc; }
|
||||
|
||||
protected Statement(Location loc) {
|
||||
this._loc = loc;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.values.FunctionStatement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public class VariableDeclareStatement extends Statement {
|
||||
public static record Pair(String name, Statement value) {}
|
||||
|
||||
public final List<Pair> values;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
@Override
|
||||
public void declare(ScopeRecord varsScope) {
|
||||
for (var key : values) {
|
||||
varsScope.define(key.name());
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
for (var entry : values) {
|
||||
if (entry.name() == null) continue;
|
||||
var key = scope.getKey(entry.name());
|
||||
if (key instanceof String) target.add(Instruction.makeVar((String)key).locate(loc()));
|
||||
|
||||
if (entry.value() instanceof FunctionStatement) {
|
||||
((FunctionStatement)entry.value()).compile(target, scope, entry.name(), false);
|
||||
target.add(Instruction.storeVar(key).locate(loc()));
|
||||
}
|
||||
else if (entry.value() != null) {
|
||||
entry.value().compileWithPollution(target, scope);
|
||||
target.add(Instruction.storeVar(key).locate(loc()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public VariableDeclareStatement(Location loc, List<Pair> values) {
|
||||
super(loc);
|
||||
this.values = values;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
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.engine.scope.ScopeRecord;
|
||||
|
||||
public class BreakStatement extends Statement {
|
||||
public final String label;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
target.add(Instruction.nop("break", label).locate(loc()));
|
||||
}
|
||||
|
||||
public BreakStatement(Location loc, String label) {
|
||||
super(loc);
|
||||
this.label = label;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
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.engine.scope.ScopeRecord;
|
||||
|
||||
public class ContinueStatement extends Statement {
|
||||
public final String label;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
target.add(Instruction.nop("cont", label).locate(loc()));
|
||||
}
|
||||
|
||||
public ContinueStatement(Location loc, String label) {
|
||||
super(loc);
|
||||
this.label = label;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
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.engine.scope.ScopeRecord;
|
||||
|
||||
public class DebugStatement extends Statement {
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
target.add(Instruction.debug().locate(loc()));
|
||||
}
|
||||
|
||||
public DebugStatement(Location loc) {
|
||||
super(loc);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
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.engine.scope.ScopeRecord;
|
||||
|
||||
public class DeleteStatement extends Statement {
|
||||
public final Statement key;
|
||||
public final Statement value;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
value.compile(target, scope);
|
||||
key.compile(target, scope);
|
||||
target.add(Instruction.delete().locate(loc()));
|
||||
}
|
||||
|
||||
public DeleteStatement(Location loc, Statement key, Statement value) {
|
||||
super(loc);
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompoundStatement;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.compilation.values.ConstantStatement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
|
||||
public class DoWhileStatement extends Statement {
|
||||
public final Statement condition, body;
|
||||
public final String label;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord globScope) {
|
||||
body.declare(globScope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
if (condition instanceof ConstantStatement) {
|
||||
int start = target.size();
|
||||
body.compileNoPollution(target, scope);
|
||||
int end = target.size();
|
||||
if (Values.toBoolean(((ConstantStatement)condition).value)) {
|
||||
WhileStatement.replaceBreaks(target, label, start, end, end + 1, end + 1);
|
||||
}
|
||||
else {
|
||||
target.add(Instruction.jmp(start - end).locate(loc()));
|
||||
WhileStatement.replaceBreaks(target, label, start, end, start, end + 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
int start = target.size();
|
||||
body.compileNoPollution(target, scope, true);
|
||||
int mid = target.size();
|
||||
condition.compileWithPollution(target, scope);
|
||||
int end = target.size();
|
||||
|
||||
WhileStatement.replaceBreaks(target, label, start, mid - 1, mid, end + 1);
|
||||
target.add(Instruction.jmpIf(start - end).locate(loc()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement optimize() {
|
||||
var cond = condition.optimize();
|
||||
var b = body.optimize();
|
||||
|
||||
if (b instanceof CompoundStatement) {
|
||||
var comp = (CompoundStatement)b;
|
||||
if (comp.statements.length > 0) {
|
||||
var last = comp.statements[comp.statements.length - 1];
|
||||
if (last instanceof ContinueStatement) comp.statements[comp.statements.length - 1] = new CompoundStatement(loc());
|
||||
if (last instanceof BreakStatement) {
|
||||
comp.statements[comp.statements.length - 1] = new CompoundStatement(loc());
|
||||
return new CompoundStatement(loc());
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (b instanceof ContinueStatement) {
|
||||
b = new CompoundStatement(loc());
|
||||
}
|
||||
else if (b instanceof BreakStatement) return new CompoundStatement(loc());
|
||||
|
||||
if (b.pure()) return new DoWhileStatement(loc(), label, cond, new CompoundStatement(loc()));
|
||||
else return new DoWhileStatement(loc(), label, cond, b);
|
||||
}
|
||||
|
||||
public DoWhileStatement(Location loc, String label, Statement condition, Statement body) {
|
||||
super(loc);
|
||||
this.label = label;
|
||||
this.condition = condition;
|
||||
this.body = body;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
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.ScopeRecord;
|
||||
|
||||
public class ForInStatement extends Statement {
|
||||
public final String varName;
|
||||
public final boolean isDeclaration;
|
||||
public final Statement varValue, object, body;
|
||||
public final String label;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord globScope) {
|
||||
body.declare(globScope);
|
||||
if (isDeclaration) globScope.define(varName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
var key = scope.getKey(varName);
|
||||
if (key instanceof String) target.add(Instruction.makeVar((String)key));
|
||||
|
||||
if (varValue != null) {
|
||||
varValue.compileWithPollution(target, scope);
|
||||
target.add(Instruction.storeVar(scope.getKey(varName)));
|
||||
}
|
||||
|
||||
object.compileWithPollution(target, scope);
|
||||
target.add(Instruction.keys());
|
||||
|
||||
int start = target.size();
|
||||
target.add(Instruction.dup(1));
|
||||
target.add(Instruction.loadMember("length"));
|
||||
target.add(Instruction.loadValue(0));
|
||||
target.add(Instruction.operation(Type.LESS_EQUALS));
|
||||
int mid = target.size();
|
||||
target.add(Instruction.nop());
|
||||
|
||||
target.add(Instruction.dup(2));
|
||||
target.add(Instruction.loadMember("length"));
|
||||
target.add(Instruction.loadValue(1));
|
||||
target.add(Instruction.operation(Type.SUBTRACT));
|
||||
target.add(Instruction.dup(1, 2));
|
||||
target.add(Instruction.loadValue("length"));
|
||||
target.add(Instruction.dup(1, 2));
|
||||
target.add(Instruction.storeMember());
|
||||
target.add(Instruction.loadMember());
|
||||
target.add(Instruction.storeVar(key));
|
||||
|
||||
for (var i = start; i < target.size(); i++) target.get(i).locate(loc());
|
||||
|
||||
body.compileNoPollution(target, scope, true);
|
||||
|
||||
|
||||
int end = target.size();
|
||||
|
||||
WhileStatement.replaceBreaks(target, label, mid + 1, end, start, end + 1);
|
||||
|
||||
target.add(Instruction.jmp(start - end).locate(loc()));
|
||||
target.add(Instruction.discard().locate(loc()));
|
||||
target.set(mid, Instruction.jmpIf(end - mid + 1).locate(loc()));
|
||||
}
|
||||
|
||||
public ForInStatement(Location loc, String label, boolean isDecl, String varName, Statement varValue, Statement object, Statement body) {
|
||||
super(loc);
|
||||
this.label = label;
|
||||
this.isDeclaration = isDecl;
|
||||
this.varName = varName;
|
||||
this.varValue = varValue;
|
||||
this.object = object;
|
||||
this.body = body;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.compilation.CompoundStatement;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.values.ConstantStatement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
|
||||
public class ForStatement extends Statement {
|
||||
public final Statement declaration, assignment, condition, body;
|
||||
public final String label;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord globScope) {
|
||||
declaration.declare(globScope);
|
||||
body.declare(globScope);
|
||||
}
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
declaration.compile(target, scope);
|
||||
|
||||
if (condition instanceof ConstantStatement) {
|
||||
if (Values.toBoolean(((ConstantStatement)condition).value)) {
|
||||
int start = target.size();
|
||||
body.compileNoPollution(target, scope);
|
||||
int mid = target.size();
|
||||
assignment.compileNoPollution(target, scope, true);
|
||||
int end = target.size();
|
||||
WhileStatement.replaceBreaks(target, label, start, mid, mid, end + 1);
|
||||
target.add(Instruction.jmp(start - target.size()).locate(loc()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int start = target.size();
|
||||
condition.compileWithPollution(target, scope);
|
||||
int mid = target.size();
|
||||
target.add(Instruction.nop());
|
||||
body.compileNoPollution(target, scope);
|
||||
int beforeAssign = target.size();
|
||||
assignment.compile(target, scope);
|
||||
int end = target.size();
|
||||
|
||||
WhileStatement.replaceBreaks(target, label, mid + 1, end, beforeAssign, end + 1);
|
||||
|
||||
target.add(Instruction.jmp(start - end).locate(loc()));
|
||||
target.set(mid, Instruction.jmpIfNot(end - mid + 1).locate(loc()));
|
||||
}
|
||||
@Override
|
||||
public Statement optimize() {
|
||||
var decl = declaration.optimize();
|
||||
var asgn = assignment.optimize();
|
||||
var cond = condition.optimize();
|
||||
var b = body.optimize();
|
||||
|
||||
if (asgn.pure()) {
|
||||
if (decl.pure()) return new WhileStatement(loc(), label, cond, b).optimize();
|
||||
else return new CompoundStatement(loc(),
|
||||
decl, new WhileStatement(loc(), label, cond, b)
|
||||
).optimize();
|
||||
}
|
||||
|
||||
else if (b instanceof ContinueStatement) return new CompoundStatement(loc(),
|
||||
decl, new WhileStatement(loc(), label, cond, new CompoundStatement(loc(), b, asgn))
|
||||
);
|
||||
else if (b instanceof BreakStatement) return decl;
|
||||
|
||||
if (b.pure()) return new ForStatement(loc(), label, decl, cond, asgn, new CompoundStatement(null));
|
||||
else return new ForStatement(loc(), label, decl, cond, asgn, b);
|
||||
}
|
||||
|
||||
public ForStatement(Location loc, String label, Statement declaration, Statement condition, Statement assignment, Statement body) {
|
||||
super(loc);
|
||||
this.label = label;
|
||||
this.declaration = declaration;
|
||||
this.condition = condition;
|
||||
this.assignment = assignment;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public static CompoundStatement ofFor(Location loc, String label, Statement declaration, Statement condition, Statement increment, Statement body) {
|
||||
return new CompoundStatement(loc,
|
||||
declaration,
|
||||
new WhileStatement(loc, label, condition, new CompoundStatement(loc,
|
||||
body,
|
||||
increment
|
||||
))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompoundStatement;
|
||||
import me.topchetoeu.jscript.compilation.DiscardStatement;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.compilation.values.ConstantStatement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
|
||||
public class IfStatement extends Statement {
|
||||
public final Statement condition, body, elseBody;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord globScope) {
|
||||
body.declare(globScope);
|
||||
if (elseBody != null) elseBody.declare(globScope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
if (condition instanceof ConstantStatement) {
|
||||
if (Values.not(((ConstantStatement)condition).value)) {
|
||||
if (elseBody != null) elseBody.compileNoPollution(target, scope, true);
|
||||
}
|
||||
else {
|
||||
body.compileNoPollution(target, scope, true);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
condition.compileWithPollution(target, scope);
|
||||
if (elseBody == null) {
|
||||
int i = target.size();
|
||||
target.add(Instruction.nop());
|
||||
body.compileNoPollution(target, scope, true);
|
||||
int endI = target.size();
|
||||
target.set(i, Instruction.jmpIfNot(endI - i).locate(loc()));
|
||||
}
|
||||
else {
|
||||
int start = target.size();
|
||||
target.add(Instruction.nop());
|
||||
body.compileNoPollution(target, scope, true);
|
||||
target.add(Instruction.nop());
|
||||
int mid = target.size();
|
||||
elseBody.compileNoPollution(target, scope, true);
|
||||
int end = target.size();
|
||||
|
||||
target.set(start, Instruction.jmpIfNot(mid - start).locate(loc()));
|
||||
target.set(mid - 1, Instruction.jmp(end - mid + 1).locate(loc()));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement optimize() {
|
||||
var cond = condition.optimize();
|
||||
var b = body.optimize();
|
||||
var e = elseBody == null ? null : elseBody.optimize();
|
||||
|
||||
if (b.pure()) b = new CompoundStatement(null);
|
||||
if (e != null && e.pure()) e = null;
|
||||
|
||||
if (b.pure() && e == null) return new DiscardStatement(loc(), cond).optimize();
|
||||
else return new IfStatement(loc(), cond, b, e);
|
||||
}
|
||||
|
||||
public IfStatement(Location loc, Statement condition, Statement body, Statement elseBody) {
|
||||
super(loc);
|
||||
this.condition = condition;
|
||||
this.body = body;
|
||||
this.elseBody = elseBody;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
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.engine.scope.ScopeRecord;
|
||||
|
||||
public class ReturnStatement extends Statement {
|
||||
public final Statement value;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
if (value == null) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
else value.compileWithPollution(target, scope);
|
||||
target.add(Instruction.ret().locate(loc()));
|
||||
}
|
||||
|
||||
public ReturnStatement(Location loc, Statement value) {
|
||||
super(loc);
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import java.util.HashMap;
|
||||
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.ScopeRecord;
|
||||
|
||||
public class SwitchStatement extends Statement {
|
||||
public static record SwitchCase(Statement value, int statementI) {}
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
|
||||
public final Statement value;
|
||||
public final SwitchCase[] cases;
|
||||
public final Statement[] body;
|
||||
public final int defaultI;
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord varsScope) {
|
||||
for (var stm : body) stm.declare(varsScope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
var caseMap = new HashMap<Integer, Integer>();
|
||||
var stmIndexMap = new HashMap<Integer, Integer>();
|
||||
|
||||
value.compile(target, scope);
|
||||
|
||||
for (var ccase : cases) {
|
||||
target.add(Instruction.dup(1).locate(loc()));
|
||||
ccase.value.compileWithPollution(target, scope);
|
||||
target.add(Instruction.operation(Type.EQUALS).locate(loc()));
|
||||
caseMap.put(target.size(), ccase.statementI);
|
||||
target.add(Instruction.nop());
|
||||
}
|
||||
|
||||
int start = target.size();
|
||||
|
||||
target.add(Instruction.nop());
|
||||
|
||||
for (var stm : body) {
|
||||
stmIndexMap.put(stmIndexMap.size(), target.size());
|
||||
stm.compileNoPollution(target, scope, true);
|
||||
}
|
||||
|
||||
if (defaultI < 0 || defaultI >= body.length) target.set(start, Instruction.jmp(target.size() - start).locate(loc()));
|
||||
else target.set(start, Instruction.jmp(stmIndexMap.get(defaultI) - start)).locate(loc());
|
||||
|
||||
for (int i = start; i < target.size(); i++) {
|
||||
var instr = target.get(i);
|
||||
if (instr.type == Type.NOP && instr.is(0, "break") && instr.get(1) == null) {
|
||||
target.set(i, Instruction.jmp(target.size() - i).locate(instr.location));
|
||||
}
|
||||
if (instr.type == Type.NOP && instr.is(0, "try_break") && instr.get(1) == null) {
|
||||
target.set(i, Instruction.signal("jmp_" + (target.size() - (Integer)instr.get(2))).locate(instr.location));
|
||||
}
|
||||
}
|
||||
for (var el : caseMap.entrySet()) {
|
||||
var loc = target.get(el.getKey()).location;
|
||||
var i = stmIndexMap.get(el.getValue());
|
||||
if (i == null) i = target.size();
|
||||
target.set(el.getKey(), Instruction.jmpIf(i - el.getKey()).locate(loc).setDebug(true));
|
||||
}
|
||||
|
||||
target.add(Instruction.discard().locate(loc()));
|
||||
}
|
||||
|
||||
public SwitchStatement(Location loc, Statement value, int defaultI, SwitchCase[] cases, Statement[] body) {
|
||||
super(loc);
|
||||
this.value = value;
|
||||
this.defaultI = defaultI;
|
||||
this.cases = cases;
|
||||
this.body = body;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
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.engine.scope.ScopeRecord;
|
||||
|
||||
public class ThrowStatement extends Statement {
|
||||
public final Statement value;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
value.compileWithPollution(target, scope);
|
||||
target.add(Instruction.throwInstr().locate(loc()));
|
||||
}
|
||||
|
||||
public ThrowStatement(Location loc, Statement value) {
|
||||
super(loc);
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
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.ScopeRecord;
|
||||
|
||||
public class TryStatement extends Statement {
|
||||
public final Statement tryBody;
|
||||
public final Statement catchBody;
|
||||
public final Statement finallyBody;
|
||||
public final String name;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord globScope) {
|
||||
tryBody.declare(globScope);
|
||||
if (catchBody != null) catchBody.declare(globScope);
|
||||
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();
|
||||
|
||||
compileBody(target, scope, tryBody, null);
|
||||
|
||||
if (catchBody != null) {
|
||||
compileBody(target, scope, catchBody, name);
|
||||
}
|
||||
if (finallyBody != null) {
|
||||
compileBody(target, scope, finallyBody, null);
|
||||
}
|
||||
|
||||
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()));
|
||||
}
|
||||
|
||||
public TryStatement(Location loc, Statement tryBody, Statement catchBody, Statement finallyBody, String name) {
|
||||
super(loc);
|
||||
this.tryBody = tryBody;
|
||||
this.catchBody = catchBody;
|
||||
this.finallyBody = finallyBody;
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.compilation.CompoundStatement;
|
||||
import me.topchetoeu.jscript.compilation.DiscardStatement;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Instruction.Type;
|
||||
import me.topchetoeu.jscript.compilation.values.ConstantStatement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
|
||||
public class WhileStatement extends Statement {
|
||||
public final Statement condition, body;
|
||||
public final String label;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord globScope) {
|
||||
body.declare(globScope);
|
||||
}
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
if (condition instanceof ConstantStatement) {
|
||||
if (Values.toBoolean(((ConstantStatement)condition).value)) {
|
||||
int start = target.size();
|
||||
body.compileNoPollution(target, scope);
|
||||
int end = target.size();
|
||||
replaceBreaks(target, label, start, end, start, end + 1);
|
||||
target.add(Instruction.jmp(start - target.size()).locate(loc()));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
int start = target.size();
|
||||
condition.compileWithPollution(target, scope);
|
||||
int mid = target.size();
|
||||
target.add(Instruction.nop());
|
||||
body.compileNoPollution(target, scope);
|
||||
|
||||
int end = target.size();
|
||||
|
||||
replaceBreaks(target, label, mid + 1, end, start, end + 1);
|
||||
|
||||
target.add(Instruction.jmp(start - end).locate(loc()));
|
||||
target.set(mid, Instruction.jmpIfNot(end - mid + 1).locate(loc()));
|
||||
}
|
||||
@Override
|
||||
public Statement optimize() {
|
||||
var cond = condition.optimize();
|
||||
var b = body.optimize();
|
||||
|
||||
if (b instanceof ContinueStatement) {
|
||||
b = new CompoundStatement(loc());
|
||||
}
|
||||
else if (b instanceof BreakStatement) return new DiscardStatement(loc(), cond).optimize();
|
||||
|
||||
if (b.pure()) return new WhileStatement(loc(), label, cond, new CompoundStatement(null));
|
||||
else return new WhileStatement(loc(), label, cond, b);
|
||||
}
|
||||
|
||||
public WhileStatement(Location loc, String label, Statement condition, Statement body) {
|
||||
super(loc);
|
||||
this.label = label;
|
||||
this.condition = condition;
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public static void replaceBreaks(List<Instruction> target, String label, int start, int end, int continuePoint, int breakPoint) {
|
||||
for (int i = start; i < end; i++) {
|
||||
var instr = target.get(i);
|
||||
if (instr.type == Type.NOP && instr.is(0, "cont") && (instr.get(1) == null || instr.is(1, label))) {
|
||||
target.set(i, Instruction.jmp(continuePoint - i));
|
||||
target.get(i).location = instr.location;
|
||||
}
|
||||
if (instr.type == Type.NOP && instr.is(0, "break") && (instr.get(1) == null || instr.is(1, label))) {
|
||||
target.set(i, Instruction.jmp(breakPoint - i));
|
||||
target.get(i).location = instr.location;
|
||||
}
|
||||
if (instr.type == Type.NOP && instr.is(0, "try_cont") && (instr.get(1) == null || instr.is(1, label))) {
|
||||
target.set(i, Instruction.signal("jmp_" + (continuePoint - (Integer)instr.get(2))));
|
||||
target.get(i).location = instr.location;
|
||||
}
|
||||
if (instr.type == Type.NOP && instr.is(0, "try_break") && (instr.get(1) == null || instr.is(1, label))) {
|
||||
target.set(i, Instruction.signal("jmp_" + (breakPoint - (Integer)instr.get(2))));
|
||||
target.get(i).location = instr.location;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// public static CompoundStatement ofFor(Location loc, String label, Statement declaration, Statement condition, Statement increment, Statement body) {
|
||||
// return new CompoundStatement(loc,
|
||||
// declaration,
|
||||
// new WhileStatement(loc, label, condition, new CompoundStatement(loc,
|
||||
// body,
|
||||
// increment
|
||||
// ))
|
||||
// );
|
||||
// }
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
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.engine.scope.ScopeRecord;
|
||||
|
||||
public class ArrayStatement extends Statement {
|
||||
public final Statement[] statements;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
@Override
|
||||
public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
target.add(Instruction.loadArr(statements.length).locate(loc()));
|
||||
var i = 0;
|
||||
for (var el : statements) {
|
||||
if (el != null) {
|
||||
target.add(Instruction.dup(1).locate(loc()));
|
||||
target.add(Instruction.loadValue(i).locate(loc()));
|
||||
el.compileWithPollution(target, scope);
|
||||
target.add(Instruction.storeMember().locate(loc()));
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
public ArrayStatement(Location loc, Statement[] statements) {
|
||||
super(loc);
|
||||
this.statements = statements;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
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.engine.scope.ScopeRecord;
|
||||
|
||||
public class CallStatement extends Statement {
|
||||
public final Statement func;
|
||||
public final Statement[] args;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
if (func instanceof IndexStatement) {
|
||||
((IndexStatement)func).compile(target, scope, true);
|
||||
}
|
||||
else {
|
||||
target.add(Instruction.loadValue(null).locate(loc()));
|
||||
func.compileWithPollution(target, scope);
|
||||
}
|
||||
|
||||
for (var arg : args) {
|
||||
arg.compileWithPollution(target, scope);
|
||||
}
|
||||
|
||||
target.add(Instruction.call(args.length).locate(loc()).setDebug(true));
|
||||
}
|
||||
|
||||
public CallStatement(Location loc, Statement func, Statement... args) {
|
||||
super(loc);
|
||||
this.func = func;
|
||||
this.args = args;
|
||||
}
|
||||
public CallStatement(Location loc, Statement obj, Object key, Statement... args) {
|
||||
super(loc);
|
||||
this.func = new IndexStatement(loc, obj, new ConstantStatement(loc, key));
|
||||
this.args = args;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
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.scope.ScopeRecord;
|
||||
|
||||
public class ChangeStatement extends Statement {
|
||||
public final AssignableStatement value;
|
||||
public final double addAmount;
|
||||
public final boolean postfix;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
|
||||
@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()));
|
||||
}
|
||||
}
|
||||
|
||||
public ChangeStatement(Location loc, AssignableStatement value, double addAmount, boolean postfix) {
|
||||
super(loc);
|
||||
this.value = value;
|
||||
this.addAmount = addAmount;
|
||||
this.postfix = postfix;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
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.engine.scope.ScopeRecord;
|
||||
|
||||
public class CommaStatement extends Statement {
|
||||
public final Statement first;
|
||||
public final Statement second;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
@Override
|
||||
public boolean pure() { return first.pure() && second.pure(); }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
first.compileNoPollution(target, scope);
|
||||
second.compileWithPollution(target, scope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement optimize() {
|
||||
var f = first.optimize();
|
||||
var s = second.optimize();
|
||||
if (f.pure()) return s;
|
||||
else return new CommaStatement(loc(), f, s);
|
||||
}
|
||||
|
||||
public CommaStatement(Location loc, Statement first, Statement second) {
|
||||
super(loc);
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
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.engine.scope.ScopeRecord;
|
||||
|
||||
public class ConstantStatement extends Statement {
|
||||
public final Object value;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
@Override
|
||||
public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
target.add(Instruction.loadValue(value).locate(loc()));
|
||||
}
|
||||
|
||||
public ConstantStatement(Location loc, Object val) {
|
||||
super(loc);
|
||||
this.value = val;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompoundStatement;
|
||||
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.ScopeRecord;
|
||||
import me.topchetoeu.jscript.exceptions.SyntaxException;
|
||||
|
||||
public class FunctionStatement extends Statement {
|
||||
public final CompoundStatement body;
|
||||
public final String name;
|
||||
public final String[] args;
|
||||
|
||||
@Override
|
||||
public boolean pure() { return name == null; }
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord scope) {
|
||||
if (name != null) scope.define(name);
|
||||
}
|
||||
|
||||
public static void checkBreakAndCont(List<Instruction> target, int start) {
|
||||
for (int i = start; i < target.size(); i++) {
|
||||
if (target.get(i).type == Type.NOP) {
|
||||
if (target.get(i).is(0, "break") || target.get(i).is(0, "try_break")) {
|
||||
throw new SyntaxException(target.get(i).location, "Break was placed outside a loop.");
|
||||
}
|
||||
if (target.get(i).is(0, "cont") || target.get(i).is(0, "try_cont")) {
|
||||
throw new SyntaxException(target.get(i).location, "Continue was placed outside a loop.");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void compile(List<Instruction> target, ScopeRecord scope, String name, boolean isStatement) {
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
for (var j = 0; j < i; j++) {
|
||||
if (args[i].equals(args[j])){
|
||||
target.add(Instruction.throwSyntax(new SyntaxException(loc(), "Duplicate parameter '" + args[i] + "'.")));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
var subscope = scope.child();
|
||||
|
||||
int start = target.size();
|
||||
|
||||
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()));
|
||||
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
target.add(Instruction.loadMember(i).locate(loc()));
|
||||
target.add(Instruction.storeVar(subscope.define(args[i])).locate(loc()));
|
||||
}
|
||||
}
|
||||
|
||||
if (!isStatement && this.name != null) {
|
||||
target.add(Instruction.storeSelfFunc((int)subscope.define(this.name)));
|
||||
}
|
||||
|
||||
body.declare(subscope);
|
||||
target.add(Instruction.debugVarNames(subscope.locals()));
|
||||
body.compile(target, subscope);
|
||||
|
||||
checkBreakAndCont(target, start);
|
||||
|
||||
if (!(body instanceof CompoundStatement)) target.add(Instruction.ret().locate(loc()));
|
||||
|
||||
target.set(start, Instruction.loadFunc(target.size() - start, subscope.localsCount(), args.length, subscope.getCaptures()).locate(loc()));
|
||||
|
||||
if (name == null) name = this.name;
|
||||
|
||||
if (name != null) {
|
||||
target.add(Instruction.dup(1).locate(loc()));
|
||||
target.add(Instruction.loadValue("name").locate(loc()));
|
||||
target.add(Instruction.loadValue(name).locate(loc()));
|
||||
target.add(Instruction.storeMember().locate(loc()));
|
||||
}
|
||||
|
||||
if (this.name != null && isStatement) {
|
||||
var key = scope.getKey(this.name);
|
||||
|
||||
if (key instanceof String) target.add(Instruction.makeVar((String)key).locate(loc()));
|
||||
target.add(Instruction.storeVar(scope.getKey(this.name), true).locate(loc()));
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
compile(target, scope, null, false);
|
||||
}
|
||||
|
||||
public FunctionStatement(Location loc, String name, String[] args, CompoundStatement body) {
|
||||
super(loc);
|
||||
this.name = name;
|
||||
|
||||
this.args = args;
|
||||
this.body = body;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public class GlobalThisStatement extends Statement {
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
@Override
|
||||
public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
target.add(Instruction.loadGlob().locate(loc()));
|
||||
}
|
||||
|
||||
public GlobalThisStatement(Location loc) {
|
||||
super(loc);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
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.ScopeRecord;
|
||||
|
||||
public class IndexAssignStatement extends Statement {
|
||||
public final Statement object;
|
||||
public final Statement index;
|
||||
public final Statement value;
|
||||
public final Type operation;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
int start = 0;
|
||||
if (operation != null) {
|
||||
object.compileWithPollution(target, scope);
|
||||
index.compileWithPollution(target, scope);
|
||||
|
||||
target.add(Instruction.dup(1, 1).locate(loc()));
|
||||
target.add(Instruction.dup(1, 1).locate(loc()));
|
||||
target.add(Instruction.loadMember().locate(loc()));
|
||||
value.compileWithPollution(target, scope);
|
||||
target.add(Instruction.operation(operation).locate(loc()));
|
||||
|
||||
target.add(Instruction.storeMember(true).locate(loc()));
|
||||
}
|
||||
else {
|
||||
object.compileWithPollution(target, scope);
|
||||
index.compileWithPollution(target, scope);
|
||||
value.compileWithPollution(target, scope);
|
||||
target.add(Instruction.storeMember(true).locate(loc()));
|
||||
}
|
||||
target.get(start).setDebug(true);
|
||||
}
|
||||
|
||||
public IndexAssignStatement(Location loc, Statement object, Statement index, Statement value, Type operation) {
|
||||
super(loc);
|
||||
this.object = object;
|
||||
this.index = index;
|
||||
this.value = value;
|
||||
this.operation = operation;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
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.scope.ScopeRecord;
|
||||
|
||||
public class IndexStatement extends AssignableStatement {
|
||||
public final Statement object;
|
||||
public final Statement index;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
@Override
|
||||
public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public Statement toAssign(Statement val, Type 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 (index instanceof ConstantStatement) {
|
||||
target.add(Instruction.loadMember(((ConstantStatement)index).value).locate(loc()));
|
||||
return;
|
||||
}
|
||||
|
||||
index.compileWithPollution(target, scope);
|
||||
target.add(Instruction.loadMember().locate(loc()));
|
||||
target.get(start).setDebug(true);
|
||||
}
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
compile(target, scope, false);
|
||||
}
|
||||
|
||||
public IndexStatement(Location loc, Statement object, Statement index) {
|
||||
super(loc);
|
||||
this.object = object;
|
||||
this.index = index;
|
||||
}
|
||||
public IndexStatement(Location loc, Statement object, Object index) {
|
||||
super(loc);
|
||||
this.object = object;
|
||||
this.index = new ConstantStatement(loc, index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
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.engine.scope.ScopeRecord;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
|
||||
public class LazyAndStatement extends Statement {
|
||||
public final Statement first, second;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
@Override
|
||||
public boolean pure() {
|
||||
return first.pure() && second.pure();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
if (first instanceof ConstantStatement) {
|
||||
if (Values.not(((ConstantStatement)first).value)) {
|
||||
first.compileWithPollution(target, scope);
|
||||
}
|
||||
else second.compileWithPollution(target, scope);
|
||||
return;
|
||||
}
|
||||
|
||||
first.compileWithPollution(target, scope);
|
||||
target.add(Instruction.dup(1).locate(loc()));
|
||||
int start = target.size();
|
||||
target.add(Instruction.nop());
|
||||
target.add(Instruction.discard().locate(loc()));
|
||||
second.compileWithPollution(target, scope);
|
||||
target.set(start, Instruction.jmpIfNot(target.size() - start).locate(loc()));
|
||||
}
|
||||
|
||||
public LazyAndStatement(Location loc, Statement first, Statement second) {
|
||||
super(loc);
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
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.engine.scope.ScopeRecord;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
|
||||
public class LazyOrStatement extends Statement {
|
||||
public final Statement first, second;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
@Override
|
||||
public boolean pure() {
|
||||
return first.pure() && second.pure();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
if (first instanceof ConstantStatement) {
|
||||
if (Values.not(((ConstantStatement)first).value)) {
|
||||
second.compileWithPollution(target, scope);
|
||||
}
|
||||
else first.compileWithPollution(target, scope);
|
||||
return;
|
||||
}
|
||||
|
||||
first.compileWithPollution(target, scope);
|
||||
target.add(Instruction.dup(1).locate(loc()));
|
||||
int start = target.size();
|
||||
target.add(Instruction.nop());
|
||||
target.add(Instruction.discard().locate(loc()));
|
||||
second.compileWithPollution(target, scope);
|
||||
target.set(start, Instruction.jmpIf(target.size() - start).locate(loc()));
|
||||
}
|
||||
|
||||
public LazyOrStatement(Location loc, Statement first, Statement second) {
|
||||
super(loc);
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
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.engine.scope.ScopeRecord;
|
||||
|
||||
public class NewStatement extends Statement {
|
||||
public final Statement func;
|
||||
public final Statement[] args;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
func.compileWithPollution(target, scope);
|
||||
for (var arg : args) {
|
||||
arg.compileWithPollution(target, scope);
|
||||
}
|
||||
|
||||
target.add(Instruction.callNew(args.length).locate(loc()).setDebug(true));
|
||||
}
|
||||
|
||||
public NewStatement(Location loc, Statement func, Statement... args) {
|
||||
super(loc);
|
||||
this.func = func;
|
||||
this.args = args;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public class ObjectStatement extends Statement {
|
||||
public final Map<Object, Statement> map;
|
||||
public final Map<Object, FunctionStatement> getters;
|
||||
public final Map<Object, FunctionStatement> setters;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
|
||||
@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.loadValue(el.getKey()).locate(loc()));
|
||||
var val = el.getValue();
|
||||
if (val instanceof FunctionStatement) ((FunctionStatement)val).compile(target, scope, el.getKey().toString(), false);
|
||||
else val.compileWithPollution(target, scope);
|
||||
target.add(Instruction.storeMember().locate(loc()));
|
||||
}
|
||||
|
||||
var keys = new ArrayList<Object>();
|
||||
keys.addAll(getters.keySet());
|
||||
keys.addAll(setters.keySet());
|
||||
|
||||
for (var key : keys) {
|
||||
if (key instanceof String) target.add(Instruction.loadValue((String)key).locate(loc()));
|
||||
else target.add(Instruction.loadValue((Double)key).locate(loc()));
|
||||
|
||||
if (getters.containsKey(key)) getters.get(key).compileWithPollution(target, scope);
|
||||
else target.add(Instruction.loadValue(null).locate(loc()));
|
||||
|
||||
if (setters.containsKey(key)) setters.get(key).compileWithPollution(target, scope);
|
||||
else target.add(Instruction.loadValue(null).locate(loc()));
|
||||
|
||||
target.add(Instruction.defProp().locate(loc()));
|
||||
}
|
||||
}
|
||||
|
||||
public ObjectStatement(Location loc, Map<Object, Statement> map, Map<Object, FunctionStatement> getters, Map<Object, FunctionStatement> setters) {
|
||||
super(loc);
|
||||
this.map = Map.copyOf(map);
|
||||
this.getters = Map.copyOf(getters);
|
||||
this.setters = Map.copyOf(setters);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
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.control.ThrowStatement;
|
||||
import me.topchetoeu.jscript.engine.CallContext;
|
||||
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;
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
for (var arg : args) {
|
||||
arg.compileWithPollution(target, scope);
|
||||
}
|
||||
target.add(Instruction.operation(operation).locate(loc()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
@Override
|
||||
public boolean pure() {
|
||||
for (var arg : args) {
|
||||
if (!arg.pure()) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement optimize() {
|
||||
var args = new Statement[this.args.length];
|
||||
var allConst = true;
|
||||
|
||||
for (var i = 0; i < this.args.length; i++) {
|
||||
args[i] = this.args[i].optimize();
|
||||
if (!(args[i] instanceof ConstantStatement)) allConst = false;
|
||||
}
|
||||
|
||||
if (allConst) {
|
||||
var vals = new Object[this.args.length];
|
||||
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
vals[i] = ((ConstantStatement)args[i]).value;
|
||||
}
|
||||
|
||||
try {
|
||||
var ctx = new CallContext(null);
|
||||
|
||||
switch (operation) {
|
||||
case ADD: return new ConstantStatement(loc(), Values.add(ctx, vals[0], vals[1]));
|
||||
case SUBTRACT: return new ConstantStatement(loc(), Values.subtract(ctx, vals[0], vals[1]));
|
||||
case DIVIDE: return new ConstantStatement(loc(), Values.divide(ctx, vals[0], vals[1]));
|
||||
case MULTIPLY: return new ConstantStatement(loc(), Values.multiply(ctx, vals[0], vals[1]));
|
||||
case MODULO: return new ConstantStatement(loc(), Values.modulo(ctx, vals[0], vals[1]));
|
||||
|
||||
case AND: return new ConstantStatement(loc(), Values.and(ctx, vals[0], vals[1]));
|
||||
case OR: return new ConstantStatement(loc(), Values.or(ctx, vals[0], vals[1]));
|
||||
case XOR: return new ConstantStatement(loc(), Values.xor(ctx, vals[0], vals[1]));
|
||||
|
||||
case EQUALS: return new ConstantStatement(loc(), Values.strictEquals(vals[0], vals[1]));
|
||||
case NOT_EQUALS: return new ConstantStatement(loc(), !Values.strictEquals(vals[0], vals[1]));
|
||||
case LOOSE_EQUALS: return new ConstantStatement(loc(), Values.looseEqual(ctx, vals[0], vals[1]));
|
||||
case LOOSE_NOT_EQUALS: return new ConstantStatement(loc(), !Values.looseEqual(ctx, vals[0], vals[1]));
|
||||
|
||||
case GREATER: return new ConstantStatement(loc(), Values.compare(ctx, vals[0], vals[1]) < 0);
|
||||
case GREATER_EQUALS: return new ConstantStatement(loc(), Values.compare(ctx, vals[0], vals[1]) <= 0);
|
||||
case LESS: return new ConstantStatement(loc(), Values.compare(ctx, vals[0], vals[1]) > 0);
|
||||
case LESS_EQUALS: return new ConstantStatement(loc(), Values.compare(ctx, vals[0], vals[1]) >= 0);
|
||||
|
||||
case INVERSE: return new ConstantStatement(loc(), Values.bitwiseNot(ctx, vals[0]));
|
||||
case NOT: return new ConstantStatement(loc(), Values.not(vals[0]));
|
||||
case POS: return new ConstantStatement(loc(), Values.toNumber(ctx, vals[0]));
|
||||
case NEG: return new ConstantStatement(loc(), Values.negative(ctx, vals[0]));
|
||||
|
||||
case SHIFT_LEFT: return new ConstantStatement(loc(), Values.shiftLeft(ctx, vals[0], vals[1]));
|
||||
case SHIFT_RIGHT: return new ConstantStatement(loc(), Values.shiftRight(ctx, vals[0], vals[1]));
|
||||
case USHIFT_RIGHT: return new ConstantStatement(loc(), Values.unsignedShiftRight(ctx, vals[0], vals[1]));
|
||||
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
catch (EngineException e) {
|
||||
return new ThrowStatement(loc(), new ConstantStatement(loc(), e.value));
|
||||
}
|
||||
catch (InterruptedException e) { return null; }
|
||||
}
|
||||
|
||||
return new OperationStatement(loc(), operation, args);
|
||||
|
||||
}
|
||||
|
||||
public OperationStatement(Location loc, Instruction.Type operation, Statement... args) {
|
||||
super(loc);
|
||||
this.operation = operation;
|
||||
this.args = args;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
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.engine.scope.ScopeRecord;
|
||||
|
||||
public class RegexStatement extends Statement {
|
||||
public final String pattern, flags;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
@Override
|
||||
public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
target.add(Instruction.loadRegex(pattern, flags).locate(loc()));
|
||||
}
|
||||
|
||||
public RegexStatement(Location loc, String pattern, String flags) {
|
||||
super(loc);
|
||||
this.pattern = pattern;
|
||||
this.flags = flags;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
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.engine.scope.ScopeRecord;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
|
||||
public class TernaryStatement extends Statement {
|
||||
public final Statement condition;
|
||||
public final Statement first;
|
||||
public final Statement second;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
@Override
|
||||
public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
if (condition instanceof ConstantStatement) {
|
||||
if (!Values.toBoolean(((ConstantStatement)condition).value)) {
|
||||
second.compileWithPollution(target, scope);
|
||||
}
|
||||
else first.compileWithPollution(target, scope);
|
||||
return;
|
||||
}
|
||||
|
||||
condition.compileWithPollution(target, scope);
|
||||
int start = target.size();
|
||||
target.add(Instruction.nop());
|
||||
first.compileWithPollution(target, scope);
|
||||
int mid = target.size();
|
||||
target.add(Instruction.nop());
|
||||
second.compileWithPollution(target, scope);
|
||||
int end = target.size();
|
||||
|
||||
target.set(start, Instruction.jmpIfNot(mid - start + 1).locate(loc()));
|
||||
target.set(mid, Instruction.jmp(end - mid).locate(loc()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement optimize() {
|
||||
var cond = condition.optimize();
|
||||
var f = first.optimize();
|
||||
var s = second.optimize();
|
||||
return new TernaryStatement(loc(), cond, f, s);
|
||||
}
|
||||
|
||||
public TernaryStatement(Location loc, Statement condition, Statement first, Statement second) {
|
||||
super(loc);
|
||||
this.condition = condition;
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
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.engine.scope.ScopeRecord;
|
||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.engine.values.Symbol;
|
||||
|
||||
public class TypeofStatement extends Statement {
|
||||
public final Statement value;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
@Override
|
||||
public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
if (value instanceof VariableStatement) {
|
||||
var i = scope.getKey(((VariableStatement)value).name);
|
||||
if (i instanceof String) {
|
||||
target.add(Instruction.typeof((String)i));
|
||||
return;
|
||||
}
|
||||
}
|
||||
value.compileWithPollution(target, scope);
|
||||
target.add(Instruction.typeof().locate(loc()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement optimize() {
|
||||
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 TypeofStatement(loc(), val);
|
||||
}
|
||||
|
||||
public TypeofStatement(Location loc, Statement value) {
|
||||
super(loc);
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Instruction.Type;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public class VariableAssignStatement extends Statement {
|
||||
public final String name;
|
||||
public final Statement value;
|
||||
public final Type operation;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
var i = scope.getKey(name);
|
||||
if (operation != null) {
|
||||
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.operation(operation).locate(loc()));
|
||||
}
|
||||
else {
|
||||
if (value instanceof FunctionStatement) ((FunctionStatement)value).compile(target, scope, name, false);
|
||||
else value.compileWithPollution(target, scope);
|
||||
}
|
||||
|
||||
target.add(Instruction.storeVar(i, true).locate(loc()));
|
||||
}
|
||||
|
||||
public VariableAssignStatement(Location loc, String name, Statement val, Type operation) {
|
||||
super(loc);
|
||||
this.name = name;
|
||||
this.value = val;
|
||||
this.operation = operation;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public class VariableIndexStatement extends Statement {
|
||||
public final int index;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
@Override
|
||||
public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
target.add(Instruction.loadVar(index).locate(loc()));
|
||||
}
|
||||
|
||||
public VariableIndexStatement(Location loc, int i) {
|
||||
super(loc);
|
||||
this.index = i;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
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.scope.ScopeRecord;
|
||||
|
||||
public class VariableStatement extends AssignableStatement {
|
||||
public final String name;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
@Override
|
||||
public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public Statement toAssign(Statement val, Type operation) {
|
||||
return new VariableAssignStatement(loc(), name, val, operation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
var i = scope.getKey(name);
|
||||
target.add(Instruction.loadVar(i).locate(loc()));
|
||||
}
|
||||
|
||||
public VariableStatement(Location loc, String name) {
|
||||
super(loc);
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
|
||||
public class VoidStatement extends Statement {
|
||||
public final Statement value;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
if (value != null) value.compileNoPollution(target, scope);
|
||||
target.add(Instruction.loadValue(null).locate(loc()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement optimize() {
|
||||
if (value == null) return this;
|
||||
var val = value.optimize();
|
||||
if (val.pure()) return new ConstantStatement(loc(), null);
|
||||
else return new VoidStatement(loc(), val);
|
||||
}
|
||||
|
||||
public VoidStatement(Location loc, Statement value) {
|
||||
super(loc);
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
5
src/me/topchetoeu/jscript/engine/BreakpointData.java
Normal file
5
src/me/topchetoeu/jscript/engine/BreakpointData.java
Normal file
@@ -0,0 +1,5 @@
|
||||
package me.topchetoeu.jscript.engine;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
|
||||
public record BreakpointData(Location loc, CallContext ctx) { }
|
||||
58
src/me/topchetoeu/jscript/engine/CallContext.java
Normal file
58
src/me/topchetoeu/jscript/engine/CallContext.java
Normal file
@@ -0,0 +1,58 @@
|
||||
package me.topchetoeu.jscript.engine;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
|
||||
public class CallContext {
|
||||
public static final class DataKey<T> {}
|
||||
|
||||
public final Engine engine;
|
||||
private final Map<DataKey<?>, Object> data = new Hashtable<>();
|
||||
|
||||
public Engine engine() { return engine; }
|
||||
public Map<DataKey<?>, Object> data() { return Collections.unmodifiableMap(data); }
|
||||
|
||||
public CallContext copy() {
|
||||
return new CallContext(engine).mergeData(data);
|
||||
}
|
||||
public CallContext mergeData(Map<DataKey<?>, Object> objs) {
|
||||
data.putAll(objs);
|
||||
return this;
|
||||
}
|
||||
public <T> CallContext setData(DataKey<T> key, T val) {
|
||||
data.put(key, val);
|
||||
return this;
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T addData(DataKey<T> key, T val) {
|
||||
if (data.containsKey(key)) return (T)data.get(key);
|
||||
else {
|
||||
data.put(key, val);
|
||||
return val;
|
||||
}
|
||||
}
|
||||
public boolean hasData(DataKey<?> key) { return data.containsKey(key); }
|
||||
public <T> T getData(DataKey<T> key) {
|
||||
return getData(key, null);
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getData(DataKey<T> key, T defaultVal) {
|
||||
if (!hasData(key)) return defaultVal;
|
||||
else return (T)data.get(key);
|
||||
}
|
||||
|
||||
public CallContext changeData(DataKey<Integer> key, int n, int start) {
|
||||
return setData(key, getData(key, start) + n);
|
||||
}
|
||||
public CallContext changeData(DataKey<Integer> key, int n) {
|
||||
return changeData(key, n, 0);
|
||||
}
|
||||
public CallContext changeData(DataKey<Integer> key) {
|
||||
return changeData(key, 1, 0);
|
||||
}
|
||||
|
||||
public CallContext(Engine engine) {
|
||||
this.engine = engine;
|
||||
}
|
||||
}
|
||||
8
src/me/topchetoeu/jscript/engine/DebugCommand.java
Normal file
8
src/me/topchetoeu/jscript/engine/DebugCommand.java
Normal file
@@ -0,0 +1,8 @@
|
||||
package me.topchetoeu.jscript.engine;
|
||||
|
||||
public enum DebugCommand {
|
||||
NORMAL,
|
||||
STEP_OVER,
|
||||
STEP_OUT,
|
||||
STEP_INTO,
|
||||
}
|
||||
195
src/me/topchetoeu/jscript/engine/Engine.java
Normal file
195
src/me/topchetoeu/jscript/engine/Engine.java
Normal file
@@ -0,0 +1,195 @@
|
||||
package me.topchetoeu.jscript.engine;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
|
||||
import me.topchetoeu.jscript.engine.CallContext.DataKey;
|
||||
import me.topchetoeu.jscript.engine.debug.DebugState;
|
||||
import me.topchetoeu.jscript.engine.modules.ModuleManager;
|
||||
import me.topchetoeu.jscript.engine.scope.GlobalScope;
|
||||
import me.topchetoeu.jscript.engine.values.CodeFunction;
|
||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto;
|
||||
import me.topchetoeu.jscript.events.Awaitable;
|
||||
import me.topchetoeu.jscript.events.DataNotifier;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.interop.NativeTypeRegister;
|
||||
import me.topchetoeu.jscript.parsing.Parsing;
|
||||
|
||||
public class Engine {
|
||||
private static record RawFunction(GlobalScope scope, String filename, String raw) { }
|
||||
private static class Task {
|
||||
public final Object func;
|
||||
public final Object thisArg;
|
||||
public final Object[] args;
|
||||
public final Map<DataKey<?>, Object> data;
|
||||
public final DataNotifier<Object> notifier = new DataNotifier<>();
|
||||
|
||||
public Task(Object func, Map<DataKey<?>, Object> data, Object thisArg, Object[] args) {
|
||||
this.func = func;
|
||||
this.data = data;
|
||||
this.thisArg = thisArg;
|
||||
this.args = args;
|
||||
}
|
||||
}
|
||||
|
||||
public static final DataKey<DebugState> DEBUG_STATE_KEY = new DataKey<>();
|
||||
private static int nextId = 0;
|
||||
|
||||
private Map<DataKey<?>, Object> callCtxVals = new HashMap<>();
|
||||
private GlobalScope global = new GlobalScope();
|
||||
private ObjectValue arrayProto = new ObjectValue();
|
||||
private ObjectValue boolProto = new ObjectValue();
|
||||
private ObjectValue funcProto = new ObjectValue();
|
||||
private ObjectValue numProto = new ObjectValue();
|
||||
private ObjectValue objProto = new ObjectValue(PlaceholderProto.NONE);
|
||||
private ObjectValue strProto = new ObjectValue();
|
||||
private ObjectValue symProto = new ObjectValue();
|
||||
private ObjectValue errProto = new ObjectValue();
|
||||
private ObjectValue syntaxErrProto = new ObjectValue(PlaceholderProto.ERROR);
|
||||
private ObjectValue typeErrProto = new ObjectValue(PlaceholderProto.ERROR);
|
||||
private ObjectValue rangeErrProto = new ObjectValue(PlaceholderProto.ERROR);
|
||||
private NativeTypeRegister typeRegister;
|
||||
private Thread thread;
|
||||
|
||||
private LinkedBlockingDeque<Task> macroTasks = new LinkedBlockingDeque<>();
|
||||
private LinkedBlockingDeque<Task> microTasks = new LinkedBlockingDeque<>();
|
||||
|
||||
public final int id = ++nextId;
|
||||
public final DebugState debugState = new DebugState();
|
||||
|
||||
public ObjectValue arrayProto() { return arrayProto; }
|
||||
public ObjectValue booleanProto() { return boolProto; }
|
||||
public ObjectValue functionProto() { return funcProto; }
|
||||
public ObjectValue numberProto() { return numProto; }
|
||||
public ObjectValue objectProto() { return objProto; }
|
||||
public ObjectValue stringProto() { return strProto; }
|
||||
public ObjectValue symbolProto() { return symProto; }
|
||||
public ObjectValue errorProto() { return errProto; }
|
||||
public ObjectValue syntaxErrorProto() { return syntaxErrProto; }
|
||||
public ObjectValue typeErrorProto() { return typeErrProto; }
|
||||
public ObjectValue rangeErrorProto() { return rangeErrProto; }
|
||||
|
||||
public GlobalScope global() { return global; }
|
||||
public NativeTypeRegister typeRegister() { return typeRegister; }
|
||||
|
||||
public void copyFrom(Engine other) {
|
||||
global = other.global;
|
||||
typeRegister = other.typeRegister;
|
||||
arrayProto = other.arrayProto;
|
||||
boolProto = other.boolProto;
|
||||
funcProto = other.funcProto;
|
||||
numProto = other.numProto;
|
||||
objProto = other.objProto;
|
||||
strProto = other.strProto;
|
||||
symProto = other.symProto;
|
||||
errProto = other.errProto;
|
||||
syntaxErrProto = other.syntaxErrProto;
|
||||
typeErrProto = other.typeErrProto;
|
||||
rangeErrProto = other.rangeErrProto;
|
||||
}
|
||||
|
||||
private void runTask(Task task) throws InterruptedException {
|
||||
try {
|
||||
FunctionValue func;
|
||||
if (task.func instanceof FunctionValue) func = (FunctionValue)task.func;
|
||||
else {
|
||||
var raw = (RawFunction)task.func;
|
||||
func = compile(raw.scope, raw.filename, raw.raw);
|
||||
}
|
||||
|
||||
task.notifier.next(func.call(context().mergeData(task.data), task.thisArg, task.args));
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
task.notifier.error(new RuntimeException(e));
|
||||
throw e;
|
||||
}
|
||||
catch (EngineException e) {
|
||||
task.notifier.error(e);
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
task.notifier.error(e);
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
private void run() {
|
||||
while (true) {
|
||||
try {
|
||||
runTask(macroTasks.take());
|
||||
|
||||
while (!microTasks.isEmpty()) {
|
||||
runTask(microTasks.take());
|
||||
}
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
for (var msg : macroTasks) {
|
||||
msg.notifier.error(new RuntimeException(e));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void exposeClass(String name, Class<?> clazz) {
|
||||
global.define(name, true, typeRegister.getConstr(clazz));
|
||||
}
|
||||
public void exposeNamespace(String name, Class<?> clazz) {
|
||||
global.define(name, true, NativeTypeRegister.makeNamespace(clazz));
|
||||
}
|
||||
|
||||
public Thread start() {
|
||||
if (this.thread == null) {
|
||||
this.thread = new Thread(this::run, "JavaScript Runner #" + id);
|
||||
this.thread.start();
|
||||
}
|
||||
return this.thread;
|
||||
}
|
||||
public void stop() {
|
||||
thread.interrupt();
|
||||
thread = null;
|
||||
}
|
||||
public boolean inExecThread() {
|
||||
return Thread.currentThread() == thread;
|
||||
}
|
||||
public boolean isRunning() {
|
||||
return this.thread != null;
|
||||
}
|
||||
|
||||
public Object makeRegex(String pattern, String flags) {
|
||||
throw EngineException.ofError("Regular expressions not supported.");
|
||||
}
|
||||
public ModuleManager modules() {
|
||||
return null;
|
||||
}
|
||||
public ObjectValue getPrototype(Class<?> clazz) {
|
||||
return typeRegister.getProto(clazz);
|
||||
}
|
||||
public CallContext context() { return new CallContext(this).mergeData(callCtxVals); }
|
||||
|
||||
public Awaitable<Object> pushMsg(boolean micro, FunctionValue func, Map<DataKey<?>, Object> data, Object thisArg, Object... args) {
|
||||
var msg = new Task(func, data, thisArg, args);
|
||||
if (micro) microTasks.addLast(msg);
|
||||
else macroTasks.addLast(msg);
|
||||
return msg.notifier;
|
||||
}
|
||||
public Awaitable<Object> pushMsg(boolean micro, GlobalScope scope, Map<DataKey<?>, Object> data, String filename, String raw, Object thisArg, Object... args) {
|
||||
var msg = new Task(new RawFunction(scope, filename, raw), data, thisArg, args);
|
||||
if (micro) microTasks.addLast(msg);
|
||||
else macroTasks.addLast(msg);
|
||||
return msg.notifier;
|
||||
}
|
||||
|
||||
public CodeFunction compile(GlobalScope scope, String filename, String raw) throws InterruptedException {
|
||||
return Parsing.compile(scope, filename, raw);
|
||||
}
|
||||
|
||||
public Engine(NativeTypeRegister register) {
|
||||
this.typeRegister = register;
|
||||
this.callCtxVals.put(DEBUG_STATE_KEY, debugState);
|
||||
}
|
||||
public Engine() {
|
||||
this(new NativeTypeRegister());
|
||||
}
|
||||
}
|
||||
154
src/me/topchetoeu/jscript/engine/debug/DebugServer.java
Normal file
154
src/me/topchetoeu/jscript/engine/debug/DebugServer.java
Normal file
@@ -0,0 +1,154 @@
|
||||
package me.topchetoeu.jscript.engine.debug;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Base64;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Engine;
|
||||
import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type;
|
||||
import me.topchetoeu.jscript.engine.debug.handlers.DebuggerHandles;
|
||||
import me.topchetoeu.jscript.exceptions.SyntaxException;
|
||||
|
||||
public class DebugServer {
|
||||
public static String browserDisplayName = "jscript";
|
||||
public static String targetName = "target";
|
||||
|
||||
public final Engine engine;
|
||||
|
||||
private static void send(Socket socket, String val) throws IOException {
|
||||
Http.writeResponse(socket.getOutputStream(), 200, "OK", "application/json", val.getBytes());
|
||||
}
|
||||
|
||||
// SILENCE JAVA
|
||||
private MessageDigest getDigestInstance() {
|
||||
try {
|
||||
return MessageDigest.getInstance("sha1");
|
||||
}
|
||||
catch (Throwable a) { return null; }
|
||||
}
|
||||
|
||||
private static Thread runAsync(Runnable func, String name) {
|
||||
var res = new Thread(func);
|
||||
res.setName(name);
|
||||
res.start();
|
||||
return res;
|
||||
}
|
||||
|
||||
private void handle(WebSocket ws) throws InterruptedException, IOException {
|
||||
WebSocketMessage raw;
|
||||
|
||||
while ((raw = ws.receive()) != null) {
|
||||
if (raw.type != Type.Text) {
|
||||
ws.send(new V8Error("Expected a text message."));
|
||||
continue;
|
||||
}
|
||||
|
||||
V8Message msg;
|
||||
|
||||
try {
|
||||
msg = new V8Message(raw.textData());
|
||||
}
|
||||
catch (SyntaxException e) {
|
||||
ws.send(new V8Error(e.getMessage()));
|
||||
return;
|
||||
}
|
||||
|
||||
switch (msg.name) {
|
||||
case "Debugger.enable": DebuggerHandles.enable(msg, engine, ws); continue;
|
||||
case "Debugger.disable": DebuggerHandles.disable(msg, engine, ws); continue;
|
||||
case "Debugger.stepInto": DebuggerHandles.stepInto(msg, engine, ws); continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
private void onWsConnect(HttpRequest req, Socket socket) throws IOException {
|
||||
var key = req.headers().get("sec-websocket-key");
|
||||
|
||||
if (key == null) {
|
||||
Http.writeResponse(
|
||||
socket.getOutputStream(), 426, "Upgrade Required", "text/txt",
|
||||
"Expected a WS upgrade".getBytes()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
var resKey = Base64.getEncoder().encodeToString(getDigestInstance().digest(
|
||||
(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes()
|
||||
));
|
||||
|
||||
Http.writeCode(socket.getOutputStream(), 101, "Switching Protocols");
|
||||
Http.writeHeader(socket.getOutputStream(), "Connection", "Upgrade");
|
||||
Http.writeHeader(socket.getOutputStream(), "Sec-WebSocket-Accept", resKey);
|
||||
Http.writeLastHeader(socket.getOutputStream(), "Upgrade", "WebSocket");
|
||||
|
||||
var ws = new WebSocket(socket);
|
||||
|
||||
runAsync(() -> {
|
||||
try {
|
||||
handle(ws);
|
||||
}
|
||||
catch (InterruptedException e) { return; }
|
||||
catch (IOException e) { e.printStackTrace(); }
|
||||
finally { ws.close(); }
|
||||
}, "Debug Server Message Reader");
|
||||
runAsync(() -> {
|
||||
try {
|
||||
handle(ws);
|
||||
}
|
||||
catch (InterruptedException e) { return; }
|
||||
catch (IOException e) { e.printStackTrace(); }
|
||||
finally { ws.close(); }
|
||||
}, "Debug Server Event Writer");
|
||||
}
|
||||
|
||||
public void open(InetSocketAddress address) throws IOException {
|
||||
ServerSocket server = new ServerSocket();
|
||||
server.bind(address);
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
var socket = server.accept();
|
||||
var req = Http.readRequest(socket.getInputStream());
|
||||
|
||||
switch (req.path()) {
|
||||
case "/json/version":
|
||||
send(socket, "{\"Browser\":\"" + browserDisplayName + "\",\"Protocol-Version\":\"1.2\"}");
|
||||
break;
|
||||
case "/json/list":
|
||||
case "/json":
|
||||
var addr = "ws://" + address.getHostString() + ":" + address.getPort() + "/devtools/page/" + targetName;
|
||||
send(socket, "{\"id\":\"" + browserDisplayName + "\",\"webSocketDebuggerUrl\":\"" + addr + "\"}");
|
||||
break;
|
||||
case "/json/new":
|
||||
case "/json/activate":
|
||||
case "/json/protocol":
|
||||
case "/json/close":
|
||||
case "/devtools/inspector.html":
|
||||
Http.writeResponse(
|
||||
socket.getOutputStream(),
|
||||
501, "Not Implemented", "text/txt",
|
||||
"This feature isn't (and won't be) implemented.".getBytes()
|
||||
);
|
||||
break;
|
||||
default:
|
||||
if (req.path().equals("/devtools/page/" + targetName)) onWsConnect(req, socket);
|
||||
else {
|
||||
Http.writeResponse(
|
||||
socket.getOutputStream(),
|
||||
404, "Not Found", "text/txt",
|
||||
"Not found :/".getBytes()
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally { server.close(); }
|
||||
}
|
||||
|
||||
public DebugServer(Engine engine) {
|
||||
this.engine = engine;
|
||||
}
|
||||
}
|
||||
52
src/me/topchetoeu/jscript/engine/debug/DebugState.java
Normal file
52
src/me/topchetoeu/jscript/engine/debug/DebugState.java
Normal file
@@ -0,0 +1,52 @@
|
||||
package me.topchetoeu.jscript.engine.debug;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.engine.BreakpointData;
|
||||
import me.topchetoeu.jscript.engine.DebugCommand;
|
||||
import me.topchetoeu.jscript.engine.frame.CodeFrame;
|
||||
import me.topchetoeu.jscript.events.Event;
|
||||
|
||||
public class DebugState {
|
||||
private boolean paused = false;
|
||||
|
||||
public final HashSet<Location> breakpoints = new HashSet<>();
|
||||
public final List<CodeFrame> frames = new ArrayList<>();
|
||||
public final Map<String, String> sources = new HashMap<>();
|
||||
|
||||
public final Event<BreakpointData> breakpointNotifier = new Event<>();
|
||||
public final Event<DebugCommand> commandNotifier = new Event<>();
|
||||
public final Event<String> sourceAdded = new Event<>();
|
||||
|
||||
public DebugState pushFrame(CodeFrame frame) {
|
||||
frames.add(frame);
|
||||
return this;
|
||||
}
|
||||
public DebugState popFrame() {
|
||||
if (frames.size() > 0) frames.remove(frames.size() - 1);
|
||||
return this;
|
||||
}
|
||||
|
||||
public DebugCommand pause(BreakpointData data) throws InterruptedException {
|
||||
paused = true;
|
||||
breakpointNotifier.next(data);
|
||||
return commandNotifier.toAwaitable().await();
|
||||
}
|
||||
public void resume(DebugCommand command) {
|
||||
paused = false;
|
||||
commandNotifier.next(command);
|
||||
}
|
||||
|
||||
// public void addSource()?
|
||||
|
||||
public boolean paused() { return paused; }
|
||||
|
||||
public boolean isBreakpoint(Location loc) {
|
||||
return breakpoints.contains(loc);
|
||||
}
|
||||
}
|
||||
65
src/me/topchetoeu/jscript/engine/debug/Http.java
Normal file
65
src/me/topchetoeu/jscript/engine/debug/Http.java
Normal file
@@ -0,0 +1,65 @@
|
||||
package me.topchetoeu.jscript.engine.debug;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.IllegalFormatException;
|
||||
|
||||
// We dont need no http library
|
||||
public class Http {
|
||||
public static void writeCode(OutputStream str, int code, String name) throws IOException {
|
||||
str.write(("HTTP/1.1 " + code + " " + name + "\r\n").getBytes());
|
||||
}
|
||||
public static void writeHeader(OutputStream str, String name, String value) throws IOException {
|
||||
str.write((name + ": " + value + "\r\n").getBytes());
|
||||
}
|
||||
public static void writeLastHeader(OutputStream str, String name, String value) throws IOException {
|
||||
str.write((name + ": " + value + "\r\n").getBytes());
|
||||
writeHeadersEnd(str);
|
||||
}
|
||||
public static void writeHeadersEnd(OutputStream str) throws IOException {
|
||||
str.write("\n".getBytes());
|
||||
}
|
||||
|
||||
public static void writeResponse(OutputStream str, int code, String name, String type, byte[] data) throws IOException {
|
||||
writeCode(str, code, name);
|
||||
writeHeader(str, "Content-Type", type);
|
||||
writeLastHeader(str, "Content-Length", data.length + "");
|
||||
str.write(data);
|
||||
str.close();
|
||||
}
|
||||
|
||||
public static HttpRequest readRequest(InputStream str) throws IOException {
|
||||
var lines = new BufferedReader(new InputStreamReader(str));
|
||||
var line = lines.readLine();
|
||||
var i1 = line.indexOf(" ");
|
||||
var i2 = line.lastIndexOf(" ");
|
||||
|
||||
var method = line.substring(0, i1).trim().toUpperCase();
|
||||
var path = line.substring(i1 + 1, i2).trim();
|
||||
var headers = new HashMap<String, String>();
|
||||
|
||||
while (!(line = lines.readLine()).isEmpty()) {
|
||||
var i = line.indexOf(":");
|
||||
if (i < 0) continue;
|
||||
var name = line.substring(0, i).trim().toLowerCase();
|
||||
var value = line.substring(i + 1).trim();
|
||||
|
||||
if (name.length() == 0) continue;
|
||||
headers.put(name, value);
|
||||
}
|
||||
|
||||
if (headers.containsKey("content-length")) {
|
||||
try {
|
||||
var i = Integer.parseInt(headers.get("content-length"));
|
||||
str.skip(i);
|
||||
}
|
||||
catch (IllegalFormatException e) { /* ¯\_(ツ)_/¯ */ }
|
||||
}
|
||||
|
||||
return new HttpRequest(method, path, headers);
|
||||
}
|
||||
}
|
||||
6
src/me/topchetoeu/jscript/engine/debug/HttpRequest.java
Normal file
6
src/me/topchetoeu/jscript/engine/debug/HttpRequest.java
Normal file
@@ -0,0 +1,6 @@
|
||||
package me.topchetoeu.jscript.engine.debug;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public record HttpRequest(String method, String path, Map<String, String> headers) {}
|
||||
|
||||
19
src/me/topchetoeu/jscript/engine/debug/V8Error.java
Normal file
19
src/me/topchetoeu/jscript/engine/debug/V8Error.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package me.topchetoeu.jscript.engine.debug;
|
||||
|
||||
import me.topchetoeu.jscript.json.JSON;
|
||||
import me.topchetoeu.jscript.json.JSONMap;
|
||||
|
||||
public class V8Error {
|
||||
public final String message;
|
||||
|
||||
public V8Error(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return JSON.stringify(new JSONMap().set("error", new JSONMap()
|
||||
.set("message", message)
|
||||
));
|
||||
}
|
||||
}
|
||||
22
src/me/topchetoeu/jscript/engine/debug/V8Event.java
Normal file
22
src/me/topchetoeu/jscript/engine/debug/V8Event.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package me.topchetoeu.jscript.engine.debug;
|
||||
|
||||
import me.topchetoeu.jscript.json.JSON;
|
||||
import me.topchetoeu.jscript.json.JSONMap;
|
||||
|
||||
public class V8Event {
|
||||
public final String name;
|
||||
public final JSONMap params;
|
||||
|
||||
public V8Event(String name, JSONMap params) {
|
||||
this.name = name;
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return JSON.stringify(new JSONMap()
|
||||
.set("method", name)
|
||||
.set("params", params)
|
||||
);
|
||||
}
|
||||
}
|
||||
50
src/me/topchetoeu/jscript/engine/debug/V8Message.java
Normal file
50
src/me/topchetoeu/jscript/engine/debug/V8Message.java
Normal file
@@ -0,0 +1,50 @@
|
||||
package me.topchetoeu.jscript.engine.debug;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import me.topchetoeu.jscript.json.JSON;
|
||||
import me.topchetoeu.jscript.json.JSONElement;
|
||||
import me.topchetoeu.jscript.json.JSONMap;
|
||||
|
||||
public class V8Message {
|
||||
public final String name;
|
||||
public final int id;
|
||||
public final JSONMap params;
|
||||
|
||||
public V8Message(String name, int id, Map<String, JSONElement> params) {
|
||||
this.name = name;
|
||||
this.params = new JSONMap(params);
|
||||
this.id = id;
|
||||
}
|
||||
public V8Result respond(JSONMap result) {
|
||||
return new V8Result(id, result);
|
||||
}
|
||||
public V8Result respond() {
|
||||
return new V8Result(id, new JSONMap());
|
||||
}
|
||||
|
||||
public V8Message(JSONMap raw) {
|
||||
if (!raw.isNumber("id")) throw new IllegalArgumentException("Expected number property 'id'.");
|
||||
if (!raw.isString("method")) throw new IllegalArgumentException("Expected string property 'method'.");
|
||||
|
||||
this.name = raw.string("method");
|
||||
this.id = (int)raw.number("id");
|
||||
this.params = raw.contains("params") ? raw.map("params") : new JSONMap();
|
||||
}
|
||||
public V8Message(String raw) {
|
||||
this(JSON.parse("json", raw).map());
|
||||
}
|
||||
|
||||
public JSONMap toMap() {
|
||||
var res = new JSONMap();
|
||||
return res;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return JSON.stringify(new JSONMap()
|
||||
.set("method", name)
|
||||
.set("params", params)
|
||||
.set("id", id)
|
||||
);
|
||||
}
|
||||
}
|
||||
22
src/me/topchetoeu/jscript/engine/debug/V8Result.java
Normal file
22
src/me/topchetoeu/jscript/engine/debug/V8Result.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package me.topchetoeu.jscript.engine.debug;
|
||||
|
||||
import me.topchetoeu.jscript.json.JSON;
|
||||
import me.topchetoeu.jscript.json.JSONMap;
|
||||
|
||||
public class V8Result {
|
||||
public final int id;
|
||||
public final JSONMap result;
|
||||
|
||||
public V8Result(int id, JSONMap result) {
|
||||
this.id = id;
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return JSON.stringify(new JSONMap()
|
||||
.set("id", id)
|
||||
.set("result", result)
|
||||
);
|
||||
}
|
||||
}
|
||||
185
src/me/topchetoeu/jscript/engine/debug/WebSocket.java
Normal file
185
src/me/topchetoeu/jscript/engine/debug/WebSocket.java
Normal file
@@ -0,0 +1,185 @@
|
||||
package me.topchetoeu.jscript.engine.debug;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
|
||||
import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type;
|
||||
|
||||
public class WebSocket implements AutoCloseable {
|
||||
public long maxLength = 2000000;
|
||||
|
||||
private Socket socket;
|
||||
private boolean closed = false;
|
||||
|
||||
private OutputStream out() throws IOException {
|
||||
return socket.getOutputStream();
|
||||
}
|
||||
private InputStream in() throws IOException {
|
||||
return socket.getInputStream();
|
||||
}
|
||||
|
||||
private long readLen(int byteLen) throws IOException {
|
||||
long res = 0;
|
||||
|
||||
if (byteLen == 126) {
|
||||
res |= in().read() << 8;
|
||||
res |= in().read();
|
||||
return res;
|
||||
}
|
||||
else if (byteLen == 127) {
|
||||
res |= in().read() << 56;
|
||||
res |= in().read() << 48;
|
||||
res |= in().read() << 40;
|
||||
res |= in().read() << 32;
|
||||
res |= in().read() << 24;
|
||||
res |= in().read() << 16;
|
||||
res |= in().read() << 8;
|
||||
res |= in().read();
|
||||
return res;
|
||||
}
|
||||
else return byteLen;
|
||||
}
|
||||
private byte[] readMask(boolean has) throws IOException {
|
||||
if (has) {
|
||||
return new byte[] {
|
||||
(byte)in().read(),
|
||||
(byte)in().read(),
|
||||
(byte)in().read(),
|
||||
(byte)in().read()
|
||||
};
|
||||
}
|
||||
else return new byte[4];
|
||||
}
|
||||
|
||||
private void writeLength(long len) throws IOException {
|
||||
if (len < 126) {
|
||||
out().write((int)len);
|
||||
}
|
||||
else if (len < 0xFFFF) {
|
||||
out().write(126);
|
||||
out().write((int)(len >> 8) & 0xFF);
|
||||
out().write((int)len & 0xFF);
|
||||
}
|
||||
else {
|
||||
out().write(127);
|
||||
out().write((int)(len >> 56) & 0xFF);
|
||||
out().write((int)(len >> 48) & 0xFF);
|
||||
out().write((int)(len >> 40) & 0xFF);
|
||||
out().write((int)(len >> 32) & 0xFF);
|
||||
out().write((int)(len >> 24) & 0xFF);
|
||||
out().write((int)(len >> 16) & 0xFF);
|
||||
out().write((int)(len >> 8) & 0xFF);
|
||||
out().write((int)len & 0xFF);
|
||||
}
|
||||
}
|
||||
private synchronized void write(int type, byte[] data) throws IOException {
|
||||
out().write(type | 0x80);
|
||||
writeLength(data.length);
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
out().write(data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public void send(String data) throws IOException {
|
||||
if (closed) throw new IllegalStateException("Object is closed.");
|
||||
write(1, data.getBytes());
|
||||
}
|
||||
public void send(byte[] data) throws IOException {
|
||||
if (closed) throw new IllegalStateException("Object is closed.");
|
||||
write(2, data);
|
||||
}
|
||||
public void send(WebSocketMessage msg) throws IOException {
|
||||
if (msg.type == Type.Binary) send(msg.binaryData());
|
||||
else send(msg.textData());
|
||||
}
|
||||
public void send(Object data) throws IOException {
|
||||
if (closed) throw new IllegalStateException("Object is closed.");
|
||||
write(1, data.toString().getBytes());
|
||||
}
|
||||
|
||||
public void close(String reason) {
|
||||
if (socket != null) {
|
||||
try { write(8, reason.getBytes()); } catch (IOException e) { /* ¯\_(ツ)_/¯ */ }
|
||||
try { socket.close(); } catch (IOException e) { e.printStackTrace(); }
|
||||
}
|
||||
|
||||
socket = null;
|
||||
closed = true;
|
||||
}
|
||||
public void close() {
|
||||
close("");
|
||||
}
|
||||
|
||||
private WebSocketMessage fail(String reason) {
|
||||
System.out.println("WebSocket Error: " + reason);
|
||||
close(reason);
|
||||
return null;
|
||||
}
|
||||
|
||||
private byte[] readData() throws IOException {
|
||||
var maskLen = in().read();
|
||||
var hasMask = (maskLen & 0x80) != 0;
|
||||
var len = (int)readLen(maskLen & 0x7F);
|
||||
var mask = readMask(hasMask);
|
||||
|
||||
if (len > maxLength) fail("WebSocket Error: client exceeded configured max message size");
|
||||
else {
|
||||
var buff = new byte[len];
|
||||
|
||||
if (in().read(buff) < len) fail("WebSocket Error: payload too short");
|
||||
else {
|
||||
for (int i = 0; i < len; i++) {
|
||||
buff[i] ^= mask[(int)(i % 4)];
|
||||
}
|
||||
return buff;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public WebSocketMessage receive() throws InterruptedException {
|
||||
try {
|
||||
var data = new ByteArrayOutputStream();
|
||||
var type = 0;
|
||||
|
||||
while (socket != null && !closed) {
|
||||
var finId = in().read();
|
||||
var fin = (finId & 0x80) != 0;
|
||||
int id = finId & 0x0F;
|
||||
|
||||
if (id == 0x8) { close(); return null; }
|
||||
if (id >= 0x8) {
|
||||
if (!fin) return fail("WebSocket Error: client-sent control frame was fragmented");
|
||||
if (id == 0x9) write(0xA, data.toByteArray());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (type == 0) type = id;
|
||||
if (type == 0) return fail("WebSocket Error: client used opcode 0x00 for first fragment");
|
||||
|
||||
var buff = readData();
|
||||
if (buff == null) break;
|
||||
|
||||
if (data.size() + buff.length > maxLength) return fail("WebSocket Error: client exceeded configured max message size");
|
||||
data.write(buff);
|
||||
|
||||
if (!fin) continue;
|
||||
var raw = data.toByteArray();
|
||||
|
||||
if (type == 1) return new WebSocketMessage(new String(raw));
|
||||
else return new WebSocketMessage(raw);
|
||||
}
|
||||
}
|
||||
catch (IOException e) { close(); }
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public WebSocket(Socket socket) {
|
||||
this.socket = socket;
|
||||
}
|
||||
}
|
||||
29
src/me/topchetoeu/jscript/engine/debug/WebSocketMessage.java
Normal file
29
src/me/topchetoeu/jscript/engine/debug/WebSocketMessage.java
Normal file
@@ -0,0 +1,29 @@
|
||||
package me.topchetoeu.jscript.engine.debug;
|
||||
|
||||
public class WebSocketMessage {
|
||||
public static enum Type {
|
||||
Text,
|
||||
Binary,
|
||||
}
|
||||
|
||||
public final Type type;
|
||||
private final Object data;
|
||||
|
||||
public final String textData() {
|
||||
if (type != Type.Text) throw new IllegalStateException("Message is not text.");
|
||||
return (String)data;
|
||||
}
|
||||
public final byte[] binaryData() {
|
||||
if (type != Type.Binary) throw new IllegalStateException("Message is not binary.");
|
||||
return (byte[])data;
|
||||
}
|
||||
|
||||
public WebSocketMessage(String data) {
|
||||
this.type = Type.Text;
|
||||
this.data = data;
|
||||
}
|
||||
public WebSocketMessage(byte[] data) {
|
||||
this.type = Type.Binary;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package me.topchetoeu.jscript.engine.debug.handlers;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import me.topchetoeu.jscript.engine.DebugCommand;
|
||||
import me.topchetoeu.jscript.engine.Engine;
|
||||
import me.topchetoeu.jscript.engine.debug.V8Error;
|
||||
import me.topchetoeu.jscript.engine.debug.V8Message;
|
||||
import me.topchetoeu.jscript.engine.debug.WebSocket;
|
||||
import me.topchetoeu.jscript.json.JSONMap;
|
||||
|
||||
public class DebuggerHandles {
|
||||
public static void enable(V8Message msg, Engine engine, WebSocket ws) throws IOException {
|
||||
if (engine.debugState == null) ws.send(new V8Error("Debugging is disabled for this engine."));
|
||||
else ws.send(msg.respond(new JSONMap().set("debuggerId", 1)));
|
||||
}
|
||||
public static void disable(V8Message msg, Engine engine, WebSocket ws) throws IOException {
|
||||
if (engine.debugState == null) ws.send(msg.respond());
|
||||
else ws.send(new V8Error("Debugger may not be disabled."));
|
||||
}
|
||||
public static void stepInto(V8Message msg, Engine engine, WebSocket ws) throws IOException {
|
||||
if (engine.debugState == null) ws.send(new V8Error("Debugging is disabled for this engine."));
|
||||
else if (!engine.debugState.paused()) ws.send(new V8Error("Debugger is not paused."));
|
||||
else {
|
||||
engine.debugState.resume(DebugCommand.STEP_INTO);
|
||||
ws.send(msg.respond());
|
||||
}
|
||||
}
|
||||
}
|
||||
177
src/me/topchetoeu/jscript/engine/frame/CodeFrame.java
Normal file
177
src/me/topchetoeu/jscript/engine/frame/CodeFrame.java
Normal file
@@ -0,0 +1,177 @@
|
||||
package me.topchetoeu.jscript.engine.frame;
|
||||
|
||||
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.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 {
|
||||
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<>();
|
||||
public static final DataKey<Boolean> STEPPING_TROUGH_KEY = new DataKey<>();
|
||||
|
||||
public final LocalScope scope;
|
||||
public final Object thisArg;
|
||||
public final Object[] args;
|
||||
public final List<Object> stack = new ArrayList<>();
|
||||
public final CodeFunction function;
|
||||
|
||||
public int codePtr = 0;
|
||||
private DebugCommand debugCmd = null;
|
||||
private Location prevLoc = null;
|
||||
|
||||
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);
|
||||
}
|
||||
public Object pop() {
|
||||
if (stack.size() == 0) return null;
|
||||
else return stack.remove(stack.size() - 1);
|
||||
}
|
||||
public void push(Object val) {
|
||||
stack.add(stack.size(), Values.normalize(val));
|
||||
}
|
||||
|
||||
private void cleanup(CallContext ctx) {
|
||||
stack.clear();
|
||||
codePtr = 0;
|
||||
debugCmd = null;
|
||||
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, 1000))
|
||||
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);
|
||||
}
|
||||
|
||||
if (Thread.currentThread().isInterrupted()) throw new InterruptedException();
|
||||
var instr = function.body[codePtr];
|
||||
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);
|
||||
|
||||
debugState.breakpointNotifier.next(new BreakpointData(loc, ctx));
|
||||
debugCmd = debugState.commandNotifier.toAwaitable().await();
|
||||
if (debugCmd == DebugCommand.NORMAL) ctx.setData(STEPPING_TROUGH_KEY, false);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
var res = Runners.exec(debugCmd, instr, this, ctx);
|
||||
if (res != Runners.NO_RETURN) cleanup(ctx);
|
||||
return res;
|
||||
}
|
||||
catch (EngineException e) {
|
||||
cleanup(ctx);
|
||||
throw e.add(function.name, prevLoc);
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
cleanup(ctx);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public Object run(CallContext ctx) throws InterruptedException {
|
||||
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;
|
||||
|
||||
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) {
|
||||
this.args = args.clone();
|
||||
this.scope = new LocalScope(func.localsN, func.captures);
|
||||
this.scope.get(0).set(null, thisArg);
|
||||
var argsObj = new ArrayValue();
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
argsObj.set(i, args[i]);
|
||||
}
|
||||
this.scope.get(1).value = argsObj;
|
||||
|
||||
this.thisArg = thisArg;
|
||||
this.function = func;
|
||||
}
|
||||
}
|
||||
6
src/me/topchetoeu/jscript/engine/frame/ConvertHint.java
Normal file
6
src/me/topchetoeu/jscript/engine/frame/ConvertHint.java
Normal file
@@ -0,0 +1,6 @@
|
||||
package me.topchetoeu.jscript.engine.frame;
|
||||
|
||||
public enum ConvertHint {
|
||||
TOSTRING,
|
||||
VALUEOF,
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package me.topchetoeu.jscript.engine.frame;
|
||||
|
||||
public class InstructionResult {
|
||||
public final Object value;
|
||||
|
||||
public InstructionResult(Object value) {
|
||||
this.value = value;
|
||||
}
|
||||
}
|
||||
605
src/me/topchetoeu/jscript/engine/frame/Runners.java
Normal file
605
src/me/topchetoeu/jscript/engine/frame/Runners.java
Normal file
@@ -0,0 +1,605 @@
|
||||
package me.topchetoeu.jscript.engine.frame;
|
||||
|
||||
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.scope.ValueVariable;
|
||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.engine.values.CodeFunction;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.SignalValue;
|
||||
import me.topchetoeu.jscript.engine.values.Symbol;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
|
||||
public class Runners {
|
||||
public static final Object NO_RETURN = new Object();
|
||||
|
||||
public static Object execReturn(Instruction instr, CodeFrame frame, CallContext ctx) {
|
||||
frame.codePtr++;
|
||||
return frame.pop();
|
||||
}
|
||||
public static Object execSignal(Instruction instr, CodeFrame frame, CallContext ctx) {
|
||||
frame.codePtr++;
|
||||
return new SignalValue(instr.get(0));
|
||||
}
|
||||
public static Object execThrow(Instruction instr, CodeFrame frame, CallContext ctx) {
|
||||
throw new EngineException(frame.pop());
|
||||
}
|
||||
public static Object execThrowSyntax(Instruction instr, CodeFrame frame, CallContext ctx) {
|
||||
throw EngineException.ofSyntax((String)instr.get(0));
|
||||
}
|
||||
|
||||
private static Object call(DebugCommand state, CallContext ctx, Object func, Object thisArg, Object... args) throws InterruptedException {
|
||||
ctx.setData(CodeFrame.STOP_AT_START_KEY, state == DebugCommand.STEP_INTO);
|
||||
return Values.call(ctx, func, thisArg, args);
|
||||
}
|
||||
|
||||
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 func = frame.pop();
|
||||
var thisArg = frame.pop();
|
||||
|
||||
frame.push(call(state, ctx, func, thisArg, callArgs));
|
||||
|
||||
frame.codePtr++;
|
||||
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 funcObj = frame.pop();
|
||||
|
||||
if (Values.isFunction(funcObj) && Values.function(funcObj).special) {
|
||||
frame.push(call(state, ctx, funcObj, null, callArgs));
|
||||
}
|
||||
else {
|
||||
var proto = Values.getMember(ctx, funcObj, "prototype");
|
||||
var obj = new ObjectValue();
|
||||
obj.setPrototype(ctx, proto);
|
||||
call(state, ctx, funcObj, obj, callArgs);
|
||||
frame.push(obj);
|
||||
}
|
||||
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object execMakeVar(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
|
||||
var name = (String)instr.get(0);
|
||||
frame.function.globals.define(name);
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execDefProp(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
|
||||
var setter = frame.pop();
|
||||
var getter = frame.pop();
|
||||
var name = frame.pop();
|
||||
var obj = frame.pop();
|
||||
|
||||
if (getter != null && !Values.isFunction(getter)) throw EngineException.ofType("Getter must be a function or undefined.");
|
||||
if (setter != null && !Values.isFunction(setter)) throw EngineException.ofType("Setter must be a function or undefined.");
|
||||
if (!Values.isObject(obj)) throw EngineException.ofType("Property apply target must be an object.");
|
||||
Values.object(obj).defineProperty(name, Values.function(getter), Values.function(setter), false, false);
|
||||
|
||||
frame.push(obj);
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execInstanceof(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
|
||||
var type = frame.pop();
|
||||
var obj = frame.pop();
|
||||
|
||||
if (!Values.isPrimitive(type)) {
|
||||
var proto = Values.getMember(ctx, type, "prototype");
|
||||
frame.push(Values.isInstanceOf(ctx, obj, proto));
|
||||
}
|
||||
else {
|
||||
frame.push(false);
|
||||
}
|
||||
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execKeys(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
|
||||
var val = frame.pop();
|
||||
|
||||
var arr = new ObjectValue();
|
||||
var i = 0;
|
||||
|
||||
var members = Values.getMembers(ctx, val, false, false);
|
||||
Collections.reverse(members);
|
||||
for (var el : members) {
|
||||
if (el instanceof Symbol) continue;
|
||||
arr.defineProperty(i++, el);
|
||||
}
|
||||
|
||||
arr.defineProperty("length", i);
|
||||
|
||||
frame.push(arr);
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execLoadUndefined(Instruction instr, CodeFrame frame, CallContext ctx) {
|
||||
frame.push(null);
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execLoadValue(Instruction instr, CodeFrame frame, CallContext ctx) {
|
||||
frame.push(instr.get(0));
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execLoadVar(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
|
||||
var i = instr.get(0);
|
||||
|
||||
if (i instanceof String) frame.push(frame.function.globals.get(ctx, (String)i));
|
||||
else frame.push(frame.scope.get((int)i).get(ctx));
|
||||
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execLoadObj(Instruction instr, CodeFrame frame, CallContext ctx) {
|
||||
frame.push(new ObjectValue());
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execLoadGlob(Instruction instr, CodeFrame frame, CallContext ctx) {
|
||||
frame.push(frame.function.globals.obj);
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execLoadArr(Instruction instr, CodeFrame frame, CallContext ctx) {
|
||||
var res = new ArrayValue();
|
||||
res.setSize(instr.get(0));
|
||||
frame.push(res);
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execLoadFunc(Instruction instr, CodeFrame frame, CallContext ctx) {
|
||||
int n = (Integer)instr.get(0);
|
||||
int localsN = (Integer)instr.get(1);
|
||||
int len = (Integer)instr.get(2);
|
||||
var captures = new ValueVariable[instr.params.length - 3];
|
||||
|
||||
for (var i = 3; i < instr.params.length; i++) {
|
||||
captures[i - 3] = frame.scope.get(instr.get(i));
|
||||
}
|
||||
|
||||
var start = frame.codePtr + 1;
|
||||
var end = start + n - 1;
|
||||
var body = new Instruction[end - start];
|
||||
System.arraycopy(frame.function.body, start, body, 0, end - start);
|
||||
|
||||
var func = new CodeFunction("", localsN, len, frame.function.globals, captures, body);
|
||||
frame.push(func);
|
||||
|
||||
frame.codePtr += n;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execLoadMember(DebugCommand state, Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
|
||||
var key = frame.pop();
|
||||
var obj = frame.pop();
|
||||
|
||||
try {
|
||||
ctx.setData(CodeFrame.STOP_AT_START_KEY, state == DebugCommand.STEP_INTO);
|
||||
frame.push(Values.getMember(ctx, obj, key));
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
throw EngineException.ofType(e.getMessage());
|
||||
}
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execLoadKeyMember(DebugCommand state, Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
|
||||
frame.push(instr.get(0));
|
||||
return execLoadMember(state, instr, frame, ctx);
|
||||
}
|
||||
public static Object execLoadRegEx(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
|
||||
frame.push(ctx.engine().makeRegex(instr.get(0), instr.get(1)));
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object execDiscard(Instruction instr, CodeFrame frame, CallContext ctx) {
|
||||
frame.pop();
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execStoreMember(DebugCommand state, Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
|
||||
var val = frame.pop();
|
||||
var key = frame.pop();
|
||||
var obj = frame.pop();
|
||||
|
||||
ctx.setData(CodeFrame.STOP_AT_START_KEY, state == DebugCommand.STEP_INTO);
|
||||
if (!Values.setMember(ctx, obj, key, val)) throw EngineException.ofSyntax("Can't set member '" + key + "'.");
|
||||
if ((boolean)instr.get(0)) frame.push(val);
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execStoreVar(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
|
||||
var val = (boolean)instr.get(1) ? frame.peek() : frame.pop();
|
||||
var i = instr.get(0);
|
||||
|
||||
if (i instanceof String) frame.function.globals.set(ctx, (String)i, val);
|
||||
else frame.scope.get((int)i).set(ctx, val);
|
||||
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execStoreSelfFunc(Instruction instr, CodeFrame frame, CallContext ctx) {
|
||||
frame.scope.locals[(int)instr.get(0)].set(ctx, frame.function);
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object execJmp(Instruction instr, CodeFrame frame, CallContext ctx) {
|
||||
frame.codePtr += (int)instr.get(0);
|
||||
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;
|
||||
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;
|
||||
return NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object execIn(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
|
||||
var obj = frame.pop();
|
||||
var index = frame.pop();
|
||||
|
||||
frame.push(Values.hasMember(ctx, obj, index, false));
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execTypeof(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
|
||||
String name = instr.get(0);
|
||||
Object obj;
|
||||
|
||||
if (name != null) {
|
||||
if (frame.function.globals.has(ctx, name)) {
|
||||
obj = frame.function.globals.get(ctx, name);
|
||||
}
|
||||
else obj = null;
|
||||
}
|
||||
else obj = frame.pop();
|
||||
|
||||
frame.push(Values.type(obj));
|
||||
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execNop(Instruction instr, CodeFrame frame, CallContext ctx) {
|
||||
if (instr.is(0, "dbg_names")) {
|
||||
var names = new String[instr.params.length - 1];
|
||||
for (var i = 0; i < instr.params.length - 1; i++) {
|
||||
if (!(instr.params[i + 1] instanceof String)) throw EngineException.ofSyntax("NOP dbg_names instruction must specify only string parameters.");
|
||||
names[i] = (String)instr.params[i + 1];
|
||||
}
|
||||
frame.scope.setNames(names);
|
||||
}
|
||||
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object execDelete(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
|
||||
var key = frame.pop();
|
||||
var val = frame.pop();
|
||||
|
||||
if (!Values.deleteMember(ctx, val, key)) throw EngineException.ofSyntax("Can't delete member '" + key + "'.");
|
||||
frame.push(true);
|
||||
frame.codePtr++;
|
||||
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 execAnd(Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
|
||||
Object b = frame.pop(), a = 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.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object exec(DebugCommand state, Instruction instr, CodeFrame frame, CallContext ctx) throws InterruptedException {
|
||||
switch (instr.type) {
|
||||
case NOP: return execNop(instr, frame, ctx);
|
||||
case RETURN: return execReturn(instr, frame, ctx);
|
||||
case SIGNAL: return execSignal(instr, frame, ctx);
|
||||
case THROW: return execThrow(instr, frame, ctx);
|
||||
case THROW_SYNTAX: return execThrowSyntax(instr, frame, ctx);
|
||||
case CALL: return execCall(state, instr, frame, ctx);
|
||||
case CALL_NEW: return execCallNew(state, instr, frame, ctx);
|
||||
case TRY: return execTry(state, instr, frame, ctx);
|
||||
|
||||
case DUP: return execDup(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);
|
||||
case LOAD_ARR: return execLoadArr(instr, frame, ctx);
|
||||
case LOAD_FUNC: return execLoadFunc(instr, frame, ctx);
|
||||
case LOAD_MEMBER: return execLoadMember(state, instr, frame, ctx);
|
||||
case LOAD_VAL_MEMBER: return execLoadKeyMember(state, instr, frame, ctx);
|
||||
case LOAD_REGEX: return execLoadRegEx(instr, frame, ctx);
|
||||
case LOAD_GLOB: return execLoadGlob(instr, frame, ctx);
|
||||
|
||||
case DISCARD: return execDiscard(instr, frame, ctx);
|
||||
case STORE_MEMBER: return execStoreMember(state, instr, frame, ctx);
|
||||
case STORE_VAR: return execStoreVar(instr, frame, ctx);
|
||||
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);
|
||||
|
||||
default: throw EngineException.ofSyntax("Invalid instruction " + instr.type.name() + ".");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package me.topchetoeu.jscript.engine.modules;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import me.topchetoeu.jscript.polyfills.PolyfillEngine;
|
||||
|
||||
public class FileModuleProvider implements ModuleProvider {
|
||||
public File root;
|
||||
public final boolean allowOutside;
|
||||
|
||||
private boolean checkInside(Path modFile) {
|
||||
return modFile.toAbsolutePath().startsWith(root.toPath().toAbsolutePath());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Module getModule(File cwd, String name) {
|
||||
var realName = getRealName(cwd, name);
|
||||
if (realName == null) return null;
|
||||
var path = Path.of(realName + ".js").normalize();
|
||||
|
||||
try {
|
||||
var res = PolyfillEngine.streamToString(new FileInputStream(path.toFile()));
|
||||
return new Module(realName, path.toString(), res);
|
||||
}
|
||||
catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public String getRealName(File cwd, String name) {
|
||||
var path = Path.of(".", Path.of(cwd.toString(), name).normalize().toString());
|
||||
var fileName = path.getFileName().toString();
|
||||
if (fileName == null) return null;
|
||||
if (!fileName.equals("index") && path.toFile().isDirectory()) return getRealName(cwd, name + "/index");
|
||||
path = Path.of(path.toString() + ".js");
|
||||
if (!allowOutside && !checkInside(path)) return null;
|
||||
if (!path.toFile().isFile() || !path.toFile().canRead()) return null;
|
||||
var res = path.toString().replace('\\', '/');
|
||||
var i = res.lastIndexOf('.');
|
||||
return res.substring(0, i);
|
||||
}
|
||||
|
||||
public FileModuleProvider(File root, boolean allowOutside) {
|
||||
this.root = root.toPath().normalize().toFile();
|
||||
this.allowOutside = allowOutside;
|
||||
}
|
||||
}
|
||||
57
src/me/topchetoeu/jscript/engine/modules/Module.java
Normal file
57
src/me/topchetoeu/jscript/engine/modules/Module.java
Normal file
@@ -0,0 +1,57 @@
|
||||
package me.topchetoeu.jscript.engine.modules;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import me.topchetoeu.jscript.engine.CallContext;
|
||||
import me.topchetoeu.jscript.engine.CallContext.DataKey;
|
||||
import me.topchetoeu.jscript.engine.scope.Variable;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
import me.topchetoeu.jscript.interop.NativeSetter;
|
||||
|
||||
public class Module {
|
||||
public class ExportsVariable implements Variable {
|
||||
@Override
|
||||
public boolean readonly() { return false; }
|
||||
@Override
|
||||
public Object get(CallContext ctx) { return exports; }
|
||||
@Override
|
||||
public void set(CallContext ctx, Object val) { exports = val; }
|
||||
}
|
||||
|
||||
public static DataKey<Module> KEY = new DataKey<>();
|
||||
|
||||
public final String filename;
|
||||
public final String source;
|
||||
public final String name;
|
||||
private Object exports = new ObjectValue();
|
||||
private boolean executing = false;
|
||||
|
||||
@NativeGetter("name")
|
||||
public String name() { return name; }
|
||||
@NativeGetter("exports")
|
||||
public Object exports() { return exports; }
|
||||
@NativeSetter("exports")
|
||||
public void setExports(Object val) { exports = val; }
|
||||
|
||||
public void execute(CallContext ctx) throws InterruptedException {
|
||||
if (executing) return;
|
||||
|
||||
executing = true;
|
||||
var scope = ctx.engine().scope().globalChild();
|
||||
scope.define("module", true, this);
|
||||
scope.define("exports", new ExportsVariable());
|
||||
|
||||
var parent = new File(filename).getParentFile();
|
||||
if (parent == null) parent = new File(".");
|
||||
|
||||
ctx.engine().compile(scope, filename, source).call(ctx.copy().setData(KEY, this), null);
|
||||
executing = false;
|
||||
}
|
||||
|
||||
public Module(String name, String filename, String source) {
|
||||
this.name = name;
|
||||
this.filename = filename;
|
||||
this.source = source;
|
||||
}
|
||||
}
|
||||
80
src/me/topchetoeu/jscript/engine/modules/ModuleManager.java
Normal file
80
src/me/topchetoeu/jscript/engine/modules/ModuleManager.java
Normal file
@@ -0,0 +1,80 @@
|
||||
package me.topchetoeu.jscript.engine.modules;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.engine.CallContext;
|
||||
|
||||
public class ModuleManager {
|
||||
private final List<ModuleProvider> providers = new ArrayList<>();
|
||||
private final HashMap<String, Module> cache = new HashMap<>();
|
||||
public final FileModuleProvider files;
|
||||
|
||||
public void addProvider(ModuleProvider provider) {
|
||||
this.providers.add(provider);
|
||||
}
|
||||
|
||||
public boolean isCached(File cwd, String name) {
|
||||
name = name.replace("\\", "/");
|
||||
|
||||
// Absolute paths are forbidden
|
||||
if (name.startsWith("/")) return false;
|
||||
// Look for files if we have a relative path
|
||||
if (name.startsWith("../") || name.startsWith("./")) {
|
||||
var realName = files.getRealName(cwd, name);
|
||||
if (cache.containsKey(realName)) return true;
|
||||
else return false;
|
||||
}
|
||||
|
||||
for (var provider : providers) {
|
||||
var realName = provider.getRealName(cwd, name);
|
||||
if (realName == null) continue;
|
||||
if (cache.containsKey(realName)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
public Module tryLoad(CallContext ctx, String name) throws InterruptedException {
|
||||
name = name.replace('\\', '/');
|
||||
|
||||
var pcwd = Path.of(".");
|
||||
|
||||
if (ctx.hasData(Module.KEY)) {
|
||||
pcwd = Path.of(((Module)ctx.getData(Module.KEY)).filename).getParent();
|
||||
if (pcwd == null) pcwd = Path.of(".");
|
||||
}
|
||||
|
||||
|
||||
var cwd = pcwd.toFile();
|
||||
|
||||
if (name.startsWith("/")) return null;
|
||||
if (name.startsWith("../") || name.startsWith("./")) {
|
||||
var realName = files.getRealName(cwd, name);
|
||||
if (realName == null) return null;
|
||||
if (cache.containsKey(realName)) return cache.get(realName);
|
||||
var mod = files.getModule(cwd, name);
|
||||
// cache.put(mod.name(), mod);
|
||||
mod.execute(ctx);
|
||||
return mod;
|
||||
}
|
||||
|
||||
for (var provider : providers) {
|
||||
var realName = provider.getRealName(cwd, name);
|
||||
if (realName == null) continue;
|
||||
if (cache.containsKey(realName)) return cache.get(realName);
|
||||
var mod = provider.getModule(cwd, name);
|
||||
// cache.put(mod.name(), mod);
|
||||
mod.execute(ctx);
|
||||
return mod;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public ModuleManager(File root) {
|
||||
files = new FileModuleProvider(root, false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package me.topchetoeu.jscript.engine.modules;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public interface ModuleProvider {
|
||||
Module getModule(File cwd, String name);
|
||||
String getRealName(File cwd, String name);
|
||||
default boolean hasModule(File cwd, String name) { return getRealName(cwd, name) != null; }
|
||||
}
|
||||
83
src/me/topchetoeu/jscript/engine/scope/GlobalScope.java
Normal file
83
src/me/topchetoeu/jscript/engine/scope/GlobalScope.java
Normal file
@@ -0,0 +1,83 @@
|
||||
package me.topchetoeu.jscript.engine.scope;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import me.topchetoeu.jscript.engine.CallContext;
|
||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.engine.values.NativeFunction;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
|
||||
public class GlobalScope implements ScopeRecord {
|
||||
public final ObjectValue obj;
|
||||
|
||||
public boolean has(CallContext ctx, String name) throws InterruptedException {
|
||||
return obj.hasMember(ctx, name, false);
|
||||
}
|
||||
public Object getKey(String name) {
|
||||
return name;
|
||||
}
|
||||
|
||||
public GlobalScope globalChild() {
|
||||
return new GlobalScope(this);
|
||||
}
|
||||
public LocalScopeRecord child() {
|
||||
return new LocalScopeRecord(this);
|
||||
}
|
||||
|
||||
public Object define(String name) {
|
||||
try {
|
||||
if (obj.hasMember(null, name, true)) return name;
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return name;
|
||||
}
|
||||
obj.defineProperty(name, null);
|
||||
return name;
|
||||
}
|
||||
public void define(String name, Variable val) {
|
||||
obj.defineProperty(name,
|
||||
new NativeFunction("get " + name, (ctx, th, a) -> val.get(ctx)),
|
||||
new NativeFunction("set " + name, (ctx, th, args) -> { val.set(ctx, args.length > 0 ? args[0] : null); return null; }),
|
||||
true, true
|
||||
);
|
||||
}
|
||||
public void define(String name, boolean readonly, Object val) {
|
||||
obj.defineProperty(name, val, readonly, true, true);
|
||||
}
|
||||
public void define(String... names) {
|
||||
for (var n : names) define(n);
|
||||
}
|
||||
public void define(boolean readonly, FunctionValue val) {
|
||||
define(val.name, readonly, val);
|
||||
}
|
||||
|
||||
public Object get(CallContext ctx, String name) throws InterruptedException {
|
||||
if (!obj.hasMember(ctx, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist.");
|
||||
else return obj.getMember(ctx, name);
|
||||
}
|
||||
public void set(CallContext ctx, String name, Object val) throws InterruptedException {
|
||||
if (!obj.hasMember(ctx, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist.");
|
||||
if (!obj.setMember(ctx, name, val, false)) throw EngineException.ofSyntax("The global '" + name + "' is readonly.");
|
||||
}
|
||||
|
||||
public Set<String> keys() {
|
||||
var res = new HashSet<String>();
|
||||
|
||||
for (var key : keys()) {
|
||||
if (key instanceof String) res.add((String)key);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public GlobalScope() {
|
||||
this.obj = new ObjectValue();
|
||||
}
|
||||
public GlobalScope(GlobalScope parent) {
|
||||
this.obj = new ObjectValue();
|
||||
this.obj.setPrototype(null, parent.obj);
|
||||
}
|
||||
}
|
||||
58
src/me/topchetoeu/jscript/engine/scope/LocalScope.java
Normal file
58
src/me/topchetoeu/jscript/engine/scope/LocalScope.java
Normal file
@@ -0,0 +1,58 @@
|
||||
package me.topchetoeu.jscript.engine.scope;
|
||||
|
||||
public class LocalScope {
|
||||
private String[] names;
|
||||
private LocalScope parent;
|
||||
public final ValueVariable[] captures;
|
||||
public final ValueVariable[] locals;
|
||||
|
||||
public ValueVariable get(int i) {
|
||||
if (i >= 0) return locals[i];
|
||||
else return captures[~i];
|
||||
}
|
||||
|
||||
public String[] getNames() {
|
||||
var res = new String[locals.length];
|
||||
|
||||
for (var i = 0; i < locals.length; i++) {
|
||||
if (names == null || i >= names.length) res[i] = "local_" + i;
|
||||
else res[i] = names[i];
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
public void setNames(String[] names) {
|
||||
this.names = names;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return captures.length + locals.length;
|
||||
}
|
||||
|
||||
public GlobalScope toGlobal(GlobalScope global) {
|
||||
GlobalScope res;
|
||||
|
||||
if (parent == null) res = new GlobalScope(global);
|
||||
else res = new GlobalScope(parent.toGlobal(global));
|
||||
|
||||
var names = getNames();
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
res.define(names[i], locals[i]);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public LocalScope(int n, ValueVariable[] captures) {
|
||||
locals = new ValueVariable[n];
|
||||
this.captures = captures;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
locals[i] = new ValueVariable(false, null);
|
||||
}
|
||||
}
|
||||
public LocalScope(int n, ValueVariable[] captures, LocalScope parent) {
|
||||
this(n, captures);
|
||||
this.parent = parent;
|
||||
}
|
||||
}
|
||||
79
src/me/topchetoeu/jscript/engine/scope/LocalScopeRecord.java
Normal file
79
src/me/topchetoeu/jscript/engine/scope/LocalScopeRecord.java
Normal file
@@ -0,0 +1,79 @@
|
||||
package me.topchetoeu.jscript.engine.scope;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
import me.topchetoeu.jscript.engine.CallContext;
|
||||
|
||||
public class LocalScopeRecord implements ScopeRecord {
|
||||
public final LocalScopeRecord parent;
|
||||
public final GlobalScope global;
|
||||
|
||||
private final ArrayList<String> captures = new ArrayList<>();
|
||||
private final ArrayList<String> locals = new ArrayList<>();
|
||||
|
||||
public String[] locals() {
|
||||
return locals.toArray(String[]::new);
|
||||
}
|
||||
|
||||
public LocalScopeRecord child() {
|
||||
return new LocalScopeRecord(this, global);
|
||||
}
|
||||
|
||||
public int localsCount() {
|
||||
return locals.size();
|
||||
}
|
||||
public int capturesCount() {
|
||||
return captures.size();
|
||||
}
|
||||
|
||||
public int[] getCaptures() {
|
||||
var buff = new int[captures.size()];
|
||||
var i = 0;
|
||||
|
||||
for (var name : captures) {
|
||||
var index = parent.getKey(name);
|
||||
if (index instanceof Integer) buff[i++] = (int)index;
|
||||
}
|
||||
|
||||
var res = new int[i];
|
||||
System.arraycopy(buff, 0, res, 0, i);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public Object getKey(String name) {
|
||||
var capI = captures.indexOf(name);
|
||||
var locI = locals.indexOf(name);
|
||||
if (locI >= 0) return locI;
|
||||
if (capI >= 0) return ~capI;
|
||||
if (parent != null) {
|
||||
var res = parent.getKey(name);
|
||||
if (res != null && res instanceof Integer) {
|
||||
captures.add(name);
|
||||
return -captures.size();
|
||||
}
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
public boolean has(CallContext ctx, String name) throws InterruptedException {
|
||||
return
|
||||
global.has(ctx, name) ||
|
||||
locals.contains(name) ||
|
||||
parent != null && parent.has(ctx, name);
|
||||
}
|
||||
public Object define(String name) {
|
||||
if (locals.contains(name)) return locals.indexOf(name);
|
||||
locals.add(name);
|
||||
return locals.size() - 1;
|
||||
}
|
||||
|
||||
public LocalScopeRecord(GlobalScope global) {
|
||||
this.parent = null;
|
||||
this.global = global;
|
||||
}
|
||||
public LocalScopeRecord(LocalScopeRecord parent, GlobalScope global) {
|
||||
this.parent = parent;
|
||||
this.global = global;
|
||||
}
|
||||
}
|
||||
7
src/me/topchetoeu/jscript/engine/scope/ScopeRecord.java
Normal file
7
src/me/topchetoeu/jscript/engine/scope/ScopeRecord.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package me.topchetoeu.jscript.engine.scope;
|
||||
|
||||
public interface ScopeRecord {
|
||||
public Object getKey(String name);
|
||||
public Object define(String name);
|
||||
public LocalScopeRecord child();
|
||||
}
|
||||
28
src/me/topchetoeu/jscript/engine/scope/ValueVariable.java
Normal file
28
src/me/topchetoeu/jscript/engine/scope/ValueVariable.java
Normal file
@@ -0,0 +1,28 @@
|
||||
package me.topchetoeu.jscript.engine.scope;
|
||||
|
||||
import me.topchetoeu.jscript.engine.CallContext;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
|
||||
public class ValueVariable implements Variable {
|
||||
public boolean readonly;
|
||||
public Object value;
|
||||
|
||||
@Override
|
||||
public boolean readonly() { return readonly; }
|
||||
|
||||
@Override
|
||||
public Object get(CallContext ctx) {
|
||||
return value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void set(CallContext ctx, Object val) {
|
||||
if (readonly) return;
|
||||
this.value = Values.normalize(val);
|
||||
}
|
||||
|
||||
public ValueVariable(boolean readonly, Object val) {
|
||||
this.readonly = readonly;
|
||||
this.value = val;
|
||||
}
|
||||
}
|
||||
9
src/me/topchetoeu/jscript/engine/scope/Variable.java
Normal file
9
src/me/topchetoeu/jscript/engine/scope/Variable.java
Normal file
@@ -0,0 +1,9 @@
|
||||
package me.topchetoeu.jscript.engine.scope;
|
||||
|
||||
import me.topchetoeu.jscript.engine.CallContext;
|
||||
|
||||
public interface Variable {
|
||||
Object get(CallContext ctx) throws InterruptedException;
|
||||
default boolean readonly() { return true; }
|
||||
default void set(CallContext ctx, Object val) throws InterruptedException { }
|
||||
}
|
||||
160
src/me/topchetoeu/jscript/engine/values/ArrayValue.java
Normal file
160
src/me/topchetoeu/jscript/engine/values/ArrayValue.java
Normal file
@@ -0,0 +1,160 @@
|
||||
package me.topchetoeu.jscript.engine.values;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.engine.CallContext;
|
||||
|
||||
public class ArrayValue extends ObjectValue {
|
||||
private static final Object EMPTY = new Object();
|
||||
private final ArrayList<Object> values = new ArrayList<>();
|
||||
|
||||
public int size() { return values.size(); }
|
||||
public boolean setSize(int val) {
|
||||
if (val < 0) return false;
|
||||
while (size() > val) {
|
||||
values.remove(values.size() - 1);
|
||||
}
|
||||
while (size() < val) {
|
||||
values.add(EMPTY);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public Object get(int i) {
|
||||
if (i < 0 || i >= values.size()) return null;
|
||||
var res = values.get(i);
|
||||
if (res == EMPTY) return null;
|
||||
else return res;
|
||||
}
|
||||
public void set(int i, Object val) {
|
||||
if (i < 0) return;
|
||||
|
||||
while (values.size() <= i) {
|
||||
values.add(EMPTY);
|
||||
}
|
||||
|
||||
values.set(i, Values.normalize(val));
|
||||
}
|
||||
public boolean has(int i) {
|
||||
return i >= 0 && i < values.size() && values.get(i) != EMPTY;
|
||||
}
|
||||
public void remove(int i) {
|
||||
if (i < 0 || i >= values.size()) return;
|
||||
values.set(i, EMPTY);
|
||||
}
|
||||
public void shrink(int n) {
|
||||
if (n > values.size()) values.clear();
|
||||
else {
|
||||
for (int i = 0; i < n && values.size() > 0; i++) {
|
||||
values.remove(values.size() - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void sort(Comparator<Object> comparator) {
|
||||
values.sort((a, b) -> {
|
||||
var _a = 0;
|
||||
var _b = 0;
|
||||
|
||||
if (a == null) _a = 1;
|
||||
if (a == EMPTY) _a = 2;
|
||||
|
||||
if (b == null) _b = 1;
|
||||
if (b == EMPTY) _b = 2;
|
||||
|
||||
if (Integer.compare(_a, _b) != 0) return Integer.compare(_a, _b);
|
||||
|
||||
return comparator.compare(a, b);
|
||||
});
|
||||
}
|
||||
|
||||
public Object[] toArray() {
|
||||
Object[] res = new Object[values.size()];
|
||||
|
||||
for (var i = 0; i < values.size(); i++) {
|
||||
if (values.get(i) == EMPTY) res[i] = null;
|
||||
else res[i] = values.get(i);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object getField(CallContext ctx, Object key) throws InterruptedException {
|
||||
if (key.equals("length")) return values.size();
|
||||
if (key instanceof Number) {
|
||||
var i = ((Number)key).doubleValue();
|
||||
if (i >= 0 && i - Math.floor(i) == 0) {
|
||||
return get((int)i);
|
||||
}
|
||||
}
|
||||
|
||||
return super.getField(ctx, key);
|
||||
}
|
||||
@Override
|
||||
protected boolean setField(CallContext ctx, Object key, Object val) throws InterruptedException {
|
||||
if (key.equals("length")) {
|
||||
return setSize((int)Values.toNumber(ctx, val));
|
||||
}
|
||||
if (key instanceof Number) {
|
||||
var i = Values.number(key);
|
||||
if (i >= 0 && i - Math.floor(i) == 0) {
|
||||
set((int)i, val);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return super.setField(ctx, key, val);
|
||||
}
|
||||
@Override
|
||||
protected boolean hasField(CallContext ctx, Object key) throws InterruptedException {
|
||||
if (key.equals("length")) return true;
|
||||
if (key instanceof Number) {
|
||||
var i = Values.number(key);
|
||||
if (i >= 0 && i - Math.floor(i) == 0) {
|
||||
return has((int)i);
|
||||
}
|
||||
}
|
||||
|
||||
return super.hasField(ctx, key);
|
||||
}
|
||||
@Override
|
||||
protected void deleteField(CallContext ctx, Object key) throws InterruptedException {
|
||||
if (key instanceof Number) {
|
||||
var i = Values.number(key);
|
||||
if (i >= 0 && i - Math.floor(i) == 0) {
|
||||
remove((int)i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
super.deleteField(ctx, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Object> keys(boolean includeNonEnumerable) {
|
||||
var res = super.keys(includeNonEnumerable);
|
||||
for (var i = 0; i < size(); i++) {
|
||||
if (has(i)) res.add(i);
|
||||
}
|
||||
if (includeNonEnumerable) res.add("length");
|
||||
return res;
|
||||
}
|
||||
|
||||
public ArrayValue() {
|
||||
super(PlaceholderProto.ARRAY);
|
||||
nonEnumerableSet.add("length");
|
||||
nonConfigurableSet.add("length");
|
||||
}
|
||||
public ArrayValue(Object ...values) {
|
||||
this();
|
||||
for (var i = 0; i < values.length; i++) this.values.add(values[i]);
|
||||
}
|
||||
|
||||
public static ArrayValue of(Collection<Object> values) {
|
||||
return new ArrayValue(values.toArray(Object[]::new));
|
||||
}
|
||||
}
|
||||
50
src/me/topchetoeu/jscript/engine/values/CodeFunction.java
Normal file
50
src/me/topchetoeu/jscript/engine/values/CodeFunction.java
Normal file
@@ -0,0 +1,50 @@
|
||||
package me.topchetoeu.jscript.engine.values;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Instruction.Type;
|
||||
import me.topchetoeu.jscript.engine.CallContext;
|
||||
import me.topchetoeu.jscript.engine.frame.CodeFrame;
|
||||
import me.topchetoeu.jscript.engine.scope.GlobalScope;
|
||||
import me.topchetoeu.jscript.engine.scope.ValueVariable;
|
||||
|
||||
public class CodeFunction extends FunctionValue {
|
||||
public final int localsN;
|
||||
public final int length;
|
||||
public final Instruction[] body;
|
||||
public final LinkedHashMap<Location, Integer> breakableLocToIndex = new LinkedHashMap<>();
|
||||
public final LinkedHashMap<Integer, Location> breakableIndexToLoc = new LinkedHashMap<>();
|
||||
public final ValueVariable[] captures;
|
||||
public final GlobalScope globals;
|
||||
|
||||
public Location loc() {
|
||||
for (var instr : body) {
|
||||
if (instr.location != null) return instr.location;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object call(CallContext ctx, Object thisArg, Object... args) throws InterruptedException {
|
||||
return new CodeFrame(thisArg, args, this).run(ctx);
|
||||
}
|
||||
|
||||
public CodeFunction(String name, int localsN, int length, GlobalScope globals, ValueVariable[] captures, Instruction[] body) {
|
||||
super(name, length);
|
||||
this.captures = captures;
|
||||
this.globals = globals;
|
||||
this.localsN = localsN;
|
||||
this.length = length;
|
||||
this.body = body;
|
||||
|
||||
for (var i = 0; i < body.length; i++) {
|
||||
if (body[i].type == Type.LOAD_FUNC) i += (int)body[i].get(0);
|
||||
if (body[i].debugged && body[i].location != null) {
|
||||
breakableLocToIndex.put(body[i].location, i);
|
||||
breakableIndexToLoc.put(i, body[i].location);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
70
src/me/topchetoeu/jscript/engine/values/FunctionValue.java
Normal file
70
src/me/topchetoeu/jscript/engine/values/FunctionValue.java
Normal file
@@ -0,0 +1,70 @@
|
||||
package me.topchetoeu.jscript.engine.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.engine.CallContext;
|
||||
|
||||
public abstract class FunctionValue extends ObjectValue {
|
||||
public String name = "";
|
||||
public boolean special = false;
|
||||
public int length;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "function(...) { ... }";
|
||||
}
|
||||
|
||||
public abstract Object call(CallContext ctx, Object thisArg, Object... args) throws InterruptedException;
|
||||
public Object call(CallContext ctx) throws InterruptedException {
|
||||
return call(ctx, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object getField(CallContext ctx, Object key) throws InterruptedException {
|
||||
if (key.equals("name")) return name;
|
||||
if (key.equals("length")) return length;
|
||||
return super.getField(ctx, key);
|
||||
}
|
||||
@Override
|
||||
protected boolean setField(CallContext ctx, Object key, Object val) throws InterruptedException {
|
||||
if (key.equals("name")) name = Values.toString(ctx, val);
|
||||
else if (key.equals("length")) length = (int)Values.toNumber(ctx, val);
|
||||
else return super.setField(ctx, key, val);
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
protected boolean hasField(CallContext ctx, Object key) throws InterruptedException {
|
||||
if (key.equals("name")) return true;
|
||||
if (key.equals("length")) return true;
|
||||
return super.hasField(ctx, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Object> keys(boolean includeNonEnumerable) {
|
||||
var res = super.keys(includeNonEnumerable);
|
||||
if (includeNonEnumerable) {
|
||||
res.add("name");
|
||||
res.add("length");
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
public FunctionValue(String name, int length) {
|
||||
super(PlaceholderProto.FUNCTION);
|
||||
|
||||
if (name == null) name = "";
|
||||
this.length = length;
|
||||
this.name = name;
|
||||
|
||||
nonConfigurableSet.add("name");
|
||||
nonEnumerableSet.add("name");
|
||||
nonWritableSet.add("length");
|
||||
nonConfigurableSet.add("length");
|
||||
nonEnumerableSet.add("length");
|
||||
|
||||
var proto = new ObjectValue();
|
||||
proto.defineProperty("constructor", this, true, false, false);
|
||||
this.defineProperty("prototype", proto, true, false, false);
|
||||
}
|
||||
}
|
||||
|
||||
21
src/me/topchetoeu/jscript/engine/values/NativeFunction.java
Normal file
21
src/me/topchetoeu/jscript/engine/values/NativeFunction.java
Normal file
@@ -0,0 +1,21 @@
|
||||
package me.topchetoeu.jscript.engine.values;
|
||||
|
||||
import me.topchetoeu.jscript.engine.CallContext;
|
||||
|
||||
public class NativeFunction extends FunctionValue {
|
||||
public static interface NativeFunctionRunner {
|
||||
Object run(CallContext ctx, Object thisArg, Object[] values) throws InterruptedException;
|
||||
}
|
||||
|
||||
public final NativeFunctionRunner action;
|
||||
|
||||
@Override
|
||||
public Object call(CallContext ctx, Object thisArg, Object... args) throws InterruptedException {
|
||||
return action.run(ctx, thisArg, args);
|
||||
}
|
||||
|
||||
public NativeFunction(String name, NativeFunctionRunner action) {
|
||||
super(name, 0);
|
||||
this.action = action;
|
||||
}
|
||||
}
|
||||
19
src/me/topchetoeu/jscript/engine/values/NativeWrapper.java
Normal file
19
src/me/topchetoeu/jscript/engine/values/NativeWrapper.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package me.topchetoeu.jscript.engine.values;
|
||||
|
||||
import me.topchetoeu.jscript.engine.CallContext;
|
||||
|
||||
public class NativeWrapper extends ObjectValue {
|
||||
private static final Object NATIVE_PROTO = new Object();
|
||||
public final Object wrapped;
|
||||
|
||||
@Override
|
||||
public ObjectValue getPrototype(CallContext ctx) throws InterruptedException {
|
||||
if (prototype == NATIVE_PROTO) return ctx.engine.getPrototype(wrapped.getClass());
|
||||
else return super.getPrototype(ctx);
|
||||
}
|
||||
|
||||
public NativeWrapper(Object wrapped) {
|
||||
this.wrapped = wrapped;
|
||||
prototype = NATIVE_PROTO;
|
||||
}
|
||||
}
|
||||
331
src/me/topchetoeu/jscript/engine/values/ObjectValue.java
Normal file
331
src/me/topchetoeu/jscript/engine/values/ObjectValue.java
Normal file
@@ -0,0 +1,331 @@
|
||||
package me.topchetoeu.jscript.engine.values;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import me.topchetoeu.jscript.engine.CallContext;
|
||||
|
||||
public class ObjectValue {
|
||||
public static enum PlaceholderProto {
|
||||
NONE,
|
||||
OBJECT,
|
||||
ARRAY,
|
||||
FUNCTION,
|
||||
ERROR,
|
||||
SYNTAX_ERROR,
|
||||
TYPE_ERROR,
|
||||
RANGE_ERROR,
|
||||
}
|
||||
public static enum State {
|
||||
NORMAL,
|
||||
NO_EXTENSIONS,
|
||||
SEALED,
|
||||
FROZEN,
|
||||
}
|
||||
|
||||
public static record Property(FunctionValue getter, FunctionValue setter) {}
|
||||
|
||||
private static final Object OBJ_PROTO = new Object();
|
||||
private static final Object ARR_PROTO = new Object();
|
||||
private static final Object FUNC_PROTO = new Object();
|
||||
private static final Object ERR_PROTO = new Object();
|
||||
private static final Object SYNTAX_ERR_PROTO = new Object();
|
||||
private static final Object TYPE_ERR_PROTO = new Object();
|
||||
private static final Object RANGE_ERR_PROTO = new Object();
|
||||
|
||||
protected Object prototype;
|
||||
|
||||
public State state = State.NORMAL;
|
||||
public HashMap<Object, Object> values = new HashMap<>();
|
||||
public HashMap<Object, Property> properties = new HashMap<>();
|
||||
public HashSet<Object> nonWritableSet = new HashSet<>();
|
||||
public HashSet<Object> nonConfigurableSet = new HashSet<>();
|
||||
public HashSet<Object> nonEnumerableSet = new HashSet<>();
|
||||
|
||||
public final boolean memberWritable(Object key) {
|
||||
if (state == State.FROZEN) return false;
|
||||
return !nonWritableSet.contains(key);
|
||||
}
|
||||
public final boolean memberConfigurable(Object key) {
|
||||
if (state == State.SEALED || state == State.FROZEN) return false;
|
||||
return !nonConfigurableSet.contains(key);
|
||||
}
|
||||
public final boolean memberEnumerable(Object key) {
|
||||
return !nonEnumerableSet.contains(key);
|
||||
}
|
||||
public final boolean extensible() {
|
||||
return state == State.NORMAL;
|
||||
}
|
||||
|
||||
public final void preventExtensions() {
|
||||
if (state == State.NORMAL) state = State.NO_EXTENSIONS;
|
||||
}
|
||||
public final void seal() {
|
||||
if (state == State.NORMAL || state == State.NO_EXTENSIONS) state = State.SEALED;
|
||||
}
|
||||
public final void freeze() {
|
||||
state = State.FROZEN;
|
||||
}
|
||||
|
||||
public final boolean defineProperty(Object key, Object val, boolean writable, boolean configurable, boolean enumerable) {
|
||||
key = Values.normalize(key); val = Values.normalize(val);
|
||||
boolean reconfigured =
|
||||
writable != memberWritable(key) ||
|
||||
configurable != memberConfigurable(key) ||
|
||||
enumerable != memberEnumerable(key);
|
||||
|
||||
if (!reconfigured) {
|
||||
if (!memberWritable(key)) {
|
||||
var a = values.get(key);
|
||||
var b = val;
|
||||
if (a == null || b == null) return a == null && b == null;
|
||||
return a == b || a.equals(b);
|
||||
}
|
||||
values.put(key, val);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (
|
||||
properties.containsKey(key) &&
|
||||
values.get(key) == val &&
|
||||
!reconfigured
|
||||
) return true;
|
||||
|
||||
if (!extensible() && !values.containsKey(key) && !properties.containsKey(key)) return false;
|
||||
if (!memberConfigurable(key))
|
||||
return false;
|
||||
|
||||
nonWritableSet.remove(key);
|
||||
nonEnumerableSet.remove(key);
|
||||
properties.remove(key);
|
||||
values.remove(key);
|
||||
|
||||
if (!writable) nonWritableSet.add(key);
|
||||
if (!configurable) nonConfigurableSet.add(key);
|
||||
if (!enumerable) nonEnumerableSet.add(key);
|
||||
|
||||
values.put(key, val);
|
||||
return true;
|
||||
}
|
||||
public final boolean defineProperty(Object key, Object val) {
|
||||
return defineProperty(Values.normalize(key), Values.normalize(val), true, true, true);
|
||||
}
|
||||
public final boolean defineProperty(Object key, FunctionValue getter, FunctionValue setter, boolean configurable, boolean enumerable) {
|
||||
key = Values.normalize(key);
|
||||
if (
|
||||
properties.containsKey(key) &&
|
||||
properties.get(key).getter == getter &&
|
||||
properties.get(key).setter == setter &&
|
||||
!configurable == nonConfigurableSet.contains(key) &&
|
||||
!enumerable == nonEnumerableSet.contains(key)
|
||||
) return true;
|
||||
if (!extensible() && !values.containsKey(key) && !properties.containsKey(key)) return false;
|
||||
if (!memberConfigurable(key)) return false;
|
||||
|
||||
nonWritableSet.remove(key);
|
||||
nonEnumerableSet.remove(key);
|
||||
properties.remove(key);
|
||||
values.remove(key);
|
||||
|
||||
if (!configurable) nonConfigurableSet.add(key);
|
||||
if (!enumerable) nonEnumerableSet.add(key);
|
||||
|
||||
properties.put(key, new Property(getter, setter));
|
||||
return true;
|
||||
}
|
||||
|
||||
public ObjectValue getPrototype(CallContext ctx) throws InterruptedException {
|
||||
try {
|
||||
if (prototype == OBJ_PROTO) return ctx.engine().objectProto();
|
||||
if (prototype == ARR_PROTO) return ctx.engine().arrayProto();
|
||||
if (prototype == FUNC_PROTO) return ctx.engine().functionProto();
|
||||
if (prototype == ERR_PROTO) return ctx.engine().errorProto();
|
||||
if (prototype == RANGE_ERR_PROTO) return ctx.engine().rangeErrorProto();
|
||||
if (prototype == SYNTAX_ERR_PROTO) return ctx.engine().syntaxErrorProto();
|
||||
if (prototype == TYPE_ERR_PROTO) return ctx.engine().typeErrorProto();
|
||||
}
|
||||
catch (NullPointerException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (ObjectValue)prototype;
|
||||
}
|
||||
public final boolean setPrototype(CallContext ctx, Object val) {
|
||||
val = Values.normalize(val);
|
||||
|
||||
if (!extensible()) return false;
|
||||
if (val == null || val == Values.NULL) prototype = null;
|
||||
else if (Values.isObject(val)) {
|
||||
var obj = Values.object(val);
|
||||
|
||||
if (ctx != null && ctx.engine() != null) {
|
||||
if (obj == ctx.engine().objectProto()) prototype = OBJ_PROTO;
|
||||
else if (obj == ctx.engine().arrayProto()) prototype = ARR_PROTO;
|
||||
else if (obj == ctx.engine().functionProto()) prototype = FUNC_PROTO;
|
||||
else if (obj == ctx.engine().errorProto()) prototype = ERR_PROTO;
|
||||
else if (obj == ctx.engine().syntaxErrorProto()) prototype = SYNTAX_ERR_PROTO;
|
||||
else if (obj == ctx.engine().typeErrorProto()) prototype = TYPE_ERR_PROTO;
|
||||
else if (obj == ctx.engine().rangeErrorProto()) prototype = RANGE_ERR_PROTO;
|
||||
else prototype = obj;
|
||||
}
|
||||
else prototype = obj;
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public final boolean setPrototype(PlaceholderProto val) {
|
||||
if (!extensible()) return false;
|
||||
switch (val) {
|
||||
case OBJECT: prototype = OBJ_PROTO; break;
|
||||
case FUNCTION: prototype = FUNC_PROTO; break;
|
||||
case ARRAY: prototype = ARR_PROTO; break;
|
||||
case ERROR: prototype = ERR_PROTO; break;
|
||||
case SYNTAX_ERROR: prototype = SYNTAX_ERR_PROTO; break;
|
||||
case TYPE_ERROR: prototype = TYPE_ERR_PROTO; break;
|
||||
case RANGE_ERROR: prototype = RANGE_ERR_PROTO; break;
|
||||
case NONE: prototype = null; break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected Property getProperty(CallContext ctx, Object key) throws InterruptedException {
|
||||
if (properties.containsKey(key)) return properties.get(key);
|
||||
var proto = getPrototype(ctx);
|
||||
if (proto != null) return proto.getProperty(ctx, key);
|
||||
else return null;
|
||||
}
|
||||
protected Object getField(CallContext ctx, Object key) throws InterruptedException {
|
||||
if (values.containsKey(key)) return values.get(key);
|
||||
var proto = getPrototype(ctx);
|
||||
if (proto != null) return proto.getField(ctx, key);
|
||||
else return null;
|
||||
}
|
||||
protected boolean setField(CallContext ctx, Object key, Object val) throws InterruptedException {
|
||||
values.put(key, val);
|
||||
return true;
|
||||
}
|
||||
protected void deleteField(CallContext ctx, Object key) throws InterruptedException {
|
||||
values.remove(key);
|
||||
}
|
||||
protected boolean hasField(CallContext ctx, Object key) throws InterruptedException {
|
||||
return values.containsKey(key);
|
||||
}
|
||||
|
||||
public final Object getMember(CallContext ctx, Object key, Object thisArg) throws InterruptedException {
|
||||
key = Values.normalize(key);
|
||||
|
||||
if (key.equals("__proto__")) {
|
||||
var res = getPrototype(ctx);
|
||||
return res == null ? Values.NULL : res;
|
||||
}
|
||||
|
||||
var prop = getProperty(ctx, key);
|
||||
|
||||
if (prop != null) {
|
||||
if (prop.getter == null) return null;
|
||||
else return prop.getter.call(ctx, Values.normalize(thisArg));
|
||||
}
|
||||
else return getField(ctx, key);
|
||||
}
|
||||
public final Object getMember(CallContext ctx, Object key) throws InterruptedException {
|
||||
return getMember(ctx, key, this);
|
||||
}
|
||||
|
||||
public final boolean setMember(CallContext ctx, Object key, Object val, Object thisArg, boolean onlyProps) throws InterruptedException {
|
||||
key = Values.normalize(key); val = Values.normalize(val);
|
||||
|
||||
var prop = getProperty(ctx, key);
|
||||
if (prop != null) {
|
||||
if (prop.setter == null) return false;
|
||||
prop.setter.call(ctx, Values.normalize(thisArg), val);
|
||||
return true;
|
||||
}
|
||||
else if (onlyProps) return false;
|
||||
else if (!extensible() && !values.containsKey(key)) return false;
|
||||
else if (key == null) {
|
||||
values.put(key, val);
|
||||
return true;
|
||||
}
|
||||
else if (key.equals("__proto__")) return setPrototype(ctx, val);
|
||||
else if (nonWritableSet.contains(key)) return false;
|
||||
else return setField(ctx, key, val);
|
||||
}
|
||||
public final boolean setMember(CallContext ctx, Object key, Object val, boolean onlyProps) throws InterruptedException {
|
||||
return setMember(ctx, Values.normalize(key), Values.normalize(val), this, onlyProps);
|
||||
}
|
||||
|
||||
public final boolean hasMember(CallContext ctx, Object key, boolean own) throws InterruptedException {
|
||||
key = Values.normalize(key);
|
||||
|
||||
if (key != null && key.equals("__proto__")) return true;
|
||||
if (hasField(ctx, key)) return true;
|
||||
if (properties.containsKey(key)) return true;
|
||||
if (own) return false;
|
||||
return prototype != null && getPrototype(ctx).hasMember(ctx, key, own);
|
||||
}
|
||||
public final boolean deleteMember(CallContext ctx, Object key) throws InterruptedException {
|
||||
key = Values.normalize(key);
|
||||
|
||||
if (!memberConfigurable(key)) return false;
|
||||
properties.remove(key);
|
||||
nonWritableSet.remove(key);
|
||||
nonEnumerableSet.remove(key);
|
||||
deleteField(ctx, key);
|
||||
return true;
|
||||
}
|
||||
|
||||
public final ObjectValue getMemberDescriptor(CallContext ctx, Object key) throws InterruptedException {
|
||||
key = Values.normalize(key);
|
||||
|
||||
var prop = properties.get(key);
|
||||
var res = new ObjectValue();
|
||||
|
||||
res.defineProperty("configurable", memberConfigurable(key));
|
||||
res.defineProperty("enumerable", memberEnumerable(key));
|
||||
|
||||
if (prop != null) {
|
||||
res.defineProperty("get", prop.getter);
|
||||
res.defineProperty("set", prop.setter);
|
||||
}
|
||||
else if (hasField(ctx, key)) {
|
||||
res.defineProperty("value", values.get(key));
|
||||
res.defineProperty("writable", memberWritable(key));
|
||||
}
|
||||
else return null;
|
||||
return res;
|
||||
}
|
||||
|
||||
public List<Object> keys(boolean includeNonEnumerable) {
|
||||
var res = new ArrayList<Object>();
|
||||
|
||||
for (var key : values.keySet()) {
|
||||
if (nonEnumerableSet.contains(key) && !includeNonEnumerable) continue;
|
||||
res.add(key);
|
||||
}
|
||||
for (var key : properties.keySet()) {
|
||||
if (nonEnumerableSet.contains(key) && !includeNonEnumerable) continue;
|
||||
res.add(key);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public ObjectValue(Map<?, ?> values) {
|
||||
this(PlaceholderProto.OBJECT);
|
||||
for (var el : values.entrySet()) {
|
||||
defineProperty(el.getKey(), el.getValue());
|
||||
}
|
||||
}
|
||||
public ObjectValue(PlaceholderProto proto) {
|
||||
nonConfigurableSet.add("__proto__");
|
||||
nonEnumerableSet.add("__proto__");
|
||||
setPrototype(proto);
|
||||
}
|
||||
public ObjectValue() {
|
||||
this(PlaceholderProto.OBJECT);
|
||||
}
|
||||
}
|
||||
17
src/me/topchetoeu/jscript/engine/values/SignalValue.java
Normal file
17
src/me/topchetoeu/jscript/engine/values/SignalValue.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package me.topchetoeu.jscript.engine.values;
|
||||
|
||||
public final class SignalValue {
|
||||
public final String data;
|
||||
|
||||
public SignalValue(String data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public static boolean isSignal(Object signal, String value) {
|
||||
if (!(signal instanceof SignalValue)) return false;
|
||||
var val = ((SignalValue)signal).data;
|
||||
|
||||
if (value.endsWith("*")) return val.startsWith(value.substring(0, value.length() - 1));
|
||||
else return val.equals(value);
|
||||
}
|
||||
}
|
||||
15
src/me/topchetoeu/jscript/engine/values/Symbol.java
Normal file
15
src/me/topchetoeu/jscript/engine/values/Symbol.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package me.topchetoeu.jscript.engine.values;
|
||||
|
||||
public final class Symbol {
|
||||
public final String value;
|
||||
|
||||
public Symbol(String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (value == null) return "Symbol";
|
||||
else return "Symbol(" + value + ")";
|
||||
}
|
||||
}
|
||||
608
src/me/topchetoeu/jscript/engine/values/Values.java
Normal file
608
src/me/topchetoeu/jscript/engine/values/Values.java
Normal file
@@ -0,0 +1,608 @@
|
||||
package me.topchetoeu.jscript.engine.values;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import me.topchetoeu.jscript.engine.CallContext;
|
||||
import me.topchetoeu.jscript.engine.frame.ConvertHint;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
|
||||
public class Values {
|
||||
public static final Object NULL = new Object();
|
||||
|
||||
public static boolean isObject(Object val) { return val instanceof ObjectValue; }
|
||||
public static boolean isFunction(Object val) { return val instanceof FunctionValue; }
|
||||
public static boolean isArray(Object val) { return val instanceof ArrayValue; }
|
||||
public static boolean isWrapper(Object val) { return val instanceof NativeWrapper; }
|
||||
public static boolean isWrapper(Object val, Class<?> clazz) {
|
||||
if (!isWrapper(val)) return false;
|
||||
var res = (NativeWrapper)val;
|
||||
return res != null && clazz.isInstance(res.wrapped);
|
||||
}
|
||||
public static boolean isNan(Object val) { return val instanceof Number && Double.isNaN(number(val)); }
|
||||
|
||||
public static ObjectValue object(Object val) {
|
||||
if (val instanceof ObjectValue) return (ObjectValue)val;
|
||||
else return null;
|
||||
}
|
||||
public static ArrayValue array(Object val) {
|
||||
if (val instanceof ArrayValue) return (ArrayValue)val;
|
||||
else return null;
|
||||
}
|
||||
public static FunctionValue function(Object val) {
|
||||
if (val instanceof FunctionValue) return (FunctionValue)val;
|
||||
else return null;
|
||||
}
|
||||
public static double number(Object val) {
|
||||
if (val instanceof Number) return ((Number)val).doubleValue();
|
||||
else return Double.NaN;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T wrapper(Object val, Class<T> clazz) {
|
||||
if (!isWrapper(val)) return null;
|
||||
|
||||
var res = (NativeWrapper)val;
|
||||
if (res != null && clazz.isInstance(res.wrapped)) return (T)res.wrapped;
|
||||
else return null;
|
||||
}
|
||||
|
||||
public static String type(Object val) {
|
||||
if (val == null) return "undefined";
|
||||
if (val instanceof String) return "string";
|
||||
if (val instanceof Number) return "number";
|
||||
if (val instanceof Boolean) return "boolean";
|
||||
if (val instanceof Symbol) return "symbol";
|
||||
if (val instanceof FunctionValue) return "function";
|
||||
return "object";
|
||||
}
|
||||
|
||||
private static Object tryCallConvertFunc(CallContext ctx, Object obj, String name) throws InterruptedException {
|
||||
var func = getMember(ctx, obj, name);
|
||||
|
||||
if (func != null) {
|
||||
var res = ((FunctionValue)func).call(ctx, obj);
|
||||
if (isPrimitive(res)) return res;
|
||||
}
|
||||
|
||||
throw EngineException.ofType("Value couldn't be converted to a primitive.");
|
||||
}
|
||||
|
||||
public static boolean isPrimitive(Object obj) {
|
||||
return
|
||||
obj instanceof Number ||
|
||||
obj instanceof String ||
|
||||
obj instanceof Boolean ||
|
||||
obj instanceof Symbol ||
|
||||
obj instanceof SignalValue ||
|
||||
obj == null ||
|
||||
obj == NULL;
|
||||
}
|
||||
|
||||
public static Object toPrimitive(CallContext ctx, Object obj, ConvertHint hint) throws InterruptedException {
|
||||
obj = normalize(obj);
|
||||
if (isPrimitive(obj)) return obj;
|
||||
|
||||
var first = hint == ConvertHint.VALUEOF ? "valueOf" : "toString";
|
||||
var second = hint == ConvertHint.VALUEOF ? "toString" : "valueOf";
|
||||
|
||||
if (ctx != null) {
|
||||
try {
|
||||
return tryCallConvertFunc(ctx, obj, first);
|
||||
}
|
||||
catch (EngineException unused) {
|
||||
return tryCallConvertFunc(ctx, obj, second);
|
||||
}
|
||||
}
|
||||
|
||||
throw EngineException.ofType("Value couldn't be converted to a primitive.");
|
||||
}
|
||||
public static boolean toBoolean(Object obj) {
|
||||
if (obj == NULL || obj == null) return false;
|
||||
if (obj instanceof Number && number(obj) == 0) return false;
|
||||
if (obj instanceof String && ((String)obj).equals("")) return false;
|
||||
if (obj instanceof Boolean) return (Boolean)obj;
|
||||
return true;
|
||||
}
|
||||
public static double toNumber(CallContext ctx, Object obj) throws InterruptedException {
|
||||
var val = toPrimitive(ctx, obj, ConvertHint.VALUEOF);
|
||||
|
||||
if (val instanceof Number) return number(obj);
|
||||
if (val instanceof Boolean) return ((Boolean)obj) ? 1 : 0;
|
||||
if (val instanceof String) {
|
||||
try {
|
||||
return Double.parseDouble((String)val);
|
||||
}
|
||||
catch (NumberFormatException e) { }
|
||||
}
|
||||
return Double.NaN;
|
||||
}
|
||||
public static String toString(CallContext ctx, Object obj) throws InterruptedException {
|
||||
var val = toPrimitive(ctx, obj, ConvertHint.VALUEOF);
|
||||
|
||||
if (val == null) return "undefined";
|
||||
if (val == NULL) return "null";
|
||||
|
||||
if (val instanceof Number) {
|
||||
var d = number(obj);
|
||||
if (d == Double.NEGATIVE_INFINITY) return "-Infinity";
|
||||
if (d == Double.POSITIVE_INFINITY) return "Infinity";
|
||||
if (Double.isNaN(d)) return "NaN";
|
||||
return BigDecimal.valueOf(d).stripTrailingZeros().toPlainString();
|
||||
}
|
||||
if (val instanceof Boolean) return (Boolean)val ? "true" : "false";
|
||||
if (val instanceof String) return (String)val;
|
||||
if (val instanceof Symbol) return ((Symbol)val).toString();
|
||||
if (val instanceof SignalValue) return "[signal '" + ((SignalValue)val).data + "']";
|
||||
|
||||
return "Unknown value";
|
||||
}
|
||||
|
||||
public static Object add(CallContext ctx, Object a, Object b) throws InterruptedException {
|
||||
if (a instanceof String || b instanceof String) return toString(ctx, a) + toString(ctx, b);
|
||||
else return toNumber(ctx, a) + toNumber(ctx, b);
|
||||
}
|
||||
public static double subtract(CallContext ctx, Object a, Object b) throws InterruptedException {
|
||||
return toNumber(ctx, a) - toNumber(ctx, b);
|
||||
}
|
||||
public static double multiply(CallContext ctx, Object a, Object b) throws InterruptedException {
|
||||
return toNumber(ctx, a) * toNumber(ctx, b);
|
||||
}
|
||||
public static double divide(CallContext ctx, Object a, Object b) throws InterruptedException {
|
||||
return toNumber(ctx, a) / toNumber(ctx, b);
|
||||
}
|
||||
public static double modulo(CallContext ctx, Object a, Object b) throws InterruptedException {
|
||||
return toNumber(ctx, a) % toNumber(ctx, b);
|
||||
}
|
||||
|
||||
public static double negative(CallContext ctx, Object obj) throws InterruptedException {
|
||||
return -toNumber(ctx, obj);
|
||||
}
|
||||
|
||||
public static int and(CallContext ctx, Object a, Object b) throws InterruptedException {
|
||||
return (int)toNumber(ctx, a) & (int)toNumber(ctx, b);
|
||||
}
|
||||
public static int or(CallContext ctx, Object a, Object b) throws InterruptedException {
|
||||
return (int)toNumber(ctx, a) | (int)toNumber(ctx, b);
|
||||
}
|
||||
public static int xor(CallContext ctx, Object a, Object b) throws InterruptedException {
|
||||
return (int)toNumber(ctx, a) ^ (int)toNumber(ctx, b);
|
||||
}
|
||||
public static int bitwiseNot(CallContext ctx, Object obj) throws InterruptedException {
|
||||
return ~(int)toNumber(ctx, obj);
|
||||
}
|
||||
|
||||
public static int shiftLeft(CallContext ctx, Object a, Object b) throws InterruptedException {
|
||||
return (int)toNumber(ctx, a) << (int)toNumber(ctx, b);
|
||||
}
|
||||
public static int shiftRight(CallContext ctx, Object a, Object b) throws InterruptedException {
|
||||
return (int)toNumber(ctx, a) >> (int)toNumber(ctx, b);
|
||||
}
|
||||
public static long unsignedShiftRight(CallContext ctx, Object a, Object b) throws InterruptedException {
|
||||
long _a = (long)toNumber(ctx, a);
|
||||
long _b = (long)toNumber(ctx, b);
|
||||
|
||||
if (_a < 0) _a += 0x100000000l;
|
||||
if (_b < 0) _b += 0x100000000l;
|
||||
return _a >>> _b;
|
||||
}
|
||||
|
||||
public static int compare(CallContext ctx, Object a, Object b) throws InterruptedException {
|
||||
a = toPrimitive(ctx, a, ConvertHint.VALUEOF);
|
||||
b = toPrimitive(ctx, b, ConvertHint.VALUEOF);
|
||||
|
||||
if (a instanceof String && b instanceof String) return ((String)a).compareTo((String)b);
|
||||
else return Double.compare(toNumber(ctx, a), toNumber(ctx, b));
|
||||
}
|
||||
|
||||
public static boolean not(Object obj) {
|
||||
return !toBoolean(obj);
|
||||
}
|
||||
|
||||
public static boolean isInstanceOf(CallContext ctx, Object obj, Object proto) throws InterruptedException {
|
||||
if (obj == null || obj == NULL || proto == null || proto == NULL) return false;
|
||||
var val = getPrototype(ctx, obj);
|
||||
|
||||
while (val != null) {
|
||||
if (val.equals(proto)) return true;
|
||||
val = val.getPrototype(ctx);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
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.");
|
||||
if (obj == NULL) throw new IllegalArgumentException("Tried to access member of null.");
|
||||
if (isObject(obj)) return object(obj).getMember(ctx, key);
|
||||
|
||||
if (obj instanceof String && key instanceof Number) {
|
||||
var i = number(key);
|
||||
var s = (String)obj;
|
||||
if (i >= 0 && i < s.length() && i - Math.floor(i) == 0) {
|
||||
return s.charAt((int)i) + "";
|
||||
}
|
||||
}
|
||||
|
||||
var proto = getPrototype(ctx, obj);
|
||||
|
||||
if (proto == null) return key.equals("__proto__") ? NULL : null;
|
||||
else if (key != null && key.equals("__proto__")) return proto;
|
||||
else return proto.getMember(ctx, key, obj);
|
||||
}
|
||||
public static boolean setMember(CallContext ctx, Object obj, Object key, Object val) throws InterruptedException {
|
||||
obj = normalize(obj); key = normalize(key); val = normalize(val);
|
||||
if (obj == null) throw EngineException.ofType("Tried to access member of undefined.");
|
||||
if (obj == NULL) throw EngineException.ofType("Tried to access member of null.");
|
||||
if (key.equals("__proto__")) return setPrototype(ctx, obj, val);
|
||||
if (isObject(obj)) return object(obj).setMember(ctx, key, val, false);
|
||||
|
||||
var proto = getPrototype(ctx, obj);
|
||||
return proto.setMember(ctx, key, val, obj, true);
|
||||
}
|
||||
public static boolean hasMember(CallContext ctx, Object obj, Object key, boolean own) throws InterruptedException {
|
||||
if (obj == null || obj == NULL) return false;
|
||||
obj = normalize(obj); key = normalize(key);
|
||||
|
||||
if (key.equals("__proto__")) return true;
|
||||
if (isObject(obj)) return object(obj).hasMember(ctx, key, own);
|
||||
|
||||
if (obj instanceof String && key instanceof Number) {
|
||||
var i = number(key);
|
||||
var s = (String)obj;
|
||||
if (i >= 0 && i < s.length() && i - Math.floor(i) == 0) return true;
|
||||
}
|
||||
|
||||
if (own) return false;
|
||||
|
||||
var proto = getPrototype(ctx, obj);
|
||||
return proto != null && proto.hasMember(ctx, key, own);
|
||||
}
|
||||
public static boolean deleteMember(CallContext ctx, Object obj, Object key) throws InterruptedException {
|
||||
if (obj == null || obj == NULL) return false;
|
||||
obj = normalize(obj); key = normalize(key);
|
||||
|
||||
if (isObject(obj)) return object(obj).deleteMember(ctx, key);
|
||||
else return false;
|
||||
}
|
||||
public static ObjectValue getPrototype(CallContext ctx, Object obj) throws InterruptedException {
|
||||
if (obj == null || obj == NULL) return null;
|
||||
obj = normalize(obj);
|
||||
if (isObject(obj)) return object(obj).getPrototype(ctx);
|
||||
if (ctx == null) return null;
|
||||
|
||||
if (obj instanceof String) return ctx.engine().stringProto();
|
||||
else if (obj instanceof Number) return ctx.engine().numberProto();
|
||||
else if (obj instanceof Boolean) return ctx.engine().booleanProto();
|
||||
else if (obj instanceof Symbol) return ctx.engine().symbolProto();
|
||||
|
||||
return null;
|
||||
}
|
||||
public static boolean setPrototype(CallContext ctx, Object obj, Object proto) throws InterruptedException {
|
||||
obj = normalize(obj); proto = normalize(proto);
|
||||
return isObject(obj) && object(obj).setPrototype(ctx, proto);
|
||||
}
|
||||
public static List<Object> getMembers(CallContext ctx, Object obj, boolean own, boolean includeNonEnumerable) throws InterruptedException {
|
||||
List<Object> res = new ArrayList<>();
|
||||
|
||||
if (isObject(obj)) res = object(obj).keys(includeNonEnumerable);
|
||||
if (obj instanceof String) {
|
||||
for (var i = 0; i < ((String)obj).length(); i++) res.add((double)i);
|
||||
}
|
||||
|
||||
if (!own) {
|
||||
var proto = getPrototype(ctx, obj);
|
||||
|
||||
while (proto != null) {
|
||||
res.addAll(proto.keys(includeNonEnumerable));
|
||||
proto = proto.getPrototype(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
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.");
|
||||
return function(func).call(ctx, thisArg, args);
|
||||
}
|
||||
|
||||
public static boolean strictEquals(Object a, Object b) {
|
||||
a = normalize(a); b = normalize(b);
|
||||
|
||||
if (a == null || b == null) return a == null && b == null;
|
||||
if (isNan(a) || isNan(b)) return false;
|
||||
if (a instanceof Number && number(a) == -0.) a = 0.;
|
||||
if (b instanceof Number && number(b) == -0.) b = 0.;
|
||||
|
||||
return a == b || a.equals(b);
|
||||
}
|
||||
public static boolean looseEqual(CallContext ctx, Object a, Object b) throws InterruptedException {
|
||||
a = normalize(a); b = normalize(b);
|
||||
|
||||
// In loose equality, null is equivalent to undefined
|
||||
if (a == NULL) a = null;
|
||||
if (b == NULL) b = null;
|
||||
|
||||
if (a == null || b == null) return a == null && b == null;
|
||||
// If both are objects, just compare their references
|
||||
if (!isPrimitive(a) && !isPrimitive(b)) return a == b;
|
||||
|
||||
// Convert values to primitives
|
||||
a = toPrimitive(ctx, a, ConvertHint.VALUEOF);
|
||||
b = toPrimitive(ctx, b, ConvertHint.VALUEOF);
|
||||
|
||||
// Compare symbols by reference
|
||||
if (a instanceof Symbol || b instanceof Symbol) return a == b;
|
||||
if (a instanceof Boolean || b instanceof Boolean) return toBoolean(a) == toBoolean(b);
|
||||
if (a instanceof Number || b instanceof Number) return strictEquals(toNumber(ctx, a), toNumber(ctx, b));
|
||||
|
||||
// Default to strings
|
||||
return toString(ctx, a).equals(toString(ctx, b));
|
||||
}
|
||||
|
||||
public static Object normalize(Object val) {
|
||||
if (val instanceof Number) return number(val);
|
||||
if (isPrimitive(val) || val instanceof ObjectValue) return val;
|
||||
if (val instanceof Character) return val + "";
|
||||
|
||||
if (val instanceof Map) {
|
||||
var res = new ObjectValue();
|
||||
|
||||
for (var entry : ((Map<?, ?>)val).entrySet()) {
|
||||
res.defineProperty(entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
if (val instanceof Iterable) {
|
||||
var res = new ArrayValue();
|
||||
|
||||
for (var entry : ((Iterable<?>)val)) {
|
||||
res.set(res.size(), entry);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
return new NativeWrapper(val);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T convert(CallContext ctx, Object obj, Class<T> clazz) throws InterruptedException {
|
||||
if (clazz == Void.class) return null;
|
||||
if (clazz == null || clazz == Object.class) return (T)obj;
|
||||
|
||||
var err = new IllegalArgumentException("Cannot convert '%s' to '%s'.".formatted(type(obj), clazz.getName()));
|
||||
|
||||
if (obj instanceof NativeWrapper) {
|
||||
var res = ((NativeWrapper)obj).wrapped;
|
||||
if (clazz.isInstance(res)) return (T)res;
|
||||
}
|
||||
|
||||
if (obj instanceof ArrayValue) {
|
||||
var raw = array(obj).toArray();
|
||||
|
||||
if (clazz.isAssignableFrom(ArrayList.class)) {
|
||||
var res = new ArrayList<>();
|
||||
for (var i = 0; i < raw.length; i++) res.add(convert(ctx, raw[i], Object.class));
|
||||
return (T)new ArrayList<>(res);
|
||||
}
|
||||
if (clazz.isAssignableFrom(HashSet.class)) {
|
||||
var res = new HashSet<>();
|
||||
for (var i = 0; i < raw.length; i++) res.add(convert(ctx, raw[i], Object.class));
|
||||
return (T)new HashSet<>(res);
|
||||
}
|
||||
if (clazz.isArray()) {
|
||||
Object res = Array.newInstance(clazz.arrayType(), raw.length);
|
||||
for (var i = 0; i < raw.length; i++) Array.set(res, i, convert(ctx, raw[i], Object.class));
|
||||
return (T)res;
|
||||
}
|
||||
}
|
||||
|
||||
if (obj instanceof ObjectValue && clazz.isAssignableFrom(HashMap.class)) {
|
||||
var res = new HashMap<>();
|
||||
for (var el : object(obj).values.entrySet()) res.put(
|
||||
convert(ctx, el.getKey(), null),
|
||||
convert(ctx, el.getValue(), null)
|
||||
);
|
||||
return (T)res;
|
||||
}
|
||||
|
||||
if (clazz == String.class) return (T)toString(ctx, obj);
|
||||
if (clazz == Boolean.class || clazz == Boolean.TYPE) return (T)(Boolean)toBoolean(obj);
|
||||
if (clazz == Byte.class || clazz == byte.class) return (T)(Byte)(byte)toNumber(ctx, obj);
|
||||
if (clazz == Integer.class || clazz == int.class) return (T)(Integer)(int)toNumber(ctx, obj);
|
||||
if (clazz == Long.class || clazz == long.class) return (T)(Long)(long)toNumber(ctx, obj);
|
||||
if (clazz == Short.class || clazz == short.class) return (T)(Short)(short)toNumber(ctx, obj);
|
||||
if (clazz == Float.class || clazz == float.class) return (T)(Float)(float)toNumber(ctx, obj);
|
||||
if (clazz == Double.class || clazz == double.class) return (T)(Double)toNumber(ctx, obj);
|
||||
|
||||
if (clazz == Character.class || clazz == char.class) {
|
||||
if (obj instanceof Number) return (T)(Character)(char)number(obj);
|
||||
else if (obj == NULL) throw new IllegalArgumentException("Cannot convert null to character.");
|
||||
else if (obj == null) throw new IllegalArgumentException("Cannot convert undefined to character.");
|
||||
else {
|
||||
var res = toString(ctx, obj);
|
||||
if (res.length() == 0) throw new IllegalArgumentException("Cannot convert empty string to character.");
|
||||
else return (T)(Character)res.charAt(0);
|
||||
}
|
||||
}
|
||||
|
||||
if (obj == null) return null;
|
||||
if (clazz.isInstance(obj)) return (T)obj;
|
||||
|
||||
throw err;
|
||||
}
|
||||
|
||||
public static Iterable<Object> toJavaIterable(CallContext ctx, Object obj) throws InterruptedException {
|
||||
return () -> {
|
||||
try {
|
||||
var constr = getMember(ctx, ctx.engine().symbolProto(), "constructor");
|
||||
var symbol = getMember(ctx, constr, "iterator");
|
||||
|
||||
var iteratorFunc = getMember(ctx, obj, symbol);
|
||||
if (!isFunction(iteratorFunc)) return Collections.emptyIterator();
|
||||
var iterator = getMember(ctx, call(ctx, iteratorFunc, obj), "next");
|
||||
if (!isFunction(iterator)) return Collections.emptyIterator();
|
||||
var iterable = obj;
|
||||
|
||||
return new Iterator<Object>() {
|
||||
private Object value = null;
|
||||
public boolean consumed = true;
|
||||
private FunctionValue next = function(iterator);
|
||||
|
||||
private void loadNext() throws InterruptedException {
|
||||
if (next == null) value = null;
|
||||
else if (consumed) {
|
||||
var curr = object(next.call(ctx, iterable));
|
||||
if (curr == null) { next = null; value = null; }
|
||||
if (toBoolean(curr.getMember(ctx, "done"))) { next = null; value = null; }
|
||||
else {
|
||||
this.value = curr.getMember(ctx, "value");
|
||||
consumed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
try {
|
||||
loadNext();
|
||||
return next != null;
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public Object next() {
|
||||
try {
|
||||
loadNext();
|
||||
var res = value;
|
||||
value = null;
|
||||
consumed = true;
|
||||
return res;
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
return null;
|
||||
}
|
||||
catch (IllegalArgumentException | NullPointerException e) {
|
||||
return Collections.emptyIterator();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static ObjectValue fromJavaIterable(CallContext ctx, Iterable<?> iterable) throws InterruptedException {
|
||||
var res = new ObjectValue();
|
||||
var it = iterable.iterator();
|
||||
|
||||
try {
|
||||
var key = getMember(ctx, getMember(ctx, ctx.engine().symbolProto(), "constructor"), "iterable");
|
||||
res.defineProperty(key, new NativeFunction("", (_ctx, thisArg, args) -> fromJavaIterable(ctx, iterable)));
|
||||
}
|
||||
catch (IllegalArgumentException | NullPointerException e) { }
|
||||
|
||||
res.defineProperty("next", new NativeFunction("", (_ctx, _th, _args) -> {
|
||||
if (!it.hasNext()) return new ObjectValue(Map.of("done", true));
|
||||
else return new ObjectValue(Map.of("value", it.next()));
|
||||
}));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private static void printValue(CallContext ctx, Object val, HashSet<Object> passed, int tab) throws InterruptedException {
|
||||
if (passed.contains(val)) {
|
||||
System.out.print("[circular]");
|
||||
return;
|
||||
}
|
||||
|
||||
var printed = true;
|
||||
|
||||
if (val instanceof FunctionValue) {
|
||||
System.out.print("function ");
|
||||
var name = Values.getMember(ctx, val, "name");
|
||||
if (name != null) System.out.print(Values.toString(ctx, name));
|
||||
System.out.print("(...)");
|
||||
var loc = val instanceof CodeFunction ? ((CodeFunction)val).loc() : null;
|
||||
|
||||
if (loc != null) System.out.print(" @ " + loc);
|
||||
}
|
||||
else if (val instanceof ArrayValue) {
|
||||
System.out.print("[");
|
||||
var obj = ((ArrayValue)val);
|
||||
for (int i = 0; i < obj.size(); i++) {
|
||||
if (i != 0) System.out.print(", ");
|
||||
else System.out.print(" ");
|
||||
if (obj.has(i)) printValue(ctx, obj.get(i), passed, tab);
|
||||
else System.out.print(", ");
|
||||
}
|
||||
System.out.print(" ] ");
|
||||
}
|
||||
else if (val instanceof NativeWrapper) {
|
||||
var obj = ((NativeWrapper)val).wrapped;
|
||||
System.out.print("Native " + obj.toString() + " ");
|
||||
}
|
||||
else printed = false;
|
||||
|
||||
if (val instanceof ObjectValue) {
|
||||
if (tab > 3) {
|
||||
System.out.print("{...}");
|
||||
return;
|
||||
}
|
||||
passed.add(val);
|
||||
|
||||
var obj = (ObjectValue)val;
|
||||
if (obj.values.size() + obj.properties.size() == 0) {
|
||||
if (!printed) System.out.println("{}");
|
||||
}
|
||||
else {
|
||||
System.out.println("{");
|
||||
|
||||
for (var el : obj.values.entrySet()) {
|
||||
for (int i = 0; i < tab + 1; i++) System.out.print(" ");
|
||||
printValue(ctx, el.getKey(), passed, tab + 1);
|
||||
System.out.print(": ");
|
||||
printValue(ctx, el.getValue(), passed, tab + 1);
|
||||
System.out.println(",");
|
||||
}
|
||||
for (var el : obj.properties.entrySet()) {
|
||||
for (int i = 0; i < tab + 1; i++) System.out.print(" ");
|
||||
printValue(ctx, el.getKey(), passed, tab + 1);
|
||||
System.out.println(": [prop],");
|
||||
}
|
||||
|
||||
for (int i = 0; i < tab; i++) System.out.print(" ");
|
||||
System.out.print("}");
|
||||
|
||||
passed.remove(val);
|
||||
}
|
||||
}
|
||||
else if (val == null) System.out.print("undefined");
|
||||
else if (val == Values.NULL) System.out.print("null");
|
||||
else if (val instanceof String) System.out.print("'" + val + "'");
|
||||
else System.out.print(Values.toString(ctx, val));
|
||||
}
|
||||
public static void printValue(CallContext ctx, Object val) throws InterruptedException {
|
||||
printValue(ctx, val, new HashSet<>(), 0);
|
||||
}
|
||||
}
|
||||
25
src/me/topchetoeu/jscript/events/Awaitable.java
Normal file
25
src/me/topchetoeu/jscript/events/Awaitable.java
Normal file
@@ -0,0 +1,25 @@
|
||||
package me.topchetoeu.jscript.events;
|
||||
|
||||
public interface Awaitable<T> {
|
||||
T await() throws FinishedException, InterruptedException;
|
||||
|
||||
default Observable<T> toObservable() {
|
||||
return sub -> {
|
||||
var thread = new Thread(() -> {
|
||||
try {
|
||||
sub.next(await());
|
||||
sub.finish();
|
||||
}
|
||||
catch (InterruptedException | FinishedException e) {
|
||||
sub.finish();
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
sub.error(e);
|
||||
}
|
||||
}, "Awaiter");
|
||||
thread.start();
|
||||
|
||||
return () -> thread.interrupt();
|
||||
};
|
||||
}
|
||||
}
|
||||
34
src/me/topchetoeu/jscript/events/DataNotifier.java
Normal file
34
src/me/topchetoeu/jscript/events/DataNotifier.java
Normal file
@@ -0,0 +1,34 @@
|
||||
package me.topchetoeu.jscript.events;
|
||||
|
||||
public class DataNotifier<T> implements Awaitable<T> {
|
||||
private Notifier notifier = new Notifier();
|
||||
private boolean isErr;
|
||||
private T val;
|
||||
private RuntimeException err;
|
||||
|
||||
public void error(RuntimeException t) {
|
||||
err = t;
|
||||
isErr = true;
|
||||
notifier.next();
|
||||
}
|
||||
public void error(Throwable t) {
|
||||
error(new RuntimeException(t));
|
||||
}
|
||||
public void next(T val) {
|
||||
this.val = val;
|
||||
isErr = false;
|
||||
notifier.next();
|
||||
}
|
||||
public T await() throws InterruptedException {
|
||||
notifier.await();
|
||||
|
||||
try {
|
||||
if (isErr) throw err;
|
||||
else return val;
|
||||
}
|
||||
finally {
|
||||
this.err = null;
|
||||
this.val = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
49
src/me/topchetoeu/jscript/events/Event.java
Normal file
49
src/me/topchetoeu/jscript/events/Event.java
Normal file
@@ -0,0 +1,49 @@
|
||||
package me.topchetoeu.jscript.events;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
||||
public class Event<T> implements Observer<T>, Observable<T> {
|
||||
private HashSet<Observer<T>> handlers = new HashSet<>();
|
||||
|
||||
public Handle on(Observer<T> handler) {
|
||||
if (handlers == null) {
|
||||
handler.finish();
|
||||
return () -> {};
|
||||
}
|
||||
|
||||
handlers.add(handler);
|
||||
return () -> {
|
||||
if (handlers == null) return;
|
||||
handlers.remove(handler);
|
||||
};
|
||||
}
|
||||
|
||||
public boolean isFinished() {
|
||||
return handlers == null;
|
||||
}
|
||||
|
||||
public void next(T value) {
|
||||
if (handlers == null) throw new IllegalStateException("Cannot use a finished event.");
|
||||
for (var handler : handlers) {
|
||||
handler.next(value);
|
||||
}
|
||||
}
|
||||
public void error(RuntimeException value) {
|
||||
if (handlers == null) throw new IllegalStateException("Cannot use a finished event.");
|
||||
for (var handler : handlers) {
|
||||
handler.error(value);
|
||||
}
|
||||
|
||||
handlers.clear();
|
||||
handlers = null;
|
||||
}
|
||||
public void finish() {
|
||||
if (handlers == null) throw new IllegalStateException("Cannot use a finished event.");
|
||||
for (var handler : handlers) {
|
||||
handler.finish();
|
||||
}
|
||||
|
||||
handlers.clear();
|
||||
handlers = null;
|
||||
}
|
||||
}
|
||||
7
src/me/topchetoeu/jscript/events/FinishedException.java
Normal file
7
src/me/topchetoeu/jscript/events/FinishedException.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package me.topchetoeu.jscript.events;
|
||||
|
||||
public class FinishedException extends RuntimeException {
|
||||
public FinishedException() {
|
||||
super("The observable has ended.");
|
||||
}
|
||||
}
|
||||
5
src/me/topchetoeu/jscript/events/Handle.java
Normal file
5
src/me/topchetoeu/jscript/events/Handle.java
Normal file
@@ -0,0 +1,5 @@
|
||||
package me.topchetoeu.jscript.events;
|
||||
|
||||
public interface Handle {
|
||||
void free();
|
||||
}
|
||||
14
src/me/topchetoeu/jscript/events/Notifier.java
Normal file
14
src/me/topchetoeu/jscript/events/Notifier.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package me.topchetoeu.jscript.events;
|
||||
|
||||
public class Notifier {
|
||||
private boolean ok = false;
|
||||
|
||||
public synchronized void next() {
|
||||
ok = true;
|
||||
notifyAll();
|
||||
}
|
||||
public synchronized void await() throws InterruptedException {
|
||||
while (!ok) wait();
|
||||
ok = false;
|
||||
}
|
||||
}
|
||||
75
src/me/topchetoeu/jscript/events/Observable.java
Normal file
75
src/me/topchetoeu/jscript/events/Observable.java
Normal file
@@ -0,0 +1,75 @@
|
||||
package me.topchetoeu.jscript.events;
|
||||
|
||||
public interface Observable<T> {
|
||||
Handle on(Observer<T> val);
|
||||
|
||||
default Handle once(Observer<T> observer) {
|
||||
// Java is fucking retarded
|
||||
var unhandler = new Handle[1];
|
||||
var shouldUnsub = new boolean[1];
|
||||
|
||||
unhandler[0] = on(new Observer<>() {
|
||||
public void next(T data) {
|
||||
observer.next(data);
|
||||
if (unhandler[0] == null) shouldUnsub[0] = true;
|
||||
else unhandler[0].free();
|
||||
}
|
||||
public void error(RuntimeException err) {
|
||||
observer.error(err);
|
||||
if (unhandler[0] == null) shouldUnsub[0] = true;
|
||||
else unhandler[0].free();
|
||||
}
|
||||
public void finish() {
|
||||
observer.finish();
|
||||
if (unhandler[0] == null) shouldUnsub[0] = true;
|
||||
else unhandler[0].free();
|
||||
}
|
||||
});
|
||||
|
||||
if (shouldUnsub[0]) {
|
||||
unhandler[0].free();
|
||||
return () -> {};
|
||||
}
|
||||
else return unhandler[0];
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
default Awaitable<T> toAwaitable() {
|
||||
return () -> {
|
||||
var notifier = new Notifier();
|
||||
var valRef = new Object[1];
|
||||
var isErrRef = new boolean[1];
|
||||
|
||||
once(new Observer<>() {
|
||||
public void next(T data) {
|
||||
valRef[0] = data;
|
||||
notifier.next();
|
||||
}
|
||||
public void error(RuntimeException err) {
|
||||
isErrRef[0] = true;
|
||||
valRef[0] = err;
|
||||
notifier.next();
|
||||
}
|
||||
public void finish() {
|
||||
isErrRef[0] = true;
|
||||
valRef[0] = new FinishedException();
|
||||
notifier.next();
|
||||
}
|
||||
});
|
||||
|
||||
notifier.await();
|
||||
|
||||
if (isErrRef[0]) throw (RuntimeException)valRef[0];
|
||||
else return (T)valRef[0];
|
||||
};
|
||||
}
|
||||
default Observable<T> encapsulate() {
|
||||
return val -> on(val);
|
||||
}
|
||||
|
||||
default <T2> Observable<T2> pipe(Pipe<T, T2> pipe) {
|
||||
return sub -> on(pipe.apply(sub));
|
||||
}
|
||||
default WarmObservable<T> warmUp() {
|
||||
return new WarmObservable<>(this);
|
||||
}
|
||||
}
|
||||
7
src/me/topchetoeu/jscript/events/Observer.java
Normal file
7
src/me/topchetoeu/jscript/events/Observer.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package me.topchetoeu.jscript.events;
|
||||
|
||||
public interface Observer<T> {
|
||||
public void next(T data);
|
||||
public default void error(RuntimeException err) {}
|
||||
public default void finish() { }
|
||||
}
|
||||
59
src/me/topchetoeu/jscript/events/Pipe.java
Normal file
59
src/me/topchetoeu/jscript/events/Pipe.java
Normal file
@@ -0,0 +1,59 @@
|
||||
package me.topchetoeu.jscript.events;
|
||||
|
||||
public interface Pipe<T, T2> {
|
||||
Observer<T> apply(Observer<T2> obs);
|
||||
// void next(T val, Observer<T2> target);
|
||||
// default void error(RuntimeException err, Observer<T2> target) {
|
||||
// target.error(err);
|
||||
// }
|
||||
// default void finish(Observer<T2> target) {
|
||||
// target.finish();
|
||||
// }
|
||||
|
||||
public static interface MapFunc<T1, T2> {
|
||||
T2 map(T1 val);
|
||||
}
|
||||
|
||||
public static <T1, T2> Pipe<T1, T2> map(MapFunc<T1, T2> func) {
|
||||
return o -> val -> o.next(func.map(val));
|
||||
}
|
||||
public static <T> Pipe<T, T> filter(MapFunc<T, Boolean> func) {
|
||||
return o -> val -> {
|
||||
if (func.map(val)) o.next(val);
|
||||
};
|
||||
}
|
||||
public static <T> Pipe<T, T> skip(int n) {
|
||||
var i = new int[1];
|
||||
|
||||
return target -> val -> {
|
||||
if (i[0] >= n) target.next(val);
|
||||
else i[0]++;
|
||||
};
|
||||
}
|
||||
public static <T> Pipe<T, T> limit(int n) {
|
||||
return target -> new Observer<T>() {
|
||||
private int i;
|
||||
|
||||
public void next(T val) {
|
||||
if (i >= n) target.finish();
|
||||
else {
|
||||
target.next(val);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
public void error(RuntimeException err) {
|
||||
if (i < n) target.error(err);
|
||||
}
|
||||
public void finish() {
|
||||
if (i < n) target.finish();
|
||||
}
|
||||
};
|
||||
}
|
||||
public static <T> Pipe<T, T> first() {
|
||||
return limit(1);
|
||||
}
|
||||
|
||||
public static <T> Pipe<Observable<T>, T> merge() {
|
||||
return target -> val -> val.on(target);
|
||||
}
|
||||
}
|
||||
46
src/me/topchetoeu/jscript/events/WarmObservable.java
Normal file
46
src/me/topchetoeu/jscript/events/WarmObservable.java
Normal file
@@ -0,0 +1,46 @@
|
||||
package me.topchetoeu.jscript.events;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
||||
public class WarmObservable<T> implements Observable<T>, Handle {
|
||||
private HashSet<Observer<T>> observers = new HashSet<>();
|
||||
private Handle handle;
|
||||
|
||||
@Override
|
||||
public Handle on(Observer<T> val) {
|
||||
if (observers == null) return () -> {};
|
||||
observers.add(val);
|
||||
return () -> observers.remove(val);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void free() {
|
||||
if (observers == null) return;
|
||||
handle.free();
|
||||
handle = null;
|
||||
observers = null;
|
||||
}
|
||||
|
||||
public WarmObservable(Observable<T> observable) {
|
||||
observable.on(new Observer<>() {
|
||||
public void next(T data) {
|
||||
for (var obs : observers) obs.next(data);
|
||||
}
|
||||
public void error(RuntimeException err) {
|
||||
for (var obs : observers) obs.error(err);
|
||||
handle = null;
|
||||
observers = null;
|
||||
}
|
||||
public void finish() {
|
||||
for (var obs : observers) obs.finish();
|
||||
handle = null;
|
||||
observers = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public WarmObservable<T> warmUp() {
|
||||
return this;
|
||||
}
|
||||
}
|
||||
70
src/me/topchetoeu/jscript/exceptions/EngineException.java
Normal file
70
src/me/topchetoeu/jscript/exceptions/EngineException.java
Normal file
@@ -0,0 +1,70 @@
|
||||
package me.topchetoeu.jscript.exceptions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.engine.CallContext;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto;
|
||||
|
||||
public class EngineException extends RuntimeException {
|
||||
public final Object value;
|
||||
public EngineException cause;
|
||||
public final List<String> stackTrace = new ArrayList<>();
|
||||
|
||||
public EngineException add(String name, Location location) {
|
||||
var res = "";
|
||||
|
||||
if (location != null) res += "at " + location.toString() + " ";
|
||||
if (name != null && !name.equals("")) res += "in " + name + " ";
|
||||
|
||||
this.stackTrace.add(res.trim());
|
||||
return this;
|
||||
}
|
||||
public EngineException setCause(EngineException cause) {
|
||||
this.cause = cause;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String toString(CallContext ctx) throws InterruptedException {
|
||||
var ss = new StringBuilder();
|
||||
ss.append(Values.toString(ctx, value)).append('\n');
|
||||
for (var line : stackTrace) {
|
||||
ss.append(" ").append(line).append('\n');
|
||||
}
|
||||
if (cause != null) ss.append("Caused by ").append(cause.toString(ctx)).append('\n');
|
||||
ss.deleteCharAt(ss.length() - 1);
|
||||
return ss.toString();
|
||||
}
|
||||
|
||||
private static Object err(String msg, PlaceholderProto proto) {
|
||||
var res = new ObjectValue(proto);
|
||||
res.defineProperty("message", msg);
|
||||
return res;
|
||||
}
|
||||
|
||||
public EngineException(Object error) {
|
||||
super(error == null ? "null" : error.toString());
|
||||
|
||||
this.value = error;
|
||||
this.cause = null;
|
||||
}
|
||||
|
||||
public static EngineException ofError(String msg) {
|
||||
return new EngineException(err(msg, PlaceholderProto.ERROR));
|
||||
}
|
||||
public static EngineException ofSyntax(SyntaxException e) {
|
||||
return new EngineException(err(e.msg, PlaceholderProto.SYNTAX_ERROR)).add(null, e.loc);
|
||||
}
|
||||
public static EngineException ofSyntax(String msg) {
|
||||
return new EngineException(err(msg, PlaceholderProto.SYNTAX_ERROR));
|
||||
}
|
||||
public static EngineException ofType(String msg) {
|
||||
return new EngineException(err(msg, PlaceholderProto.TYPE_ERROR));
|
||||
}
|
||||
public static EngineException ofRange(String msg) {
|
||||
return new EngineException(err(msg, PlaceholderProto.RANGE_ERROR));
|
||||
}
|
||||
}
|
||||
14
src/me/topchetoeu/jscript/exceptions/SyntaxException.java
Normal file
14
src/me/topchetoeu/jscript/exceptions/SyntaxException.java
Normal file
@@ -0,0 +1,14 @@
|
||||
package me.topchetoeu.jscript.exceptions;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
|
||||
public class SyntaxException extends RuntimeException {
|
||||
public final Location loc;
|
||||
public final String msg;
|
||||
|
||||
public SyntaxException(Location loc, String msg) {
|
||||
super("Syntax error (at %s): %s".formatted(loc, msg));
|
||||
this.loc = loc;
|
||||
this.msg = msg;
|
||||
}
|
||||
}
|
||||
22
src/me/topchetoeu/jscript/filesystem/File.java
Normal file
22
src/me/topchetoeu/jscript/filesystem/File.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package me.topchetoeu.jscript.filesystem;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public interface File {
|
||||
int read(byte[] buff) throws IOException;
|
||||
void write(byte[] buff) throws IOException;
|
||||
long tell() throws IOException;
|
||||
void seek(long offset, int pos) throws IOException;
|
||||
void close() throws IOException;
|
||||
Permissions perms();
|
||||
|
||||
default String readToString() throws IOException {
|
||||
seek(0, 2);
|
||||
long len = tell();
|
||||
if (len < 0) return null;
|
||||
seek(0, 0);
|
||||
byte[] res = new byte[(int)len];
|
||||
if (read(res) < 0) return null;
|
||||
return new String(res);
|
||||
}
|
||||
}
|
||||
17
src/me/topchetoeu/jscript/filesystem/Filesystem.java
Normal file
17
src/me/topchetoeu/jscript/filesystem/Filesystem.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package me.topchetoeu.jscript.filesystem;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
public interface Filesystem extends PermissionsProvider {
|
||||
public static enum EntryType {
|
||||
NONE,
|
||||
FILE,
|
||||
FOLDER,
|
||||
}
|
||||
|
||||
File open(Path path) throws IOException;
|
||||
EntryType type(Path path);
|
||||
boolean mkdir(Path path);
|
||||
boolean rm(Path path) throws IOException;
|
||||
}
|
||||
27
src/me/topchetoeu/jscript/filesystem/InaccessibleFile.java
Normal file
27
src/me/topchetoeu/jscript/filesystem/InaccessibleFile.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package me.topchetoeu.jscript.filesystem;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class InaccessibleFile implements File {
|
||||
@Override
|
||||
public int read(byte[] buff) throws IOException {
|
||||
return -1;
|
||||
}
|
||||
@Override
|
||||
public void write(byte[] buff) throws IOException {
|
||||
}
|
||||
@Override
|
||||
public long tell() throws IOException {
|
||||
return -1;
|
||||
}
|
||||
@Override
|
||||
public void seek(long offset, int pos) throws IOException {
|
||||
}
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
}
|
||||
@Override
|
||||
public Permissions perms() {
|
||||
return Permissions.NONE;
|
||||
}
|
||||
}
|
||||
51
src/me/topchetoeu/jscript/filesystem/MemoryFile.java
Normal file
51
src/me/topchetoeu/jscript/filesystem/MemoryFile.java
Normal file
@@ -0,0 +1,51 @@
|
||||
package me.topchetoeu.jscript.filesystem;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public class MemoryFile implements File {
|
||||
public byte[] data;
|
||||
private int ptr = 0;
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
data = null;
|
||||
ptr = -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] buff) throws IOException {
|
||||
if (data == null) return -1;
|
||||
if (ptr == data.length) return -1;
|
||||
int n = Math.min(buff.length, data.length - ptr);
|
||||
System.arraycopy(data, ptr, buff, 0, n);
|
||||
return n;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(long offset, int pos) throws IOException {
|
||||
if (data == null) return;
|
||||
if (pos == 0) ptr = (int)offset;
|
||||
else if (pos == 1) ptr += (int)offset;
|
||||
else ptr = data.length - (int)offset;
|
||||
|
||||
ptr = (int)Math.max(Math.min(ptr, data.length), 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long tell() throws IOException {
|
||||
if (data == null) return -1;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] buff) throws IOException { }
|
||||
@Override
|
||||
public Permissions perms() {
|
||||
if (data == null) return Permissions.NONE;
|
||||
else return Permissions.READ;
|
||||
}
|
||||
|
||||
public MemoryFile(byte[] data) {
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
17
src/me/topchetoeu/jscript/filesystem/Permissions.java
Normal file
17
src/me/topchetoeu/jscript/filesystem/Permissions.java
Normal file
@@ -0,0 +1,17 @@
|
||||
package me.topchetoeu.jscript.filesystem;
|
||||
|
||||
public enum Permissions {
|
||||
NONE("", false, false),
|
||||
READ("r", true, false),
|
||||
READ_WRITE("rw", true, true);
|
||||
|
||||
public final String readMode;
|
||||
public final boolean readable;
|
||||
public final boolean writable;
|
||||
|
||||
private Permissions(String mode, boolean r, boolean w) {
|
||||
this.readMode = mode;
|
||||
this.readable = r;
|
||||
this.writable = w;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package me.topchetoeu.jscript.filesystem;
|
||||
|
||||
import java.nio.file.Path;
|
||||
|
||||
public interface PermissionsProvider {
|
||||
Permissions perms(Path file);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user