major changes in preparations for environments
This commit is contained in:
@@ -2,25 +2,54 @@ package me.topchetoeu.jscript;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Map;
|
||||
|
||||
import me.topchetoeu.jscript.engine.CallContext;
|
||||
import me.topchetoeu.jscript.engine.Engine;
|
||||
import me.topchetoeu.jscript.engine.Environment;
|
||||
import me.topchetoeu.jscript.engine.values.NativeFunction;
|
||||
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.interop.NativeTypeRegister;
|
||||
import me.topchetoeu.jscript.polyfills.Internals;
|
||||
|
||||
public class Main {
|
||||
static Thread task;
|
||||
static Engine engine;
|
||||
static Environment env;
|
||||
|
||||
public static String streamToString(InputStream in) {
|
||||
try {
|
||||
StringBuilder out = new StringBuilder();
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(in));
|
||||
|
||||
for(var line = br.readLine(); line != null; line = br.readLine()) {
|
||||
out.append(line).append('\n');
|
||||
}
|
||||
|
||||
br.close();
|
||||
return out.toString();
|
||||
}
|
||||
catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public static String resourceToString(String name) {
|
||||
var str = Main.class.getResourceAsStream("/me/topchetoeu/jscript/" + name);
|
||||
if (str == null) return null;
|
||||
return streamToString(str);
|
||||
}
|
||||
|
||||
private static Observer<Object> valuePrinter = new Observer<Object>() {
|
||||
public void next(Object data) {
|
||||
try {
|
||||
Values.printValue(engine.context(), data);
|
||||
Values.printValue(null, data);
|
||||
}
|
||||
catch (InterruptedException e) { }
|
||||
System.out.println();
|
||||
@@ -28,20 +57,24 @@ public class Main {
|
||||
|
||||
public void error(RuntimeException err) {
|
||||
try {
|
||||
if (err instanceof EngineException) {
|
||||
System.out.println("Uncaught " + ((EngineException)err).toString(engine.context()));
|
||||
try {
|
||||
if (err instanceof EngineException) {
|
||||
System.out.println("Uncaught " + ((EngineException)err).toString(new CallContext(engine, env)));
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
else if (err instanceof SyntaxException) {
|
||||
System.out.println("Syntax error:" + ((SyntaxException)err).msg);
|
||||
catch (EngineException ex) {
|
||||
System.out.println("Uncaught ");
|
||||
Values.printValue(null, ((EngineException)err).value);
|
||||
System.out.println();
|
||||
}
|
||||
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;
|
||||
@@ -53,23 +86,33 @@ public class Main {
|
||||
System.out.println(String.format("Running %s v%s by %s", Metadata.NAME, Metadata.VERSION, Metadata.AUTHOR));
|
||||
var in = new BufferedReader(new InputStreamReader(System.in));
|
||||
engine = new Engine();
|
||||
var scope = engine.global().globalChild();
|
||||
env = new Environment(null, null, null);
|
||||
var exited = new boolean[1];
|
||||
|
||||
scope.define("exit", ctx -> {
|
||||
env.global.define("exit", ctx -> {
|
||||
exited[0] = true;
|
||||
task.interrupt();
|
||||
throw new InterruptedException();
|
||||
});
|
||||
scope.define("go", ctx -> {
|
||||
env.global.define("go", ctx -> {
|
||||
try {
|
||||
var func = engine.compile(scope, "do.js", new String(Files.readAllBytes(Path.of("do.js"))));
|
||||
var func = engine.compile(ctx, "do.js", new String(Files.readAllBytes(Path.of("do.js"))));
|
||||
return func.call(ctx);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new EngineException("Couldn't open do.js");
|
||||
}
|
||||
});
|
||||
env.global.define(true, new NativeFunction("log", (el, t, _args) -> {
|
||||
for (var obj : _args) Values.printValue(el, obj);
|
||||
System.out.println();
|
||||
return null;
|
||||
}));
|
||||
|
||||
var builderEnv = env.child();
|
||||
builderEnv.wrappersProvider = new NativeTypeRegister();
|
||||
|
||||
engine.pushMsg(false, Map.of(), builderEnv, "core.js", resourceToString("js/core.js"), null, env, new Internals());
|
||||
|
||||
task = engine.start();
|
||||
var reader = new Thread(() -> {
|
||||
@@ -79,11 +122,11 @@ public class Main {
|
||||
var raw = in.readLine();
|
||||
|
||||
if (raw == null) break;
|
||||
engine.pushMsg(false, scope, Map.of(), "<stdio>", raw, null).toObservable().once(valuePrinter);
|
||||
engine.pushMsg(false, Map.of(), env, "<stdio>", raw, null).toObservable().once(valuePrinter);
|
||||
}
|
||||
catch (EngineException e) {
|
||||
try {
|
||||
System.out.println("Uncaught " + e.toString(engine.context()));
|
||||
System.out.println("Uncaught " + e.toString(null));
|
||||
}
|
||||
catch (EngineException ex) {
|
||||
System.out.println("Uncaught [error while converting to string]");
|
||||
|
||||
@@ -4,18 +4,20 @@ import java.util.Collections;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Map;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public class CallContext {
|
||||
public static final class DataKey<T> {}
|
||||
|
||||
public final Engine engine;
|
||||
private final Map<DataKey<?>, Object> data = new Hashtable<>();
|
||||
public Environment environment;
|
||||
private final Map<DataKey<?>, Object> data;
|
||||
|
||||
public Engine engine() { return engine; }
|
||||
public Map<DataKey<?>, Object> data() { return Collections.unmodifiableMap(data); }
|
||||
|
||||
public CallContext copy() {
|
||||
return new CallContext(engine).mergeData(data);
|
||||
return new CallContext(engine, environment).mergeData(data);
|
||||
}
|
||||
|
||||
public CallContext mergeData(Map<DataKey<?>, Object> objs) {
|
||||
data.putAll(objs);
|
||||
return this;
|
||||
@@ -25,7 +27,6 @@ public class CallContext {
|
||||
else 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 {
|
||||
@@ -34,15 +35,14 @@ public class CallContext {
|
||||
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 boolean hasData(DataKey<?> key) { return data.containsKey(key); }
|
||||
|
||||
public CallContext changeData(DataKey<Integer> key, int n, int start) {
|
||||
return setData(key, getData(key, start) + n);
|
||||
@@ -54,7 +54,14 @@ public class CallContext {
|
||||
return changeData(key, 1, 0);
|
||||
}
|
||||
|
||||
public CallContext(Engine engine) {
|
||||
public CallContext(Engine engine, Environment env) {
|
||||
this.engine = engine;
|
||||
this.data = new Hashtable<>();
|
||||
this.environment = env;
|
||||
}
|
||||
public CallContext(CallContext parent, Environment env) {
|
||||
this.engine = parent.engine;
|
||||
this.data = parent.data;
|
||||
this.environment = env;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,43 +1,43 @@
|
||||
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.FunctionValue;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
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 class RawFunction {
|
||||
public final GlobalScope scope;
|
||||
private class UncompiledFunction extends FunctionValue {
|
||||
public final String filename;
|
||||
public final String raw;
|
||||
|
||||
public RawFunction(GlobalScope scope, String filename, String raw) {
|
||||
this.scope = scope;
|
||||
@Override
|
||||
public Object call(CallContext ctx, Object thisArg, Object... args) throws InterruptedException {
|
||||
return compile(ctx, filename, raw).call(ctx, thisArg, args);
|
||||
}
|
||||
|
||||
public UncompiledFunction(String filename, String raw) {
|
||||
super(filename, 0);
|
||||
this.filename = filename;
|
||||
this.raw = raw;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Task {
|
||||
public final Object func;
|
||||
public final FunctionValue func;
|
||||
public final Object thisArg;
|
||||
public final Object[] args;
|
||||
public final Map<DataKey<?>, Object> data;
|
||||
public final DataNotifier<Object> notifier = new DataNotifier<>();
|
||||
public final Environment env;
|
||||
|
||||
public Task(Object func, Map<DataKey<?>, Object> data, Object thisArg, Object[] args) {
|
||||
public Task(Environment env, FunctionValue func, Map<DataKey<?>, Object> data, Object thisArg, Object[] args) {
|
||||
this.env = env;
|
||||
this.func = func;
|
||||
this.data = data;
|
||||
this.thisArg = thisArg;
|
||||
@@ -45,72 +45,22 @@ public class Engine {
|
||||
}
|
||||
}
|
||||
|
||||
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 Map<DataKey<?>, Object> callCtxVals = new HashMap<>();
|
||||
// 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;
|
||||
}
|
||||
// public NativeTypeRegister typeRegister() { return typeRegister; }
|
||||
|
||||
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));
|
||||
task.notifier.next(task.func.call(new CallContext(this, task.env).mergeData(task.data), task.thisArg, task.args));
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
task.notifier.error(new RuntimeException(e));
|
||||
@@ -160,42 +110,22 @@ public class Engine {
|
||||
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 FunctionValue getConstructor(Class<?> clazz) {
|
||||
return typeRegister.getConstr(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);
|
||||
public Awaitable<Object> pushMsg(boolean micro, Map<DataKey<?>, Object> data, Environment env, FunctionValue func, Object thisArg, Object... args) {
|
||||
var msg = new Task(env, 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 Awaitable<Object> pushMsg(boolean micro, Map<DataKey<?>, Object> data, Environment env, String filename, String raw, Object thisArg, Object... args) {
|
||||
return pushMsg(micro, data, env, new UncompiledFunction(filename, raw), thisArg, args);
|
||||
}
|
||||
|
||||
public FunctionValue compile(GlobalScope scope, String filename, String raw) throws InterruptedException {
|
||||
return Parsing.compile(scope, filename, raw);
|
||||
public FunctionValue compile(CallContext ctx, String filename, String raw) throws InterruptedException {
|
||||
var res = Values.toString(ctx, ctx.environment.compile.call(ctx, null, raw, filename));
|
||||
return Parsing.compile(ctx.environment, filename, res);
|
||||
}
|
||||
|
||||
public Engine(NativeTypeRegister register) {
|
||||
this.typeRegister = register;
|
||||
this.callCtxVals.put(DEBUG_STATE_KEY, debugState);
|
||||
}
|
||||
public Engine() {
|
||||
this(new NativeTypeRegister());
|
||||
}
|
||||
// public Engine() {
|
||||
// this.typeRegister = new NativeTypeRegister();
|
||||
// }
|
||||
}
|
||||
|
||||
81
src/me/topchetoeu/jscript/engine/Environment.java
Normal file
81
src/me/topchetoeu/jscript/engine/Environment.java
Normal file
@@ -0,0 +1,81 @@
|
||||
package me.topchetoeu.jscript.engine;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import me.topchetoeu.jscript.engine.scope.GlobalScope;
|
||||
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;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
import me.topchetoeu.jscript.interop.NativeSetter;
|
||||
|
||||
public class Environment {
|
||||
private HashMap<String, ObjectValue> prototypes = new HashMap<>();
|
||||
public GlobalScope global;
|
||||
public WrappersProvider wrappersProvider;
|
||||
|
||||
@Native public FunctionValue compile;
|
||||
@Native public FunctionValue regexConstructor = new NativeFunction("RegExp", (ctx, thisArg, args) -> {
|
||||
throw EngineException.ofError("Regular expressions not supported.");
|
||||
});
|
||||
@Native public ObjectValue proto(String name) {
|
||||
return prototypes.get(name);
|
||||
}
|
||||
@Native public void setProto(String name, ObjectValue val) {
|
||||
prototypes.put(name, val);
|
||||
}
|
||||
// @Native public ObjectValue arrayPrototype = new ObjectValue();
|
||||
// @Native public ObjectValue boolPrototype = new ObjectValue();
|
||||
// @Native public ObjectValue functionPrototype = new ObjectValue();
|
||||
// @Native public ObjectValue numberPrototype = new ObjectValue();
|
||||
// @Native public ObjectValue objectPrototype = new ObjectValue(PlaceholderProto.NONE);
|
||||
// @Native public ObjectValue stringPrototype = new ObjectValue();
|
||||
// @Native public ObjectValue symbolPrototype = new ObjectValue();
|
||||
// @Native public ObjectValue errorPrototype = new ObjectValue();
|
||||
// @Native public ObjectValue syntaxErrPrototype = new ObjectValue(PlaceholderProto.ERROR);
|
||||
// @Native public ObjectValue typeErrPrototype = new ObjectValue(PlaceholderProto.ERROR);
|
||||
// @Native public ObjectValue rangeErrPrototype = new ObjectValue(PlaceholderProto.ERROR);
|
||||
|
||||
@NativeGetter("global")
|
||||
public ObjectValue getGlobal() {
|
||||
return global.obj;
|
||||
}
|
||||
@NativeSetter("global")
|
||||
public void setGlobal(ObjectValue val) {
|
||||
global = new GlobalScope(val);
|
||||
}
|
||||
|
||||
@Native
|
||||
public Environment fork() {
|
||||
var res = new Environment(compile, wrappersProvider, global);
|
||||
res.regexConstructor = regexConstructor;
|
||||
res.prototypes = new HashMap<>(prototypes);
|
||||
return res;
|
||||
}
|
||||
|
||||
@Native
|
||||
public Environment child() {
|
||||
var res = fork();
|
||||
res.global = res.global.globalChild();
|
||||
return res;
|
||||
}
|
||||
|
||||
public Environment(FunctionValue compile, WrappersProvider nativeConverter, GlobalScope global) {
|
||||
if (compile == null) compile = new NativeFunction("compile", (ctx, thisArg, args) -> args.length == 0 ? "" : args[0]);
|
||||
if (nativeConverter == null) nativeConverter = new WrappersProvider() {
|
||||
public ObjectValue getConstr(Class<?> obj) {
|
||||
throw EngineException.ofType("Java objects not passable to Javascript.");
|
||||
}
|
||||
public ObjectValue getProto(Class<?> obj) {
|
||||
throw EngineException.ofType("Java objects not passable to Javascript.");
|
||||
}
|
||||
};
|
||||
if (global == null) global = new GlobalScope();
|
||||
|
||||
this.wrappersProvider = nativeConverter;
|
||||
this.compile = compile;
|
||||
this.global = global;
|
||||
}
|
||||
}
|
||||
8
src/me/topchetoeu/jscript/engine/WrappersProvider.java
Normal file
8
src/me/topchetoeu/jscript/engine/WrappersProvider.java
Normal file
@@ -0,0 +1,8 @@
|
||||
package me.topchetoeu.jscript.engine;
|
||||
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
|
||||
public interface WrappersProvider {
|
||||
public ObjectValue getProto(Class<?> obj);
|
||||
public ObjectValue getConstr(Class<?> obj);
|
||||
}
|
||||
@@ -1,154 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package me.topchetoeu.jscript.engine.debug;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class HttpRequest {
|
||||
public final String method;
|
||||
public final String path;
|
||||
public final Map<String, String> headers;
|
||||
|
||||
public HttpRequest(String method, String path, Map<String, String> headers) {
|
||||
this.method = method;
|
||||
this.path = path;
|
||||
this.headers = headers;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
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)
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
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)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
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)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
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)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,185 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,6 @@ import java.util.List;
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.engine.CallContext;
|
||||
import me.topchetoeu.jscript.engine.DebugCommand;
|
||||
import me.topchetoeu.jscript.engine.Engine;
|
||||
import me.topchetoeu.jscript.engine.CallContext.DataKey;
|
||||
import me.topchetoeu.jscript.engine.scope.LocalScope;
|
||||
import me.topchetoeu.jscript.engine.scope.ValueVariable;
|
||||
@@ -106,14 +105,14 @@ public class CodeFrame {
|
||||
if (ctx.getData(STACK_N_KEY, 0) >= ctx.addData(MAX_STACK_KEY, 10000)) throw EngineException.ofRange("Stack overflow!");
|
||||
ctx.changeData(STACK_N_KEY);
|
||||
|
||||
var debugState = ctx.getData(Engine.DEBUG_STATE_KEY);
|
||||
if (debugState != null) debugState.pushFrame(this);
|
||||
// var debugState = ctx.getData(Engine.DEBUG_STATE_KEY);
|
||||
// if (debugState != null) debugState.pushFrame(this);
|
||||
}
|
||||
public void end(CallContext ctx) {
|
||||
var debugState = ctx.getData(Engine.DEBUG_STATE_KEY);
|
||||
|
||||
if (debugState != null) debugState.popFrame();
|
||||
ctx.changeData(STACK_N_KEY, -1);
|
||||
|
||||
// var debugState = ctx.getData(Engine.DEBUG_STATE_KEY);
|
||||
// if (debugState != null) debugState.popFrame();
|
||||
}
|
||||
|
||||
private Object nextNoTry(CallContext ctx) throws InterruptedException {
|
||||
|
||||
@@ -69,7 +69,7 @@ public class Runners {
|
||||
|
||||
public static Object execMakeVar(CallContext ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
var name = (String)instr.get(0);
|
||||
frame.function.globals.define(name);
|
||||
frame.function.environment.global.define(name);
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
@@ -164,7 +164,7 @@ public class Runners {
|
||||
public static Object execLoadVar(CallContext ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
var i = instr.get(0);
|
||||
|
||||
if (i instanceof String) frame.push(ctx, frame.function.globals.get(ctx, (String)i));
|
||||
if (i instanceof String) frame.push(ctx, frame.function.environment.global.get(ctx, (String)i));
|
||||
else frame.push(ctx, frame.scope.get((int)i).get(ctx));
|
||||
|
||||
frame.codePtr++;
|
||||
@@ -176,7 +176,7 @@ public class Runners {
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execLoadGlob(CallContext ctx, Instruction instr, CodeFrame frame) {
|
||||
frame.push(ctx, frame.function.globals.obj);
|
||||
frame.push(ctx, frame.function.environment.global.obj);
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
@@ -202,7 +202,7 @@ public class Runners {
|
||||
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);
|
||||
var func = new CodeFunction("", localsN, len, frame.function.environment, captures, body);
|
||||
frame.push(ctx, func);
|
||||
|
||||
frame.codePtr += n;
|
||||
@@ -227,7 +227,7 @@ public class Runners {
|
||||
return execLoadMember(ctx, state, instr, frame);
|
||||
}
|
||||
public static Object execLoadRegEx(CallContext ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
frame.push(ctx, ctx.engine().makeRegex(instr.get(0), instr.get(1)));
|
||||
frame.push(ctx, ctx.environment.regexConstructor.call(ctx, null, instr.get(0), instr.get(1)));
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
@@ -252,7 +252,7 @@ public class Runners {
|
||||
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);
|
||||
if (i instanceof String) frame.function.environment.global.set(ctx, (String)i, val);
|
||||
else frame.scope.get((int)i).set(ctx, val);
|
||||
|
||||
frame.codePtr++;
|
||||
@@ -299,8 +299,8 @@ public class Runners {
|
||||
Object obj;
|
||||
|
||||
if (name != null) {
|
||||
if (frame.function.globals.has(ctx, name)) {
|
||||
obj = frame.function.globals.get(ctx, name);
|
||||
if (frame.function.environment.global.has(ctx, name)) {
|
||||
obj = frame.function.environment.global.get(ctx, name);
|
||||
}
|
||||
else obj = null;
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
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().global().globalChild();
|
||||
scope.define(null, "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;
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
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; }
|
||||
}
|
||||
@@ -11,10 +11,9 @@ import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
|
||||
public class GlobalScope implements ScopeRecord {
|
||||
public final ObjectValue obj;
|
||||
public final GlobalScope parent;
|
||||
|
||||
@Override
|
||||
public GlobalScope parent() { return parent; }
|
||||
public GlobalScope parent() { return null; }
|
||||
|
||||
public boolean has(CallContext ctx, String name) throws InterruptedException {
|
||||
return obj.hasMember(ctx, name, false);
|
||||
@@ -24,7 +23,9 @@ public class GlobalScope implements ScopeRecord {
|
||||
}
|
||||
|
||||
public GlobalScope globalChild() {
|
||||
return new GlobalScope(this);
|
||||
var obj = new ObjectValue();
|
||||
obj.setPrototype(null, this.obj);
|
||||
return new GlobalScope(obj);
|
||||
}
|
||||
public LocalScopeRecord child() {
|
||||
return new LocalScopeRecord(this);
|
||||
@@ -78,12 +79,9 @@ public class GlobalScope implements ScopeRecord {
|
||||
}
|
||||
|
||||
public GlobalScope() {
|
||||
this.parent = null;
|
||||
this.obj = new ObjectValue();
|
||||
}
|
||||
public GlobalScope(GlobalScope parent) {
|
||||
this.parent = null;
|
||||
this.obj = new ObjectValue();
|
||||
this.obj.setPrototype(null, parent.obj);
|
||||
public GlobalScope(ObjectValue val) {
|
||||
this.obj = val;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import java.util.ArrayList;
|
||||
|
||||
public class LocalScope {
|
||||
private String[] names;
|
||||
private LocalScope parent;
|
||||
public final ValueVariable[] captures;
|
||||
public final ValueVariable[] locals;
|
||||
public final ArrayList<ValueVariable> catchVars = new ArrayList<>();
|
||||
@@ -33,18 +32,11 @@ public class LocalScope {
|
||||
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));
|
||||
|
||||
public void toGlobal(GlobalScope global) {
|
||||
var names = getNames();
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
res.define(names[i], locals[i]);
|
||||
global.define(names[i], locals[i]);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public LocalScope(int n, ValueVariable[] captures) {
|
||||
@@ -55,8 +47,4 @@ public class LocalScope {
|
||||
locals[i] = new ValueVariable(false, null);
|
||||
}
|
||||
}
|
||||
public LocalScope(int n, ValueVariable[] captures, LocalScope parent) {
|
||||
this(n, captures);
|
||||
this.parent = parent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,8 @@ 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.Environment;
|
||||
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 {
|
||||
@@ -17,7 +17,7 @@ public class CodeFunction extends FunctionValue {
|
||||
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 Environment environment;
|
||||
|
||||
public Location loc() {
|
||||
for (var instr : body) {
|
||||
@@ -34,13 +34,13 @@ public class CodeFunction extends FunctionValue {
|
||||
|
||||
@Override
|
||||
public Object call(CallContext ctx, Object thisArg, Object... args) throws InterruptedException {
|
||||
return new CodeFrame(ctx, thisArg, args, this).run(ctx);
|
||||
return new CodeFrame(ctx, thisArg, args, this).run(new CallContext(ctx, environment));
|
||||
}
|
||||
|
||||
public CodeFunction(String name, int localsN, int length, GlobalScope globals, ValueVariable[] captures, Instruction[] body) {
|
||||
public CodeFunction(String name, int localsN, int length, Environment environment, ValueVariable[] captures, Instruction[] body) {
|
||||
super(name, length);
|
||||
this.captures = captures;
|
||||
this.globals = globals;
|
||||
this.environment = environment;
|
||||
this.localsN = localsN;
|
||||
this.length = length;
|
||||
this.body = body;
|
||||
|
||||
@@ -8,7 +8,7 @@ public class NativeWrapper extends ObjectValue {
|
||||
|
||||
@Override
|
||||
public ObjectValue getPrototype(CallContext ctx) throws InterruptedException {
|
||||
if (prototype == NATIVE_PROTO) return ctx.engine.getPrototype(wrapped.getClass());
|
||||
if (prototype == NATIVE_PROTO) return ctx.environment.wrappersProvider.getProto(wrapped.getClass());
|
||||
else return super.getPrototype(ctx);
|
||||
}
|
||||
|
||||
|
||||
@@ -147,13 +147,13 @@ public class ObjectValue {
|
||||
|
||||
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();
|
||||
if (prototype == OBJ_PROTO) return ctx.environment.proto("object");
|
||||
if (prototype == ARR_PROTO) return ctx.environment.proto("array");
|
||||
if (prototype == FUNC_PROTO) return ctx.environment.proto("function");
|
||||
if (prototype == ERR_PROTO) return ctx.environment.proto("error");
|
||||
if (prototype == RANGE_ERR_PROTO) return ctx.environment.proto("rangeErr");
|
||||
if (prototype == SYNTAX_ERR_PROTO) return ctx.environment.proto("syntaxErr");
|
||||
if (prototype == TYPE_ERR_PROTO) return ctx.environment.proto("typeErr");
|
||||
}
|
||||
catch (NullPointerException e) {
|
||||
return null;
|
||||
@@ -165,18 +165,21 @@ public class ObjectValue {
|
||||
val = Values.normalize(ctx, val);
|
||||
|
||||
if (!extensible()) return false;
|
||||
if (val == null || val == Values.NULL) prototype = null;
|
||||
if (val == null || val == Values.NULL) {
|
||||
prototype = null;
|
||||
return true;
|
||||
}
|
||||
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;
|
||||
if (ctx != null && ctx.environment != null) {
|
||||
if (obj == ctx.environment.proto("object")) prototype = OBJ_PROTO;
|
||||
else if (obj == ctx.environment.proto("array")) prototype = ARR_PROTO;
|
||||
else if (obj == ctx.environment.proto("function")) prototype = FUNC_PROTO;
|
||||
else if (obj == ctx.environment.proto("error")) prototype = ERR_PROTO;
|
||||
else if (obj == ctx.environment.proto("syntaxErr")) prototype = SYNTAX_ERR_PROTO;
|
||||
else if (obj == ctx.environment.proto("typeErr")) prototype = TYPE_ERR_PROTO;
|
||||
else if (obj == ctx.environment.proto("rangeErr")) prototype = RANGE_ERR_PROTO;
|
||||
else prototype = obj;
|
||||
}
|
||||
else prototype = obj;
|
||||
@@ -277,7 +280,8 @@ public class ObjectValue {
|
||||
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);
|
||||
var proto = getPrototype(ctx);
|
||||
return proto != null && proto.hasMember(ctx, key, own);
|
||||
}
|
||||
public final boolean deleteMember(CallContext ctx, Object key) throws InterruptedException {
|
||||
key = Values.normalize(ctx, key);
|
||||
|
||||
@@ -321,15 +321,15 @@ public class Values {
|
||||
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();
|
||||
if (obj instanceof String) return ctx.environment.proto("string");
|
||||
else if (obj instanceof Number) return ctx.environment.proto("number");
|
||||
else if (obj instanceof Boolean) return ctx.environment.proto("bool");
|
||||
else if (obj instanceof Symbol) return ctx.environment.proto("symbol");
|
||||
|
||||
return null;
|
||||
}
|
||||
public static boolean setPrototype(CallContext ctx, Object obj, Object proto) throws InterruptedException {
|
||||
obj = normalize(ctx, obj); proto = normalize(ctx, proto);
|
||||
obj = normalize(ctx, obj);
|
||||
return isObject(obj) && object(obj).setPrototype(ctx, proto);
|
||||
}
|
||||
public static List<Object> getMembers(CallContext ctx, Object obj, boolean own, boolean includeNonEnumerable) throws InterruptedException {
|
||||
@@ -420,7 +420,7 @@ public class Values {
|
||||
|
||||
if (val instanceof Class) {
|
||||
if (ctx == null) return null;
|
||||
else return ctx.engine.getConstructor((Class<?>)val);
|
||||
else return ctx.environment.wrappersProvider.getConstr((Class<?>)val);
|
||||
}
|
||||
|
||||
return new NativeWrapper(val);
|
||||
@@ -498,7 +498,7 @@ public class Values {
|
||||
public static Iterable<Object> toJavaIterable(CallContext ctx, Object obj) throws InterruptedException {
|
||||
return () -> {
|
||||
try {
|
||||
var constr = getMember(ctx, ctx.engine().symbolProto(), "constructor");
|
||||
var constr = getMember(ctx, ctx.environment.proto("symbol"), "constructor");
|
||||
var symbol = getMember(ctx, constr, "iterator");
|
||||
|
||||
var iteratorFunc = getMember(ctx, obj, symbol);
|
||||
@@ -567,7 +567,7 @@ public class Values {
|
||||
var it = iterable.iterator();
|
||||
|
||||
try {
|
||||
var key = getMember(ctx, getMember(ctx, ctx.engine().symbolProto(), "constructor"), "iterator");
|
||||
var key = getMember(ctx, getMember(ctx, ctx.environment.proto("symbol"), "constructor"), "iterator");
|
||||
res.defineProperty(ctx, key, new NativeFunction("", (_ctx, thisArg, args) -> thisArg));
|
||||
}
|
||||
catch (IllegalArgumentException | NullPointerException e) { }
|
||||
|
||||
@@ -3,12 +3,13 @@ package me.topchetoeu.jscript.interop;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.HashMap;
|
||||
|
||||
import me.topchetoeu.jscript.engine.WrappersProvider;
|
||||
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 NativeTypeRegister {
|
||||
public class NativeTypeRegister implements WrappersProvider {
|
||||
private final HashMap<Class<?>, FunctionValue> constructors = new HashMap<>();
|
||||
private final HashMap<Class<?>, ObjectValue> prototypes = new HashMap<>();
|
||||
|
||||
@@ -80,9 +81,7 @@ public class NativeTypeRegister {
|
||||
var name = nat.value();
|
||||
if (name.equals("")) name = cl.getSimpleName();
|
||||
|
||||
var getter = new OverloadFunction("get " + name).add(Overload.getter(member ? clazz : null, (ctx, thisArg, args) -> {
|
||||
return ctx.engine().typeRegister().getConstr(cl);
|
||||
}));
|
||||
var getter = new NativeFunction("get " + name, (ctx, thisArg, args) -> cl);
|
||||
|
||||
target.defineProperty(null, name, getter, null, true, false);
|
||||
}
|
||||
|
||||
@@ -13,8 +13,8 @@ import me.topchetoeu.jscript.compilation.VariableDeclareStatement.Pair;
|
||||
import me.topchetoeu.jscript.compilation.control.*;
|
||||
import me.topchetoeu.jscript.compilation.control.SwitchStatement.SwitchCase;
|
||||
import me.topchetoeu.jscript.compilation.values.*;
|
||||
import me.topchetoeu.jscript.engine.Environment;
|
||||
import me.topchetoeu.jscript.engine.Operation;
|
||||
import me.topchetoeu.jscript.engine.scope.GlobalScope;
|
||||
import me.topchetoeu.jscript.engine.scope.ValueVariable;
|
||||
import me.topchetoeu.jscript.engine.values.CodeFunction;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
@@ -1842,8 +1842,8 @@ public class Parsing {
|
||||
return list.toArray(Statement[]::new);
|
||||
}
|
||||
|
||||
public static CodeFunction compile(GlobalScope scope, Statement... statements) {
|
||||
var target = scope.globalChild();
|
||||
public static CodeFunction compile(Environment environment, Statement... statements) {
|
||||
var target = environment.global.globalChild();
|
||||
var subscope = target.child();
|
||||
var res = new ArrayList<Instruction>();
|
||||
var body = new CompoundStatement(null, statements);
|
||||
@@ -1870,14 +1870,14 @@ public class Parsing {
|
||||
}
|
||||
else res.add(Instruction.ret());
|
||||
|
||||
return new CodeFunction("", subscope.localsCount(), 0, scope, new ValueVariable[0], res.toArray(Instruction[]::new));
|
||||
return new CodeFunction("", subscope.localsCount(), 0, environment, new ValueVariable[0], res.toArray(Instruction[]::new));
|
||||
}
|
||||
public static CodeFunction compile(GlobalScope scope, String filename, String raw) {
|
||||
public static CodeFunction compile(Environment environment, String filename, String raw) {
|
||||
try {
|
||||
return compile(scope, parse(filename, raw));
|
||||
return compile(environment, parse(filename, raw));
|
||||
}
|
||||
catch (SyntaxException e) {
|
||||
return new CodeFunction(null, 2, 0, scope, new ValueVariable[0], new Instruction[] { Instruction.throwSyntax(e) });
|
||||
return new CodeFunction(null, 2, 0, environment, new ValueVariable[0], new Instruction[] { Instruction.throwSyntax(e) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
|
||||
public class GeneratorFunction extends FunctionValue {
|
||||
public final CodeFunction factory;
|
||||
public final FunctionValue factory;
|
||||
|
||||
public static class Generator {
|
||||
private boolean yielding = true;
|
||||
@@ -92,7 +92,7 @@ public class GeneratorFunction extends FunctionValue {
|
||||
return handler;
|
||||
}
|
||||
|
||||
public GeneratorFunction(CodeFunction factory) {
|
||||
public GeneratorFunction(FunctionValue factory) {
|
||||
super(factory.name, factory.length);
|
||||
this.factory = factory;
|
||||
}
|
||||
|
||||
@@ -1,150 +1,130 @@
|
||||
package me.topchetoeu.jscript.polyfills;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import me.topchetoeu.jscript.engine.CallContext;
|
||||
import me.topchetoeu.jscript.engine.Environment;
|
||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.engine.values.CodeFunction;
|
||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.engine.values.NativeFunction;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.Symbol;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
|
||||
public class Internals {
|
||||
private HashMap<Integer, Thread> intervals = new HashMap<>();
|
||||
private HashMap<Integer, Thread> timeouts = new HashMap<>();
|
||||
private HashMap<String, Symbol> symbols = new HashMap<>();
|
||||
private int nextId = 0;
|
||||
|
||||
@Native
|
||||
public double parseFloat(CallContext ctx, Object val) throws InterruptedException {
|
||||
return Values.toNumber(ctx, val);
|
||||
}
|
||||
|
||||
@Native
|
||||
public boolean isArr(Object val) {
|
||||
return val instanceof ArrayValue;
|
||||
}
|
||||
|
||||
@NativeGetter("symbolProto")
|
||||
public ObjectValue symbolProto(CallContext ctx) {
|
||||
return ctx.engine().symbolProto();
|
||||
}
|
||||
@Native
|
||||
public final Object apply(CallContext ctx, FunctionValue func, Object th, ArrayValue args) throws InterruptedException {
|
||||
return func.call(ctx, th, args.toArray());
|
||||
}
|
||||
@Native
|
||||
public boolean defineProp(CallContext ctx, ObjectValue obj, Object key, FunctionValue getter, FunctionValue setter, boolean enumerable, boolean configurable) {
|
||||
return obj.defineProperty(ctx, key, getter, setter, configurable, enumerable);
|
||||
}
|
||||
@Native
|
||||
public boolean defineField(CallContext ctx, ObjectValue obj, Object key, Object val, boolean writable, boolean enumerable, boolean configurable) {
|
||||
return obj.defineProperty(ctx, key, val, writable, configurable, enumerable);
|
||||
}
|
||||
|
||||
@Native
|
||||
public int strlen(String str) {
|
||||
return str.length();
|
||||
}
|
||||
@Native
|
||||
public String substring(String str, int start, int end) {
|
||||
if (start > end) return substring(str, end, start);
|
||||
|
||||
if (start < 0) start = 0;
|
||||
if (start >= str.length()) return "";
|
||||
|
||||
if (end < 0) end = 0;
|
||||
if (end > str.length()) end = str.length();
|
||||
|
||||
return str.substring(start, end);
|
||||
}
|
||||
@Native
|
||||
public String toLower(String str) {
|
||||
return str.toLowerCase();
|
||||
}
|
||||
@Native
|
||||
public String toUpper(String str) {
|
||||
return str.toUpperCase();
|
||||
}
|
||||
@Native
|
||||
public int toCharCode(String str) {
|
||||
return str.codePointAt(0);
|
||||
}
|
||||
@Native
|
||||
public String fromCharCode(int code) {
|
||||
return Character.toString((char)code);
|
||||
}
|
||||
@Native
|
||||
public boolean startsWith(String str, String term, int offset) {
|
||||
return str.startsWith(term, offset);
|
||||
}
|
||||
@Native
|
||||
public boolean endsWith(String str, String term, int offset) {
|
||||
try {
|
||||
return str.substring(0, offset).endsWith(term);
|
||||
@Native public void markSpecial(FunctionValue... funcs) {
|
||||
for (var func : funcs) {
|
||||
func.special = true;
|
||||
}
|
||||
catch (IndexOutOfBoundsException e) { return false; }
|
||||
|
||||
}
|
||||
|
||||
@Native
|
||||
public int setInterval(CallContext ctx, FunctionValue func, double delay) {
|
||||
@Native public Environment getEnv(Object func) {
|
||||
if (func instanceof CodeFunction) return ((CodeFunction)func).environment;
|
||||
else return null;
|
||||
}
|
||||
@Native public Object setEnv(Object func, Environment env) {
|
||||
if (func instanceof CodeFunction) ((CodeFunction)func).environment = env;
|
||||
return func;
|
||||
}
|
||||
@Native public Object apply(CallContext ctx, FunctionValue func, Object thisArg, ArrayValue args) throws InterruptedException {
|
||||
return func.call(ctx, thisArg, args.toArray());
|
||||
}
|
||||
@Native public FunctionValue delay(CallContext ctx, double delay, FunctionValue callback) throws InterruptedException {
|
||||
var thread = new Thread((Runnable)() -> {
|
||||
var ms = (long)delay;
|
||||
var ns = (int)((delay - ms) * 10000000);
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
Thread.sleep(ms, ns);
|
||||
}
|
||||
catch (InterruptedException e) { return; }
|
||||
|
||||
ctx.engine().pushMsg(false, func, ctx.data(), null);
|
||||
}
|
||||
});
|
||||
thread.start();
|
||||
|
||||
intervals.put(++nextId, thread);
|
||||
|
||||
return nextId;
|
||||
}
|
||||
@Native
|
||||
public int setTimeout(CallContext ctx, FunctionValue func, double delay) {
|
||||
var thread = new Thread((Runnable)() -> {
|
||||
var ms = (long)delay;
|
||||
var ns = (int)((delay - ms) * 1000000);
|
||||
|
||||
try {
|
||||
Thread.sleep(ms, ns);
|
||||
}
|
||||
catch (InterruptedException e) { return; }
|
||||
|
||||
ctx.engine().pushMsg(false, func, ctx.data(), null);
|
||||
ctx.engine.pushMsg(false, ctx.data(), ctx.environment, callback, null);
|
||||
});
|
||||
thread.start();
|
||||
|
||||
timeouts.put(++nextId, thread);
|
||||
|
||||
return nextId;
|
||||
return new NativeFunction((_ctx, thisArg, args) -> {
|
||||
thread.interrupt();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
@Native public void pushMessage(CallContext ctx, boolean micro, FunctionValue func, Object thisArg, Object[] args) {
|
||||
ctx.engine.pushMsg(micro, ctx.data(), ctx.environment, func, thisArg, args);
|
||||
}
|
||||
|
||||
@Native
|
||||
public void clearInterval(int id) {
|
||||
var thread = intervals.remove(id);
|
||||
if (thread != null) thread.interrupt();
|
||||
@Native public int strlen(String str) {
|
||||
return str.length();
|
||||
}
|
||||
@Native
|
||||
public void clearTimeout(int id) {
|
||||
var thread = timeouts.remove(id);
|
||||
if (thread != null) thread.interrupt();
|
||||
@Native("char") public int _char(String str) {
|
||||
return str.charAt(0);
|
||||
}
|
||||
@Native public String stringFromChars(char[] str) {
|
||||
return new String(str);
|
||||
}
|
||||
@Native public String stringFromStrings(String[] str) {
|
||||
var res = new char[str.length];
|
||||
|
||||
for (var i = 0; i < str.length; i++) res[i] = str[i].charAt(0);
|
||||
|
||||
return stringFromChars(res);
|
||||
}
|
||||
@Native public Symbol symbol(String str) {
|
||||
return new Symbol(str);
|
||||
}
|
||||
@Native public String symbolToString(Symbol str) {
|
||||
return str.value;
|
||||
}
|
||||
|
||||
@Native
|
||||
public void sort(CallContext ctx, ArrayValue arr, FunctionValue cmp) {
|
||||
@Native public boolean isArray(Object obj) {
|
||||
return obj instanceof ArrayValue;
|
||||
}
|
||||
@Native public GeneratorFunction generator(FunctionValue obj) {
|
||||
return new GeneratorFunction(obj);
|
||||
}
|
||||
|
||||
@Native public boolean defineField(CallContext ctx, ObjectValue obj, Object key, Object val, boolean writable, boolean enumerable, boolean configurable) {
|
||||
return obj.defineProperty(ctx, key, val, writable, configurable, enumerable);
|
||||
}
|
||||
@Native public boolean defineProp(CallContext ctx, ObjectValue obj, Object key, FunctionValue getter, FunctionValue setter, boolean enumerable, boolean configurable) {
|
||||
return obj.defineProperty(ctx, key, getter, setter, configurable, enumerable);
|
||||
}
|
||||
|
||||
@Native public ArrayValue keys(CallContext ctx, Object obj, boolean onlyString) throws InterruptedException {
|
||||
var res = new ArrayValue();
|
||||
|
||||
var i = 0;
|
||||
var list = Values.getMembers(ctx, obj, true, false);
|
||||
|
||||
for (var el : list) res.set(ctx, i++, el);
|
||||
|
||||
return res;
|
||||
}
|
||||
@Native public ArrayValue ownPropKeys(CallContext ctx, Object obj, boolean symbols) throws InterruptedException {
|
||||
var res = new ArrayValue();
|
||||
|
||||
if (Values.isObject(obj)) {
|
||||
var i = 0;
|
||||
var list = Values.object(obj).keys(true);
|
||||
|
||||
for (var el : list) res.set(ctx, i++, el);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@Native public ObjectValue ownProp(CallContext ctx, ObjectValue val, Object key) throws InterruptedException {
|
||||
return val.getMemberDescriptor(ctx, key);
|
||||
}
|
||||
@Native public void lock(ObjectValue val, String type) {
|
||||
switch (type) {
|
||||
case "ext": val.preventExtensions(); break;
|
||||
case "seal": val.seal(); break;
|
||||
case "freeze": val.freeze(); break;
|
||||
}
|
||||
}
|
||||
@Native public boolean extensible(ObjectValue val) {
|
||||
return val.extensible();
|
||||
}
|
||||
|
||||
@Native public void sort(CallContext ctx, ArrayValue arr, FunctionValue cmp) {
|
||||
arr.sort((a, b) -> {
|
||||
try {
|
||||
var res = Values.toNumber(ctx, cmp.call(ctx, null, a, b));
|
||||
@@ -158,109 +138,4 @@ public class Internals {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Native
|
||||
public void special(FunctionValue... funcs) {
|
||||
for (var func : funcs) {
|
||||
func.special = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Native
|
||||
public Symbol symbol(String name, boolean unique) {
|
||||
if (!unique && symbols.containsKey(name)) {
|
||||
return symbols.get(name);
|
||||
}
|
||||
else {
|
||||
var val = new Symbol(name);
|
||||
if (!unique) symbols.put(name, val);
|
||||
return val;
|
||||
}
|
||||
}
|
||||
@Native
|
||||
public String symStr(Symbol symbol) {
|
||||
return symbol.value;
|
||||
}
|
||||
|
||||
@Native
|
||||
public void freeze(ObjectValue val) {
|
||||
val.freeze();
|
||||
}
|
||||
@Native
|
||||
public void seal(ObjectValue val) {
|
||||
val.seal();
|
||||
}
|
||||
@Native
|
||||
public void preventExtensions(ObjectValue val) {
|
||||
val.preventExtensions();
|
||||
}
|
||||
|
||||
@Native
|
||||
public boolean extensible(Object val) {
|
||||
return Values.isObject(val) && Values.object(val).extensible();
|
||||
}
|
||||
|
||||
@Native
|
||||
public ArrayValue keys(CallContext ctx, Object obj, boolean onlyString) throws InterruptedException {
|
||||
var res = new ArrayValue();
|
||||
|
||||
var i = 0;
|
||||
var list = Values.getMembers(ctx, obj, true, false);
|
||||
|
||||
for (var el : list) {
|
||||
if (el instanceof Symbol && onlyString) continue;
|
||||
res.set(ctx, i++, el);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@Native
|
||||
public ArrayValue ownPropKeys(CallContext ctx, Object obj, boolean symbols) throws InterruptedException {
|
||||
var res = new ArrayValue();
|
||||
|
||||
if (Values.isObject(obj)) {
|
||||
var i = 0;
|
||||
var list = Values.object(obj).keys(true);
|
||||
|
||||
for (var el : list) {
|
||||
if (el instanceof Symbol == symbols) res.set(ctx, i++, el);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@Native
|
||||
public ObjectValue ownProp(CallContext ctx, ObjectValue val, Object key) throws InterruptedException {
|
||||
return val.getMemberDescriptor(ctx, key);
|
||||
}
|
||||
|
||||
@Native
|
||||
public Object require(CallContext ctx, Object name) throws InterruptedException {
|
||||
var res = ctx.engine().modules().tryLoad(ctx, Values.toString(ctx, name));
|
||||
if (res == null) throw EngineException.ofError("The module '" + name + "' doesn\'t exist.");
|
||||
return res.exports();
|
||||
}
|
||||
|
||||
@Native
|
||||
public GeneratorFunction makeGenerator(FunctionValue func) {
|
||||
if (func instanceof CodeFunction) return new GeneratorFunction((CodeFunction)func);
|
||||
else throw EngineException.ofType("Can't create a generator with a non-js function.");
|
||||
}
|
||||
|
||||
@NativeGetter("err")
|
||||
public ObjectValue errProto(CallContext ctx) {
|
||||
return ctx.engine.errorProto();
|
||||
}
|
||||
@NativeGetter("syntax")
|
||||
public ObjectValue syntaxProto(CallContext ctx) {
|
||||
return ctx.engine.syntaxErrorProto();
|
||||
}
|
||||
@NativeGetter("range")
|
||||
public ObjectValue rangeProto(CallContext ctx) {
|
||||
return ctx.engine.rangeErrorProto();
|
||||
}
|
||||
@NativeGetter("type")
|
||||
public ObjectValue typeProto(CallContext ctx) {
|
||||
return ctx.engine.typeErrorProto();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,105 +1,105 @@
|
||||
package me.topchetoeu.jscript.polyfills;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Engine;
|
||||
import me.topchetoeu.jscript.engine.modules.ModuleManager;
|
||||
|
||||
public class PolyfillEngine extends Engine {
|
||||
public static String streamToString(InputStream in) {
|
||||
try {
|
||||
StringBuilder out = new StringBuilder();
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(in));
|
||||
|
||||
for(var line = br.readLine(); line != null; line = br.readLine()) {
|
||||
out.append(line).append('\n');
|
||||
}
|
||||
|
||||
br.close();
|
||||
return out.toString();
|
||||
}
|
||||
catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public static String resourceToString(String name) {
|
||||
var str = PolyfillEngine.class.getResourceAsStream("/me/topchetoeu/jscript/" + name);
|
||||
if (str == null) return null;
|
||||
return streamToString(str);
|
||||
}
|
||||
|
||||
public final ModuleManager modules;
|
||||
|
||||
@Override
|
||||
public Object makeRegex(String pattern, String flags) {
|
||||
return new RegExp(pattern, flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModuleManager modules() {
|
||||
return modules;
|
||||
}
|
||||
public PolyfillEngine(File root) {
|
||||
super();
|
||||
|
||||
this.modules = new ModuleManager(root);
|
||||
|
||||
// exposeNamespace("Math", Math.class);
|
||||
// exposeNamespace("JSON", JSON.class);
|
||||
// exposeClass("Promise", Promise.class);
|
||||
// exposeClass("RegExp", RegExp.class);
|
||||
// exposeClass("Date", Date.class);
|
||||
// exposeClass("Map", Map.class);
|
||||
// exposeClass("Set", Set.class);
|
||||
|
||||
// global().define("Object", "Function", "String", "Number", "Boolean", "Symbol");
|
||||
// global().define("Array", "require");
|
||||
// global().define("Error", "SyntaxError", "TypeError", "RangeError");
|
||||
// global().define("setTimeout", "setInterval", "clearTimeout", "clearInterval");
|
||||
// global().define("debugger");
|
||||
|
||||
// global().define(true, new NativeFunction("measure", (ctx, thisArg, values) -> {
|
||||
// var start = System.nanoTime();
|
||||
// try {
|
||||
// return Values.call(ctx, values[0], ctx);
|
||||
// }
|
||||
// finally {
|
||||
// System.out.println(String.format("Function took %s ms", (System.nanoTime() - start) / 1000000.));
|
||||
// }
|
||||
// }));
|
||||
// global().define(true, new NativeFunction("isNaN", (ctx, thisArg, args) -> {
|
||||
// if (args.length == 0) return true;
|
||||
// else return Double.isNaN(Values.toNumber(ctx, args[0]));
|
||||
// }));
|
||||
// global().define(true, new NativeFunction("log", (el, t, args) -> {
|
||||
// for (var obj : args) Values.printValue(el, obj);
|
||||
// System.out.println();
|
||||
// return null;
|
||||
// }));
|
||||
|
||||
// var scope = global().globalChild();
|
||||
// scope.define("gt", true, global().obj);
|
||||
// scope.define("lgt", true, scope.obj);
|
||||
// scope.define("setProps", "setConstr");
|
||||
// scope.define("internals", true, new Internals());
|
||||
// scope.define(true, new NativeFunction("run", (ctx, thisArg, args) -> {
|
||||
// var filename = (String)args[0];
|
||||
// boolean pollute = args.length > 1 && args[1].equals(true);
|
||||
// FunctionValue func;
|
||||
// var src = resourceToString("js/" + filename);
|
||||
// if (src == null) throw new RuntimeException("The source '" + filename + "' doesn't exist.");
|
||||
|
||||
// if (pollute) func = Parsing.compile(global(), filename, src);
|
||||
// else func = Parsing.compile(scope.globalChild(), filename, src);
|
||||
|
||||
// func.call(ctx);
|
||||
// return null;
|
||||
// }));
|
||||
|
||||
// pushMsg(false, scope.globalChild(), java.util.Map.of(), "core.js", resourceToString("js/core.js"), null);
|
||||
}
|
||||
}
|
||||
package me.topchetoeu.jscript.polyfills;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Engine;
|
||||
import me.topchetoeu.jscript.engine.modules.ModuleManager;
|
||||
|
||||
public class PolyfillEngine extends Engine {
|
||||
public static String streamToString(InputStream in) {
|
||||
try {
|
||||
StringBuilder out = new StringBuilder();
|
||||
BufferedReader br = new BufferedReader(new InputStreamReader(in));
|
||||
|
||||
for(var line = br.readLine(); line != null; line = br.readLine()) {
|
||||
out.append(line).append('\n');
|
||||
}
|
||||
|
||||
br.close();
|
||||
return out.toString();
|
||||
}
|
||||
catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
public static String resourceToString(String name) {
|
||||
var str = PolyfillEngine.class.getResourceAsStream("/me/topchetoeu/jscript/" + name);
|
||||
if (str == null) return null;
|
||||
return streamToString(str);
|
||||
}
|
||||
|
||||
public final ModuleManager modules;
|
||||
|
||||
@Override
|
||||
public Object makeRegex(String pattern, String flags) {
|
||||
return new RegExp(pattern, flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ModuleManager modules() {
|
||||
return modules;
|
||||
}
|
||||
public PolyfillEngine(File root) {
|
||||
super();
|
||||
|
||||
this.modules = new ModuleManager(root);
|
||||
|
||||
// exposeNamespace("Math", Math.class);
|
||||
// exposeNamespace("JSON", JSON.class);
|
||||
// exposeClass("Promise", Promise.class);
|
||||
// exposeClass("RegExp", RegExp.class);
|
||||
// exposeClass("Date", Date.class);
|
||||
// exposeClass("Map", Map.class);
|
||||
// exposeClass("Set", Set.class);
|
||||
|
||||
// global().define("Object", "Function", "String", "Number", "Boolean", "Symbol");
|
||||
// global().define("Array", "require");
|
||||
// global().define("Error", "SyntaxError", "TypeError", "RangeError");
|
||||
// global().define("setTimeout", "setInterval", "clearTimeout", "clearInterval");
|
||||
// global().define("debugger");
|
||||
|
||||
// global().define(true, new NativeFunction("measure", (ctx, thisArg, values) -> {
|
||||
// var start = System.nanoTime();
|
||||
// try {
|
||||
// return Values.call(ctx, values[0], ctx);
|
||||
// }
|
||||
// finally {
|
||||
// System.out.println(String.format("Function took %s ms", (System.nanoTime() - start) / 1000000.));
|
||||
// }
|
||||
// }));
|
||||
// global().define(true, new NativeFunction("isNaN", (ctx, thisArg, args) -> {
|
||||
// if (args.length == 0) return true;
|
||||
// else return Double.isNaN(Values.toNumber(ctx, args[0]));
|
||||
// }));
|
||||
// global().define(true, new NativeFunction("log", (el, t, args) -> {
|
||||
// for (var obj : args) Values.printValue(el, obj);
|
||||
// System.out.println();
|
||||
// return null;
|
||||
// }));
|
||||
|
||||
// var scope = global().globalChild();
|
||||
// scope.define("gt", true, global().obj);
|
||||
// scope.define("lgt", true, scope.obj);
|
||||
// scope.define("setProps", "setConstr");
|
||||
// scope.define("internals", true, new Internals());
|
||||
// scope.define(true, new NativeFunction("run", (ctx, thisArg, args) -> {
|
||||
// var filename = (String)args[0];
|
||||
// boolean pollute = args.length > 1 && args[1].equals(true);
|
||||
// FunctionValue func;
|
||||
// var src = resourceToString("js/" + filename);
|
||||
// if (src == null) throw new RuntimeException("The source '" + filename + "' doesn't exist.");
|
||||
|
||||
// if (pollute) func = Parsing.compile(global(), filename, src);
|
||||
// else func = Parsing.compile(scope.globalChild(), filename, src);
|
||||
|
||||
// func.call(ctx);
|
||||
// return null;
|
||||
// }));
|
||||
|
||||
// pushMsg(false, scope.globalChild(), java.util.Map.of(), "core.js", resourceToString("js/core.js"), null);
|
||||
}
|
||||
}
|
||||
@@ -1,360 +1,360 @@
|
||||
package me.topchetoeu.jscript.polyfills;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import me.topchetoeu.jscript.engine.CallContext;
|
||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||
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.engine.values.Values;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
|
||||
public class Promise {
|
||||
private static class Handle {
|
||||
public final CallContext ctx;
|
||||
public final FunctionValue fulfilled;
|
||||
public final FunctionValue rejected;
|
||||
|
||||
public Handle(CallContext ctx, FunctionValue fulfilled, FunctionValue rejected) {
|
||||
this.ctx = ctx;
|
||||
this.fulfilled = fulfilled;
|
||||
this.rejected = rejected;
|
||||
}
|
||||
}
|
||||
|
||||
@Native("resolve")
|
||||
public static Promise ofResolved(CallContext ctx, Object val) {
|
||||
if (Values.isWrapper(val, Promise.class)) return Values.wrapper(val, Promise.class);
|
||||
var res = new Promise();
|
||||
res.fulfill(ctx, val);
|
||||
return res;
|
||||
}
|
||||
public static Promise ofResolved(Object val) {
|
||||
if (Values.isWrapper(val, Promise.class)) return Values.wrapper(val, Promise.class);
|
||||
var res = new Promise();
|
||||
res.fulfill(val);
|
||||
return res;
|
||||
}
|
||||
|
||||
@Native("reject")
|
||||
public static Promise ofRejected(CallContext ctx, Object val) {
|
||||
var res = new Promise();
|
||||
res.reject(ctx, val);
|
||||
return res;
|
||||
}
|
||||
public static Promise ofRejected(Object val) {
|
||||
var res = new Promise();
|
||||
res.fulfill(val);
|
||||
return res;
|
||||
}
|
||||
|
||||
@Native
|
||||
public static Promise any(CallContext ctx, Object _promises) {
|
||||
if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array.");
|
||||
var promises = Values.array(_promises);
|
||||
if (promises.size() == 0) return ofResolved(new ArrayValue());
|
||||
var n = new int[] { promises.size() };
|
||||
var res = new Promise();
|
||||
|
||||
var errors = new ArrayValue();
|
||||
|
||||
for (var i = 0; i < promises.size(); i++) {
|
||||
var index = i;
|
||||
var val = promises.get(i);
|
||||
if (Values.isWrapper(val, Promise.class)) Values.wrapper(val, Promise.class).then(
|
||||
ctx,
|
||||
new NativeFunction(null, (e, th, args) -> { res.fulfill(e, args[0]); return null; }),
|
||||
new NativeFunction(null, (e, th, args) -> {
|
||||
errors.set(ctx, index, args[0]);
|
||||
n[0]--;
|
||||
if (n[0] <= 0) res.reject(e, errors);
|
||||
return null;
|
||||
})
|
||||
);
|
||||
else {
|
||||
res.fulfill(ctx, val);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@Native
|
||||
public static Promise race(CallContext ctx, Object _promises) {
|
||||
if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array.");
|
||||
var promises = Values.array(_promises);
|
||||
if (promises.size() == 0) return ofResolved(new ArrayValue());
|
||||
var res = new Promise();
|
||||
|
||||
for (var i = 0; i < promises.size(); i++) {
|
||||
var val = promises.get(i);
|
||||
if (Values.isWrapper(val, Promise.class)) Values.wrapper(val, Promise.class).then(
|
||||
ctx,
|
||||
new NativeFunction(null, (e, th, args) -> { res.fulfill(e, args[0]); return null; }),
|
||||
new NativeFunction(null, (e, th, args) -> { res.reject(e, args[0]); return null; })
|
||||
);
|
||||
else {
|
||||
res.fulfill(val);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@Native
|
||||
public static Promise all(CallContext ctx, Object _promises) {
|
||||
if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array.");
|
||||
var promises = Values.array(_promises);
|
||||
if (promises.size() == 0) return ofResolved(new ArrayValue());
|
||||
var n = new int[] { promises.size() };
|
||||
var res = new Promise();
|
||||
|
||||
var result = new ArrayValue();
|
||||
|
||||
for (var i = 0; i < promises.size(); i++) {
|
||||
var index = i;
|
||||
var val = promises.get(i);
|
||||
if (Values.isWrapper(val, Promise.class)) Values.wrapper(val, Promise.class).then(
|
||||
ctx,
|
||||
new NativeFunction(null, (e, th, args) -> {
|
||||
result.set(ctx, index, args[0]);
|
||||
n[0]--;
|
||||
if (n[0] <= 0) res.fulfill(e, result);
|
||||
return null;
|
||||
}),
|
||||
new NativeFunction(null, (e, th, args) -> { res.reject(e, args[0]); return null; })
|
||||
);
|
||||
else {
|
||||
result.set(ctx, i, val);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (n[0] <= 0) res.fulfill(ctx, result);
|
||||
|
||||
return res;
|
||||
}
|
||||
@Native
|
||||
public static Promise allSettled(CallContext ctx, Object _promises) {
|
||||
if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array.");
|
||||
var promises = Values.array(_promises);
|
||||
if (promises.size() == 0) return ofResolved(new ArrayValue());
|
||||
var n = new int[] { promises.size() };
|
||||
var res = new Promise();
|
||||
|
||||
var result = new ArrayValue();
|
||||
|
||||
for (var i = 0; i < promises.size(); i++) {
|
||||
var index = i;
|
||||
var val = promises.get(i);
|
||||
if (Values.isWrapper(val, Promise.class)) Values.wrapper(val, Promise.class).then(
|
||||
ctx,
|
||||
new NativeFunction(null, (e, th, args) -> {
|
||||
result.set(ctx, index, new ObjectValue(ctx, Map.of(
|
||||
"status", "fulfilled",
|
||||
"value", args[0]
|
||||
)));
|
||||
n[0]--;
|
||||
if (n[0] <= 0) res.fulfill(e, result);
|
||||
return null;
|
||||
}),
|
||||
new NativeFunction(null, (e, th, args) -> {
|
||||
result.set(ctx, index, new ObjectValue(ctx, Map.of(
|
||||
"status", "rejected",
|
||||
"reason", args[0]
|
||||
)));
|
||||
n[0]--;
|
||||
if (n[0] <= 0) res.fulfill(e, result);
|
||||
return null;
|
||||
})
|
||||
);
|
||||
else {
|
||||
result.set(ctx, i, new ObjectValue(ctx, Map.of(
|
||||
"status", "fulfilled",
|
||||
"value", val
|
||||
)));
|
||||
n[0]--;
|
||||
}
|
||||
}
|
||||
|
||||
if (n[0] <= 0) res.fulfill(ctx, result);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private List<Handle> handles = new ArrayList<>();
|
||||
|
||||
private static final int STATE_PENDING = 0;
|
||||
private static final int STATE_FULFILLED = 1;
|
||||
private static final int STATE_REJECTED = 2;
|
||||
|
||||
private int state = STATE_PENDING;
|
||||
private Object val;
|
||||
|
||||
/**
|
||||
* Thread safe - call from any thread
|
||||
*/
|
||||
public void fulfill(Object val) {
|
||||
if (Values.isWrapper(val, Promise.class)) throw new IllegalArgumentException("A promise may not be a fulfil value.");
|
||||
if (state != STATE_PENDING) return;
|
||||
|
||||
this.state = STATE_FULFILLED;
|
||||
this.val = val;
|
||||
for (var el : handles) el.ctx.engine().pushMsg(true, el.fulfilled, el.ctx.data(), null, val);
|
||||
handles = null;
|
||||
}
|
||||
/**
|
||||
* Thread safe - call from any thread
|
||||
*/
|
||||
public void fulfill(CallContext ctx, Object val) {
|
||||
if (Values.isWrapper(val, Promise.class)) Values.wrapper(val, Promise.class).then(ctx,
|
||||
new NativeFunction(null, (e, th, args) -> {
|
||||
this.fulfill(e, args[0]);
|
||||
return null;
|
||||
}),
|
||||
new NativeFunction(null, (e, th, args) -> {
|
||||
this.reject(e, args[0]);
|
||||
return null;
|
||||
})
|
||||
);
|
||||
else this.fulfill(val);
|
||||
}
|
||||
/**
|
||||
* Thread safe - call from any thread
|
||||
*/
|
||||
public void reject(Object val) {
|
||||
if (Values.isWrapper(val, Promise.class)) throw new IllegalArgumentException("A promise may not be a reject value.");
|
||||
if (state != STATE_PENDING) return;
|
||||
|
||||
this.state = STATE_REJECTED;
|
||||
this.val = val;
|
||||
for (var el : handles) el.ctx.engine().pushMsg(true, el.rejected, el.ctx.data(), null, val);
|
||||
handles = null;
|
||||
}
|
||||
/**
|
||||
* Thread safe - call from any thread
|
||||
*/
|
||||
public void reject(CallContext ctx, Object val) {
|
||||
if (Values.isWrapper(val, Promise.class)) Values.wrapper(val, Promise.class).then(ctx,
|
||||
new NativeFunction(null, (e, th, args) -> {
|
||||
this.reject(e, args[0]);
|
||||
return null;
|
||||
}),
|
||||
new NativeFunction(null, (e, th, args) -> {
|
||||
this.reject(e, args[0]);
|
||||
return null;
|
||||
})
|
||||
);
|
||||
else this.reject(val);
|
||||
}
|
||||
|
||||
private void handle(CallContext ctx, FunctionValue fulfill, FunctionValue reject) {
|
||||
if (state == STATE_FULFILLED) ctx.engine().pushMsg(true, fulfill, ctx.data(), null, val);
|
||||
else if (state == STATE_REJECTED) ctx.engine().pushMsg(true, reject, ctx.data(), null, val);
|
||||
else handles.add(new Handle(ctx, fulfill, reject));
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread safe - you can call this from anywhere
|
||||
* HOWEVER, it's strongly recommended to use this only in javascript
|
||||
*/
|
||||
@Native
|
||||
public Promise then(CallContext ctx, Object onFulfill, Object onReject) {
|
||||
if (!(onFulfill instanceof FunctionValue)) onFulfill = null;
|
||||
if (!(onReject instanceof FunctionValue)) onReject = null;
|
||||
|
||||
var res = new Promise();
|
||||
|
||||
var fulfill = (FunctionValue)onFulfill;
|
||||
var reject = (FunctionValue)onReject;
|
||||
|
||||
handle(ctx,
|
||||
new NativeFunction(null, (e, th, args) -> {
|
||||
if (fulfill == null) res.fulfill(e, args[0]);
|
||||
else {
|
||||
try { res.fulfill(e, fulfill.call(e, null, args[0])); }
|
||||
catch (EngineException err) { res.reject(e, err.value); }
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
new NativeFunction(null, (e, th, args) -> {
|
||||
if (reject == null) res.reject(e, args[0]);
|
||||
else {
|
||||
try { res.fulfill(e, reject.call(e, null, args[0])); }
|
||||
catch (EngineException err) { res.reject(e, err.value); }
|
||||
}
|
||||
return null;
|
||||
})
|
||||
);
|
||||
|
||||
return res;
|
||||
}
|
||||
/**
|
||||
* Thread safe - you can call this from anywhere
|
||||
* HOWEVER, it's strongly recommended to use this only in javascript
|
||||
*/
|
||||
@Native("catch")
|
||||
public Promise _catch(CallContext ctx, Object onReject) {
|
||||
return then(ctx, null, onReject);
|
||||
}
|
||||
/**
|
||||
* Thread safe - you can call this from anywhere
|
||||
* HOWEVER, it's strongly recommended to use this only in javascript
|
||||
*/
|
||||
@Native("finally")
|
||||
public Promise _finally(CallContext ctx, Object handle) {
|
||||
return then(ctx,
|
||||
new NativeFunction(null, (e, th, args) -> {
|
||||
if (handle instanceof FunctionValue) ((FunctionValue)handle).call(ctx);
|
||||
return args[0];
|
||||
}),
|
||||
new NativeFunction(null, (e, th, args) -> {
|
||||
if (handle instanceof FunctionValue) ((FunctionValue)handle).call(ctx);
|
||||
throw new EngineException(args[0]);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* NOT THREAD SAFE - must be called from the engine executor thread
|
||||
*/
|
||||
@Native
|
||||
public Promise(CallContext ctx, FunctionValue func) throws InterruptedException {
|
||||
if (!(func instanceof FunctionValue)) throw EngineException.ofType("A function must be passed to the promise constructor.");
|
||||
try {
|
||||
func.call(
|
||||
ctx, null,
|
||||
new NativeFunction(null, (e, th, args) -> {
|
||||
fulfill(e, args.length > 0 ? args[0] : null);
|
||||
return null;
|
||||
}),
|
||||
new NativeFunction(null, (e, th, args) -> {
|
||||
reject(e, args.length > 0 ? args[0] : null);
|
||||
return null;
|
||||
})
|
||||
);
|
||||
}
|
||||
catch (EngineException e) {
|
||||
reject(ctx, e.value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override @Native
|
||||
public String toString() {
|
||||
if (state == STATE_PENDING) return "Promise (pending)";
|
||||
else if (state == STATE_FULFILLED) return "Promise (fulfilled)";
|
||||
else return "Promise (rejected)";
|
||||
}
|
||||
|
||||
private Promise(int state, Object val) {
|
||||
this.state = state;
|
||||
this.val = val;
|
||||
}
|
||||
public Promise() {
|
||||
this(STATE_PENDING, null);
|
||||
}
|
||||
}
|
||||
package me.topchetoeu.jscript.polyfills;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import me.topchetoeu.jscript.engine.CallContext;
|
||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||
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.engine.values.Values;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
|
||||
public class Promise {
|
||||
private static class Handle {
|
||||
public final CallContext ctx;
|
||||
public final FunctionValue fulfilled;
|
||||
public final FunctionValue rejected;
|
||||
|
||||
public Handle(CallContext ctx, FunctionValue fulfilled, FunctionValue rejected) {
|
||||
this.ctx = ctx;
|
||||
this.fulfilled = fulfilled;
|
||||
this.rejected = rejected;
|
||||
}
|
||||
}
|
||||
|
||||
@Native("resolve")
|
||||
public static Promise ofResolved(CallContext ctx, Object val) {
|
||||
if (Values.isWrapper(val, Promise.class)) return Values.wrapper(val, Promise.class);
|
||||
var res = new Promise();
|
||||
res.fulfill(ctx, val);
|
||||
return res;
|
||||
}
|
||||
public static Promise ofResolved(Object val) {
|
||||
if (Values.isWrapper(val, Promise.class)) return Values.wrapper(val, Promise.class);
|
||||
var res = new Promise();
|
||||
res.fulfill(val);
|
||||
return res;
|
||||
}
|
||||
|
||||
@Native("reject")
|
||||
public static Promise ofRejected(CallContext ctx, Object val) {
|
||||
var res = new Promise();
|
||||
res.reject(ctx, val);
|
||||
return res;
|
||||
}
|
||||
public static Promise ofRejected(Object val) {
|
||||
var res = new Promise();
|
||||
res.fulfill(val);
|
||||
return res;
|
||||
}
|
||||
|
||||
@Native
|
||||
public static Promise any(CallContext ctx, Object _promises) {
|
||||
if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array.");
|
||||
var promises = Values.array(_promises);
|
||||
if (promises.size() == 0) return ofResolved(new ArrayValue());
|
||||
var n = new int[] { promises.size() };
|
||||
var res = new Promise();
|
||||
|
||||
var errors = new ArrayValue();
|
||||
|
||||
for (var i = 0; i < promises.size(); i++) {
|
||||
var index = i;
|
||||
var val = promises.get(i);
|
||||
if (Values.isWrapper(val, Promise.class)) Values.wrapper(val, Promise.class).then(
|
||||
ctx,
|
||||
new NativeFunction(null, (e, th, args) -> { res.fulfill(e, args[0]); return null; }),
|
||||
new NativeFunction(null, (e, th, args) -> {
|
||||
errors.set(ctx, index, args[0]);
|
||||
n[0]--;
|
||||
if (n[0] <= 0) res.reject(e, errors);
|
||||
return null;
|
||||
})
|
||||
);
|
||||
else {
|
||||
res.fulfill(ctx, val);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@Native
|
||||
public static Promise race(CallContext ctx, Object _promises) {
|
||||
if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array.");
|
||||
var promises = Values.array(_promises);
|
||||
if (promises.size() == 0) return ofResolved(new ArrayValue());
|
||||
var res = new Promise();
|
||||
|
||||
for (var i = 0; i < promises.size(); i++) {
|
||||
var val = promises.get(i);
|
||||
if (Values.isWrapper(val, Promise.class)) Values.wrapper(val, Promise.class).then(
|
||||
ctx,
|
||||
new NativeFunction(null, (e, th, args) -> { res.fulfill(e, args[0]); return null; }),
|
||||
new NativeFunction(null, (e, th, args) -> { res.reject(e, args[0]); return null; })
|
||||
);
|
||||
else {
|
||||
res.fulfill(val);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@Native
|
||||
public static Promise all(CallContext ctx, Object _promises) {
|
||||
if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array.");
|
||||
var promises = Values.array(_promises);
|
||||
if (promises.size() == 0) return ofResolved(new ArrayValue());
|
||||
var n = new int[] { promises.size() };
|
||||
var res = new Promise();
|
||||
|
||||
var result = new ArrayValue();
|
||||
|
||||
for (var i = 0; i < promises.size(); i++) {
|
||||
var index = i;
|
||||
var val = promises.get(i);
|
||||
if (Values.isWrapper(val, Promise.class)) Values.wrapper(val, Promise.class).then(
|
||||
ctx,
|
||||
new NativeFunction(null, (e, th, args) -> {
|
||||
result.set(ctx, index, args[0]);
|
||||
n[0]--;
|
||||
if (n[0] <= 0) res.fulfill(e, result);
|
||||
return null;
|
||||
}),
|
||||
new NativeFunction(null, (e, th, args) -> { res.reject(e, args[0]); return null; })
|
||||
);
|
||||
else {
|
||||
result.set(ctx, i, val);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (n[0] <= 0) res.fulfill(ctx, result);
|
||||
|
||||
return res;
|
||||
}
|
||||
@Native
|
||||
public static Promise allSettled(CallContext ctx, Object _promises) {
|
||||
if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array.");
|
||||
var promises = Values.array(_promises);
|
||||
if (promises.size() == 0) return ofResolved(new ArrayValue());
|
||||
var n = new int[] { promises.size() };
|
||||
var res = new Promise();
|
||||
|
||||
var result = new ArrayValue();
|
||||
|
||||
for (var i = 0; i < promises.size(); i++) {
|
||||
var index = i;
|
||||
var val = promises.get(i);
|
||||
if (Values.isWrapper(val, Promise.class)) Values.wrapper(val, Promise.class).then(
|
||||
ctx,
|
||||
new NativeFunction(null, (e, th, args) -> {
|
||||
result.set(ctx, index, new ObjectValue(ctx, Map.of(
|
||||
"status", "fulfilled",
|
||||
"value", args[0]
|
||||
)));
|
||||
n[0]--;
|
||||
if (n[0] <= 0) res.fulfill(e, result);
|
||||
return null;
|
||||
}),
|
||||
new NativeFunction(null, (e, th, args) -> {
|
||||
result.set(ctx, index, new ObjectValue(ctx, Map.of(
|
||||
"status", "rejected",
|
||||
"reason", args[0]
|
||||
)));
|
||||
n[0]--;
|
||||
if (n[0] <= 0) res.fulfill(e, result);
|
||||
return null;
|
||||
})
|
||||
);
|
||||
else {
|
||||
result.set(ctx, i, new ObjectValue(ctx, Map.of(
|
||||
"status", "fulfilled",
|
||||
"value", val
|
||||
)));
|
||||
n[0]--;
|
||||
}
|
||||
}
|
||||
|
||||
if (n[0] <= 0) res.fulfill(ctx, result);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private List<Handle> handles = new ArrayList<>();
|
||||
|
||||
private static final int STATE_PENDING = 0;
|
||||
private static final int STATE_FULFILLED = 1;
|
||||
private static final int STATE_REJECTED = 2;
|
||||
|
||||
private int state = STATE_PENDING;
|
||||
private Object val;
|
||||
|
||||
/**
|
||||
* Thread safe - call from any thread
|
||||
*/
|
||||
public void fulfill(Object val) {
|
||||
if (Values.isWrapper(val, Promise.class)) throw new IllegalArgumentException("A promise may not be a fulfil value.");
|
||||
if (state != STATE_PENDING) return;
|
||||
|
||||
this.state = STATE_FULFILLED;
|
||||
this.val = val;
|
||||
for (var el : handles) el.ctx.engine().pushMsg(true, el.fulfilled, el.ctx.data(), null, val);
|
||||
handles = null;
|
||||
}
|
||||
/**
|
||||
* Thread safe - call from any thread
|
||||
*/
|
||||
public void fulfill(CallContext ctx, Object val) {
|
||||
if (Values.isWrapper(val, Promise.class)) Values.wrapper(val, Promise.class).then(ctx,
|
||||
new NativeFunction(null, (e, th, args) -> {
|
||||
this.fulfill(e, args[0]);
|
||||
return null;
|
||||
}),
|
||||
new NativeFunction(null, (e, th, args) -> {
|
||||
this.reject(e, args[0]);
|
||||
return null;
|
||||
})
|
||||
);
|
||||
else this.fulfill(val);
|
||||
}
|
||||
/**
|
||||
* Thread safe - call from any thread
|
||||
*/
|
||||
public void reject(Object val) {
|
||||
if (Values.isWrapper(val, Promise.class)) throw new IllegalArgumentException("A promise may not be a reject value.");
|
||||
if (state != STATE_PENDING) return;
|
||||
|
||||
this.state = STATE_REJECTED;
|
||||
this.val = val;
|
||||
for (var el : handles) el.ctx.engine().pushMsg(true, el.rejected, el.ctx.data(), null, val);
|
||||
handles = null;
|
||||
}
|
||||
/**
|
||||
* Thread safe - call from any thread
|
||||
*/
|
||||
public void reject(CallContext ctx, Object val) {
|
||||
if (Values.isWrapper(val, Promise.class)) Values.wrapper(val, Promise.class).then(ctx,
|
||||
new NativeFunction(null, (e, th, args) -> {
|
||||
this.reject(e, args[0]);
|
||||
return null;
|
||||
}),
|
||||
new NativeFunction(null, (e, th, args) -> {
|
||||
this.reject(e, args[0]);
|
||||
return null;
|
||||
})
|
||||
);
|
||||
else this.reject(val);
|
||||
}
|
||||
|
||||
private void handle(CallContext ctx, FunctionValue fulfill, FunctionValue reject) {
|
||||
if (state == STATE_FULFILLED) ctx.engine().pushMsg(true, fulfill, ctx.data(), null, val);
|
||||
else if (state == STATE_REJECTED) ctx.engine().pushMsg(true, reject, ctx.data(), null, val);
|
||||
else handles.add(new Handle(ctx, fulfill, reject));
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread safe - you can call this from anywhere
|
||||
* HOWEVER, it's strongly recommended to use this only in javascript
|
||||
*/
|
||||
@Native
|
||||
public Promise then(CallContext ctx, Object onFulfill, Object onReject) {
|
||||
if (!(onFulfill instanceof FunctionValue)) onFulfill = null;
|
||||
if (!(onReject instanceof FunctionValue)) onReject = null;
|
||||
|
||||
var res = new Promise();
|
||||
|
||||
var fulfill = (FunctionValue)onFulfill;
|
||||
var reject = (FunctionValue)onReject;
|
||||
|
||||
handle(ctx,
|
||||
new NativeFunction(null, (e, th, args) -> {
|
||||
if (fulfill == null) res.fulfill(e, args[0]);
|
||||
else {
|
||||
try { res.fulfill(e, fulfill.call(e, null, args[0])); }
|
||||
catch (EngineException err) { res.reject(e, err.value); }
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
new NativeFunction(null, (e, th, args) -> {
|
||||
if (reject == null) res.reject(e, args[0]);
|
||||
else {
|
||||
try { res.fulfill(e, reject.call(e, null, args[0])); }
|
||||
catch (EngineException err) { res.reject(e, err.value); }
|
||||
}
|
||||
return null;
|
||||
})
|
||||
);
|
||||
|
||||
return res;
|
||||
}
|
||||
/**
|
||||
* Thread safe - you can call this from anywhere
|
||||
* HOWEVER, it's strongly recommended to use this only in javascript
|
||||
*/
|
||||
@Native("catch")
|
||||
public Promise _catch(CallContext ctx, Object onReject) {
|
||||
return then(ctx, null, onReject);
|
||||
}
|
||||
/**
|
||||
* Thread safe - you can call this from anywhere
|
||||
* HOWEVER, it's strongly recommended to use this only in javascript
|
||||
*/
|
||||
@Native("finally")
|
||||
public Promise _finally(CallContext ctx, Object handle) {
|
||||
return then(ctx,
|
||||
new NativeFunction(null, (e, th, args) -> {
|
||||
if (handle instanceof FunctionValue) ((FunctionValue)handle).call(ctx);
|
||||
return args[0];
|
||||
}),
|
||||
new NativeFunction(null, (e, th, args) -> {
|
||||
if (handle instanceof FunctionValue) ((FunctionValue)handle).call(ctx);
|
||||
throw new EngineException(args[0]);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* NOT THREAD SAFE - must be called from the engine executor thread
|
||||
*/
|
||||
@Native
|
||||
public Promise(CallContext ctx, FunctionValue func) throws InterruptedException {
|
||||
if (!(func instanceof FunctionValue)) throw EngineException.ofType("A function must be passed to the promise constructor.");
|
||||
try {
|
||||
func.call(
|
||||
ctx, null,
|
||||
new NativeFunction(null, (e, th, args) -> {
|
||||
fulfill(e, args.length > 0 ? args[0] : null);
|
||||
return null;
|
||||
}),
|
||||
new NativeFunction(null, (e, th, args) -> {
|
||||
reject(e, args.length > 0 ? args[0] : null);
|
||||
return null;
|
||||
})
|
||||
);
|
||||
}
|
||||
catch (EngineException e) {
|
||||
reject(ctx, e.value);
|
||||
}
|
||||
}
|
||||
|
||||
@Override @Native
|
||||
public String toString() {
|
||||
if (state == STATE_PENDING) return "Promise (pending)";
|
||||
else if (state == STATE_FULFILLED) return "Promise (fulfilled)";
|
||||
else return "Promise (rejected)";
|
||||
}
|
||||
|
||||
private Promise(int state, Object val) {
|
||||
this.state = state;
|
||||
this.val = val;
|
||||
}
|
||||
public Promise() {
|
||||
this(STATE_PENDING, null);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user