feat: make better native wrapper API
This commit is contained in:
parent
e372941e99
commit
978ee8db79
@ -94,15 +94,15 @@ public class Main {
|
|||||||
private static void initEnv() {
|
private static void initEnv() {
|
||||||
environment = Internals.apply(environment);
|
environment = Internals.apply(environment);
|
||||||
|
|
||||||
environment.global.define(false, new NativeFunction("exit", (_ctx, th, args) -> {
|
environment.global.define(false, new NativeFunction("exit", args -> {
|
||||||
exited = true;
|
exited = true;
|
||||||
throw new InterruptException();
|
throw new InterruptException();
|
||||||
}));
|
}));
|
||||||
environment.global.define(false, new NativeFunction("go", (_ctx, th, args) -> {
|
environment.global.define(false, new NativeFunction("go", args -> {
|
||||||
try {
|
try {
|
||||||
var f = Path.of("do.js");
|
var f = Path.of("do.js");
|
||||||
var func = _ctx.compile(new Filename("do", "do/" + j++ + ".js"), new String(Files.readAllBytes(f)));
|
var func = args.ctx.compile(new Filename("do", "do/" + j++ + ".js"), new String(Files.readAllBytes(f)));
|
||||||
return func.call(_ctx);
|
return func.call(args.ctx);
|
||||||
}
|
}
|
||||||
catch (IOException e) {
|
catch (IOException e) {
|
||||||
throw new EngineException("Couldn't open do.js");
|
throw new EngineException("Couldn't open do.js");
|
||||||
@ -119,7 +119,7 @@ public class Main {
|
|||||||
}
|
}
|
||||||
private static void initEngine() {
|
private static void initEngine() {
|
||||||
var ctx = new DebugContext();
|
var ctx = new DebugContext();
|
||||||
// engine.globalEnvironment.add(DebugContext.ENV_KEY, ctx);
|
engine.globalEnvironment.add(DebugContext.ENV_KEY, ctx);
|
||||||
|
|
||||||
debugServer.targets.put("target", (ws, req) -> new SimpleDebugger(ws).attach(ctx));
|
debugServer.targets.put("target", (ws, req) -> new SimpleDebugger(ws).attach(ctx));
|
||||||
engineTask = engine.start();
|
engineTask = engine.start();
|
||||||
|
@ -5,6 +5,7 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
import me.topchetoeu.jscript.Filename;
|
import me.topchetoeu.jscript.Filename;
|
||||||
import me.topchetoeu.jscript.Location;
|
import me.topchetoeu.jscript.Location;
|
||||||
|
import me.topchetoeu.jscript.engine.debug.DebugContext;
|
||||||
import me.topchetoeu.jscript.engine.scope.GlobalScope;
|
import me.topchetoeu.jscript.engine.scope.GlobalScope;
|
||||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||||
@ -16,7 +17,6 @@ import me.topchetoeu.jscript.exceptions.EngineException;
|
|||||||
import me.topchetoeu.jscript.interop.NativeWrapperProvider;
|
import me.topchetoeu.jscript.interop.NativeWrapperProvider;
|
||||||
import me.topchetoeu.jscript.parsing.Parsing;
|
import me.topchetoeu.jscript.parsing.Parsing;
|
||||||
|
|
||||||
// TODO: Remove hardcoded extensions form environment
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public class Environment implements Extensions {
|
public class Environment implements Extensions {
|
||||||
|
|
||||||
@ -64,31 +64,31 @@ public class Environment implements Extensions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static FunctionValue compileFunc(Extensions ext) {
|
public static FunctionValue compileFunc(Extensions ext) {
|
||||||
return ext.init(COMPILE_FUNC, new NativeFunction("compile", (ctx, thisArg, args) -> {
|
return ext.init(COMPILE_FUNC, new NativeFunction("compile", args -> {
|
||||||
var source = Values.toString(ctx, args[0]);
|
var source = args.getString(0);
|
||||||
var filename = Values.toString(ctx, args[1]);
|
var filename = args.getString(1);
|
||||||
var isDebug = Values.toBoolean(args[2]);
|
var env = Values.wrapper(args.get(2, ObjectValue.class).getMember(args.ctx, Symbol.get("env")), Environment.class);
|
||||||
|
var isDebug = args.ctx.has(DebugContext.ENV_KEY);
|
||||||
var env = Values.wrapper(Values.getMember(ctx, args[2], Symbol.get("env")), Environment.class);
|
|
||||||
var res = new ObjectValue();
|
var res = new ObjectValue();
|
||||||
|
|
||||||
var target = Parsing.compile(env, Filename.parse(filename), source);
|
var target = Parsing.compile(env, Filename.parse(filename), source);
|
||||||
Engine.functions.putAll(target.functions);
|
Engine.functions.putAll(target.functions);
|
||||||
Engine.functions.remove(0l);
|
Engine.functions.remove(0l);
|
||||||
|
|
||||||
res.defineProperty(ctx, "function", target.func(env));
|
res.defineProperty(args.ctx, "function", target.func(env));
|
||||||
res.defineProperty(ctx, "mapChain", new ArrayValue());
|
res.defineProperty(args.ctx, "mapChain", new ArrayValue());
|
||||||
|
|
||||||
if (isDebug) {
|
if (isDebug) res.defineProperty(
|
||||||
res.defineProperty(ctx, "breakpoints", ArrayValue.of(ctx, target.breakpoints.stream().map(Location::toString).collect(Collectors.toList())));
|
args.ctx, "breakpoints",
|
||||||
}
|
ArrayValue.of(args.ctx, target.breakpoints.stream().map(Location::toString).collect(Collectors.toList()))
|
||||||
|
);
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
public static FunctionValue regexConstructor(Extensions ext) {
|
public static FunctionValue regexConstructor(Extensions ext) {
|
||||||
return ext.init(COMPILE_FUNC, new NativeFunction("RegExp", (ctx, thisArg, args) -> {
|
return ext.init(COMPILE_FUNC, new NativeFunction("RegExp", args -> {
|
||||||
throw EngineException.ofError("Regular expressions not supported.").setCtx(ctx.environment, ctx.engine);
|
throw EngineException.ofError("Regular expressions not supported.").setCtx(args.ctx.environment, args.ctx.engine);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,8 +35,8 @@ public class GlobalScope implements ScopeRecord {
|
|||||||
}
|
}
|
||||||
public void define(String name, Variable val) {
|
public void define(String name, Variable val) {
|
||||||
obj.defineProperty(null, name,
|
obj.defineProperty(null, name,
|
||||||
new NativeFunction("get " + name, (ctx, th, a) -> val.get(ctx)),
|
new NativeFunction("get " + name, args -> val.get(args.ctx)),
|
||||||
new NativeFunction("set " + name, (ctx, th, args) -> { val.set(ctx, args.length > 0 ? args[0] : null); return null; }),
|
new NativeFunction("set " + name, args -> { val.set(args.ctx, args.get(0)); return null; }),
|
||||||
true, true
|
true, true
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,17 +1,18 @@
|
|||||||
package me.topchetoeu.jscript.engine.values;
|
package me.topchetoeu.jscript.engine.values;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.engine.Context;
|
import me.topchetoeu.jscript.engine.Context;
|
||||||
|
import me.topchetoeu.jscript.interop.Arguments;
|
||||||
|
|
||||||
public class NativeFunction extends FunctionValue {
|
public class NativeFunction extends FunctionValue {
|
||||||
public static interface NativeFunctionRunner {
|
public static interface NativeFunctionRunner {
|
||||||
Object run(Context ctx, Object thisArg, Object[] args);
|
Object run(Arguments args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public final NativeFunctionRunner action;
|
public final NativeFunctionRunner action;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object call(Context ctx, Object thisArg, Object ...args) {
|
public Object call(Context ctx, Object thisArg, Object ...args) {
|
||||||
return action.run(ctx, thisArg, args);
|
return action.run(new Arguments(ctx, thisArg, args));
|
||||||
}
|
}
|
||||||
|
|
||||||
public NativeFunction(String name, NativeFunctionRunner action) {
|
public NativeFunction(String name, NativeFunctionRunner action) {
|
||||||
|
@ -523,6 +523,9 @@ public class Values {
|
|||||||
);
|
);
|
||||||
return (T)res;
|
return (T)res;
|
||||||
}
|
}
|
||||||
|
if (clazz.isAssignableFrom(NativeWrapper.class)) {
|
||||||
|
return (T)new NativeWrapper(obj);
|
||||||
|
}
|
||||||
|
|
||||||
if (clazz == String.class) return (T)toString(ctx, obj);
|
if (clazz == String.class) return (T)toString(ctx, obj);
|
||||||
if (clazz == Boolean.class || clazz == Boolean.TYPE) return (T)(Boolean)toBoolean(obj);
|
if (clazz == Boolean.class || clazz == Boolean.TYPE) return (T)(Boolean)toBoolean(obj);
|
||||||
@ -606,15 +609,15 @@ public class Values {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
var key = getMember(ctx, getMember(ctx, ctx.get(Environment.SYMBOL_PROTO), "constructor"), "iterator");
|
var key = getMember(ctx, getMember(ctx, ctx.get(Environment.SYMBOL_PROTO), "constructor"), "iterator");
|
||||||
res.defineProperty(ctx, key, new NativeFunction("", (_ctx, thisArg, args) -> thisArg));
|
res.defineProperty(ctx, key, new NativeFunction("", args -> args.thisArg));
|
||||||
}
|
}
|
||||||
catch (IllegalArgumentException | NullPointerException e) { }
|
catch (IllegalArgumentException | NullPointerException e) { }
|
||||||
|
|
||||||
res.defineProperty(ctx, "next", new NativeFunction("", (_ctx, _th, _args) -> {
|
res.defineProperty(ctx, "next", new NativeFunction("", args -> {
|
||||||
if (!it.hasNext()) return new ObjectValue(ctx, Map.of("done", true));
|
if (!it.hasNext()) return new ObjectValue(ctx, Map.of("done", true));
|
||||||
else {
|
else {
|
||||||
var obj = new ObjectValue();
|
var obj = new ObjectValue();
|
||||||
obj.defineProperty(_ctx, "value", it.next());
|
obj.defineProperty(args.ctx, "value", it.next());
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
@ -631,16 +634,16 @@ public class Values {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
var key = getMemberPath(ctx, ctx.get(Environment.SYMBOL_PROTO), "constructor", "asyncIterator");
|
var key = getMemberPath(ctx, ctx.get(Environment.SYMBOL_PROTO), "constructor", "asyncIterator");
|
||||||
res.defineProperty(ctx, key, new NativeFunction("", (_ctx, thisArg, args) -> thisArg));
|
res.defineProperty(ctx, key, new NativeFunction("", args -> args.thisArg));
|
||||||
}
|
}
|
||||||
catch (IllegalArgumentException | NullPointerException e) { }
|
catch (IllegalArgumentException | NullPointerException e) { }
|
||||||
|
|
||||||
res.defineProperty(ctx, "next", new NativeFunction("", (_ctx, _th, _args) -> {
|
res.defineProperty(ctx, "next", new NativeFunction("", args -> {
|
||||||
return PromiseLib.await(ctx, () -> {
|
return PromiseLib.await(ctx, () -> {
|
||||||
if (!it.hasNext()) return new ObjectValue(ctx, Map.of("done", true));
|
if (!it.hasNext()) return new ObjectValue(ctx, Map.of("done", true));
|
||||||
else {
|
else {
|
||||||
var obj = new ObjectValue();
|
var obj = new ObjectValue();
|
||||||
obj.defineProperty(_ctx, "value", it.next());
|
obj.defineProperty(args.ctx, "value", it.next());
|
||||||
return obj;
|
return obj;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
95
src/me/topchetoeu/jscript/interop/Arguments.java
Normal file
95
src/me/topchetoeu/jscript/interop/Arguments.java
Normal file
@ -0,0 +1,95 @@
|
|||||||
|
package me.topchetoeu.jscript.interop;
|
||||||
|
|
||||||
|
import java.lang.reflect.Array;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.engine.Context;
|
||||||
|
import me.topchetoeu.jscript.engine.values.NativeWrapper;
|
||||||
|
import me.topchetoeu.jscript.engine.values.Values;
|
||||||
|
|
||||||
|
public class Arguments {
|
||||||
|
public final Object thisArg;
|
||||||
|
public final Object[] args;
|
||||||
|
public final Context ctx;
|
||||||
|
|
||||||
|
public <T> T get(int i, Class<T> type) {
|
||||||
|
return Values.convert(ctx, get(i), type);
|
||||||
|
}
|
||||||
|
public Object get(int i, boolean unwrap) {
|
||||||
|
Object res = null;
|
||||||
|
|
||||||
|
if (i == -1) res = thisArg;
|
||||||
|
if (i >= 0 && i < args.length) res = args[i];
|
||||||
|
if (unwrap && res instanceof NativeWrapper) res = ((NativeWrapper)res).wrapped;
|
||||||
|
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
public Object get(int i) {
|
||||||
|
return get(i, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> T[] slice(int i, Class<T> type) {
|
||||||
|
var res = Array.newInstance(type, Math.max(0, args.length - i));
|
||||||
|
for (; i < args.length; i++) Array.set(res, i - args.length, get(i, type));
|
||||||
|
return ((T[])res);
|
||||||
|
}
|
||||||
|
public Object slice(int i, boolean unwrap) {
|
||||||
|
var res = new Object[Math.max(0, args.length - i)];
|
||||||
|
for (; i < args.length; i++) res[i - args.length] = get(i, unwrap);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
public Object slice(int i) {
|
||||||
|
return slice(i, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int[] sliceInt(int i) {
|
||||||
|
var res = new int[Math.max(0, args.length - i)];
|
||||||
|
for (; i < args.length; i++) res[i - args.length] = get(i, Integer.class);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
public long[] sliceLong(int i) {
|
||||||
|
var res = new long[Math.max(0, args.length - i)];
|
||||||
|
for (; i < args.length; i++) res[i - args.length] = get(i, Long.class);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
public short[] sliceShort(int i) {
|
||||||
|
var res = new short[Math.max(0, args.length - i)];
|
||||||
|
for (; i < args.length; i++) res[i - args.length] = get(i, Short.class);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
public float[] sliceFloat(int i) {
|
||||||
|
var res = new float[Math.max(0, args.length - i)];
|
||||||
|
for (; i < args.length; i++) res[i - args.length] = get(i, Float.class);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
public double[] sliceDouble(int i) {
|
||||||
|
var res = new double[Math.max(0, args.length - i)];
|
||||||
|
for (; i < args.length; i++) res[i - args.length] = get(i, Double.class);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
public byte[] sliceByte(int i) {
|
||||||
|
var res = new byte[Math.max(0, args.length - i)];
|
||||||
|
for (; i < args.length; i++) res[i - args.length] = get(i, Byte.class);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
public char[] sliceChar(int i) {
|
||||||
|
var res = new char[Math.max(0, args.length - i)];
|
||||||
|
for (; i < args.length; i++) res[i - args.length] = get(i, Character.class);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
public boolean[] sliceBool(int i) {
|
||||||
|
var res = new boolean[Math.max(0, args.length - i)];
|
||||||
|
for (; i < args.length; i++) res[i - args.length] = get(i, Boolean.class);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getString(int i) { return Values.toString(ctx, get(i)); }
|
||||||
|
public boolean getBoolean(int i) { return Values.toBoolean(get(i)); }
|
||||||
|
public int getInt(int i) { return (int)Values.toNumber(ctx, get(i)); }
|
||||||
|
|
||||||
|
public Arguments(Context ctx, Object thisArg, Object... args) {
|
||||||
|
this.ctx = ctx;
|
||||||
|
this.args = args;
|
||||||
|
this.thisArg = thisArg;
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,8 @@ import java.lang.annotation.Target;
|
|||||||
|
|
||||||
@Target({ ElementType.METHOD })
|
@Target({ ElementType.METHOD })
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
public @interface NativeSetter {
|
public @interface Expose {
|
||||||
public String value() default "";
|
public String value() default "";
|
||||||
public boolean thisArg() default false;
|
public ExposeType type() default ExposeType.METHOD;
|
||||||
|
public ExposeTarget target() default ExposeTarget.MEMBER;
|
||||||
}
|
}
|
@ -7,7 +7,4 @@ import java.lang.annotation.Target;
|
|||||||
|
|
||||||
@Target({ ElementType.METHOD })
|
@Target({ ElementType.METHOD })
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
public @interface NativeConstructor {
|
public @interface ExposeConstructor { }
|
||||||
public boolean thisArg() default false;
|
|
||||||
}
|
|
||||||
|
|
28
src/me/topchetoeu/jscript/interop/ExposeTarget.java
Normal file
28
src/me/topchetoeu/jscript/interop/ExposeTarget.java
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
package me.topchetoeu.jscript.interop;
|
||||||
|
|
||||||
|
public enum ExposeTarget {
|
||||||
|
STATIC(true, true, false),
|
||||||
|
MEMBER(false, false, true),
|
||||||
|
NAMESPACE(false, true, false),
|
||||||
|
CONSTRUCTOR(true, false, false),
|
||||||
|
PROTOTYPE(false, false, true),
|
||||||
|
ALL(true, true, true);
|
||||||
|
|
||||||
|
public final boolean constructor;
|
||||||
|
public final boolean namespace;
|
||||||
|
public final boolean prototype;
|
||||||
|
|
||||||
|
public boolean shouldApply(ExposeTarget other) {
|
||||||
|
if (other.constructor && !constructor) return false;
|
||||||
|
if (other.namespace && !namespace) return false;
|
||||||
|
if (other.prototype && !prototype) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ExposeTarget(boolean constructor, boolean namespace, boolean prototype) {
|
||||||
|
this.constructor = constructor;
|
||||||
|
this.namespace = namespace;
|
||||||
|
this.prototype = prototype;
|
||||||
|
}
|
||||||
|
}
|
9
src/me/topchetoeu/jscript/interop/ExposeType.java
Normal file
9
src/me/topchetoeu/jscript/interop/ExposeType.java
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
package me.topchetoeu.jscript.interop;
|
||||||
|
|
||||||
|
public enum ExposeType {
|
||||||
|
INIT,
|
||||||
|
METHOD,
|
||||||
|
FIELD,
|
||||||
|
GETTER,
|
||||||
|
SETTER,
|
||||||
|
}
|
@ -1,7 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.interop;
|
|
||||||
|
|
||||||
public enum InitType {
|
|
||||||
CONSTRUCTOR,
|
|
||||||
PROTOTYPE,
|
|
||||||
NAMESPACE,
|
|
||||||
}
|
|
@ -1,13 +0,0 @@
|
|||||||
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, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.TYPE })
|
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
|
||||||
public @interface Native {
|
|
||||||
public String value() default "";
|
|
||||||
public boolean thisArg() default false;
|
|
||||||
}
|
|
@ -1,16 +1,23 @@
|
|||||||
package me.topchetoeu.jscript.interop;
|
package me.topchetoeu.jscript.interop;
|
||||||
|
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.Location;
|
||||||
|
import me.topchetoeu.jscript.engine.Context;
|
||||||
import me.topchetoeu.jscript.engine.Environment;
|
import me.topchetoeu.jscript.engine.Environment;
|
||||||
import me.topchetoeu.jscript.engine.WrappersProvider;
|
import me.topchetoeu.jscript.engine.WrappersProvider;
|
||||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||||
import me.topchetoeu.jscript.engine.values.NativeFunction;
|
import me.topchetoeu.jscript.engine.values.NativeFunction;
|
||||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||||
import me.topchetoeu.jscript.engine.values.Symbol;
|
import me.topchetoeu.jscript.engine.values.Symbol;
|
||||||
|
import me.topchetoeu.jscript.engine.values.Values;
|
||||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||||
import me.topchetoeu.jscript.exceptions.UncheckedException;
|
import me.topchetoeu.jscript.exceptions.InterruptException;
|
||||||
|
|
||||||
public class NativeWrapperProvider implements WrappersProvider {
|
public class NativeWrapperProvider implements WrappersProvider {
|
||||||
private final HashMap<Class<?>, FunctionValue> constructors = new HashMap<>();
|
private final HashMap<Class<?>, FunctionValue> constructors = new HashMap<>();
|
||||||
@ -18,125 +25,153 @@ public class NativeWrapperProvider implements WrappersProvider {
|
|||||||
private final HashMap<Class<?>, ObjectValue> namespaces = new HashMap<>();
|
private final HashMap<Class<?>, ObjectValue> namespaces = new HashMap<>();
|
||||||
private final Environment env;
|
private final Environment env;
|
||||||
|
|
||||||
private static void applyMethods(Environment env, boolean member, ObjectValue target, Class<?> clazz) {
|
private static Object call(Context ctx, String name, Method method, Object thisArg, Object... args) {
|
||||||
for (var method : clazz.getDeclaredMethods()) {
|
try {
|
||||||
var nat = method.getAnnotation(Native.class);
|
var realArgs = new Object[method.getParameterCount()];
|
||||||
var get = method.getAnnotation(NativeGetter.class);
|
System.arraycopy(args, 0, realArgs, 0, realArgs.length);
|
||||||
var set = method.getAnnotation(NativeSetter.class);
|
if (Modifier.isStatic(method.getModifiers())) thisArg = null;
|
||||||
var memberMatch = !Modifier.isStatic(method.getModifiers()) == member;
|
return Values.normalize(ctx, method.invoke(Values.convert(ctx, thisArg, method.getDeclaringClass()), realArgs));
|
||||||
|
}
|
||||||
if (nat != null) {
|
catch (InvocationTargetException e) {
|
||||||
if (nat.thisArg() && !member || !nat.thisArg() && !memberMatch) continue;
|
if (e.getTargetException() instanceof EngineException) {
|
||||||
|
throw ((EngineException)e.getTargetException()).add(ctx, name, Location.INTERNAL);
|
||||||
Object name = nat.value();
|
|
||||||
if (name.toString().startsWith("@@")) name = Symbol.get(name.toString().substring(2));
|
|
||||||
else if (name.equals("")) name = method.getName();
|
|
||||||
|
|
||||||
var val = target.values.get(name);
|
|
||||||
|
|
||||||
if (!(val instanceof OverloadFunction)) target.defineProperty(null, name, val = new OverloadFunction(name.toString()), true, true, false);
|
|
||||||
|
|
||||||
((OverloadFunction)val).add(Overload.fromMethod(method, nat.thisArg()));
|
|
||||||
}
|
}
|
||||||
else {
|
else if (e.getTargetException() instanceof NullPointerException) {
|
||||||
if (get != null) {
|
e.printStackTrace();
|
||||||
if (get.thisArg() && !member || !get.thisArg() && !memberMatch) continue;
|
throw EngineException.ofType("Unexpected value of 'undefined'.").add(ctx, name, Location.INTERNAL);
|
||||||
|
|
||||||
Object name = get.value();
|
|
||||||
if (name.toString().startsWith("@@")) name = Symbol.get(name.toString().substring(2));
|
|
||||||
else if (name.equals("")) name = method.getName();
|
|
||||||
|
|
||||||
var prop = target.properties.get(name);
|
|
||||||
OverloadFunction getter = null;
|
|
||||||
var setter = prop == null ? null : prop.setter;
|
|
||||||
|
|
||||||
if (prop != null && prop.getter instanceof OverloadFunction) getter = (OverloadFunction)prop.getter;
|
|
||||||
else getter = new OverloadFunction("get " + name);
|
|
||||||
|
|
||||||
getter.add(Overload.fromMethod(method, get.thisArg()));
|
|
||||||
target.defineProperty(null, name, getter, setter, true, false);
|
|
||||||
}
|
|
||||||
if (set != null) {
|
|
||||||
if (set.thisArg() && !member || !set.thisArg() && !memberMatch) continue;
|
|
||||||
|
|
||||||
Object name = set.value();
|
|
||||||
if (name.toString().startsWith("@@")) name = Symbol.get(name.toString().substring(2));
|
|
||||||
else if (name.equals("")) name = method.getName();
|
|
||||||
|
|
||||||
var prop = target.properties.get(name);
|
|
||||||
var getter = prop == null ? null : prop.getter;
|
|
||||||
OverloadFunction setter = null;
|
|
||||||
|
|
||||||
if (prop != null && prop.setter instanceof OverloadFunction) setter = (OverloadFunction)prop.setter;
|
|
||||||
else setter = new OverloadFunction("set " + name);
|
|
||||||
|
|
||||||
setter.add(Overload.fromMethod(method, set.thisArg()));
|
|
||||||
target.defineProperty(null, name, getter, setter, true, false);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
else if (e.getTargetException() instanceof InterruptException || e.getTargetException() instanceof InterruptedException) {
|
||||||
|
throw new InterruptException();
|
||||||
|
}
|
||||||
|
else throw new EngineException(e.getTargetException()).add(ctx, name, Location.INTERNAL);
|
||||||
|
}
|
||||||
|
catch (ReflectiveOperationException e) {
|
||||||
|
throw EngineException.ofError(e.getMessage()).add(ctx, name, Location.INTERNAL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private static void applyFields(Environment env, boolean member, ObjectValue target, Class<?> clazz) {
|
private static FunctionValue create(String name, Method method) {
|
||||||
for (var field : clazz.getDeclaredFields()) {
|
return new NativeFunction(name, args -> call(args.ctx, name, method, args.thisArg, args));
|
||||||
if (!Modifier.isStatic(field.getModifiers()) != member) continue;
|
}
|
||||||
var nat = field.getAnnotation(Native.class);
|
private static void checkSignature(Method method, boolean forceStatic, Class<?> ...params) {
|
||||||
|
if (forceStatic && !Modifier.isStatic(method.getModifiers())) throw new IllegalArgumentException(String.format(
|
||||||
|
"Method %s must be static.",
|
||||||
|
method.getDeclaringClass().getName() + "." + method.getName()
|
||||||
|
));
|
||||||
|
|
||||||
if (nat != null) {
|
var actual = method.getParameterTypes();
|
||||||
Object name = nat.value();
|
|
||||||
if (name.toString().startsWith("@@")) name = Symbol.get(name.toString().substring(2));
|
boolean failed = actual.length > params.length;
|
||||||
else if (name.equals("")) name = field.getName();
|
|
||||||
|
if (!failed) for (var i = 0; i < actual.length; i++) {
|
||||||
var getter = OverloadFunction.of("get " + name, Overload.getterFromField(field));
|
if (!actual[i].isAssignableFrom(params[i])) {
|
||||||
var setter = OverloadFunction.of("set " + name, Overload.setterFromField(field));
|
failed = true;
|
||||||
target.defineProperty(null, name, getter, setter, true, false);
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (failed) throw new IllegalArgumentException(String.format(
|
||||||
|
"Method %s was expected to have a signature of '%s', found '%s' instead.",
|
||||||
|
method.getDeclaringClass().getName() + "." + method.getName(),
|
||||||
|
String.join(", ", Arrays.stream(params).map(v -> v.getName()).toList()),
|
||||||
|
String.join(", ", Arrays.stream(actual).map(v -> v.getName()).toList())
|
||||||
|
));
|
||||||
}
|
}
|
||||||
private static void applyClasses(Environment env, boolean member, ObjectValue target, Class<?> clazz) {
|
private static String getName(Class<?> clazz) {
|
||||||
for (var cl : clazz.getDeclaredClasses()) {
|
var classNat = clazz.getAnnotation(WrapperName.class);
|
||||||
if (!Modifier.isStatic(cl.getModifiers()) != member) continue;
|
|
||||||
var nat = cl.getAnnotation(Native.class);
|
|
||||||
|
|
||||||
if (nat != null) {
|
|
||||||
Object name = nat.value();
|
|
||||||
if (name.toString().startsWith("@@")) name = Symbol.get(name.toString().substring(2));
|
|
||||||
else if (name.equals("")) name = cl.getName();
|
|
||||||
|
|
||||||
var getter = new NativeFunction("get " + name, (ctx, thisArg, args) -> cl);
|
|
||||||
|
|
||||||
target.defineProperty(null, name, getter, null, true, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static String getName(Class<?> clazz) {
|
|
||||||
var classNat = clazz.getAnnotation(Native.class);
|
|
||||||
if (classNat != null && !classNat.value().trim().equals("")) return classNat.value().trim();
|
if (classNat != null && !classNat.value().trim().equals("")) return classNat.value().trim();
|
||||||
else return clazz.getSimpleName();
|
else return clazz.getSimpleName();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void apply(ObjectValue obj, Environment env, ExposeTarget target, Class<?> clazz) {
|
||||||
|
var getters = new HashMap<Object, FunctionValue>();
|
||||||
|
var setters = new HashMap<Object, FunctionValue>();
|
||||||
|
var props = new HashSet<Object>();
|
||||||
|
var nonProps = new HashSet<Object>();
|
||||||
|
|
||||||
|
for (var method : clazz.getDeclaredMethods()) {
|
||||||
|
for (var annotation : method.getAnnotationsByType(Expose.class)) {
|
||||||
|
if (!annotation.target().shouldApply(target)) continue;
|
||||||
|
|
||||||
|
Object key = annotation.value();
|
||||||
|
if (key.toString().startsWith("@@")) key = Symbol.get(key.toString().substring(2));
|
||||||
|
else if (key.equals("")) key = method.getName();
|
||||||
|
var name = key.toString();
|
||||||
|
|
||||||
|
var repeat = false;
|
||||||
|
|
||||||
|
switch (annotation.type()) {
|
||||||
|
case INIT:
|
||||||
|
checkSignature(method, true,
|
||||||
|
target == ExposeTarget.CONSTRUCTOR ? FunctionValue.class : ObjectValue.class,
|
||||||
|
Environment.class
|
||||||
|
);
|
||||||
|
call(null, null, method, obj, null, env);
|
||||||
|
break;
|
||||||
|
case FIELD:
|
||||||
|
if (props.contains(key) || nonProps.contains(key)) repeat = true;
|
||||||
|
else {
|
||||||
|
checkSignature(method, true, Environment.class);
|
||||||
|
obj.defineProperty(null, key, call(new Context(null, env), name, method, null, env));
|
||||||
|
nonProps.add(key);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case METHOD:
|
||||||
|
if (props.contains(key) || nonProps.contains(key)) repeat = true;
|
||||||
|
else {
|
||||||
|
checkSignature(method, false, Arguments.class);
|
||||||
|
obj.defineProperty(null, key, create(name, method));
|
||||||
|
nonProps.add(key);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case GETTER:
|
||||||
|
if (nonProps.contains(key) || getters.containsKey(key)) repeat = true;
|
||||||
|
else {
|
||||||
|
checkSignature(method, false, Arguments.class);
|
||||||
|
getters.put(key, create(name, method));
|
||||||
|
props.add(key);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case SETTER:
|
||||||
|
if (nonProps.contains(key) || setters.containsKey(key)) repeat = true;
|
||||||
|
else {
|
||||||
|
checkSignature(method, false, Arguments.class);
|
||||||
|
setters.put(key, create(name, method));
|
||||||
|
props.add(key);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (repeat)
|
||||||
|
throw new IllegalArgumentException(String.format(
|
||||||
|
"A member '%s' in the wrapper for '%s' of type '%s' is already present.",
|
||||||
|
name, clazz.getName(), target.toString()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var key : props) obj.defineProperty(null, key, getters.get(key), setters.get(key), true, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Method getConstructor(Environment env, Class<?> clazz) {
|
||||||
|
for (var method : clazz.getDeclaredMethods()) {
|
||||||
|
if (!method.isAnnotationPresent(ExposeConstructor.class)) continue;
|
||||||
|
checkSignature(method, true, Arguments.class);
|
||||||
|
return method;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generates a prototype for the given class.
|
* Generates a prototype for the given class.
|
||||||
* The returned object will have appropriate wrappers for all instance members.
|
* The returned object will have appropriate wrappers for all instance members.
|
||||||
* All accessors and methods will expect the this argument to be a native wrapper of the given class type.
|
* All accessors and methods will expect the this argument to be a native wrapper of the given class type.
|
||||||
* @param clazz The class for which a prototype should be generated
|
* @param clazz The class for which a prototype should be generated
|
||||||
*/
|
*/
|
||||||
public static ObjectValue makeProto(Environment ctx, Class<?> clazz) {
|
public static ObjectValue makeProto(Environment env, Class<?> clazz) {
|
||||||
var res = new ObjectValue();
|
var res = new ObjectValue();
|
||||||
|
|
||||||
res.defineProperty(null, Symbol.get("Symbol.typeName"), getName(clazz));
|
res.defineProperty(null, Symbol.get("Symbol.typeName"), getName(clazz));
|
||||||
|
apply(res, env, ExposeTarget.PROTOTYPE, clazz);
|
||||||
for (var overload : clazz.getDeclaredMethods()) {
|
|
||||||
var init = overload.getAnnotation(NativeInit.class);
|
|
||||||
if (init == null || init.value() != InitType.PROTOTYPE) continue;
|
|
||||||
try { overload.invoke(null, ctx, res); }
|
|
||||||
catch (Throwable e) { throw new UncheckedException(e); }
|
|
||||||
}
|
|
||||||
|
|
||||||
applyMethods(ctx, true, res, clazz);
|
|
||||||
applyFields(ctx, true, res, clazz);
|
|
||||||
applyClasses(ctx, true, res, clazz);
|
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@ -146,36 +181,15 @@ public class NativeWrapperProvider implements WrappersProvider {
|
|||||||
* @param clazz The class for which a constructor should be generated
|
* @param clazz The class for which a constructor should be generated
|
||||||
*/
|
*/
|
||||||
public static FunctionValue makeConstructor(Environment ctx, Class<?> clazz) {
|
public static FunctionValue makeConstructor(Environment ctx, Class<?> clazz) {
|
||||||
FunctionValue func = new OverloadFunction(getName(clazz));
|
var constr = getConstructor(ctx, clazz);
|
||||||
|
|
||||||
for (var overload : clazz.getDeclaredConstructors()) {
|
FunctionValue res = constr == null ?
|
||||||
var nat = overload.getAnnotation(Native.class);
|
new NativeFunction(getName(clazz), args -> { throw EngineException.ofError("This constructor is not invokable."); }) :
|
||||||
if (nat == null) continue;
|
create(getName(clazz), constr);
|
||||||
((OverloadFunction)func).add(Overload.fromConstructor(overload, nat.thisArg()));
|
|
||||||
}
|
|
||||||
for (var overload : clazz.getDeclaredMethods()) {
|
|
||||||
var constr = overload.getAnnotation(NativeConstructor.class);
|
|
||||||
if (constr == null) continue;
|
|
||||||
((OverloadFunction)func).add(Overload.fromMethod(overload, constr.thisArg()));
|
|
||||||
}
|
|
||||||
for (var overload : clazz.getDeclaredMethods()) {
|
|
||||||
var init = overload.getAnnotation(NativeInit.class);
|
|
||||||
if (init == null || init.value() != InitType.CONSTRUCTOR) continue;
|
|
||||||
try { overload.invoke(null, ctx, func); }
|
|
||||||
catch (Throwable e) { throw new UncheckedException(e); }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (((OverloadFunction)func).overloads.size() == 0) {
|
apply(res, ctx, ExposeTarget.CONSTRUCTOR, clazz);
|
||||||
func = new NativeFunction(getName(clazz), (a, b, c) -> { throw EngineException.ofError("This constructor is not invokable."); });
|
|
||||||
}
|
|
||||||
|
|
||||||
applyMethods(ctx, false, func, clazz);
|
return res;
|
||||||
applyFields(ctx, false, func, clazz);
|
|
||||||
applyClasses(ctx, false, func, clazz);
|
|
||||||
|
|
||||||
func.special = true;
|
|
||||||
|
|
||||||
return func;
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Generates a namespace for the given class.
|
* Generates a namespace for the given class.
|
||||||
@ -184,19 +198,9 @@ public class NativeWrapperProvider implements WrappersProvider {
|
|||||||
* @param clazz The class for which a constructor should be generated
|
* @param clazz The class for which a constructor should be generated
|
||||||
*/
|
*/
|
||||||
public static ObjectValue makeNamespace(Environment ctx, Class<?> clazz) {
|
public static ObjectValue makeNamespace(Environment ctx, Class<?> clazz) {
|
||||||
ObjectValue res = new ObjectValue();
|
var res = new ObjectValue();
|
||||||
|
res.defineProperty(null, Symbol.get("Symbol.typeName"), getName(clazz));
|
||||||
for (var overload : clazz.getDeclaredMethods()) {
|
apply(res, ctx, ExposeTarget.NAMESPACE, clazz);
|
||||||
var init = overload.getAnnotation(NativeInit.class);
|
|
||||||
if (init == null || init.value() != InitType.NAMESPACE) continue;
|
|
||||||
try { overload.invoke(null, ctx, res); }
|
|
||||||
catch (Throwable e) { throw new UncheckedException(e); }
|
|
||||||
}
|
|
||||||
|
|
||||||
applyMethods(ctx, false, res, clazz);
|
|
||||||
applyFields(ctx, false, res, clazz);
|
|
||||||
applyClasses(ctx, false, res, clazz);
|
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,8 +253,7 @@ public class NativeWrapperProvider implements WrappersProvider {
|
|||||||
return constructors.get(clazz);
|
return constructors.get(clazz);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public WrappersProvider fork(Environment env) {
|
||||||
public WrappersProvider fork(Environment env) {
|
|
||||||
return new NativeWrapperProvider(env);
|
return new NativeWrapperProvider(env);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -263,12 +266,12 @@ public class NativeWrapperProvider implements WrappersProvider {
|
|||||||
|
|
||||||
private void initError() {
|
private void initError() {
|
||||||
var proto = new ObjectValue();
|
var proto = new ObjectValue();
|
||||||
proto.defineProperty(null, "message", new NativeFunction("message", (ctx, thisArg, args) -> {
|
proto.defineProperty(null, "message", new NativeFunction("message", args -> {
|
||||||
if (thisArg instanceof Throwable) return ((Throwable)thisArg).getMessage();
|
if (args.thisArg instanceof Throwable) return ((Throwable)args.thisArg).getMessage();
|
||||||
else return null;
|
else return null;
|
||||||
}));
|
}));
|
||||||
proto.defineProperty(null, "name", new NativeFunction("name", (ctx, thisArg, args) -> getName(thisArg.getClass())));
|
proto.defineProperty(null, "name", new NativeFunction("name", args -> getName(args.thisArg.getClass())));
|
||||||
proto.defineProperty(null, "toString", new NativeFunction("toString", (ctx, thisArg, args) -> thisArg.toString()));
|
proto.defineProperty(null, "toString", new NativeFunction("toString", args -> args.thisArg.toString()));
|
||||||
|
|
||||||
var constr = makeConstructor(null, Throwable.class);
|
var constr = makeConstructor(null, Throwable.class);
|
||||||
proto.defineProperty(null, "constructor", constr, true, false, false);
|
proto.defineProperty(null, "constructor", constr, true, false, false);
|
||||||
|
@ -7,6 +7,6 @@ import java.lang.annotation.Target;
|
|||||||
|
|
||||||
@Target({ ElementType.METHOD })
|
@Target({ ElementType.METHOD })
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
public @interface NativeInit {
|
public @interface OnWrapperInit {
|
||||||
InitType value();
|
|
||||||
}
|
}
|
@ -1,70 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.interop;
|
|
||||||
|
|
||||||
import java.lang.reflect.Constructor;
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.lang.reflect.Modifier;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.engine.Context;
|
|
||||||
|
|
||||||
public class Overload {
|
|
||||||
public static interface OverloadRunner {
|
|
||||||
Object run(Context ctx, Object thisArg, Object[] args) throws ReflectiveOperationException, IllegalArgumentException;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final OverloadRunner runner;
|
|
||||||
public final boolean variadic;
|
|
||||||
public final boolean passThis;
|
|
||||||
public final Class<?> thisArg;
|
|
||||||
public final Class<?>[] params;
|
|
||||||
|
|
||||||
public static Overload fromMethod(Method method, boolean passThis) {
|
|
||||||
return new Overload(
|
|
||||||
(ctx, th, args) -> method.invoke(th, args),
|
|
||||||
method.isVarArgs(), passThis,
|
|
||||||
Modifier.isStatic(method.getModifiers()) ? null : method.getDeclaringClass(),
|
|
||||||
method.getParameterTypes()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
public static Overload fromConstructor(Constructor<?> method, boolean passThis) {
|
|
||||||
return new Overload(
|
|
||||||
(ctx, th, args) -> method.newInstance(args),
|
|
||||||
method.isVarArgs(), passThis,
|
|
||||||
null,
|
|
||||||
method.getParameterTypes()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
public static Overload getterFromField(Field field) {
|
|
||||||
return new Overload(
|
|
||||||
(ctx, th, args) -> field.get(th), false, false,
|
|
||||||
Modifier.isStatic(field.getModifiers()) ? null : field.getDeclaringClass(),
|
|
||||||
new Class[0]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
public static Overload setterFromField(Field field) {
|
|
||||||
if (Modifier.isFinal(field.getModifiers())) return null;
|
|
||||||
return new Overload(
|
|
||||||
(ctx, th, args) -> {
|
|
||||||
field.set(th, args[0]); return null;
|
|
||||||
}, false, false,
|
|
||||||
Modifier.isStatic(field.getModifiers()) ? null : field.getDeclaringClass(),
|
|
||||||
new Class[] { field.getType() }
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Overload getter(Class<?> thisArg, OverloadRunner runner, boolean passThis) {
|
|
||||||
return new Overload(
|
|
||||||
(ctx, th, args) -> runner.run(ctx, th, args), false, passThis,
|
|
||||||
thisArg,
|
|
||||||
new Class[0]
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Overload(OverloadRunner runner, boolean variadic, boolean passThis, Class<?> thisArg, Class<?> args[]) {
|
|
||||||
this.runner = runner;
|
|
||||||
this.variadic = variadic;
|
|
||||||
this.passThis = passThis;
|
|
||||||
this.thisArg = thisArg;
|
|
||||||
this.params = args;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,127 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.interop;
|
|
||||||
|
|
||||||
import java.lang.reflect.Array;
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.Location;
|
|
||||||
import me.topchetoeu.jscript.engine.Context;
|
|
||||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
|
||||||
import me.topchetoeu.jscript.engine.values.NativeWrapper;
|
|
||||||
import me.topchetoeu.jscript.engine.values.Values;
|
|
||||||
import me.topchetoeu.jscript.exceptions.ConvertException;
|
|
||||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
|
||||||
import me.topchetoeu.jscript.exceptions.InterruptException;
|
|
||||||
|
|
||||||
public class OverloadFunction extends FunctionValue {
|
|
||||||
public final List<Overload> overloads = new ArrayList<>();
|
|
||||||
|
|
||||||
public Object call(Context ctx, Object thisArg, Object ...args) {
|
|
||||||
loop: for (var overload : overloads) {
|
|
||||||
Object[] newArgs = new Object[overload.params.length];
|
|
||||||
|
|
||||||
boolean consumesEngine = overload.params.length > 0 && overload.params[0] == Context.class;
|
|
||||||
int start = (consumesEngine ? 1 : 0) + (overload.passThis ? 1 : 0);
|
|
||||||
int end = overload.params.length - (overload.variadic ? 1 : 0);
|
|
||||||
|
|
||||||
for (var i = start; i < end; i++) {
|
|
||||||
Object val;
|
|
||||||
|
|
||||||
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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
var thisArgType = overload.passThis ? overload.params[consumesEngine ? 1 : 0] : overload.thisArg;
|
|
||||||
Object _this;
|
|
||||||
|
|
||||||
try {
|
|
||||||
_this = thisArgType == null ? null : Values.convert(ctx, thisArg, thisArgType);
|
|
||||||
}
|
|
||||||
catch (ConvertException e) {
|
|
||||||
if (overloads.size() > 1) continue loop;
|
|
||||||
else throw EngineException.ofType(String.format("This argument can't be converted from %s to %s", e.source, e.target));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (consumesEngine) newArgs[0] = ctx;
|
|
||||||
if (overload.passThis) {
|
|
||||||
newArgs[consumesEngine ? 1 : 0] = _this;
|
|
||||||
_this = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
return Values.normalize(ctx, overload.runner.run(ctx, _this, newArgs));
|
|
||||||
}
|
|
||||||
catch (InstantiationException e) { throw EngineException.ofError("The class may not be instantiated."); }
|
|
||||||
catch (IllegalAccessException | IllegalArgumentException e) { continue; }
|
|
||||||
catch (InvocationTargetException e) {
|
|
||||||
var loc = Location.INTERNAL;
|
|
||||||
if (e.getTargetException() instanceof EngineException) {
|
|
||||||
throw ((EngineException)e.getTargetException()).add(ctx, name, loc);
|
|
||||||
}
|
|
||||||
else if (e.getTargetException() instanceof NullPointerException) {
|
|
||||||
e.printStackTrace();
|
|
||||||
throw EngineException.ofType("Unexpected value of 'undefined'.").add(ctx, name, loc);
|
|
||||||
}
|
|
||||||
else if (e.getTargetException() instanceof InterruptException || e.getTargetException() instanceof InterruptedException) {
|
|
||||||
throw new InterruptException();
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
var target = e.getTargetException();
|
|
||||||
var targetClass = target.getClass();
|
|
||||||
var err = new NativeWrapper(e.getTargetException());
|
|
||||||
|
|
||||||
err.defineProperty(ctx, "message", target.getMessage());
|
|
||||||
err.defineProperty(ctx, "name", NativeWrapperProvider.getName(targetClass));
|
|
||||||
|
|
||||||
throw new EngineException(err).add(ctx, name, loc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (ReflectiveOperationException e) {
|
|
||||||
throw EngineException.ofError(e.getMessage()).add(ctx, name, Location.INTERNAL);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw EngineException.ofType("No overload found for native method.");
|
|
||||||
}
|
|
||||||
|
|
||||||
public OverloadFunction add(Overload overload) {
|
|
||||||
this.overloads.add(overload);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
public OverloadFunction(String name) {
|
|
||||||
super(name, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static OverloadFunction of(String name, Overload overload) {
|
|
||||||
if (overload == null) return null;
|
|
||||||
else return new OverloadFunction(name).add(overload);
|
|
||||||
}
|
|
||||||
}
|
|
@ -5,9 +5,8 @@ import java.lang.annotation.Retention;
|
|||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
@Target({ ElementType.METHOD })
|
@Target({ ElementType.TYPE })
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
public @interface NativeGetter {
|
public @interface WrapperName {
|
||||||
public String value() default "";
|
String value();
|
||||||
public boolean thisArg() default false;
|
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user