feat: a lot of typescript corelibs translated to java
This commit is contained in:
@@ -10,7 +10,7 @@ import java.nio.file.Path;
|
||||
import me.topchetoeu.jscript.engine.MessageContext;
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.Engine;
|
||||
import me.topchetoeu.jscript.engine.FunctionContext;
|
||||
import me.topchetoeu.jscript.engine.Environment;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.events.Observer;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
@@ -21,7 +21,7 @@ import me.topchetoeu.jscript.polyfills.Internals;
|
||||
public class Main {
|
||||
static Thread task;
|
||||
static Engine engine;
|
||||
static FunctionContext env;
|
||||
static Environment env;
|
||||
|
||||
public static String streamToString(InputStream in) {
|
||||
try {
|
||||
@@ -47,37 +47,14 @@ public class Main {
|
||||
|
||||
private static Observer<Object> valuePrinter = new Observer<Object>() {
|
||||
public void next(Object data) {
|
||||
try {
|
||||
Values.printValue(null, data);
|
||||
}
|
||||
try { Values.printValue(null, data); }
|
||||
catch (InterruptedException e) { }
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
public void error(RuntimeException err) {
|
||||
try {
|
||||
try {
|
||||
if (err instanceof EngineException) {
|
||||
System.out.println("Uncaught " + ((EngineException)err).toString(new Context(null, new MessageContext(engine))));
|
||||
}
|
||||
else if (err instanceof SyntaxException) {
|
||||
System.out.println("Syntax error:" + ((SyntaxException)err).msg);
|
||||
}
|
||||
else if (err.getCause() instanceof InterruptedException) return;
|
||||
else {
|
||||
System.out.println("Internal error ocurred:");
|
||||
err.printStackTrace();
|
||||
}
|
||||
}
|
||||
catch (EngineException ex) {
|
||||
System.out.println("Uncaught ");
|
||||
Values.printValue(null, ((EngineException)err).value);
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
return;
|
||||
}
|
||||
try { Values.printError(err, null); }
|
||||
catch (InterruptedException ex) { return; }
|
||||
}
|
||||
};
|
||||
|
||||
@@ -85,8 +62,10 @@ 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();
|
||||
env = new FunctionContext(null, null, null);
|
||||
var builderEnv = new FunctionContext(null, new NativeTypeRegister(), null);
|
||||
|
||||
// TODO: Replace type register with safer accessor
|
||||
env = new Environment(null, new NativeTypeRegister(), null);
|
||||
var builderEnv = new Environment(null, new NativeTypeRegister(), null);
|
||||
var exited = new boolean[1];
|
||||
|
||||
env.global.define("exit", ctx -> {
|
||||
|
||||
@@ -5,16 +5,16 @@ import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.parsing.Parsing;
|
||||
|
||||
public class Context {
|
||||
public final FunctionContext function;
|
||||
public final Environment env;
|
||||
public final MessageContext message;
|
||||
|
||||
public FunctionValue compile(String filename, String raw) throws InterruptedException {
|
||||
var res = Values.toString(this, function.compile.call(this, null, raw, filename));
|
||||
return Parsing.compile(function, filename, res);
|
||||
var res = Values.toString(this, env.compile.call(this, null, raw, filename));
|
||||
return Parsing.compile(env, filename, res);
|
||||
}
|
||||
|
||||
public Context(FunctionContext funcCtx, MessageContext msgCtx) {
|
||||
this.function = funcCtx;
|
||||
public Context(Environment funcCtx, MessageContext msgCtx) {
|
||||
this.env = funcCtx;
|
||||
this.message = msgCtx;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ public class Engine {
|
||||
private class UncompiledFunction extends FunctionValue {
|
||||
public final String filename;
|
||||
public final String raw;
|
||||
public final FunctionContext ctx;
|
||||
public final Environment ctx;
|
||||
|
||||
@Override
|
||||
public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException {
|
||||
@@ -19,7 +19,7 @@ public class Engine {
|
||||
return ctx.compile(filename, raw).call(ctx, thisArg, args);
|
||||
}
|
||||
|
||||
public UncompiledFunction(FunctionContext ctx, String filename, String raw) {
|
||||
public UncompiledFunction(Environment ctx, String filename, String raw) {
|
||||
super(filename, 0);
|
||||
this.filename = filename;
|
||||
this.raw = raw;
|
||||
@@ -109,7 +109,7 @@ public class Engine {
|
||||
return msg.notifier;
|
||||
}
|
||||
public Awaitable<Object> pushMsg(boolean micro, Context ctx, String filename, String raw, Object thisArg, Object ...args) {
|
||||
return pushMsg(micro, ctx.message, new UncompiledFunction(ctx.function, filename, raw), thisArg, args);
|
||||
return pushMsg(micro, ctx.message, new UncompiledFunction(ctx.env, filename, raw), thisArg, args);
|
||||
}
|
||||
|
||||
// public Engine() {
|
||||
|
||||
@@ -6,15 +6,20 @@ 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.engine.values.Symbol;
|
||||
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 FunctionContext {
|
||||
public class Environment {
|
||||
private HashMap<String, ObjectValue> prototypes = new HashMap<>();
|
||||
public GlobalScope global;
|
||||
public WrappersProvider wrappersProvider;
|
||||
/**
|
||||
* NOTE: This is not the register for Symbol.for, but for the symbols like Symbol.iterator
|
||||
*/
|
||||
public HashMap<String, Symbol> symbols = new HashMap<>();
|
||||
|
||||
@Native public FunctionValue compile;
|
||||
@Native public FunctionValue regexConstructor = new NativeFunction("RegExp", (ctx, thisArg, args) -> {
|
||||
@@ -26,6 +31,16 @@ public class FunctionContext {
|
||||
@Native public void setProto(String name, ObjectValue val) {
|
||||
prototypes.put(name, val);
|
||||
}
|
||||
|
||||
@Native public Symbol symbol(String name) {
|
||||
if (symbols.containsKey(name)) return symbols.get(name);
|
||||
else {
|
||||
var res = new Symbol(name);
|
||||
symbols.put(name, res);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
// @Native public ObjectValue arrayPrototype = new ObjectValue();
|
||||
// @Native public ObjectValue boolPrototype = new ObjectValue();
|
||||
// @Native public ObjectValue functionPrototype = new ObjectValue();
|
||||
@@ -48,21 +63,21 @@ public class FunctionContext {
|
||||
}
|
||||
|
||||
@Native
|
||||
public FunctionContext fork() {
|
||||
var res = new FunctionContext(compile, wrappersProvider, global);
|
||||
public Environment fork() {
|
||||
var res = new Environment(compile, wrappersProvider, global);
|
||||
res.regexConstructor = regexConstructor;
|
||||
res.prototypes = new HashMap<>(prototypes);
|
||||
return res;
|
||||
}
|
||||
|
||||
@Native
|
||||
public FunctionContext child() {
|
||||
public Environment child() {
|
||||
var res = fork();
|
||||
res.global = res.global.globalChild();
|
||||
return res;
|
||||
}
|
||||
|
||||
public FunctionContext(FunctionValue compile, WrappersProvider nativeConverter, GlobalScope global) {
|
||||
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) {
|
||||
@@ -107,11 +107,11 @@ public class CodeFrame {
|
||||
return Runners.exec(ctx, instr, this);
|
||||
}
|
||||
catch (EngineException e) {
|
||||
throw e.add(function.name, prevLoc);
|
||||
throw e.add(function.name, prevLoc).setContext(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
public Object next(Context ctx, Object prevReturn, Object prevError) throws InterruptedException {
|
||||
public Object next(Context ctx, Object prevValue, Object prevReturn, Object prevError) throws InterruptedException {
|
||||
TryCtx tryCtx = null;
|
||||
if (prevError != Runners.NO_RETURN) prevReturn = Runners.NO_RETURN;
|
||||
|
||||
@@ -196,6 +196,7 @@ public class CodeFrame {
|
||||
|
||||
if (prevError != Runners.NO_RETURN) throw new EngineException(prevError);
|
||||
if (prevReturn != Runners.NO_RETURN) return prevReturn;
|
||||
if (prevValue != Runners.NO_RETURN) push(ctx, prevValue);
|
||||
|
||||
if (tryCtx == null) return nextNoTry(ctx);
|
||||
else if (tryCtx.state == TryCtx.STATE_TRY) {
|
||||
@@ -263,7 +264,7 @@ public class CodeFrame {
|
||||
try {
|
||||
ctx.message.pushFrame(this);
|
||||
while (true) {
|
||||
var res = next(ctx, Runners.NO_RETURN, Runners.NO_RETURN);
|
||||
var res = next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, Runners.NO_RETURN);
|
||||
if (res != Runners.NO_RETURN) return res;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,16 +48,18 @@ public class Runners {
|
||||
var callArgs = frame.take(instr.get(0));
|
||||
var funcObj = frame.pop();
|
||||
|
||||
if (Values.isFunction(funcObj) && Values.function(funcObj).special) {
|
||||
frame.push(ctx, call(ctx, funcObj, null, callArgs));
|
||||
}
|
||||
else {
|
||||
var proto = Values.getMember(ctx, funcObj, "prototype");
|
||||
var obj = new ObjectValue();
|
||||
obj.setPrototype(ctx, proto);
|
||||
call(ctx, funcObj, obj, callArgs);
|
||||
frame.push(ctx, obj);
|
||||
}
|
||||
frame.push(ctx, Values.callNew(ctx, funcObj, callArgs));
|
||||
|
||||
// if (Values.isFunction(funcObj) && Values.function(funcObj).special) {
|
||||
// frame.push(ctx, call(ctx, funcObj, null, callArgs));
|
||||
// }
|
||||
// else {
|
||||
// var proto = Values.getMember(ctx, funcObj, "prototype");
|
||||
// var obj = new ObjectValue();
|
||||
// obj.setPrototype(ctx, proto);
|
||||
// call(ctx, funcObj, obj, callArgs);
|
||||
// frame.push(ctx, obj);
|
||||
// }
|
||||
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
@@ -65,7 +67,7 @@ public class Runners {
|
||||
|
||||
public static Object execMakeVar(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
var name = (String)instr.get(0);
|
||||
ctx.function.global.define(name);
|
||||
ctx.env.global.define(name);
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
@@ -160,7 +162,7 @@ public class Runners {
|
||||
public static Object execLoadVar(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
var i = instr.get(0);
|
||||
|
||||
if (i instanceof String) frame.push(ctx, ctx.function.global.get(ctx, (String)i));
|
||||
if (i instanceof String) frame.push(ctx, ctx.env.global.get(ctx, (String)i));
|
||||
else frame.push(ctx, frame.scope.get((int)i).get(ctx));
|
||||
|
||||
frame.codePtr++;
|
||||
@@ -172,7 +174,7 @@ public class Runners {
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execLoadGlob(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
frame.push(ctx, ctx.function.global.obj);
|
||||
frame.push(ctx, ctx.env.global.obj);
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
@@ -198,7 +200,7 @@ public class Runners {
|
||||
var body = new Instruction[end - start];
|
||||
System.arraycopy(frame.function.body, start, body, 0, end - start);
|
||||
|
||||
var func = new CodeFunction(ctx.function, "", localsN, len, captures, body);
|
||||
var func = new CodeFunction(ctx.env, "", localsN, len, captures, body);
|
||||
frame.push(ctx, func);
|
||||
|
||||
frame.codePtr += n;
|
||||
@@ -222,7 +224,7 @@ public class Runners {
|
||||
return execLoadMember(ctx, instr, frame);
|
||||
}
|
||||
public static Object execLoadRegEx(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
frame.push(ctx, ctx.function.regexConstructor.call(ctx, null, instr.get(0), instr.get(1)));
|
||||
frame.push(ctx, ctx.env.regexConstructor.call(ctx, null, instr.get(0), instr.get(1)));
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
@@ -246,7 +248,7 @@ public class Runners {
|
||||
var val = (boolean)instr.get(1) ? frame.peek() : frame.pop();
|
||||
var i = instr.get(0);
|
||||
|
||||
if (i instanceof String) ctx.function.global.set(ctx, (String)i, val);
|
||||
if (i instanceof String) ctx.env.global.set(ctx, (String)i, val);
|
||||
else frame.scope.get((int)i).set(ctx, val);
|
||||
|
||||
frame.codePtr++;
|
||||
@@ -293,8 +295,8 @@ public class Runners {
|
||||
Object obj;
|
||||
|
||||
if (name != null) {
|
||||
if (ctx.function.global.has(ctx, name)) {
|
||||
obj = ctx.function.global.get(ctx, name);
|
||||
if (ctx.env.global.has(ctx, name)) {
|
||||
obj = ctx.env.global.get(ctx, name);
|
||||
}
|
||||
else obj = null;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package me.topchetoeu.jscript.engine.values;
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.FunctionContext;
|
||||
import me.topchetoeu.jscript.engine.Environment;
|
||||
import me.topchetoeu.jscript.engine.frame.CodeFrame;
|
||||
import me.topchetoeu.jscript.engine.scope.ValueVariable;
|
||||
|
||||
@@ -12,7 +12,7 @@ public class CodeFunction extends FunctionValue {
|
||||
public final int length;
|
||||
public final Instruction[] body;
|
||||
public final ValueVariable[] captures;
|
||||
public FunctionContext environment;
|
||||
public Environment environment;
|
||||
|
||||
public Location loc() {
|
||||
for (var instr : body) {
|
||||
@@ -32,7 +32,7 @@ public class CodeFunction extends FunctionValue {
|
||||
return new CodeFrame(ctx, thisArg, args, this).run(new Context(environment, ctx.message));
|
||||
}
|
||||
|
||||
public CodeFunction(FunctionContext environment, String name, int localsN, int length, ValueVariable[] captures, Instruction[] body) {
|
||||
public CodeFunction(Environment environment, String name, int localsN, int length, ValueVariable[] captures, Instruction[] body) {
|
||||
super(name, length);
|
||||
this.captures = captures;
|
||||
this.environment = environment;
|
||||
|
||||
@@ -8,7 +8,8 @@ public class NativeWrapper extends ObjectValue {
|
||||
|
||||
@Override
|
||||
public ObjectValue getPrototype(Context ctx) throws InterruptedException {
|
||||
if (prototype == NATIVE_PROTO) return ctx.function.wrappersProvider.getProto(wrapped.getClass());
|
||||
if (prototype == NATIVE_PROTO)
|
||||
return ctx.env.wrappersProvider.getProto(wrapped.getClass());
|
||||
else return super.getPrototype(ctx);
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ public class ObjectValue {
|
||||
|
||||
public final boolean memberWritable(Object key) {
|
||||
if (state == State.FROZEN) return false;
|
||||
return !nonWritableSet.contains(key);
|
||||
return values.containsKey(key) && !nonWritableSet.contains(key);
|
||||
}
|
||||
public final boolean memberConfigurable(Object key) {
|
||||
if (state == State.SEALED || state == State.FROZEN) return false;
|
||||
@@ -147,13 +147,13 @@ public class ObjectValue {
|
||||
|
||||
public ObjectValue getPrototype(Context ctx) throws InterruptedException {
|
||||
try {
|
||||
if (prototype == OBJ_PROTO) return ctx.function.proto("object");
|
||||
if (prototype == ARR_PROTO) return ctx.function.proto("array");
|
||||
if (prototype == FUNC_PROTO) return ctx.function.proto("function");
|
||||
if (prototype == ERR_PROTO) return ctx.function.proto("error");
|
||||
if (prototype == RANGE_ERR_PROTO) return ctx.function.proto("rangeErr");
|
||||
if (prototype == SYNTAX_ERR_PROTO) return ctx.function.proto("syntaxErr");
|
||||
if (prototype == TYPE_ERR_PROTO) return ctx.function.proto("typeErr");
|
||||
if (prototype == OBJ_PROTO) return ctx.env.proto("object");
|
||||
if (prototype == ARR_PROTO) return ctx.env.proto("array");
|
||||
if (prototype == FUNC_PROTO) return ctx.env.proto("function");
|
||||
if (prototype == ERR_PROTO) return ctx.env.proto("error");
|
||||
if (prototype == RANGE_ERR_PROTO) return ctx.env.proto("rangeErr");
|
||||
if (prototype == SYNTAX_ERR_PROTO) return ctx.env.proto("syntaxErr");
|
||||
if (prototype == TYPE_ERR_PROTO) return ctx.env.proto("typeErr");
|
||||
}
|
||||
catch (NullPointerException e) {
|
||||
return null;
|
||||
@@ -172,14 +172,14 @@ public class ObjectValue {
|
||||
else if (Values.isObject(val)) {
|
||||
var obj = Values.object(val);
|
||||
|
||||
if (ctx != null && ctx.function != null) {
|
||||
if (obj == ctx.function.proto("object")) prototype = OBJ_PROTO;
|
||||
else if (obj == ctx.function.proto("array")) prototype = ARR_PROTO;
|
||||
else if (obj == ctx.function.proto("function")) prototype = FUNC_PROTO;
|
||||
else if (obj == ctx.function.proto("error")) prototype = ERR_PROTO;
|
||||
else if (obj == ctx.function.proto("syntaxErr")) prototype = SYNTAX_ERR_PROTO;
|
||||
else if (obj == ctx.function.proto("typeErr")) prototype = TYPE_ERR_PROTO;
|
||||
else if (obj == ctx.function.proto("rangeErr")) prototype = RANGE_ERR_PROTO;
|
||||
if (ctx != null && ctx.env != null) {
|
||||
if (obj == ctx.env.proto("object")) prototype = OBJ_PROTO;
|
||||
else if (obj == ctx.env.proto("array")) prototype = ARR_PROTO;
|
||||
else if (obj == ctx.env.proto("function")) prototype = FUNC_PROTO;
|
||||
else if (obj == ctx.env.proto("error")) prototype = ERR_PROTO;
|
||||
else if (obj == ctx.env.proto("syntaxErr")) prototype = SYNTAX_ERR_PROTO;
|
||||
else if (obj == ctx.env.proto("typeErr")) prototype = TYPE_ERR_PROTO;
|
||||
else if (obj == ctx.env.proto("rangeErr")) prototype = RANGE_ERR_PROTO;
|
||||
else prototype = obj;
|
||||
}
|
||||
else prototype = obj;
|
||||
|
||||
@@ -13,7 +13,9 @@ import java.util.Map;
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.Operation;
|
||||
import me.topchetoeu.jscript.engine.frame.ConvertHint;
|
||||
import me.topchetoeu.jscript.exceptions.ConvertException;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.exceptions.SyntaxException;
|
||||
|
||||
public class Values {
|
||||
public static final Object NULL = new Object();
|
||||
@@ -321,10 +323,10 @@ public class Values {
|
||||
if (isObject(obj)) return object(obj).getPrototype(ctx);
|
||||
if (ctx == null) return null;
|
||||
|
||||
if (obj instanceof String) return ctx.function.proto("string");
|
||||
else if (obj instanceof Number) return ctx.function.proto("number");
|
||||
else if (obj instanceof Boolean) return ctx.function.proto("bool");
|
||||
else if (obj instanceof Symbol) return ctx.function.proto("symbol");
|
||||
if (obj instanceof String) return ctx.env.proto("string");
|
||||
else if (obj instanceof Number) return ctx.env.proto("number");
|
||||
else if (obj instanceof Boolean) return ctx.env.proto("bool");
|
||||
else if (obj instanceof Symbol) return ctx.env.proto("symbol");
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -352,12 +354,39 @@ public class Values {
|
||||
|
||||
return res;
|
||||
}
|
||||
public static ObjectValue getMemberDescriptor(Context ctx, Object obj, Object key) throws InterruptedException {
|
||||
if (obj instanceof ObjectValue) return ((ObjectValue)obj).getMemberDescriptor(ctx, key);
|
||||
else if (obj instanceof String && key instanceof Number) {
|
||||
var i = ((Number)key).intValue();
|
||||
var _i = ((Number)key).doubleValue();
|
||||
if (i - _i != 0) return null;
|
||||
if (i < 0 || i >= ((String)obj).length()) return null;
|
||||
|
||||
return new ObjectValue(ctx, Map.of(
|
||||
"value", ((String)obj).charAt(i) + "",
|
||||
"writable", false,
|
||||
"enumerable", true,
|
||||
"configurable", false
|
||||
));
|
||||
}
|
||||
else return null;
|
||||
}
|
||||
|
||||
public static Object call(Context ctx, Object func, Object thisArg, Object ...args) throws InterruptedException {
|
||||
if (!isFunction(func))
|
||||
throw EngineException.ofType("Tried to call a non-function value.");
|
||||
if (!isFunction(func)) throw EngineException.ofType("Tried to call a non-function value.");
|
||||
return function(func).call(ctx, thisArg, args);
|
||||
}
|
||||
public static Object callNew(Context ctx, Object func, Object ...args) throws InterruptedException {
|
||||
if (func instanceof FunctionValue && ((FunctionValue)func).special) return ((FunctionValue)func).call(ctx, null, args);
|
||||
|
||||
var res = new ObjectValue();
|
||||
var proto = Values.getMember(ctx, func, "prototype");
|
||||
res.setPrototype(ctx, proto);
|
||||
|
||||
call(ctx, func, res, args);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public static boolean strictEquals(Context ctx, Object a, Object b) {
|
||||
a = normalize(ctx, a); b = normalize(ctx, b);
|
||||
@@ -420,7 +449,7 @@ public class Values {
|
||||
|
||||
if (val instanceof Class) {
|
||||
if (ctx == null) return null;
|
||||
else return ctx.function.wrappersProvider.getConstr((Class<?>)val);
|
||||
else return ctx.env.wrappersProvider.getConstr((Class<?>)val);
|
||||
}
|
||||
|
||||
return new NativeWrapper(val);
|
||||
@@ -429,17 +458,15 @@ public class Values {
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T convert(Context ctx, Object obj, Class<T> clazz) throws InterruptedException {
|
||||
if (clazz == Void.class) return null;
|
||||
if (clazz == null || clazz == Object.class) return (T)obj;
|
||||
|
||||
var err = new IllegalArgumentException(String.format("Cannot convert '%s' to '%s'.", type(obj), clazz.getName()));
|
||||
|
||||
if (obj instanceof NativeWrapper) {
|
||||
var res = ((NativeWrapper)obj).wrapped;
|
||||
if (clazz.isInstance(res)) return (T)res;
|
||||
}
|
||||
|
||||
if (clazz == null || clazz == Object.class) return (T)obj;
|
||||
|
||||
if (obj instanceof ArrayValue) {
|
||||
|
||||
if (clazz.isAssignableFrom(ArrayList.class)) {
|
||||
var raw = array(obj).toArray();
|
||||
var res = new ArrayList<>();
|
||||
@@ -480,11 +507,9 @@ public class Values {
|
||||
|
||||
if (clazz == Character.class || clazz == char.class) {
|
||||
if (obj instanceof Number) return (T)(Character)(char)number(obj);
|
||||
else if (obj == NULL) throw new IllegalArgumentException("Cannot convert null to character.");
|
||||
else if (obj == null) throw new IllegalArgumentException("Cannot convert undefined to character.");
|
||||
else {
|
||||
var res = toString(ctx, obj);
|
||||
if (res.length() == 0) throw new IllegalArgumentException("Cannot convert empty string to character.");
|
||||
if (res.length() == 0) throw new ConvertException("\"\"", "Character");
|
||||
else return (T)(Character)res.charAt(0);
|
||||
}
|
||||
}
|
||||
@@ -492,20 +517,22 @@ public class Values {
|
||||
if (obj == null) return null;
|
||||
if (clazz.isInstance(obj)) return (T)obj;
|
||||
|
||||
throw err;
|
||||
throw new ConvertException(type(obj), clazz.getName());
|
||||
}
|
||||
|
||||
public static Iterable<Object> toJavaIterable(Context ctx, Object obj) throws InterruptedException {
|
||||
return () -> {
|
||||
try {
|
||||
var constr = getMember(ctx, ctx.function.proto("symbol"), "constructor");
|
||||
var symbol = getMember(ctx, constr, "iterator");
|
||||
var symbol = ctx.env.symbol("Symbol.iterator");
|
||||
|
||||
var iteratorFunc = getMember(ctx, obj, symbol);
|
||||
if (!isFunction(iteratorFunc)) return Collections.emptyIterator();
|
||||
var iterator = getMember(ctx, call(ctx, iteratorFunc, obj), "next");
|
||||
if (!isFunction(iterator)) return Collections.emptyIterator();
|
||||
var iterable = obj;
|
||||
var iterator = iteratorFunc instanceof FunctionValue ?
|
||||
((FunctionValue)iteratorFunc).call(ctx, iteratorFunc, obj) :
|
||||
iteratorFunc;
|
||||
var nextFunc = getMember(ctx, call(ctx, iteratorFunc, obj), "next");
|
||||
|
||||
if (!isFunction(nextFunc)) return Collections.emptyIterator();
|
||||
|
||||
return new Iterator<Object>() {
|
||||
private Object value = null;
|
||||
@@ -515,7 +542,7 @@ public class Values {
|
||||
private void loadNext() throws InterruptedException {
|
||||
if (next == null) value = null;
|
||||
else if (consumed) {
|
||||
var curr = object(next.call(ctx, iterable));
|
||||
var curr = object(next.call(ctx, iterator));
|
||||
if (curr == null) { next = null; value = null; }
|
||||
if (toBoolean(curr.getMember(ctx, "done"))) { next = null; value = null; }
|
||||
else {
|
||||
@@ -567,7 +594,7 @@ public class Values {
|
||||
var it = iterable.iterator();
|
||||
|
||||
try {
|
||||
var key = getMember(ctx, getMember(ctx, ctx.function.proto("symbol"), "constructor"), "iterator");
|
||||
var key = getMember(ctx, getMember(ctx, ctx.env.proto("symbol"), "constructor"), "iterator");
|
||||
res.defineProperty(ctx, key, new NativeFunction("", (_ctx, thisArg, args) -> thisArg));
|
||||
}
|
||||
catch (IllegalArgumentException | NullPointerException e) { }
|
||||
@@ -655,4 +682,25 @@ public class Values {
|
||||
public static void printValue(Context ctx, Object val) throws InterruptedException {
|
||||
printValue(ctx, val, new HashSet<>(), 0);
|
||||
}
|
||||
public static void printError(RuntimeException err, String prefix) throws InterruptedException {
|
||||
prefix = prefix == null ? "Uncauthg" : "Uncaught " + prefix;
|
||||
try {
|
||||
if (err instanceof EngineException) {
|
||||
System.out.println(prefix + " " + ((EngineException)err).toString(((EngineException)err).ctx));
|
||||
}
|
||||
else if (err instanceof SyntaxException) {
|
||||
System.out.println("Syntax error:" + ((SyntaxException)err).msg);
|
||||
}
|
||||
else if (err.getCause() instanceof InterruptedException) return;
|
||||
else {
|
||||
System.out.println("Internal error ocurred:");
|
||||
err.printStackTrace();
|
||||
}
|
||||
}
|
||||
catch (EngineException ex) {
|
||||
System.out.println("Uncaught ");
|
||||
Values.printValue(null, ((EngineException)err).value);
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
src/me/topchetoeu/jscript/exceptions/ConvertException.java
Normal file
11
src/me/topchetoeu/jscript/exceptions/ConvertException.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package me.topchetoeu.jscript.exceptions;
|
||||
|
||||
public class ConvertException extends RuntimeException {
|
||||
public final String source, target;
|
||||
|
||||
public ConvertException(String source, String target) {
|
||||
super(String.format("Cannot convert '%s' to '%s'.", source, target));
|
||||
this.source = source;
|
||||
this.target = target;
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto;
|
||||
public class EngineException extends RuntimeException {
|
||||
public final Object value;
|
||||
public EngineException cause;
|
||||
public Context ctx = null;
|
||||
public final List<String> stackTrace = new ArrayList<>();
|
||||
|
||||
public EngineException add(String name, Location location) {
|
||||
@@ -27,6 +28,10 @@ public class EngineException extends RuntimeException {
|
||||
this.cause = cause;
|
||||
return this;
|
||||
}
|
||||
public EngineException setContext(Context ctx) {
|
||||
this.ctx = ctx;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String toString(Context ctx) throws InterruptedException {
|
||||
var ss = new StringBuilder();
|
||||
|
||||
@@ -9,4 +9,5 @@ import java.lang.annotation.Target;
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Native {
|
||||
public String value() default "";
|
||||
public boolean raw() default false;
|
||||
}
|
||||
|
||||
13
src/me/topchetoeu/jscript/interop/NativeConstructor.java
Normal file
13
src/me/topchetoeu/jscript/interop/NativeConstructor.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package me.topchetoeu.jscript.interop;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target({ ElementType.METHOD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface NativeConstructor {
|
||||
public boolean raw() default false;
|
||||
}
|
||||
|
||||
@@ -9,4 +9,5 @@ import java.lang.annotation.Target;
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface NativeGetter {
|
||||
public String value();
|
||||
public boolean raw() default false;
|
||||
}
|
||||
|
||||
@@ -9,4 +9,5 @@ import java.lang.annotation.Target;
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface NativeSetter {
|
||||
public String value();
|
||||
public boolean raw() default false;
|
||||
}
|
||||
|
||||
@@ -13,25 +13,35 @@ public class NativeTypeRegister implements WrappersProvider {
|
||||
private final HashMap<Class<?>, FunctionValue> constructors = new HashMap<>();
|
||||
private final HashMap<Class<?>, ObjectValue> prototypes = new HashMap<>();
|
||||
|
||||
private static boolean isMember(Class<?>[] args) {
|
||||
return args.length == 3;
|
||||
}
|
||||
|
||||
private static void applyMethods(boolean member, ObjectValue target, Class<?> clazz) {
|
||||
for (var method : clazz.getDeclaredMethods()) {
|
||||
if (!Modifier.isStatic(method.getModifiers()) != member) continue;
|
||||
|
||||
var nat = method.getAnnotation(Native.class);
|
||||
var get = method.getAnnotation(NativeGetter.class);
|
||||
var set = method.getAnnotation(NativeSetter.class);
|
||||
var params = method.getParameterTypes();
|
||||
var memberMismatch = !Modifier.isStatic(method.getModifiers()) != member;
|
||||
|
||||
if (nat != null) {
|
||||
if (nat.raw()) {
|
||||
if (isMember(params) != member) continue;
|
||||
}
|
||||
else if (memberMismatch) continue;
|
||||
|
||||
var name = nat.value();
|
||||
var val = target.values.get(name);
|
||||
|
||||
if (name.equals("")) name = method.getName();
|
||||
if (!(val instanceof OverloadFunction)) target.defineProperty(null, name, val = new OverloadFunction(name));
|
||||
|
||||
((OverloadFunction)val).overloads.add(Overload.fromMethod(method));
|
||||
((OverloadFunction)val).overloads.add(Overload.fromMethod(method, nat.raw()));
|
||||
}
|
||||
else {
|
||||
if (get != null) {
|
||||
if (get.raw() && isMember(params) != member || memberMismatch) continue;
|
||||
var name = get.value();
|
||||
var prop = target.properties.get(name);
|
||||
OverloadFunction getter = null;
|
||||
@@ -40,10 +50,11 @@ public class NativeTypeRegister implements WrappersProvider {
|
||||
if (prop != null && prop.getter instanceof OverloadFunction) getter = (OverloadFunction)prop.getter;
|
||||
else getter = new OverloadFunction("get " + name);
|
||||
|
||||
getter.overloads.add(Overload.fromMethod(method));
|
||||
getter.overloads.add(Overload.fromMethod(method, get.raw()));
|
||||
target.defineProperty(null, name, getter, setter, true, true);
|
||||
}
|
||||
if (set != null) {
|
||||
if (set.raw() && isMember(params) != member || memberMismatch) continue;
|
||||
var name = set.value();
|
||||
var prop = target.properties.get(name);
|
||||
var getter = prop == null ? null : prop.getter;
|
||||
@@ -52,7 +63,7 @@ public class NativeTypeRegister implements WrappersProvider {
|
||||
if (prop != null && prop.setter instanceof OverloadFunction) setter = (OverloadFunction)prop.setter;
|
||||
else setter = new OverloadFunction("set " + name);
|
||||
|
||||
setter.overloads.add(Overload.fromMethod(method));
|
||||
setter.overloads.add(Overload.fromMethod(method, set.raw()));
|
||||
target.defineProperty(null, name, getter, setter, true, true);
|
||||
}
|
||||
}
|
||||
@@ -66,8 +77,8 @@ public class NativeTypeRegister implements WrappersProvider {
|
||||
if (nat != null) {
|
||||
var name = nat.value();
|
||||
if (name.equals("")) name = field.getName();
|
||||
var getter = new OverloadFunction("get " + name).add(Overload.getterFromField(field));
|
||||
var setter = new OverloadFunction("set " + name).add(Overload.setterFromField(field));
|
||||
var getter = new OverloadFunction("get " + name).add(Overload.getterFromField(field, nat.raw()));
|
||||
var setter = new OverloadFunction("set " + name).add(Overload.setterFromField(field, nat.raw()));
|
||||
target.defineProperty(null, name, getter, setter, true, false);
|
||||
}
|
||||
}
|
||||
@@ -113,8 +124,14 @@ public class NativeTypeRegister implements WrappersProvider {
|
||||
FunctionValue func = new OverloadFunction(clazz.getName());
|
||||
|
||||
for (var overload : clazz.getConstructors()) {
|
||||
if (overload.getAnnotation(Native.class) == null) continue;
|
||||
((OverloadFunction)func).add(Overload.fromConstructor(overload));
|
||||
var nat = overload.getAnnotation(Native.class);
|
||||
if (nat == null) continue;
|
||||
((OverloadFunction)func).add(Overload.fromConstructor(overload, nat.raw()));
|
||||
}
|
||||
for (var overload : clazz.getMethods()) {
|
||||
var constr = overload.getAnnotation(NativeConstructor.class);
|
||||
if (constr == null) continue;
|
||||
((OverloadFunction)func).add(Overload.fromMethod(overload, constr.raw()));
|
||||
}
|
||||
|
||||
if (((OverloadFunction)func).overloads.size() == 0) {
|
||||
|
||||
@@ -17,53 +17,63 @@ public class Overload {
|
||||
|
||||
public final OverloadRunner runner;
|
||||
public final boolean variadic;
|
||||
public final boolean raw;
|
||||
public final Class<?> thisArg;
|
||||
public final Class<?>[] params;
|
||||
|
||||
public static Overload fromMethod(Method method) {
|
||||
public static Overload fromMethod(Method method, boolean raw) {
|
||||
return new Overload(
|
||||
(ctx, th, args) -> method.invoke(th, args),
|
||||
method.isVarArgs(),
|
||||
method.isVarArgs(), raw,
|
||||
Modifier.isStatic(method.getModifiers()) ? null : method.getDeclaringClass(),
|
||||
method.getParameterTypes()
|
||||
);
|
||||
}
|
||||
public static Overload fromConstructor(Constructor<?> method) {
|
||||
public static Overload fromConstructor(Constructor<?> method, boolean raw) {
|
||||
return new Overload(
|
||||
(ctx, th, args) -> method.newInstance(args),
|
||||
method.isVarArgs(),
|
||||
method.isVarArgs(), raw,
|
||||
Modifier.isStatic(method.getModifiers()) ? null : method.getDeclaringClass(),
|
||||
method.getParameterTypes()
|
||||
);
|
||||
}
|
||||
public static Overload getterFromField(Field field) {
|
||||
public static Overload getterFromField(Field field, boolean raw) {
|
||||
return new Overload(
|
||||
(ctx, th, args) -> field.get(th), false,
|
||||
(ctx, th, args) -> field.get(th), false, raw,
|
||||
Modifier.isStatic(field.getModifiers()) ? null : field.getDeclaringClass(),
|
||||
new Class[0]
|
||||
);
|
||||
}
|
||||
public static Overload setterFromField(Field field) {
|
||||
public static Overload setterFromField(Field field, boolean raw) {
|
||||
if (Modifier.isFinal(field.getModifiers())) return null;
|
||||
return new Overload(
|
||||
(ctx, th, args) -> { field.set(th, args[0]); return null; }, false,
|
||||
(ctx, th, args) -> { field.set(th, args[0]); return null; }, false, raw,
|
||||
Modifier.isStatic(field.getModifiers()) ? null : field.getDeclaringClass(),
|
||||
new Class[0]
|
||||
);
|
||||
}
|
||||
|
||||
public static Overload getter(Class<?> thisArg, OverloadRunner runner) {
|
||||
public static Overload getter(Class<?> thisArg, OverloadRunner runner, boolean raw) {
|
||||
return new Overload(
|
||||
(ctx, th, args) -> runner.run(ctx, th, args), false,
|
||||
(ctx, th, args) -> runner.run(ctx, th, args), false, raw,
|
||||
thisArg,
|
||||
new Class[0]
|
||||
);
|
||||
}
|
||||
|
||||
public Overload(OverloadRunner runner, boolean variadic, Class<?> thisArg, Class<?> args[]) {
|
||||
public Overload(OverloadRunner runner, boolean variadic, boolean raw, Class<?> thisArg, Class<?> args[]) {
|
||||
this.runner = runner;
|
||||
this.variadic = variadic;
|
||||
this.raw = raw;
|
||||
this.thisArg = thisArg;
|
||||
this.params = args;
|
||||
|
||||
if (raw) {
|
||||
if (!(
|
||||
thisArg == null && (
|
||||
args.length == 3 && args[0] == Context.class && args[1] == Object.class && args[2] == Object[].class ||
|
||||
args.length == 2 && args[0] == Context.class && args[1] == Object[].class
|
||||
))) throw new IllegalArgumentException("Invalid signature for raw method.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,41 +8,61 @@ import java.util.List;
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.exceptions.ConvertException;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
|
||||
public class OverloadFunction extends FunctionValue {
|
||||
public final List<Overload> overloads = new ArrayList<>();
|
||||
|
||||
public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException {
|
||||
for (var overload : overloads) {
|
||||
boolean consumesEngine = overload.params.length > 0 && overload.params[0] == Context.class;
|
||||
int start = consumesEngine ? 1 : 0;
|
||||
int end = overload.params.length - (overload.variadic ? 1 : 0);
|
||||
|
||||
loop: for (var overload : overloads) {
|
||||
Object[] newArgs = new Object[overload.params.length];
|
||||
|
||||
for (var i = start; i < end; i++) {
|
||||
Object val;
|
||||
|
||||
if (i - start >= args.length) val = null;
|
||||
else val = args[i - start];
|
||||
|
||||
newArgs[i] = Values.convert(ctx, val, overload.params[i]);
|
||||
if (overload.raw) {
|
||||
newArgs[0] = ctx;
|
||||
newArgs[1] = thisArg;
|
||||
newArgs[2] = args;
|
||||
}
|
||||
else {
|
||||
boolean consumesEngine = overload.params.length > 0 && overload.params[0] == Context.class;
|
||||
int start = consumesEngine ? 1 : 0;
|
||||
int end = overload.params.length - (overload.variadic ? 1 : 0);
|
||||
|
||||
if (overload.variadic) {
|
||||
var type = overload.params[overload.params.length - 1].getComponentType();
|
||||
var n = Math.max(args.length - end + start, 0);
|
||||
Object varArg = Array.newInstance(type, n);
|
||||
for (var i = start; i < end; i++) {
|
||||
Object val;
|
||||
|
||||
for (var i = 0; i < n; i++) {
|
||||
Array.set(varArg, i, Values.convert(ctx, args[i + end - start], type));
|
||||
if (i - start >= args.length) val = null;
|
||||
else val = args[i - start];
|
||||
|
||||
try {
|
||||
newArgs[i] = Values.convert(ctx, val, overload.params[i]);
|
||||
}
|
||||
catch (ConvertException e) {
|
||||
if (overloads.size() > 1) continue loop;
|
||||
else throw EngineException.ofType(String.format("Argument %d can't be converted from %s to %s", i - start, e.source, e.target));
|
||||
}
|
||||
}
|
||||
|
||||
newArgs[newArgs.length - 1] = varArg;
|
||||
}
|
||||
if (overload.variadic) {
|
||||
var type = overload.params[overload.params.length - 1].getComponentType();
|
||||
var n = Math.max(args.length - end + start, 0);
|
||||
Object varArg = Array.newInstance(type, n);
|
||||
|
||||
if (consumesEngine) newArgs[0] = ctx;
|
||||
for (var i = 0; i < n; i++) {
|
||||
try {
|
||||
Array.set(varArg, i, Values.convert(ctx, args[i + end - start], type));
|
||||
}
|
||||
catch (ConvertException e) {
|
||||
if (overloads.size() > 1) continue loop;
|
||||
else throw EngineException.ofType(String.format("Element in variadic argument can't be converted from %s to %s", e.source, e.target));
|
||||
}
|
||||
}
|
||||
|
||||
newArgs[newArgs.length - 1] = varArg;
|
||||
}
|
||||
|
||||
if (consumesEngine) newArgs[0] = ctx;
|
||||
}
|
||||
|
||||
Object _this = overload.thisArg == null ? null : Values.convert(ctx, thisArg, overload.thisArg);
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import me.topchetoeu.jscript.compilation.VariableDeclareStatement.Pair;
|
||||
import me.topchetoeu.jscript.compilation.control.*;
|
||||
import me.topchetoeu.jscript.compilation.control.SwitchStatement.SwitchCase;
|
||||
import me.topchetoeu.jscript.compilation.values.*;
|
||||
import me.topchetoeu.jscript.engine.FunctionContext;
|
||||
import me.topchetoeu.jscript.engine.Environment;
|
||||
import me.topchetoeu.jscript.engine.Operation;
|
||||
import me.topchetoeu.jscript.engine.scope.ValueVariable;
|
||||
import me.topchetoeu.jscript.engine.values.CodeFunction;
|
||||
@@ -1875,7 +1875,7 @@ public class Parsing {
|
||||
return list.toArray(Statement[]::new);
|
||||
}
|
||||
|
||||
public static CodeFunction compile(FunctionContext environment, Statement ...statements) {
|
||||
public static CodeFunction compile(Environment environment, Statement ...statements) {
|
||||
var target = environment.global.globalChild();
|
||||
var subscope = target.child();
|
||||
var res = new ArrayList<Instruction>();
|
||||
@@ -1905,7 +1905,7 @@ public class Parsing {
|
||||
|
||||
return new CodeFunction(environment, "", subscope.localsCount(), 0, new ValueVariable[0], res.toArray(Instruction[]::new));
|
||||
}
|
||||
public static CodeFunction compile(FunctionContext environment, String filename, String raw) {
|
||||
public static CodeFunction compile(Environment environment, String filename, String raw) {
|
||||
try {
|
||||
return compile(environment, parse(filename, raw));
|
||||
}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
package me.topchetoeu.jscript.polyfills;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.frame.CodeFrame;
|
||||
import me.topchetoeu.jscript.engine.frame.Runners;
|
||||
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.exceptions.EngineException;
|
||||
|
||||
public class AsyncFunctionPolyfill extends FunctionValue {
|
||||
public final FunctionValue factory;
|
||||
|
||||
public static class AsyncHelper {
|
||||
public PromisePolyfill promise = new PromisePolyfill();
|
||||
public CodeFrame frame;
|
||||
|
||||
private boolean awaiting = false;
|
||||
|
||||
private void next(Context ctx, Object inducedValue, Object inducedError) throws InterruptedException {
|
||||
Object res = null;
|
||||
ctx.message.pushFrame(frame);
|
||||
|
||||
awaiting = false;
|
||||
while (!awaiting) {
|
||||
try {
|
||||
res = frame.next(ctx, inducedValue, Runners.NO_RETURN, inducedError);
|
||||
inducedValue = inducedError = Runners.NO_RETURN;
|
||||
if (res != Runners.NO_RETURN) {
|
||||
promise.fulfill(ctx, res);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (EngineException e) {
|
||||
promise.reject(ctx, e.value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ctx.message.popFrame(frame);
|
||||
|
||||
if (awaiting) {
|
||||
PromisePolyfill.then(ctx, frame.pop(), new NativeFunction(this::fulfill), new NativeFunction(this::reject));
|
||||
}
|
||||
}
|
||||
|
||||
public Object fulfill(Context ctx, Object thisArg, Object ...args) throws InterruptedException {
|
||||
next(ctx, args.length > 0 ? args[0] : null, Runners.NO_RETURN);
|
||||
return null;
|
||||
}
|
||||
public Object reject(Context ctx, Object thisArg, Object ...args) throws InterruptedException {
|
||||
next(ctx, Runners.NO_RETURN, args.length > 0 ? args[0] : null);
|
||||
return null;
|
||||
}
|
||||
|
||||
public Object await(Context ctx, Object thisArg, Object[] args) {
|
||||
this.awaiting = true;
|
||||
return args.length > 0 ? args[0] : null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException {
|
||||
var handler = new AsyncHelper();
|
||||
var func = factory.call(ctx, thisArg, new NativeFunction("await", handler::await));
|
||||
if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function.");
|
||||
handler.frame = new CodeFrame(ctx, thisArg, args, (CodeFunction)func);
|
||||
handler.next(ctx, Runners.NO_RETURN, Runners.NO_RETURN);
|
||||
return handler.promise;
|
||||
}
|
||||
|
||||
public AsyncFunctionPolyfill(FunctionValue factory) {
|
||||
super(factory.name, factory.length);
|
||||
this.factory = factory;
|
||||
}
|
||||
}
|
||||
133
src/me/topchetoeu/jscript/polyfills/AsyncGeneratorPolyfill.java
Normal file
133
src/me/topchetoeu/jscript/polyfills/AsyncGeneratorPolyfill.java
Normal file
@@ -0,0 +1,133 @@
|
||||
package me.topchetoeu.jscript.polyfills;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.frame.CodeFrame;
|
||||
import me.topchetoeu.jscript.engine.frame.Runners;
|
||||
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.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
|
||||
public class AsyncGeneratorPolyfill extends FunctionValue {
|
||||
public final FunctionValue factory;
|
||||
|
||||
public static class AsyncGenerator {
|
||||
private int state = 0;
|
||||
private boolean done = false;
|
||||
private PromisePolyfill currPromise;
|
||||
public CodeFrame frame;
|
||||
|
||||
private void next(Context ctx, Object inducedValue, Object inducedReturn, Object inducedError) throws InterruptedException {
|
||||
if (done) {
|
||||
if (inducedError != Runners.NO_RETURN)
|
||||
throw new EngineException(inducedError);
|
||||
currPromise.fulfill(ctx, new ObjectValue(ctx, Map.of(
|
||||
"done", true,
|
||||
"value", inducedReturn == Runners.NO_RETURN ? null : inducedReturn
|
||||
)));
|
||||
return;
|
||||
}
|
||||
|
||||
Object res = null;
|
||||
ctx.message.pushFrame(frame);
|
||||
state = 0;
|
||||
|
||||
while (state == 0) {
|
||||
try {
|
||||
res = frame.next(ctx, inducedValue, inducedReturn, inducedError);
|
||||
inducedValue = inducedReturn = inducedError = Runners.NO_RETURN;
|
||||
if (res != Runners.NO_RETURN) {
|
||||
var obj = new ObjectValue();
|
||||
obj.defineProperty(ctx, "done", true);
|
||||
obj.defineProperty(ctx, "value", res);
|
||||
currPromise.fulfill(ctx, obj);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (EngineException e) {
|
||||
currPromise.reject(ctx, e.value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ctx.message.popFrame(frame);
|
||||
|
||||
if (state == 1) {
|
||||
PromisePolyfill.then(ctx, frame.pop(), new NativeFunction(this::fulfill), new NativeFunction(this::reject));
|
||||
}
|
||||
else if (state == 2) {
|
||||
var obj = new ObjectValue();
|
||||
obj.defineProperty(ctx, "done", false);
|
||||
obj.defineProperty(ctx, "value", frame.pop());
|
||||
currPromise.fulfill(ctx, obj);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (done) return "Generator [closed]";
|
||||
if (state != 0) return "Generator [suspended]";
|
||||
return "Generator [running]";
|
||||
}
|
||||
|
||||
public Object fulfill(Context ctx, Object thisArg, Object ...args) throws InterruptedException {
|
||||
next(ctx, args.length > 0 ? args[0] : null, Runners.NO_RETURN, Runners.NO_RETURN);
|
||||
return null;
|
||||
}
|
||||
public Object reject(Context ctx, Object thisArg, Object ...args) throws InterruptedException {
|
||||
next(ctx, Runners.NO_RETURN, args.length > 0 ? args[0] : null, Runners.NO_RETURN);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Native
|
||||
public PromisePolyfill next(Context ctx, Object ...args) throws InterruptedException {
|
||||
this.currPromise = new PromisePolyfill();
|
||||
if (args.length == 0) next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, Runners.NO_RETURN);
|
||||
else next(ctx, args[0], Runners.NO_RETURN, Runners.NO_RETURN);
|
||||
return this.currPromise;
|
||||
}
|
||||
@Native("throw")
|
||||
public PromisePolyfill _throw(Context ctx, Object error) throws InterruptedException {
|
||||
this.currPromise = new PromisePolyfill();
|
||||
next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, error);
|
||||
return this.currPromise;
|
||||
}
|
||||
@Native("return")
|
||||
public PromisePolyfill _return(Context ctx, Object value) throws InterruptedException {
|
||||
this.currPromise = new PromisePolyfill();
|
||||
next(ctx, Runners.NO_RETURN, value, Runners.NO_RETURN);
|
||||
return this.currPromise;
|
||||
}
|
||||
|
||||
|
||||
public Object await(Context ctx, Object thisArg, Object[] args) {
|
||||
this.state = 1;
|
||||
return args.length > 0 ? args[0] : null;
|
||||
}
|
||||
public Object yield(Context ctx, Object thisArg, Object[] args) {
|
||||
this.state = 2;
|
||||
return args.length > 0 ? args[0] : null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException {
|
||||
var handler = new AsyncGenerator();
|
||||
var func = factory.call(ctx, thisArg,
|
||||
new NativeFunction("await", handler::await),
|
||||
new NativeFunction("yield", handler::yield)
|
||||
);
|
||||
if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function.");
|
||||
handler.frame = new CodeFrame(ctx, thisArg, args, (CodeFunction)func);
|
||||
return handler;
|
||||
}
|
||||
|
||||
public AsyncGeneratorPolyfill(FunctionValue factory) {
|
||||
super(factory.name, factory.length);
|
||||
this.factory = factory;
|
||||
}
|
||||
}
|
||||
60
src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java
Normal file
60
src/me/topchetoeu/jscript/polyfills/FunctionPolyfill.java
Normal file
@@ -0,0 +1,60 @@
|
||||
package me.topchetoeu.jscript.polyfills;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
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.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
|
||||
public class FunctionPolyfill {
|
||||
@Native(raw=true) public static Object apply(Context ctx, Object func, Object[] args) throws InterruptedException {
|
||||
var thisArg = args.length > 0 ? args[0] : null;
|
||||
var _args = args.length > 1 ? args[1] : null;
|
||||
|
||||
if (!(_args instanceof ArrayValue)) throw EngineException.ofError("Expected arguments to be an array.");
|
||||
if (!(func instanceof FunctionValue)) throw EngineException.ofError("Expected this to be a function.");
|
||||
|
||||
return ((FunctionValue)func).call(ctx, thisArg, ((ArrayValue)_args).toArray());
|
||||
}
|
||||
@Native(raw=true) public static Object call(Context ctx, Object func, Object[] args) throws InterruptedException {
|
||||
var thisArg = args.length > 0 ? args[0] : null;
|
||||
var _args = new Object[args.length > 1 ? args.length - 1 : 0];
|
||||
|
||||
if (!(func instanceof FunctionValue)) throw EngineException.ofError("Expected this to be a function.");
|
||||
|
||||
if (_args.length != 0) System.arraycopy(args, 1, _args, 0, _args.length);
|
||||
|
||||
return ((FunctionValue)func).call(ctx, thisArg, _args);
|
||||
}
|
||||
@Native(raw=true) public static Object bind(Context ctx, Object func, Object[] args) {
|
||||
var thisArg = args.length > 0 ? args[0] : null;
|
||||
var _args = new Object[args.length > 1 ? args.length - 1 : 0];
|
||||
FunctionValue _func = (FunctionValue)func;
|
||||
|
||||
if (!(func instanceof FunctionValue)) throw EngineException.ofError("Expected this to be a function.");
|
||||
|
||||
if (_args.length != 0) System.arraycopy(args, 1, _args, 0, _args.length);
|
||||
|
||||
return new NativeFunction(_func.name + " (bound)", (callCtx, _0, callArgs) -> {
|
||||
var resArgs = new Object[_args.length + callArgs.length];
|
||||
System.arraycopy(_args, 0, resArgs, 0, _args.length);
|
||||
System.arraycopy(callArgs, 0, resArgs, _args.length, resArgs.length - _args.length);
|
||||
|
||||
return _func.call(ctx, thisArg, resArgs);
|
||||
});
|
||||
}
|
||||
@Native(raw=true) public static String toString(Context ctx, Object func, Object[] args) {
|
||||
return "function (...) { ... }";
|
||||
}
|
||||
|
||||
@Native public static FunctionValue async(FunctionValue func) {
|
||||
return new AsyncFunctionPolyfill(func);
|
||||
}
|
||||
@Native public static FunctionValue asyncGenerator(FunctionValue func) {
|
||||
return new AsyncGeneratorPolyfill(func);
|
||||
}
|
||||
@Native public static FunctionValue generator(FunctionValue func) {
|
||||
return new GeneratorPolyfill(func);
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
|
||||
public class GeneratorFunction extends FunctionValue {
|
||||
public class GeneratorPolyfill extends FunctionValue {
|
||||
public final FunctionValue factory;
|
||||
|
||||
public static class Generator {
|
||||
@@ -28,12 +28,12 @@ public class GeneratorFunction extends FunctionValue {
|
||||
}
|
||||
|
||||
Object res = null;
|
||||
if (inducedValue != Runners.NO_RETURN) frame.push(ctx, inducedValue);
|
||||
ctx.message.pushFrame(frame);
|
||||
yielding = false;
|
||||
|
||||
while (!yielding) {
|
||||
try {
|
||||
res = frame.next(ctx, inducedReturn, inducedError);
|
||||
res = frame.next(ctx, inducedValue, inducedReturn, inducedError);
|
||||
inducedReturn = inducedError = Runners.NO_RETURN;
|
||||
if (res != Runners.NO_RETURN) {
|
||||
done = true;
|
||||
@@ -74,7 +74,7 @@ public class GeneratorFunction extends FunctionValue {
|
||||
public String toString() {
|
||||
if (done) return "Generator [closed]";
|
||||
if (yielding) return "Generator [suspended]";
|
||||
return "Generator " + (done ? "[closed]" : "[suspended]");
|
||||
return "Generator [running]";
|
||||
}
|
||||
|
||||
public Object yield(Context ctx, Object thisArg, Object[] args) {
|
||||
@@ -92,7 +92,7 @@ public class GeneratorFunction extends FunctionValue {
|
||||
return handler;
|
||||
}
|
||||
|
||||
public GeneratorFunction(FunctionValue factory) {
|
||||
public GeneratorPolyfill(FunctionValue factory) {
|
||||
super(factory.name, factory.length);
|
||||
this.factory = factory;
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
package me.topchetoeu.jscript.polyfills;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.FunctionContext;
|
||||
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;
|
||||
@@ -12,20 +12,25 @@ import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
|
||||
public class Internals {
|
||||
@Native public final Class<ObjectPolyfill> object = ObjectPolyfill.class;
|
||||
@Native public final Class<FunctionPolyfill> function = FunctionPolyfill.class;
|
||||
@Native public final Class<PromisePolyfill> promise = PromisePolyfill.class;
|
||||
|
||||
@Native public void markSpecial(FunctionValue ...funcs) {
|
||||
for (var func : funcs) {
|
||||
func.special = true;
|
||||
}
|
||||
}
|
||||
@Native public FunctionContext getEnv(Object func) {
|
||||
@Native public Environment getEnv(Object func) {
|
||||
if (func instanceof CodeFunction) return ((CodeFunction)func).environment;
|
||||
else return null;
|
||||
}
|
||||
@Native public Object setEnv(Object func, FunctionContext env) {
|
||||
@Native public Object setEnv(Object func, Environment env) {
|
||||
if (func instanceof CodeFunction) ((CodeFunction)func).environment = env;
|
||||
return func;
|
||||
}
|
||||
@Native public Object apply(Context ctx, FunctionValue func, Object thisArg, ArrayValue args) throws InterruptedException {
|
||||
@Native public Object apply(Context ctx, FunctionValue func, Object thisArg, ArrayValue args, Environment env) throws InterruptedException {
|
||||
if (env != null) ctx = new Context(env, ctx.message);
|
||||
return func.call(ctx, thisArg, args.toArray());
|
||||
}
|
||||
@Native public FunctionValue delay(Context ctx, double delay, FunctionValue callback) throws InterruptedException {
|
||||
@@ -84,8 +89,8 @@ public class Internals {
|
||||
@Native public boolean isArray(Object obj) {
|
||||
return obj instanceof ArrayValue;
|
||||
}
|
||||
@Native public GeneratorFunction generator(FunctionValue obj) {
|
||||
return new GeneratorFunction(obj);
|
||||
@Native public GeneratorPolyfill generator(FunctionValue obj) {
|
||||
return new GeneratorPolyfill(obj);
|
||||
}
|
||||
|
||||
@Native public boolean defineField(Context ctx, ObjectValue obj, Object key, Object val, boolean writable, boolean enumerable, boolean configurable) {
|
||||
|
||||
212
src/me/topchetoeu/jscript/polyfills/ObjectPolyfill.java
Normal file
212
src/me/topchetoeu/jscript/polyfills/ObjectPolyfill.java
Normal file
@@ -0,0 +1,212 @@
|
||||
package me.topchetoeu.jscript.polyfills;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||
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.NativeConstructor;
|
||||
|
||||
public class ObjectPolyfill {
|
||||
@Native public static ObjectValue assign(Context ctx, ObjectValue dst, Object... src) throws InterruptedException {
|
||||
for (var obj : src) {
|
||||
for (var key : Values.getMembers(ctx, obj, true, true)) {
|
||||
Values.setMember(ctx, dst, key, Values.getMember(ctx, obj, key));
|
||||
}
|
||||
}
|
||||
return dst;
|
||||
}
|
||||
@Native public static ObjectValue create(Context ctx, ObjectValue proto, ObjectValue props) throws InterruptedException {
|
||||
var obj = new ObjectValue();
|
||||
obj.setPrototype(ctx, proto);
|
||||
return defineProperties(ctx, obj, props);
|
||||
}
|
||||
|
||||
@Native public static ObjectValue defineProperty(Context ctx, ObjectValue obj, Object key, ObjectValue attrib) throws InterruptedException {
|
||||
var hasVal = attrib.hasMember(ctx, "value", false);
|
||||
var hasGet = attrib.hasMember(ctx, "get", false);
|
||||
var hasSet = attrib.hasMember(ctx, "set", false);
|
||||
|
||||
if (hasVal) {
|
||||
if (hasGet || hasSet) throw EngineException.ofType("Cannot specify a value and accessors for a property.");
|
||||
if (!obj.defineProperty(
|
||||
ctx, key,
|
||||
attrib.getMember(ctx, "value"),
|
||||
Values.toBoolean(attrib.getMember(ctx, "writable")),
|
||||
Values.toBoolean(attrib.getMember(ctx, "configurable")),
|
||||
Values.toBoolean(attrib.getMember(ctx, "enumerable"))
|
||||
)) throw EngineException.ofType("Can't define property '" + key + "'.");
|
||||
}
|
||||
else {
|
||||
var get = attrib.getMember(ctx, "get");
|
||||
var set = attrib.getMember(ctx, "set");
|
||||
if (get != null && !(get instanceof FunctionValue)) throw EngineException.ofType("Get accessor must be a function.");
|
||||
if (set != null && !(set instanceof FunctionValue)) throw EngineException.ofType("Set accessor must be a function.");
|
||||
|
||||
if (!obj.defineProperty(
|
||||
ctx, key,
|
||||
(FunctionValue)get, (FunctionValue)set,
|
||||
Values.toBoolean(attrib.getMember(ctx, "configurable")),
|
||||
Values.toBoolean(attrib.getMember(ctx, "enumerable"))
|
||||
)) throw EngineException.ofType("Can't define property '" + key + "'.");
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
@Native public static ObjectValue defineProperties(Context ctx, ObjectValue obj, ObjectValue attrib) throws InterruptedException {
|
||||
for (var key : Values.getMembers(null, obj, false, false)) {
|
||||
obj.defineProperty(ctx, key, attrib.getMember(ctx, key));
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
@Native public static ArrayValue keys(Context ctx, Object obj, Object all) throws InterruptedException {
|
||||
var res = new ArrayValue();
|
||||
var _all = Values.toBoolean(all);
|
||||
|
||||
for (var key : Values.getMembers(ctx, obj, true, false)) {
|
||||
if (_all || !(key instanceof Symbol)) res.set(ctx, res.size(), key);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@Native public static ArrayValue entries(Context ctx, Object obj, Object all) throws InterruptedException {
|
||||
var res = new ArrayValue();
|
||||
var _all = Values.toBoolean(all);
|
||||
|
||||
for (var key : Values.getMembers(ctx, obj, true, false)) {
|
||||
if (_all || !(key instanceof Symbol)) res.set(ctx, res.size(), new ArrayValue(ctx, key, Values.getMember(ctx, obj, key)));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@Native public static ArrayValue values(Context ctx, Object obj, Object all) throws InterruptedException {
|
||||
var res = new ArrayValue();
|
||||
var _all = Values.toBoolean(all);
|
||||
|
||||
for (var key : Values.getMembers(ctx, obj, true, false)) {
|
||||
if (_all || key instanceof String) res.set(ctx, res.size(), Values.getMember(ctx, obj, key));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@Native public static ObjectValue getOwnPropertyDescriptor(Context ctx, Object obj, Object key) throws InterruptedException {
|
||||
return Values.getMemberDescriptor(ctx, obj, key);
|
||||
}
|
||||
@Native public static ObjectValue getOwnPropertyDescriptors(Context ctx, Object obj) throws InterruptedException {
|
||||
var res = new ObjectValue();
|
||||
for (var key : Values.getMembers(ctx, obj, true, true)) {
|
||||
res.defineProperty(ctx, key, getOwnPropertyDescriptor(ctx, obj, key));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@Native public static ArrayValue getOwnPropertyNames(Context ctx, Object obj, Object all) throws InterruptedException {
|
||||
var res = new ArrayValue();
|
||||
var _all = Values.toBoolean(all);
|
||||
|
||||
for (var key : Values.getMembers(ctx, obj, true, true)) {
|
||||
if (_all || !(key instanceof Symbol)) res.set(ctx, res.size(), key);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@Native public static ArrayValue getOwnPropertySymbols(Context ctx, Object obj) throws InterruptedException {
|
||||
var res = new ArrayValue();
|
||||
|
||||
for (var key : Values.getMembers(ctx, obj, true, true)) {
|
||||
if (key instanceof Symbol) res.set(ctx, res.size(), key);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@Native public static boolean hasOwn(Context ctx, Object obj, Object key) throws InterruptedException {
|
||||
return Values.hasMember(ctx, obj, key, true);
|
||||
}
|
||||
|
||||
@Native public static ObjectValue getPrototypeOf(Context ctx, Object obj) throws InterruptedException {
|
||||
return Values.getPrototype(ctx, obj);
|
||||
}
|
||||
@Native public static Object setPrototypeOf(Context ctx, Object obj, Object proto) throws InterruptedException {
|
||||
Values.setPrototype(ctx, obj, proto);
|
||||
return obj;
|
||||
}
|
||||
|
||||
@Native public static ObjectValue fromEntries(Context ctx, Object iterable) throws InterruptedException {
|
||||
var res = new ObjectValue();
|
||||
|
||||
for (var el : Values.toJavaIterable(ctx, iterable)) {
|
||||
if (el instanceof ArrayValue) {
|
||||
res.defineProperty(ctx, ((ArrayValue)el).get(0), ((ArrayValue)el).get(1));
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@Native public static Object preventExtensions(Context ctx, Object obj) throws InterruptedException {
|
||||
if (obj instanceof ObjectValue) ((ObjectValue)obj).preventExtensions();
|
||||
return obj;
|
||||
}
|
||||
@Native public static Object seal(Context ctx, Object obj) throws InterruptedException {
|
||||
if (obj instanceof ObjectValue) ((ObjectValue)obj).seal();
|
||||
return obj;
|
||||
}
|
||||
@Native public static Object freeze(Context ctx, Object obj) throws InterruptedException {
|
||||
if (obj instanceof ObjectValue) ((ObjectValue)obj).freeze();
|
||||
return obj;
|
||||
}
|
||||
|
||||
@Native public static boolean isExtensible(Context ctx, Object obj) throws InterruptedException {
|
||||
return obj instanceof ObjectValue && ((ObjectValue)obj).extensible();
|
||||
}
|
||||
@Native public static boolean isSealed(Context ctx, Object obj) throws InterruptedException {
|
||||
if (obj instanceof ObjectValue && ((ObjectValue)obj).extensible()) {
|
||||
var _obj = (ObjectValue)obj;
|
||||
for (var key : _obj.keys(true)) {
|
||||
if (_obj.memberConfigurable(key)) return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@Native public static boolean isFrozen(Context ctx, Object obj) throws InterruptedException {
|
||||
if (obj instanceof ObjectValue && ((ObjectValue)obj).extensible()) {
|
||||
var _obj = (ObjectValue)obj;
|
||||
for (var key : _obj.keys(true)) {
|
||||
if (_obj.memberConfigurable(key)) return false;
|
||||
if (_obj.memberWritable(key)) return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Native(raw = true) public static Object valueOf(Context ctx, Object thisArg, Object[] args) {
|
||||
return thisArg;
|
||||
}
|
||||
@Native(raw = true) public static String toString(Context ctx, Object thisArg, Object[] args) throws InterruptedException {
|
||||
var name = Values.getMember(ctx, thisArg, ctx.env.symbol("Symbol.typeName"));
|
||||
if (name == null) name = "Unknown";
|
||||
else name = Values.toString(ctx, name);
|
||||
|
||||
return "[object " + name + "]";
|
||||
}
|
||||
@Native(raw = true) public static boolean hasOwnProperty(Context ctx, Object thisArg, Object[] args) throws InterruptedException {
|
||||
return ObjectPolyfill.hasOwn(ctx, thisArg, Values.convert(ctx, args.length == 0 ? null : args[0], String.class));
|
||||
}
|
||||
|
||||
@NativeConstructor public static Object constructor(Context ctx, Object arg) throws InterruptedException {
|
||||
if (arg == null || arg == Values.NULL) return new ObjectValue();
|
||||
else if (arg instanceof Boolean) return Values.callNew(ctx, ctx.env.global.get(ctx, "Boolean"), arg);
|
||||
else if (arg instanceof Number) return Values.callNew(ctx, ctx.env.global.get(ctx, "Number"), arg);
|
||||
else if (arg instanceof String) return Values.callNew(ctx, ctx.env.global.get(ctx, "String"), arg);
|
||||
else if (arg instanceof Symbol) return Values.callNew(ctx, ctx.env.global.get(ctx, "Symbol"), arg);
|
||||
else return arg;
|
||||
}
|
||||
}
|
||||
346
src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java
Normal file
346
src/me/topchetoeu/jscript/polyfills/PromisePolyfill.java
Normal file
@@ -0,0 +1,346 @@
|
||||
package me.topchetoeu.jscript.polyfills;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.MessageContext;
|
||||
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.NativeWrapper;
|
||||
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 PromisePolyfill {
|
||||
private static class Handle {
|
||||
public final Context ctx;
|
||||
public final FunctionValue fulfilled;
|
||||
public final FunctionValue rejected;
|
||||
|
||||
public Handle(Context ctx, FunctionValue fulfilled, FunctionValue rejected) {
|
||||
this.ctx = ctx;
|
||||
this.fulfilled = fulfilled;
|
||||
this.rejected = rejected;
|
||||
}
|
||||
}
|
||||
|
||||
@Native("resolve")
|
||||
public static PromisePolyfill ofResolved(Context ctx, Object val) throws InterruptedException {
|
||||
var res = new PromisePolyfill();
|
||||
res.fulfill(ctx, val);
|
||||
return res;
|
||||
}
|
||||
@Native("reject")
|
||||
public static PromisePolyfill ofRejected(Context ctx, Object val) throws InterruptedException {
|
||||
var res = new PromisePolyfill();
|
||||
res.reject(ctx, val);
|
||||
return res;
|
||||
}
|
||||
|
||||
@Native public static PromisePolyfill any(Context ctx, Object _promises) throws InterruptedException {
|
||||
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(ctx, new ArrayValue());
|
||||
var n = new int[] { promises.size() };
|
||||
var res = new PromisePolyfill();
|
||||
|
||||
var errors = new ArrayValue();
|
||||
|
||||
for (var i = 0; i < promises.size(); i++) {
|
||||
var index = i;
|
||||
var val = promises.get(i);
|
||||
then(ctx, val,
|
||||
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;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@Native public static PromisePolyfill race(Context ctx, Object _promises) throws InterruptedException {
|
||||
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(ctx, new ArrayValue());
|
||||
var res = new PromisePolyfill();
|
||||
|
||||
for (var i = 0; i < promises.size(); i++) {
|
||||
var val = promises.get(i);
|
||||
then(ctx, val,
|
||||
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; })
|
||||
);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@Native public static PromisePolyfill all(Context ctx, Object _promises) throws InterruptedException {
|
||||
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(ctx, new ArrayValue());
|
||||
var n = new int[] { promises.size() };
|
||||
var res = new PromisePolyfill();
|
||||
|
||||
var result = new ArrayValue();
|
||||
|
||||
for (var i = 0; i < promises.size(); i++) {
|
||||
var index = i;
|
||||
var val = promises.get(i);
|
||||
then(ctx, val,
|
||||
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; })
|
||||
);
|
||||
}
|
||||
|
||||
if (n[0] <= 0) res.fulfill(ctx, result);
|
||||
|
||||
return res;
|
||||
}
|
||||
@Native public static PromisePolyfill allSettled(Context ctx, Object _promises) throws InterruptedException {
|
||||
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(ctx, new ArrayValue());
|
||||
var n = new int[] { promises.size() };
|
||||
var res = new PromisePolyfill();
|
||||
|
||||
var result = new ArrayValue();
|
||||
|
||||
for (var i = 0; i < promises.size(); i++) {
|
||||
var index = i;
|
||||
var val = promises.get(i);
|
||||
then(ctx, val,
|
||||
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;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (n[0] <= 0) res.fulfill(ctx, result);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread safe - you can call this from anywhere
|
||||
* HOWEVER, it's strongly recommended to use this only in javascript
|
||||
*/
|
||||
@Native(raw = true) public static Object then(Context ctx, Object thisArg, Object ...args) throws InterruptedException {
|
||||
var onFulfill = args.length > 0 ? args[0] : null;
|
||||
var onReject = args.length > 1 ? args[1]: null;
|
||||
|
||||
if (!(onFulfill instanceof FunctionValue)) onFulfill = null;
|
||||
if (!(onReject instanceof FunctionValue)) onReject = null;
|
||||
|
||||
var res = new PromisePolyfill();
|
||||
|
||||
var fulfill = onFulfill == null ? new NativeFunction((_ctx, _thisArg, _args) -> _args.length > 0 ? _args[0] : null) : (FunctionValue)onFulfill;
|
||||
var reject = onReject == null ? new NativeFunction((_ctx, _thisArg, _args) -> {
|
||||
throw new EngineException(_args.length > 0 ? _args[0] : null);
|
||||
}) : (FunctionValue)onReject;
|
||||
|
||||
if (thisArg instanceof NativeWrapper && ((NativeWrapper)thisArg).wrapped instanceof PromisePolyfill) {
|
||||
thisArg = ((NativeWrapper)thisArg).wrapped;
|
||||
}
|
||||
|
||||
var fulfillHandle = new NativeFunction(null, (_ctx, th, a) -> {
|
||||
try {
|
||||
res.fulfill(ctx, Values.convert(ctx, fulfill.call(ctx, null, a[0]), Object.class));
|
||||
}
|
||||
catch (EngineException err) { res.reject(ctx, err.value); }
|
||||
return null;
|
||||
});
|
||||
var rejectHandle = new NativeFunction(null, (_ctx, th, a) -> {
|
||||
try { res.fulfill(ctx, reject.call(ctx, null, a[0])); }
|
||||
catch (EngineException err) { res.reject(ctx, err.value); }
|
||||
return null;
|
||||
});
|
||||
|
||||
if (thisArg instanceof PromisePolyfill) ((PromisePolyfill)thisArg).handle(ctx, fulfillHandle, rejectHandle);
|
||||
else {
|
||||
Object next;
|
||||
try {
|
||||
next = Values.getMember(ctx, thisArg, "then");
|
||||
}
|
||||
catch (IllegalArgumentException e) { next = null; }
|
||||
|
||||
try {
|
||||
if (next instanceof FunctionValue) ((FunctionValue)next).call(ctx, thisArg, fulfillHandle, rejectHandle);
|
||||
else res.fulfill(ctx, fulfill.call(ctx, null, thisArg));
|
||||
}
|
||||
catch (EngineException err) {
|
||||
res.reject(ctx, fulfill.call(ctx, null, err.value));
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
/**
|
||||
* Thread safe - you can call this from anywhere
|
||||
* HOWEVER, it's strongly recommended to use this only in javascript
|
||||
*/
|
||||
@Native(value = "catch", raw = true) public static Object _catch(Context ctx, Object thisArg, Object ...args) throws InterruptedException {
|
||||
return then(ctx, thisArg, null, args.length > 0 ? args[0] : null);
|
||||
}
|
||||
/**
|
||||
* Thread safe - you can call this from anywhere
|
||||
* HOWEVER, it's strongly recommended to use this only in javascript
|
||||
*/
|
||||
@Native(value = "finally", raw = true) public static Object _finally(Context ctx, Object thisArg, Object ...args) throws InterruptedException {
|
||||
var handleFunc = args.length > 0 ? args[0] : null;
|
||||
|
||||
return then(ctx, thisArg,
|
||||
new NativeFunction(null, (e, th, _args) -> {
|
||||
if (handleFunc instanceof FunctionValue) ((FunctionValue)handleFunc).call(ctx);
|
||||
return args[0];
|
||||
}),
|
||||
new NativeFunction(null, (e, th, _args) -> {
|
||||
if (handleFunc instanceof FunctionValue) ((FunctionValue)handleFunc).call(ctx);
|
||||
throw new EngineException(_args[0]);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
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 boolean handled = false;
|
||||
private Object val;
|
||||
|
||||
private void resolve(Context ctx, Object val, int state) throws InterruptedException {
|
||||
if (this.state != STATE_PENDING) return;
|
||||
|
||||
if (val instanceof PromisePolyfill) ((PromisePolyfill)val).handle(ctx,
|
||||
new NativeFunction(null, (e, th, a) -> { this.resolve(ctx, a[0], state); return null; }),
|
||||
new NativeFunction(null, (e, th, a) -> { this.resolve(ctx, a[0], STATE_REJECTED); return null; })
|
||||
);
|
||||
else {
|
||||
Object next;
|
||||
try { next = Values.getMember(ctx, val, "next"); }
|
||||
catch (IllegalArgumentException e) { next = null; }
|
||||
|
||||
try {
|
||||
if (next instanceof FunctionValue) ((FunctionValue)next).call(ctx, val,
|
||||
new NativeFunction((e, _thisArg, a) -> { this.resolve(ctx, a.length > 0 ? a[0] : null, state); return null; }),
|
||||
new NativeFunction((e, _thisArg, a) -> { this.resolve(ctx, a.length > 0 ? a[0] : null, STATE_REJECTED); return null; })
|
||||
);
|
||||
else {
|
||||
this.val = val;
|
||||
this.state = state;
|
||||
|
||||
if (state == STATE_FULFILLED) {
|
||||
for (var handle : handles) handle.fulfilled.call(handle.ctx, null, val);
|
||||
}
|
||||
else if (state == STATE_REJECTED) {
|
||||
for (var handle : handles) handle.rejected.call(handle.ctx, null, val);
|
||||
if (handles.size() == 0) {
|
||||
ctx.message.engine.pushMsg(true, ctx.message, new NativeFunction((_ctx, _thisArg, _args) -> {
|
||||
if (!handled) {
|
||||
try { Values.printError(new EngineException(val), "(in promise)"); }
|
||||
catch (InterruptedException ex) { }
|
||||
}
|
||||
|
||||
return null;
|
||||
}), null);
|
||||
}
|
||||
}
|
||||
|
||||
handles = null;
|
||||
}
|
||||
}
|
||||
catch (EngineException err) {
|
||||
this.reject(ctx, err.value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread safe - call from any thread
|
||||
*/
|
||||
public void fulfill(Context ctx, Object val) throws InterruptedException {
|
||||
resolve(ctx, val, STATE_FULFILLED);
|
||||
}
|
||||
/**
|
||||
* Thread safe - call from any thread
|
||||
*/
|
||||
public void reject(Context ctx, Object val) throws InterruptedException {
|
||||
resolve(ctx, val, STATE_REJECTED);
|
||||
}
|
||||
|
||||
private void handle(Context ctx, FunctionValue fulfill, FunctionValue reject) {
|
||||
if (state == STATE_FULFILLED) ctx.message.engine.pushMsg(true, new MessageContext(ctx.message.engine), fulfill, null, val);
|
||||
else if (state == STATE_REJECTED) {
|
||||
ctx.message.engine.pushMsg(true, new MessageContext(ctx.message.engine), reject, null, val);
|
||||
handled = true;
|
||||
}
|
||||
else handles.add(new Handle(ctx, fulfill, reject));
|
||||
}
|
||||
|
||||
@Override @Native public String toString() {
|
||||
if (state == STATE_PENDING) return "Promise (pending)";
|
||||
else if (state == STATE_FULFILLED) return "Promise (fulfilled)";
|
||||
else return "Promise (rejected)";
|
||||
}
|
||||
|
||||
/**
|
||||
* NOT THREAD SAFE - must be called from the engine executor thread
|
||||
*/
|
||||
@Native public PromisePolyfill(Context 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);
|
||||
}
|
||||
}
|
||||
|
||||
private PromisePolyfill(int state, Object val) {
|
||||
this.state = state;
|
||||
this.val = val;
|
||||
}
|
||||
public PromisePolyfill() {
|
||||
this(STATE_PENDING, null);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user