fix: some Argument and Engine API improvements,

This commit is contained in:
TopchetoEU 2024-01-04 10:02:01 +02:00
parent 978ee8db79
commit a61c6a494e
Signed by: topchetoeu
GPG Key ID: 6531B8583E5F6ED4
17 changed files with 345 additions and 177 deletions

View File

@ -119,19 +119,18 @@ public class Main {
}
private static void initEngine() {
var ctx = new DebugContext();
engine.globalEnvironment.add(DebugContext.ENV_KEY, ctx);
engine.add(DebugContext.ENV_KEY, ctx);
debugServer.targets.put("target", (ws, req) -> new SimpleDebugger(ws).attach(ctx));
engineTask = engine.start();
debugTask = debugServer.start(new InetSocketAddress("127.0.0.1", 9229), true);
}
private static void initTypescript() {
try {
var tsEnv = Internals.apply(new Environment());
tsEnv.add(Environment.HIDE_STACK, true);
tsEnv.global.define(null, "module", false, new ObjectValue());
var bsEnv = Internals.apply(new Environment());
bsEnv.add(Environment.HIDE_STACK, true);
try {
tsEnv.global.define(null, "module", false, new ObjectValue());
engine.pushMsg(
false, tsEnv,
@ -152,6 +151,9 @@ public class Main {
catch (EngineException e) {
Values.printError(e, "(while initializing TS)");
}
bsEnv.add(Environment.HIDE_STACK, true);
tsEnv.add(Environment.HIDE_STACK, true);
}
public static void main(String args[]) {

View File

@ -0,0 +1,5 @@
package me.topchetoeu.jscript;
public interface ResultRunnable<T> {
T run();
}

View File

@ -6,6 +6,8 @@ import java.util.Iterator;
import java.util.List;
import java.util.TreeSet;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.Location;
@ -28,33 +30,43 @@ public class Context implements Extensions {
@Override public <T> void add(Symbol key, T obj) {
if (environment != null) environment.add(key, obj);
else if (engine != null) engine.globalEnvironment.add(key, obj);
else if (engine != null) engine.add(key, obj);
}
@Override public <T> T get(Symbol key) {
if (environment != null && environment.has(key)) return environment.get(key);
else if (engine != null && engine.globalEnvironment.has(key)) return engine.globalEnvironment.get(key);
else if (engine != null && engine.has(key)) return engine.get(key);
return null;
}
@Override public boolean has(Symbol key) {
return
environment != null && environment.has(key) ||
engine != null && engine.globalEnvironment.has(key);
engine != null && engine.has(key);
}
@Override public boolean remove(Symbol key) {
var res = false;
if (environment != null) res |= environment.remove(key);
else if (engine != null) res |= engine.globalEnvironment.remove(key);
else if (engine != null) res |= engine.remove(key);
return res;
}
@Override public Iterable<Symbol> keys() {
if (engine == null && environment == null) return List.of();
if (engine == null) return environment.keys();
if (environment == null) return engine.keys();
return () -> Stream.concat(
StreamSupport.stream(engine.keys().spliterator(), false),
StreamSupport.stream(environment.keys().spliterator(), false)
).distinct().iterator();
}
public FunctionValue compile(Filename filename, String raw) {
var env = environment;
var result = Environment.compileFunc(this).call(this, null, raw, filename.toString(), new EnvironmentLib(env));
var function = (FunctionValue)Values.getMember(this, result, "function");
if (!has(DebugContext.ENV_KEY)) return function;
if (!DebugContext.enabled(this)) return function;
var rawMapChain = ((ArrayValue)Values.getMember(this, result, "mapChain")).toArray();
var breakpoints = new TreeSet<>(
@ -139,7 +151,9 @@ public class Context implements Extensions {
this.engine = engine;
this.stackSize = stackSize;
if (engine != null && stackSize > engine.maxStackFrames) throw EngineException.ofRange("Stack overflow!");
if (hasNotNull(Environment.MAX_STACK_COUNT) && stackSize > (int)get(Environment.MAX_STACK_COUNT)) {
throw EngineException.ofRange("Stack overflow!");
}
}
public Context(Engine engine) {

View File

@ -1,113 +1,55 @@
package me.topchetoeu.jscript.engine;
import java.util.HashMap;
import java.util.concurrent.PriorityBlockingQueue;
import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.compilation.FunctionBody;
import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.Symbol;
import me.topchetoeu.jscript.events.Awaitable;
import me.topchetoeu.jscript.events.DataNotifier;
import me.topchetoeu.jscript.exceptions.InterruptException;
public class Engine {
private class UncompiledFunction extends FunctionValue {
public final Filename filename;
public final String raw;
private FunctionValue compiled = null;
@Override
public Object call(Context ctx, Object thisArg, Object ...args) {
if (compiled == null) compiled = ctx.compile(filename, raw);
return compiled.call(ctx, thisArg, args);
}
public UncompiledFunction(Filename filename, String raw) {
super(filename + "", 0);
this.filename = filename;
this.raw = raw;
}
}
private static class Task implements Comparable<Task> {
public final FunctionValue func;
public final Object thisArg;
public final Object[] args;
public final DataNotifier<Object> notifier = new DataNotifier<>();
public final Context ctx;
public final boolean micro;
public Task(Context ctx, FunctionValue func, Object thisArg, Object[] args, boolean micro) {
this.ctx = ctx;
this.func = func;
this.thisArg = thisArg;
this.args = args;
this.micro = micro;
}
@Override
public int compareTo(Task other) {
return Integer.compare(this.micro ? 0 : 1, other.micro ? 0 : 1);
}
}
private static int nextId = 0;
public class Engine extends EventLoop implements Extensions {
public static final HashMap<Long, FunctionBody> functions = new HashMap<>();
public final Environment globalEnvironment = new Environment();
private final Environment env = new Environment();
public final int id = ++nextId;
public int maxStackFrames = 10000;
private Thread thread;
private PriorityBlockingQueue<Task> tasks = new PriorityBlockingQueue<>();
private void runTask(Task task) {
try {
task.notifier.next(task.func.call(task.ctx, task.thisArg, task.args));
@Override
public <T> void add(Symbol key, T obj) {
this.env.add(key, obj);
}
catch (RuntimeException e) {
if (e instanceof InterruptException) throw e;
task.notifier.error(e);
@Override
public <T> T get(Symbol key) {
return this.env.get(key);
}
@Override
public boolean has(Symbol key) {
return this.env.has(key);
}
public void run(boolean untilEmpty) {
while (!untilEmpty || !tasks.isEmpty()) {
try {
runTask(tasks.take());
}
catch (InterruptedException | InterruptException e) {
for (var msg : tasks) msg.notifier.error(new InterruptException(e));
break;
}
@Override
public boolean remove(Symbol key) {
return this.env.remove(key);
}
@Override
public Iterable<Symbol> keys() {
return env.keys();
}
public Thread start() {
if (this.thread == null) {
this.thread = new Thread(() -> run(false), "JavaScript Runner #" + id);
this.thread.start();
}
return this.thread;
}
public void stop() {
thread.interrupt();
thread = null;
}
public boolean inExecThread() {
return Thread.currentThread() == thread;
}
public synchronized boolean isRunning() {
return this.thread != null;
public Engine copy() {
var res = new Engine();
res.env.addAll(env);
return res;
}
public Awaitable<Object> pushMsg(boolean micro, Environment env, FunctionValue func, Object thisArg, Object ...args) {
var msg = new Task(new Context(this, env), func, thisArg, args, micro);
tasks.add(msg);
return msg.notifier;
return pushMsg(() -> {
return func.call(new Context(this, env), thisArg, args);
}, micro);
}
public Awaitable<Object> pushMsg(boolean micro, Environment env, Filename filename, String raw, Object thisArg, Object ...args) {
return pushMsg(micro, env, new UncompiledFunction(filename, raw), thisArg, args);
return pushMsg(() -> {
var ctx = new Context(this, env);
return ctx.compile(filename, raw).call(new Context(this, env), thisArg, args);
}, micro);
}
public Engine() {

View File

@ -27,6 +27,7 @@ public class Environment implements Extensions {
public static final Symbol REGEX_CONSTR = Symbol.get("Environment.regexConstructor");
public static final Symbol STACK = Symbol.get("Environment.stack");
public static final Symbol MAX_STACK_COUNT = Symbol.get("Environment.maxStackCount");
public static final Symbol HIDE_STACK = Symbol.get("Environment.hideStack");
public static final Symbol OBJECT_PROTO = Symbol.get("Environment.objectPrototype");
@ -62,13 +63,16 @@ public class Environment implements Extensions {
@Override public boolean has(Symbol key) {
return data.containsKey(key);
}
@Override public Iterable<Symbol> keys() {
return data.keySet();
}
public static FunctionValue compileFunc(Extensions ext) {
return ext.init(COMPILE_FUNC, new NativeFunction("compile", args -> {
var source = args.getString(0);
var filename = args.getString(1);
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(args.convert(2, ObjectValue.class).getMember(args.ctx, Symbol.get("env")), Environment.class);
var isDebug = DebugContext.enabled(args.ctx);
var res = new ObjectValue();
var target = Parsing.compile(env, Filename.parse(filename), source);
@ -92,7 +96,7 @@ public class Environment implements Extensions {
}));
}
public Environment fork() {
public Environment copy() {
var res = new Environment(null, global);
res.wrappers = wrappers.fork(res);
@ -102,7 +106,7 @@ public class Environment implements Extensions {
return res;
}
public Environment child() {
var res = fork();
var res = copy();
res.global = res.global.globalChild();
return res;
}

View File

@ -0,0 +1,81 @@
package me.topchetoeu.jscript.engine;
import java.util.concurrent.PriorityBlockingQueue;
import me.topchetoeu.jscript.ResultRunnable;
import me.topchetoeu.jscript.events.Awaitable;
import me.topchetoeu.jscript.events.DataNotifier;
import me.topchetoeu.jscript.exceptions.InterruptException;
public class EventLoop {
private static class Task implements Comparable<Task> {
public final ResultRunnable<?> runnable;
public final DataNotifier<Object> notifier = new DataNotifier<>();
public final boolean micro;
public Task(ResultRunnable<?> runnable, boolean micro) {
this.runnable = runnable;
this.micro = micro;
}
@Override
public int compareTo(Task other) {
return Integer.compare(this.micro ? 0 : 1, other.micro ? 0 : 1);
}
}
private PriorityBlockingQueue<Task> tasks = new PriorityBlockingQueue<>();
private Thread thread;
@SuppressWarnings("unchecked")
public <T> Awaitable<T> pushMsg(ResultRunnable<T> runnable, boolean micro) {
var msg = new Task(runnable, micro);
tasks.add(msg);
return (Awaitable<T>)msg.notifier;
}
public Awaitable<Object> pushMsg(Runnable runnable, boolean micro) {
return pushMsg(() -> { runnable.run(); return null; }, micro);
}
public void run(boolean untilEmpty) {
while (!untilEmpty || !tasks.isEmpty()) {
try {
var task = tasks.take();
try {
task.notifier.next(task.runnable.run());
}
catch (RuntimeException e) {
if (e instanceof InterruptException) throw e;
task.notifier.error(e);
}
}
catch (InterruptedException | InterruptException e) {
for (var msg : tasks) msg.notifier.error(new InterruptException(e));
break;
}
}
}
public Thread thread() {
return thread;
}
public Thread start() {
if (thread == null) {
thread = new Thread(() -> run(false), "Event loop #" + hashCode());
thread.start();
}
return thread;
}
public void stop() {
if (thread != null) thread.interrupt();
thread = null;
}
public boolean inLoopThread() {
return Thread.currentThread() == thread;
}
public boolean isRunning() {
return this.thread != null;
}
}

View File

@ -5,10 +5,15 @@ import me.topchetoeu.jscript.engine.values.Symbol;
public interface Extensions {
<T> T get(Symbol key);
<T> void add(Symbol key, T obj);
Iterable<Symbol> keys();
boolean has(Symbol key);
boolean remove(Symbol key);
default boolean hasNotNull(Symbol key) {
return has(key) && get(key) != null;
}
default <T> T get(Symbol key, T defaultVal) {
if (has(key)) return get(key);
else return defaultVal;
@ -21,4 +26,9 @@ public interface Extensions {
return val;
}
}
default void addAll(Extensions source) {
for (var key : source.keys()) {
add(key, source.get(key));
}
}
}

View File

@ -15,6 +15,7 @@ import me.topchetoeu.jscript.mapping.SourceMap;
public class DebugContext implements DebugController {
public static final Symbol ENV_KEY = Symbol.get("Engine.debug");
public static final Symbol IGNORE = Symbol.get("Engine.ignoreDebug");
private HashMap<Filename, String> sources;
private HashMap<Filename, TreeSet<Location>> bpts;
@ -89,8 +90,11 @@ public class DebugContext implements DebugController {
this(true);
}
public static boolean enabled(Extensions exts) {
return exts.hasNotNull(ENV_KEY) && !exts.has(IGNORE);
}
public static DebugContext get(Extensions exts) {
if (exts.has(ENV_KEY)) return exts.get(ENV_KEY);
if (enabled(exts)) return exts.get(ENV_KEY);
else return new DebugContext(false);
}
}

View File

@ -301,6 +301,8 @@ public class SimpleDebugger implements Debugger {
}
private JSONMap serializeObj(Context ctx, Object val, boolean byValue) {
val = Values.normalize(null, val);
ctx = new Context(ctx.engine.copy(), ctx.environment);
ctx.engine.add(DebugContext.IGNORE, true);
if (val == Values.NULL) {
return new JSONMap()
@ -477,7 +479,7 @@ public class SimpleDebugger implements Debugger {
private RunResult run(Frame codeFrame, String code) {
if (codeFrame == null) return new RunResult(null, code, new EngineException("Invalid code frame!"));
var engine = new Engine();
var env = codeFrame.func.environment.fork();
var env = codeFrame.func.environment.copy();
env.global = new GlobalScope(codeFrame.local);

View File

@ -198,7 +198,7 @@ public class Runners {
return execLoadMember(ctx, instr, frame);
}
public static Object execLoadRegEx(Context ctx, Instruction instr, CodeFrame frame) {
if (ctx.has(Environment.REGEX_CONSTR)) {
if (ctx.hasNotNull(Environment.REGEX_CONSTR)) {
frame.push(ctx, Values.callNew(ctx, ctx.get(Environment.REGEX_CONSTR), instr.get(0), instr.get(1)));
}
else {

View File

@ -523,9 +523,6 @@ public class Values {
);
return (T)res;
}
if (clazz.isAssignableFrom(NativeWrapper.class)) {
return (T)new NativeWrapper(obj);
}
if (clazz == String.class) return (T)toString(ctx, obj);
if (clazz == Boolean.class || clazz == Boolean.TYPE) return (T)(Boolean)toBoolean(obj);
@ -547,6 +544,9 @@ public class Values {
if (obj == null) return null;
if (clazz.isInstance(obj)) return (T)obj;
if (clazz.isAssignableFrom(NativeWrapper.class)) {
return (T)new NativeWrapper(obj);
}
throw new ConvertException(type(obj), clazz.getSimpleName());
}
@ -609,7 +609,7 @@ public class Values {
try {
var key = getMember(ctx, getMember(ctx, ctx.get(Environment.SYMBOL_PROTO), "constructor"), "iterator");
res.defineProperty(ctx, key, new NativeFunction("", args -> args.thisArg));
res.defineProperty(ctx, key, new NativeFunction("", args -> args.self));
}
catch (IllegalArgumentException | NullPointerException e) { }
@ -634,12 +634,12 @@ public class Values {
try {
var key = getMemberPath(ctx, ctx.get(Environment.SYMBOL_PROTO), "constructor", "asyncIterator");
res.defineProperty(ctx, key, new NativeFunction("", args -> args.thisArg));
res.defineProperty(ctx, key, new NativeFunction("", args -> args.self));
}
catch (IllegalArgumentException | NullPointerException e) { }
res.defineProperty(ctx, "next", new NativeFunction("", args -> {
return PromiseLib.await(ctx, () -> {
return PromiseLib.await(args.ctx, () -> {
if (!it.hasNext()) return new ObjectValue(ctx, Map.of("done", true));
else {
var obj = new ObjectValue();

View File

@ -66,6 +66,11 @@ public class EngineException extends RuntimeException {
if (this.engine == null) this.engine = engine;
return this;
}
public EngineException setCtx(Context ctx) {
if (this.env == null) this.env = ctx.environment;
if (this.engine == null) this.engine = ctx.engine;
return this;
}
public String toString(Context ctx) {
var ss = new StringBuilder();

View File

@ -7,17 +7,28 @@ import me.topchetoeu.jscript.engine.values.NativeWrapper;
import me.topchetoeu.jscript.engine.values.Values;
public class Arguments {
public final Object thisArg;
public final Object self;
public final Object[] args;
public final Context ctx;
public <T> T get(int i, Class<T> type) {
public int n() {
return args.length;
}
public boolean has(int i) {
return i == -1 || i >= 0 && i < args.length;
}
public <T> T self(Class<T> type) {
return convert(-1, type);
}
public <T> T convert(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 == -1) res = self;
if (i >= 0 && i < args.length) res = args[i];
if (unwrap && res instanceof NativeWrapper) res = ((NativeWrapper)res).wrapped;
@ -26,70 +37,85 @@ public class Arguments {
public Object get(int i) {
return get(i, false);
}
public Object getOrDefault(int i, Object def) {
if (i < 0 || i >= args.length) return def;
else return get(i);
}
public Arguments slice(int start) {
var res = new Object[Math.max(0, args.length - start)];
for (int j = start; j < args.length; j++) res[j - start] = get(j);
return new Arguments(ctx, args, res);
}
@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 <T> T[] convert(Class<T> type) {
var res = Array.newInstance(type, args.length);
for (int i = 0; i < args.length; i++) Array.set(res, i, convert(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);
public int[] convertInt() {
var res = new int[args.length];
for (int i = 0; i < args.length; i++) res[i] = convert(i, Integer.class);
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);
public long[] convertLong() {
var res = new long[Math.max(0, args.length)];
for (int i = 0; i < args.length; i++) res[i] = convert(i, Long.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);
public short[] sliceShort() {
var res = new short[Math.max(0, args.length)];
for (int i = 0; i < args.length; i++) res[i] = convert(i, Short.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);
public float[] sliceFloat() {
var res = new float[Math.max(0, args.length)];
for (int i = 0; i < args.length; i++) res[i] = convert(i, Float.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);
public double[] sliceDouble() {
var res = new double[Math.max(0, args.length)];
for (int i = 0; i < args.length; i++) res[i] = convert(i, Double.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);
public byte[] sliceByte() {
var res = new byte[Math.max(0, args.length)];
for (int i = 0; i < args.length; i++) res[i] = convert(i, Byte.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);
public char[] sliceChar() {
var res = new char[Math.max(0, args.length)];
for (int i = 0; i < args.length; i++) res[i] = convert(i, Character.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);
public boolean[] sliceBool() {
var res = new boolean[Math.max(0, args.length)];
for (int i = 0; i < args.length; i++) res[i] = convert(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 long getLong(int i) { return (long)Values.toNumber(ctx, get(i)); }
public double getDouble(int i) { return Values.toNumber(ctx, get(i)); }
public float getFloat(int i) { return (float)Values.toNumber(ctx, get(i)); }
public int getInt(int i, int def) {
var res = get(i);
if (res == null) return def;
else return (int)Values.toNumber(ctx, res);
}
public String getString(int i, String def) {
var res = get(i);
if (res == null) return def;
else return Values.toString(ctx, res);
}
public Arguments(Context ctx, Object thisArg, Object... args) {
this.ctx = ctx;
this.args = args;
this.thisArg = thisArg;
this.self = thisArg;
}
}

View 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, ElementType.FIELD })
@Retention(RetentionPolicy.RUNTIME)
public @interface ExposeField {
public String value() default "";
public ExposeTarget target() default ExposeTarget.MEMBER;
}

View File

@ -3,7 +3,6 @@ package me.topchetoeu.jscript.interop;
public enum ExposeType {
INIT,
METHOD,
FIELD,
GETTER,
SETTER,
}

View File

@ -1,6 +1,7 @@
package me.topchetoeu.jscript.interop;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
@ -50,7 +51,7 @@ public class NativeWrapperProvider implements WrappersProvider {
}
}
private static FunctionValue create(String name, Method method) {
return new NativeFunction(name, args -> call(args.ctx, name, method, args.thisArg, args));
return new NativeFunction(name, args -> call(args.ctx, name, method, args.self, args));
}
private static void checkSignature(Method method, boolean forceStatic, Class<?> ...params) {
if (forceStatic && !Modifier.isStatic(method.getModifiers())) throw new IllegalArgumentException(String.format(
@ -82,6 +83,28 @@ public class NativeWrapperProvider implements WrappersProvider {
else return clazz.getSimpleName();
}
private static void checkUnderscore(Member member) {
if (!member.getName().startsWith("__")) {
System.out.println("WARNING: The name of the exposed member '%s.%s' doesn't start with '__'.".formatted(
member.getDeclaringClass().getName(),
member.getName()
));
}
}
private static String getName(Member member, String overrideName) {
if (overrideName == null) overrideName = "";
if (overrideName.isBlank()) {
var res = member.getName();
if (res.startsWith("__")) res = res.substring(2);
return res;
}
else return overrideName.trim();
}
private static Object getKey(String name) {
if (name.startsWith("@@")) return Symbol.get(name.substring(2));
else return name;
}
private static void apply(ObjectValue obj, Environment env, ExposeTarget target, Class<?> clazz) {
var getters = new HashMap<Object, FunctionValue>();
var setters = new HashMap<Object, FunctionValue>();
@ -92,11 +115,9 @@ public class NativeWrapperProvider implements WrappersProvider {
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();
checkUnderscore(method);
var name = getName(method, annotation.value());
var key = getKey(name);
var repeat = false;
switch (annotation.type()) {
@ -107,19 +128,11 @@ public class NativeWrapperProvider implements WrappersProvider {
);
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));
obj.defineProperty(null, key, create(name, method), true, true, false);
nonProps.add(key);
}
break;
@ -147,9 +160,55 @@ public class NativeWrapperProvider implements WrappersProvider {
name, clazz.getName(), target.toString()
));
}
for (var annotation : method.getAnnotationsByType(ExposeField.class)) {
if (!annotation.target().shouldApply(target)) continue;
checkUnderscore(method);
var name = getName(method, annotation.value());
var key = getKey(name);
var repeat = false;
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), true, true, false);
nonProps.add(key);
}
for (var key : props) obj.defineProperty(null, key, getters.get(key), setters.get(key), true, true);
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 field : clazz.getDeclaredFields()) {
for (var annotation : field.getAnnotationsByType(ExposeField.class)) {
if (!annotation.target().shouldApply(target)) continue;
checkUnderscore(field);
var name = getName(field, annotation.value());
var key = getKey(name);
var repeat = false;
if (props.contains(key) || nonProps.contains(key)) repeat = true;
else {
try {
obj.defineProperty(null, key, Values.normalize(new Context(null, env), field.get(null)), true, true, false);
nonProps.add(key);
}
catch (IllegalArgumentException | IllegalAccessException e) { }
}
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, false);
}
private static Method getConstructor(Environment env, Class<?> clazz) {
@ -187,6 +246,8 @@ public class NativeWrapperProvider implements WrappersProvider {
new NativeFunction(getName(clazz), args -> { throw EngineException.ofError("This constructor is not invokable."); }) :
create(getName(clazz), constr);
res.special = true;
apply(res, ctx, ExposeTarget.CONSTRUCTOR, clazz);
return res;
@ -267,11 +328,11 @@ public class NativeWrapperProvider implements WrappersProvider {
private void initError() {
var proto = new ObjectValue();
proto.defineProperty(null, "message", new NativeFunction("message", args -> {
if (args.thisArg instanceof Throwable) return ((Throwable)args.thisArg).getMessage();
if (args.self instanceof Throwable) return ((Throwable)args.self).getMessage();
else return null;
}));
proto.defineProperty(null, "name", new NativeFunction("name", args -> getName(args.thisArg.getClass())));
proto.defineProperty(null, "toString", new NativeFunction("toString", args -> args.thisArg.toString()));
proto.defineProperty(null, "name", new NativeFunction("name", args -> getName(args.self.getClass())));
proto.defineProperty(null, "toString", new NativeFunction("toString", args -> args.self.toString()));
var constr = makeConstructor(null, Throwable.class);
proto.defineProperty(null, "constructor", constr, true, false, false);

View File

@ -19,11 +19,11 @@ public interface PermissionsProvider {
public static PermissionsProvider get(Extensions exts) {
return new PermissionsProvider() {
@Override public boolean hasPermission(Permission perm) {
if (exts.has(ENV_KEY)) return ((PermissionsProvider)exts.get(ENV_KEY)).hasPermission(perm);
if (exts.hasNotNull(ENV_KEY)) return ((PermissionsProvider)exts.get(ENV_KEY)).hasPermission(perm);
else return true;
}
@Override public boolean hasPermission(Permission perm, char delim) {
if (exts.has(ENV_KEY)) return ((PermissionsProvider)exts.get(ENV_KEY)).hasPermission(perm, delim);
if (exts.hasNotNull(ENV_KEY)) return ((PermissionsProvider)exts.get(ENV_KEY)).hasPermission(perm, delim);
else return true;
}
};