From 38acc20a6f4f00f6619de3daf862e722ea2df06d Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Tue, 26 Dec 2023 17:12:20 +0200 Subject: [PATCH 01/35] refactor: greatly improve Environment API --- src/me/topchetoeu/jscript/Main.java | 4 +- src/me/topchetoeu/jscript/engine/Context.java | 17 +- src/me/topchetoeu/jscript/engine/Engine.java | 57 +------ .../jscript/engine/Environment.java | 152 +++++++++--------- .../jscript/engine/ExtensionStack.java | 39 +++++ .../topchetoeu/jscript/engine/Extensions.java | 24 +++ .../jscript/engine/debug/DebugContext.java | 96 +++++++++++ .../jscript/engine/debug/DebugController.java | 11 ++ .../jscript/engine/debug/SimpleDebugger.java | 11 +- .../jscript/engine/frame/CodeFrame.java | 8 +- .../jscript/engine/frame/Runners.java | 9 +- .../jscript/engine/values/ObjectValue.java | 29 ++-- .../jscript/engine/values/Symbol.java | 13 ++ .../jscript/engine/values/Values.java | 15 +- .../jscript/exceptions/EngineException.java | 5 +- .../interop/NativeWrapperProvider.java | 13 +- src/me/topchetoeu/jscript/lib/ErrorLib.java | 3 +- src/me/topchetoeu/jscript/lib/Internals.java | 56 +++---- src/me/topchetoeu/jscript/lib/ObjectLib.java | 2 +- src/me/topchetoeu/jscript/lib/StringLib.java | 28 ++-- src/me/topchetoeu/jscript/lib/SymbolLib.java | 19 ++- 21 files changed, 371 insertions(+), 240 deletions(-) create mode 100644 src/me/topchetoeu/jscript/engine/ExtensionStack.java create mode 100644 src/me/topchetoeu/jscript/engine/Extensions.java create mode 100644 src/me/topchetoeu/jscript/engine/debug/DebugContext.java diff --git a/src/me/topchetoeu/jscript/Main.java b/src/me/topchetoeu/jscript/Main.java index ca32edd..399f5aa 100644 --- a/src/me/topchetoeu/jscript/Main.java +++ b/src/me/topchetoeu/jscript/Main.java @@ -131,10 +131,10 @@ public class Main { private static void initTypescript() { try { var tsEnv = Internals.apply(new Environment(null, null, null)); - tsEnv.stackVisible = false; + tsEnv.stackHidden = false; tsEnv.global.define(null, "module", false, new ObjectValue()); var bsEnv = Internals.apply(new Environment(null, null, null)); - bsEnv.stackVisible = false; + bsEnv.stackHidden = false; engine.pushMsg( false, tsEnv, diff --git a/src/me/topchetoeu/jscript/engine/Context.java b/src/me/topchetoeu/jscript/engine/Context.java index 5761c82..bd991a8 100644 --- a/src/me/topchetoeu/jscript/engine/Context.java +++ b/src/me/topchetoeu/jscript/engine/Context.java @@ -10,6 +10,7 @@ import java.util.stream.Collectors; import me.topchetoeu.jscript.Filename; import me.topchetoeu.jscript.Location; +import me.topchetoeu.jscript.engine.debug.DebugContext; import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.FunctionValue; @@ -17,11 +18,16 @@ import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.mapping.SourceMap; -public class Context { +public class Context extends ExtensionStack { private final Stack env = new Stack<>(); private final ArrayList frames = new ArrayList<>(); public final Engine engine; + @Override + protected Extensions[] extensionStack() { + return new Extensions[] { environment(), engine.globalEnvironment }; + } + public Environment environment() { return env.empty() ? null : env.peek(); } @@ -36,7 +42,7 @@ public class Context { public FunctionValue compile(Filename filename, String raw) { var env = environment(); - var result = env.compile.call(this, null, raw, filename.toString(), env); + var result = Environment.compileFunc(this).call(this, null, raw, filename.toString(), env); var function = (FunctionValue)Values.getMember(this, result, "function"); if (!engine.debugging) return function; @@ -62,24 +68,23 @@ public class Context { breakpoints = newBreakpoints; } - engine.onSource(filename, raw, breakpoints, map); + DebugContext.get(this).onSource(filename, raw, breakpoints, map); return function; } - public void pushFrame(CodeFrame frame) { frames.add(frame); if (frames.size() > engine.maxStackFrames) throw EngineException.ofRange("Stack overflow!"); pushEnv(frame.function.environment); - engine.onFramePush(this, frame); + DebugContext.get(this).onFramePush(this, frame); } public boolean popFrame(CodeFrame frame) { if (frames.size() == 0) return false; if (frames.get(frames.size() - 1) != frame) return false; frames.remove(frames.size() - 1); popEnv(); - engine.onFramePop(this, frame); + DebugContext.get(this).onFramePop(this, frame); return true; } public CodeFrame peekFrame() { diff --git a/src/me/topchetoeu/jscript/engine/Engine.java b/src/me/topchetoeu/jscript/engine/Engine.java index 50ff0d3..9066e83 100644 --- a/src/me/topchetoeu/jscript/engine/Engine.java +++ b/src/me/topchetoeu/jscript/engine/Engine.java @@ -17,7 +17,7 @@ import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.InterruptException; import me.topchetoeu.jscript.mapping.SourceMap; -public class Engine implements DebugController { +public class Engine { private class UncompiledFunction extends FunctionValue { public final Filename filename; public final String raw; @@ -61,47 +61,15 @@ public class Engine implements DebugController { private static int nextId = 0; public static final HashMap functions = new HashMap<>(); + public final Environment globalEnvironment = new Environment(); + public final int id = ++nextId; public final boolean debugging; public int maxStackFrames = 10000; - private final HashMap sources = new HashMap<>(); - private final HashMap> bpts = new HashMap<>(); - private final HashMap maps = new HashMap<>(); - - public Location mapToCompiled(Location location) { - var map = maps.get(location.filename()); - if (map == null) return location; - return map.toCompiled(location); - } - public Location mapToOriginal(Location location) { - var map = maps.get(location.filename()); - if (map == null) return location; - return map.toOriginal(location); - } - - private DebugController debugger; private Thread thread; private PriorityBlockingQueue tasks = new PriorityBlockingQueue<>(); - public synchronized boolean attachDebugger(DebugController debugger) { - if (!debugging || this.debugger != null) return false; - - for (var source : sources.entrySet()) debugger.onSource( - source.getKey(), source.getValue(), - bpts.get(source.getKey()), - maps.get(source.getKey()) - ); - - this.debugger = debugger; - return true; - } - public synchronized boolean detachDebugger() { - if (!debugging || this.debugger == null) return false; - this.debugger = null; - return true; - } - private void runTask(Task task) { try { task.notifier.next(task.func.call(task.ctx, task.thisArg, task.args)); @@ -150,25 +118,6 @@ public class Engine implements DebugController { return pushMsg(micro, env, new UncompiledFunction(filename, raw), thisArg, args); } - @Override - public void onFramePush(Context ctx, CodeFrame frame) { - if (debugging && debugger != null) debugger.onFramePush(ctx, frame); - } - @Override public void onFramePop(Context ctx, CodeFrame frame) { - if (debugging && debugger != null) debugger.onFramePop(ctx, frame); - } - @Override public boolean onInstruction(Context ctx, CodeFrame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught) { - if (debugging && debugger != null) return debugger.onInstruction(ctx, frame, instruction, returnVal, error, caught); - else return false; - } - @Override public void onSource(Filename filename, String source, TreeSet breakpoints, SourceMap map) { - if (!debugging) return; - if (debugger != null) debugger.onSource(filename, source, breakpoints, map); - sources.put(filename, source); - bpts.put(filename, breakpoints); - maps.put(filename, map); - } - public Engine(boolean debugging) { this.debugging = debugging; } diff --git a/src/me/topchetoeu/jscript/engine/Environment.java b/src/me/topchetoeu/jscript/engine/Environment.java index 8f6e1bc..5a58248 100644 --- a/src/me/topchetoeu/jscript/engine/Environment.java +++ b/src/me/topchetoeu/jscript/engine/Environment.java @@ -13,117 +13,109 @@ 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.filesystem.RootFilesystem; import me.topchetoeu.jscript.interop.Native; -import me.topchetoeu.jscript.interop.NativeGetter; -import me.topchetoeu.jscript.interop.NativeSetter; import me.topchetoeu.jscript.interop.NativeWrapperProvider; -import me.topchetoeu.jscript.modules.RootModuleRepo; import me.topchetoeu.jscript.parsing.Parsing; -import me.topchetoeu.jscript.permissions.Permission; -import me.topchetoeu.jscript.permissions.PermissionsProvider; // TODO: Remove hardcoded extensions form environment -public class Environment implements PermissionsProvider { - private HashMap prototypes = new HashMap<>(); +@SuppressWarnings("unchecked") +public class Environment implements Extensions { + private static int nextId = 0; - public final Data data = new Data(); public static final HashMap symbols = new HashMap<>(); + public static final Symbol WRAPPERS = Symbol.get("Environment.wrappers"); + public static final Symbol COMPILE_FUNC = Symbol.get("Environment.compile"); + + public static final Symbol REGEX_CONSTR = Symbol.get("Environment.regexConstructor"); + public static final Symbol STACK = Symbol.get("Environment.stack"); + public static final Symbol HIDE_STACK = Symbol.get("Environment.hideStack"); + + public static final Symbol OBJECT_PROTO = Symbol.get("Environment.objectPrototype"); + public static final Symbol FUNCTION_PROTO = Symbol.get("Environment.functionPrototype"); + public static final Symbol ARRAY_PROTO = Symbol.get("Environment.arrayPrototype"); + public static final Symbol BOOL_PROTO = Symbol.get("Environment.boolPrototype"); + public static final Symbol NUMBER_PROTO = Symbol.get("Environment.numberPrototype"); + public static final Symbol STRING_PROTO = Symbol.get("Environment.stringPrototype"); + public static final Symbol SYMBOL_PROTO = Symbol.get("Environment.symbolPrototype"); + public static final Symbol ERROR_PROTO = Symbol.get("Environment.errorPrototype"); + public static final Symbol SYNTAX_ERR_PROTO = Symbol.get("Environment.syntaxErrorPrototype"); + public static final Symbol TYPE_ERR_PROTO = Symbol.get("Environment.typeErrorPrototype"); + public static final Symbol RANGE_ERR_PROTO = Symbol.get("Environment.rangeErrorPrototype"); + + private HashMap data = new HashMap<>(); + public GlobalScope global; public WrappersProvider wrappers; - public PermissionsProvider permissions = null; - public final RootFilesystem filesystem = new RootFilesystem(this); - public final RootModuleRepo modules = new RootModuleRepo(); - public String moduleCwd = "/"; - - private static int nextId = 0; - - @Native public boolean stackVisible = true; @Native public int id = ++nextId; - @Native public FunctionValue compile = new NativeFunction("compile", (ctx, thisArg, args) -> { - var source = Values.toString(ctx, args[0]); - var filename = Values.toString(ctx, args[1]); - var isDebug = Values.toBoolean(args[2]); - - var env = Values.wrapper(args[2], Environment.class); - var res = new ObjectValue(); - - var target = Parsing.compile(env, Filename.parse(filename), source); - Engine.functions.putAll(target.functions); - Engine.functions.remove(0l); - - res.defineProperty(ctx, "function", target.func(env)); - res.defineProperty(ctx, "mapChain", new ArrayValue()); - - if (isDebug) { - res.defineProperty(ctx, "breakpoints", ArrayValue.of(ctx, target.breakpoints.stream().map(Location::toString).collect(Collectors.toList()))); + @Override public void add(Symbol key, T obj) { + data.put(key, obj); + } + @Override public T get(Symbol key) { + return (T)data.get(key); + } + @Override public boolean remove(Symbol key) { + if (data.containsKey(key)) { + data.remove(key); + return true; } - - return res; - }); - @Native public FunctionValue regexConstructor = new NativeFunction("RegExp", (ctx, thisArg, args) -> { - throw EngineException.ofError("Regular expressions not supported.").setCtx(ctx.environment(), ctx.engine); - }); - - @Native public ObjectValue proto(String name) { - return prototypes.get(name); + return false; } - @Native public void setProto(String name, ObjectValue val) { - prototypes.put(name, val); + @Override public boolean has(Symbol key) { + return data.containsKey(key); } - @Native public Symbol symbol(String name) { - return getSymbol(name); + public static FunctionValue compileFunc(Extensions ext) { + return ext.init(COMPILE_FUNC, new NativeFunction("compile", (ctx, thisArg, args) -> { + var source = Values.toString(ctx, args[0]); + var filename = Values.toString(ctx, args[1]); + var isDebug = Values.toBoolean(args[2]); + + var env = Values.wrapper(args[2], Environment.class); + var res = new ObjectValue(); + + var target = Parsing.compile(env, Filename.parse(filename), source); + Engine.functions.putAll(target.functions); + Engine.functions.remove(0l); + + res.defineProperty(ctx, "function", target.func(env)); + res.defineProperty(ctx, "mapChain", new ArrayValue()); + + if (isDebug) { + res.defineProperty(ctx, "breakpoints", ArrayValue.of(ctx, target.breakpoints.stream().map(Location::toString).collect(Collectors.toList()))); + } + + return res; + })); + } + public static FunctionValue regexConstructor(Extensions ext) { + return ext.init(COMPILE_FUNC, new NativeFunction("RegExp", (ctx, thisArg, args) -> { + throw EngineException.ofError("Regular expressions not supported.").setCtx(ctx.environment(), ctx.engine); + })); } - @NativeGetter("global") public ObjectValue getGlobal() { - return global.obj; - } - @NativeSetter("global") public void setGlobal(ObjectValue val) { - global = new GlobalScope(val); - } + public Environment fork() { + var res = new Environment(null, global); - @Native public Environment fork() { - var res = new Environment(compile, null, global); res.wrappers = wrappers.fork(res); - res.regexConstructor = regexConstructor; - res.prototypes = new HashMap<>(prototypes); + res.global = global; + res.data.putAll(data); + return res; } - @Native public Environment child() { + public Environment child() { var res = fork(); res.global = res.global.globalChild(); - res.permissions = this.permissions; - res.filesystem.protocols.putAll(this.filesystem.protocols); - res.modules.repos.putAll(this.modules.repos); return res; } - @Override public boolean hasPermission(Permission perm, char delim) { - return permissions == null || permissions.hasPermission(perm, delim); - } - @Override public boolean hasPermission(Permission perm) { - return permissions == null || permissions.hasPermission(perm); - } - public Context context(Engine engine) { return new Context(engine, this); } - public static Symbol getSymbol(String name) { - if (symbols.containsKey(name)) return symbols.get(name); - else { - var res = new Symbol(name); - symbols.put(name, res); - return res; - } - } - - public Environment(FunctionValue compile, WrappersProvider nativeConverter, GlobalScope global) { - if (compile != null) this.compile = compile; + public Environment(WrappersProvider nativeConverter, GlobalScope global) { if (nativeConverter == null) nativeConverter = new NativeWrapperProvider(this); if (global == null) global = new GlobalScope(); @@ -131,6 +123,6 @@ public class Environment implements PermissionsProvider { this.global = global; } public Environment() { - this(null, null, null); + this(null, null); } } diff --git a/src/me/topchetoeu/jscript/engine/ExtensionStack.java b/src/me/topchetoeu/jscript/engine/ExtensionStack.java new file mode 100644 index 0000000..336e903 --- /dev/null +++ b/src/me/topchetoeu/jscript/engine/ExtensionStack.java @@ -0,0 +1,39 @@ +package me.topchetoeu.jscript.engine; + +import me.topchetoeu.jscript.engine.values.Symbol; + +public abstract class ExtensionStack implements Extensions { + protected abstract Extensions[] extensionStack(); + + @Override public void add(Symbol key, T obj) { + for (var el : extensionStack()) { + if (el != null) { + el.add(key, obj); + return; + } + } + } + @Override public T get(Symbol key) { + for (var el : extensionStack()) { + if (el != null && el.has(key)) return el.get(key); + } + + return null; + } + @Override public boolean has(Symbol key) { + for (var el : extensionStack()) { + if (el != null && el.has(key)) return true; + } + + return false; + } + @Override public boolean remove(Symbol key) { + var anyRemoved = false; + + for (var el : extensionStack()) { + if (el != null) anyRemoved &= el.remove(key); + } + + return anyRemoved; + } +} diff --git a/src/me/topchetoeu/jscript/engine/Extensions.java b/src/me/topchetoeu/jscript/engine/Extensions.java new file mode 100644 index 0000000..83072c9 --- /dev/null +++ b/src/me/topchetoeu/jscript/engine/Extensions.java @@ -0,0 +1,24 @@ +package me.topchetoeu.jscript.engine; + +import me.topchetoeu.jscript.engine.values.Symbol; + +public interface Extensions { + T get(Symbol key); + void add(Symbol key, T obj); + + boolean has(Symbol key); + boolean remove(Symbol key); + + default T get(Symbol key, T defaultVal) { + if (has(key)) return get(key); + else return defaultVal; + } + + default T init(Symbol key, T val) { + if (has(key)) return get(key); + else { + add(key, val); + return val; + } + } +} diff --git a/src/me/topchetoeu/jscript/engine/debug/DebugContext.java b/src/me/topchetoeu/jscript/engine/debug/DebugContext.java new file mode 100644 index 0000000..6baae19 --- /dev/null +++ b/src/me/topchetoeu/jscript/engine/debug/DebugContext.java @@ -0,0 +1,96 @@ +package me.topchetoeu.jscript.engine.debug; + +import java.util.HashMap; +import java.util.TreeSet; + +import me.topchetoeu.jscript.Filename; +import me.topchetoeu.jscript.Location; +import me.topchetoeu.jscript.compilation.Instruction; +import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.Extensions; +import me.topchetoeu.jscript.engine.frame.CodeFrame; +import me.topchetoeu.jscript.engine.values.Symbol; +import me.topchetoeu.jscript.exceptions.EngineException; +import me.topchetoeu.jscript.mapping.SourceMap; + +public class DebugContext implements DebugController { + public static final Symbol DEBUG_CTX = Symbol.get("Engine.debug"); + + private HashMap sources; + private HashMap> bpts; + private HashMap maps; + private DebugController debugger; + + public boolean attachDebugger(DebugController debugger) { + if (this.debugger != null) return false; + + if (sources != null) { + for (var source : sources.entrySet()) debugger.onSource( + source.getKey(), source.getValue(), + bpts.get(source.getKey()), + maps.get(source.getKey()) + ); + } + + this.debugger = debugger; + return true; + } + public boolean detachDebugger() { + this.debugger = null; + return true; + } + + public DebugController debugger() { + if (debugger == null) return DebugController.empty(); + else return debugger; + } + + @Override public void onFramePop(Context ctx, CodeFrame frame) { + if (debugger != null) debugger.onFramePop(ctx, frame); + } + @Override public void onFramePush(Context ctx, CodeFrame frame) { + if (debugger != null) debugger.onFramePush(ctx, frame); + } + @Override public boolean onInstruction(Context ctx, CodeFrame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught) { + if (debugger != null) return debugger.onInstruction(ctx, frame, instruction, returnVal, error, caught); + else return false; + } + @Override public void onSource(Filename filename, String source, TreeSet breakpoints, SourceMap map) { + if (debugger != null) debugger.onSource(filename, source, breakpoints, map); + if (sources != null) sources.put(filename, source); + if (bpts != null) bpts.put(filename, breakpoints); + if (maps != null) maps.put(filename, map); + } + + public Location mapToCompiled(Location location) { + if (maps == null) return location; + + var map = maps.get(location.filename()); + if (map == null) return location; + return map.toCompiled(location); + } + public Location mapToOriginal(Location location) { + if (maps == null) return location; + + var map = maps.get(location.filename()); + if (map == null) return location; + return map.toOriginal(location); + } + + private DebugContext(boolean enabled) { + if (enabled) { + sources = new HashMap<>(); + bpts = new HashMap<>(); + maps = new HashMap<>(); + } + } + + public DebugContext() { + this(true); + } + + public static DebugContext get(Extensions exts) { + if (exts.has(DEBUG_CTX)) return exts.get(DEBUG_CTX); + else return new DebugContext(false); + } +} diff --git a/src/me/topchetoeu/jscript/engine/debug/DebugController.java b/src/me/topchetoeu/jscript/engine/debug/DebugController.java index 36fa7b1..d03d4f6 100644 --- a/src/me/topchetoeu/jscript/engine/debug/DebugController.java +++ b/src/me/topchetoeu/jscript/engine/debug/DebugController.java @@ -48,4 +48,15 @@ public interface DebugController { * @param frame The code frame which was popped out */ void onFramePop(Context ctx, CodeFrame frame); + + public static DebugController empty() { + return new DebugController() { + @Override public void onFramePop(Context ctx, CodeFrame frame) { } + @Override public void onFramePush(Context ctx, CodeFrame frame) { } + @Override public boolean onInstruction(Context ctx, CodeFrame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught) { + return false; + } + @Override public void onSource(Filename filename, String source, TreeSet breakpoints, SourceMap map) { } + }; + } } diff --git a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java index a21af73..1ac5307 100644 --- a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java +++ b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java @@ -15,6 +15,7 @@ import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction.Type; import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Engine; +import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.frame.Runners; import me.topchetoeu.jscript.engine.scope.GlobalScope; @@ -341,7 +342,7 @@ public class SimpleDebugger implements Debugger { try { defaultToString = Values.getMember(ctx, obj, "toString") == - Values.getMember(ctx, ctx.environment().proto("object"), "toString"); + Values.getMember(ctx, ctx.environment().get(Environment.OBJECT_PROTO), "toString"); } catch (Exception e) { } @@ -495,7 +496,7 @@ public class SimpleDebugger implements Debugger { var res = new ArrayValue(); var passed = new HashSet(); var tildas = "~"; - if (target == null) target = ctx.environment().getGlobal(); + if (target == null) target = ctx.environment().global; for (var proto = target; proto != null && proto != Values.NULL; proto = Values.getPrototype(ctx, proto)) { for (var el : Values.getMembers(ctx, proto, true, true)) { @@ -870,7 +871,7 @@ public class SimpleDebugger implements Debugger { if (!frame.debugData) return false; - if (instruction.location != null) frame.updateLoc(ctx.engine.mapToCompiled(instruction.location)); + if (instruction.location != null) frame.updateLoc(DebugContext.get(ctx).mapToCompiled(instruction.location)); loc = frame.location; isBreakpointable = loc != null && (instruction.breakpoint.shouldStepIn()); @@ -953,12 +954,12 @@ public class SimpleDebugger implements Debugger { } @Override public synchronized void connect() { - if (!target.attachDebugger(this)) { + if (!DebugContext.get(target.globalEnvironment).attachDebugger(this)) { ws.send(new V8Error("A debugger is already attached to this engine.")); } } @Override public synchronized void disconnect() { - target.detachDebugger(); + DebugContext.get(target.globalEnvironment).detachDebugger(); enabled = false; updateNotifier.next(); } diff --git a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java index e4411ce..bc2d032 100644 --- a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java +++ b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java @@ -6,6 +6,7 @@ import java.util.Stack; import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.debug.DebugContext; import me.topchetoeu.jscript.engine.scope.LocalScope; import me.topchetoeu.jscript.engine.scope.ValueVariable; import me.topchetoeu.jscript.engine.values.ArrayValue; @@ -198,8 +199,7 @@ public class CodeFrame { if (instr == null) returnValue = null; else { - // System.out.println(instr + "@" + instr.location); - ctx.engine.onInstruction(ctx, this, instr, Runners.NO_RETURN, null, false); + DebugContext.get(ctx).onInstruction(ctx, this, instr, Runners.NO_RETURN, null, false); if (instr.location != null) prevLoc = instr.location; @@ -291,11 +291,11 @@ public class CodeFrame { } } - ctx.engine.onInstruction(ctx, this, instr, null, error, caught); + DebugContext.get(ctx).onInstruction(ctx, this, instr, null, error, caught); throw error; } if (returnValue != Runners.NO_RETURN) { - ctx.engine.onInstruction(ctx, this, instr, returnValue, null, false); + DebugContext.get(ctx).onInstruction(ctx, this, instr, returnValue, null, false); return returnValue; } diff --git a/src/me/topchetoeu/jscript/engine/frame/Runners.java b/src/me/topchetoeu/jscript/engine/frame/Runners.java index d889a20..1d795b6 100644 --- a/src/me/topchetoeu/jscript/engine/frame/Runners.java +++ b/src/me/topchetoeu/jscript/engine/frame/Runners.java @@ -5,6 +5,7 @@ import java.util.Collections; import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Engine; +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.ArrayValue; @@ -197,7 +198,13 @@ public class Runners { return execLoadMember(ctx, instr, frame); } public static Object execLoadRegEx(Context ctx, Instruction instr, CodeFrame frame) { - frame.push(ctx, ctx.environment().regexConstructor.call(ctx, null, instr.get(0), instr.get(1))); + var env = ctx.environment(); + if (env.has(Environment.REGEX_CONSTR)) { + frame.push(ctx, Values.callNew(ctx, env.get(Environment.REGEX_CONSTR))); + } + else { + throw EngineException.ofSyntax("Regex is not supported."); + } frame.codePtr++; return NO_RETURN; } diff --git a/src/me/topchetoeu/jscript/engine/values/ObjectValue.java b/src/me/topchetoeu/jscript/engine/values/ObjectValue.java index 7cb4bae..ea2fe38 100644 --- a/src/me/topchetoeu/jscript/engine/values/ObjectValue.java +++ b/src/me/topchetoeu/jscript/engine/values/ObjectValue.java @@ -7,6 +7,7 @@ import java.util.List; import java.util.Map; import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.Environment; public class ObjectValue { public static enum PlaceholderProto { @@ -146,13 +147,13 @@ public class ObjectValue { public ObjectValue getPrototype(Context ctx) { try { - if (prototype == OBJ_PROTO) return ctx.environment().proto("object"); - if (prototype == ARR_PROTO) return ctx.environment().proto("array"); - if (prototype == FUNC_PROTO) return ctx.environment().proto("function"); - if (prototype == ERR_PROTO) return ctx.environment().proto("error"); - if (prototype == RANGE_ERR_PROTO) return ctx.environment().proto("rangeErr"); - if (prototype == SYNTAX_ERR_PROTO) return ctx.environment().proto("syntaxErr"); - if (prototype == TYPE_ERR_PROTO) return ctx.environment().proto("typeErr"); + if (prototype == OBJ_PROTO) return ctx.environment().get(Environment.OBJECT_PROTO); + if (prototype == ARR_PROTO) return ctx.environment().get(Environment.ARRAY_PROTO); + if (prototype == FUNC_PROTO) return ctx.environment().get(Environment.FUNCTION_PROTO); + if (prototype == ERR_PROTO) return ctx.environment().get(Environment.ERROR_PROTO); + if (prototype == RANGE_ERR_PROTO) return ctx.environment().get(Environment.RANGE_ERR_PROTO); + if (prototype == SYNTAX_ERR_PROTO) return ctx.environment().get(Environment.SYNTAX_ERR_PROTO); + if (prototype == TYPE_ERR_PROTO) return ctx.environment().get(Environment.TYPE_ERR_PROTO); } catch (NullPointerException e) { return null; } @@ -170,13 +171,13 @@ public class ObjectValue { var obj = Values.object(val); if (ctx != null && ctx.environment() != null) { - if (obj == ctx.environment().proto("object")) prototype = OBJ_PROTO; - else if (obj == ctx.environment().proto("array")) prototype = ARR_PROTO; - else if (obj == ctx.environment().proto("function")) prototype = FUNC_PROTO; - else if (obj == ctx.environment().proto("error")) prototype = ERR_PROTO; - else if (obj == ctx.environment().proto("syntaxErr")) prototype = SYNTAX_ERR_PROTO; - else if (obj == ctx.environment().proto("typeErr")) prototype = TYPE_ERR_PROTO; - else if (obj == ctx.environment().proto("rangeErr")) prototype = RANGE_ERR_PROTO; + if (obj == ctx.environment().get(Environment.OBJECT_PROTO)) prototype = OBJ_PROTO; + else if (obj == ctx.environment().get(Environment.ARRAY_PROTO)) prototype = ARR_PROTO; + else if (obj == ctx.environment().get(Environment.FUNCTION_PROTO)) prototype = FUNC_PROTO; + else if (obj == ctx.environment().get(Environment.ERROR_PROTO)) prototype = ERR_PROTO; + else if (obj == ctx.environment().get(Environment.SYNTAX_ERR_PROTO)) prototype = SYNTAX_ERR_PROTO; + else if (obj == ctx.environment().get(Environment.TYPE_ERR_PROTO)) prototype = TYPE_ERR_PROTO; + else if (obj == ctx.environment().get(Environment.RANGE_ERR_PROTO)) prototype = RANGE_ERR_PROTO; else prototype = obj; } else prototype = obj; diff --git a/src/me/topchetoeu/jscript/engine/values/Symbol.java b/src/me/topchetoeu/jscript/engine/values/Symbol.java index b24d8f1..65aeac9 100644 --- a/src/me/topchetoeu/jscript/engine/values/Symbol.java +++ b/src/me/topchetoeu/jscript/engine/values/Symbol.java @@ -1,6 +1,10 @@ package me.topchetoeu.jscript.engine.values; +import java.util.HashMap; + public final class Symbol { + private static final HashMap registry = new HashMap<>(); + public final String value; public Symbol(String value) { @@ -12,4 +16,13 @@ public final class Symbol { if (value == null) return "Symbol"; else return "@@" + value; } + + public static Symbol get(String name) { + if (registry.containsKey(name)) return registry.get(name); + else { + var res = new Symbol(name); + registry.put(name, res); + return res; + } + } } diff --git a/src/me/topchetoeu/jscript/engine/values/Values.java b/src/me/topchetoeu/jscript/engine/values/Values.java index d9031ee..d8b07e3 100644 --- a/src/me/topchetoeu/jscript/engine/values/Values.java +++ b/src/me/topchetoeu/jscript/engine/values/Values.java @@ -11,6 +11,7 @@ import java.util.List; import java.util.Map; import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.Operation; import me.topchetoeu.jscript.engine.frame.ConvertHint; import me.topchetoeu.jscript.exceptions.ConvertException; @@ -346,10 +347,10 @@ public class Values { if (isObject(obj)) return object(obj).getPrototype(ctx); if (ctx == null) return null; - if (obj instanceof String) return ctx.environment().proto("string"); - else if (obj instanceof Number) return ctx.environment().proto("number"); - else if (obj instanceof Boolean) return ctx.environment().proto("bool"); - else if (obj instanceof Symbol) return ctx.environment().proto("symbol"); + if (obj instanceof String) return ctx.environment().get(Environment.STRING_PROTO); + else if (obj instanceof Number) return ctx.environment().get(Environment.NUMBER_PROTO); + else if (obj instanceof Boolean) return ctx.environment().get(Environment.BOOL_PROTO); + else if (obj instanceof Symbol) return ctx.environment().get(Environment.SYMBOL_PROTO); return null; } @@ -550,7 +551,7 @@ public class Values { public static Iterable fromJSIterator(Context ctx, Object obj) { return () -> { try { - var symbol = ctx.environment().symbol("Symbol.iterator"); + var symbol = Symbol.get("Symbol.iterator"); var iteratorFunc = getMember(ctx, obj, symbol); if (!isFunction(iteratorFunc)) return Collections.emptyIterator(); @@ -604,7 +605,7 @@ public class Values { var res = new ObjectValue(); try { - var key = getMember(ctx, getMember(ctx, ctx.environment().proto("symbol"), "constructor"), "iterator"); + var key = getMember(ctx, getMember(ctx, ctx.environment().get(Environment.SYMBOL_PROTO), "constructor"), "iterator"); res.defineProperty(ctx, key, new NativeFunction("", (_ctx, thisArg, args) -> thisArg)); } catch (IllegalArgumentException | NullPointerException e) { } @@ -629,7 +630,7 @@ public class Values { var res = new ObjectValue(); try { - var key = getMemberPath(ctx, ctx.environment().proto("symbol"), "constructor", "asyncIterator"); + var key = getMemberPath(ctx, ctx.environment().get(Environment.SYMBOL_PROTO), "constructor", "asyncIterator"); res.defineProperty(ctx, key, new NativeFunction("", (_ctx, thisArg, args) -> thisArg)); } catch (IllegalArgumentException | NullPointerException e) { } diff --git a/src/me/topchetoeu/jscript/exceptions/EngineException.java b/src/me/topchetoeu/jscript/exceptions/EngineException.java index 2639aab..1b42c95 100644 --- a/src/me/topchetoeu/jscript/exceptions/EngineException.java +++ b/src/me/topchetoeu/jscript/exceptions/EngineException.java @@ -7,6 +7,7 @@ import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Engine; import me.topchetoeu.jscript.engine.Environment; +import me.topchetoeu.jscript.engine.debug.DebugContext; import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto; @@ -18,13 +19,13 @@ public class EngineException extends RuntimeException { public final Context ctx; public boolean visible() { - return ctx == null || ctx.environment() == null || ctx.environment().stackVisible; + return ctx == null || ctx.environment() == null || !ctx.environment().get(Environment.HIDE_STACK, false); } public String toString() { var res = ""; var loc = location; - if (loc != null && ctx != null && ctx.engine != null) loc = ctx.engine.mapToCompiled(loc); + if (loc != null && ctx != null && ctx.engine != null) loc = DebugContext.get(ctx).mapToCompiled(loc); if (loc != null) res += "at " + loc.toString() + " "; if (function != null && !function.equals("")) res += "in " + function + " "; diff --git a/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java b/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java index 0d02d9b..fa7be76 100644 --- a/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java +++ b/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java @@ -8,6 +8,7 @@ import me.topchetoeu.jscript.engine.WrappersProvider; import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.NativeFunction; import me.topchetoeu.jscript.engine.values.ObjectValue; +import me.topchetoeu.jscript.engine.values.Symbol; import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.UncheckedException; @@ -28,7 +29,7 @@ public class NativeWrapperProvider implements WrappersProvider { if (nat.thisArg() && !member || !nat.thisArg() && !memberMatch) continue; Object name = nat.value(); - if (((String)name).startsWith("@@")) name = env.symbol(((String)name).substring(2)); + if (name.toString().startsWith("@@")) name = Symbol.get(name.toString().substring(2)); else if (name.equals("")) name = method.getName(); var val = target.values.get(name); @@ -42,7 +43,7 @@ public class NativeWrapperProvider implements WrappersProvider { if (get.thisArg() && !member || !get.thisArg() && !memberMatch) continue; Object name = get.value(); - if (((String)name).startsWith("@@")) name = env.symbol(((String)name).substring(2)); + if (((String)name).startsWith("@@")) name = Symbol.get(((String)name).substring(2)); else if (name.equals("")) name = method.getName(); var prop = target.properties.get(name); @@ -59,7 +60,7 @@ public class NativeWrapperProvider implements WrappersProvider { if (set.thisArg() && !member || !set.thisArg() && !memberMatch) continue; Object name = set.value(); - if (((String)name).startsWith("@@")) name = env.symbol(((String)name).substring(2)); + if (((String)name).startsWith("@@")) name = Symbol.get(((String)name).substring(2)); else if (name.equals("")) name = method.getName(); var prop = target.properties.get(name); @@ -82,7 +83,7 @@ public class NativeWrapperProvider implements WrappersProvider { if (nat != null) { Object name = nat.value(); - if (((String)name).startsWith("@@")) name = env.symbol(((String)name).substring(2)); + if (((String)name).startsWith("@@")) name = Symbol.get(((String)name).substring(2)); else if (name.equals("")) name = field.getName(); var getter = OverloadFunction.of("get " + name, Overload.getterFromField(field)); @@ -98,7 +99,7 @@ public class NativeWrapperProvider implements WrappersProvider { if (nat != null) { Object name = nat.value(); - if (((String)name).startsWith("@@")) name = env.symbol(((String)name).substring(2)); + if (((String)name).startsWith("@@")) name = Symbol.get(((String)name).substring(2)); else if (name.equals("")) name = cl.getName(); var getter = new NativeFunction("get " + name, (ctx, thisArg, args) -> cl); @@ -123,7 +124,7 @@ public class NativeWrapperProvider implements WrappersProvider { public static ObjectValue makeProto(Environment ctx, Class clazz) { var res = new ObjectValue(); - res.defineProperty(null, ctx.symbol("Symbol.typeName"), getName(clazz)); + res.defineProperty(null, Symbol.get("Symbol.typeName"), getName(clazz)); for (var overload : clazz.getDeclaredMethods()) { var init = overload.getAnnotation(NativeInit.class); diff --git a/src/me/topchetoeu/jscript/lib/ErrorLib.java b/src/me/topchetoeu/jscript/lib/ErrorLib.java index c681ed4..e3cc945 100644 --- a/src/me/topchetoeu/jscript/lib/ErrorLib.java +++ b/src/me/topchetoeu/jscript/lib/ErrorLib.java @@ -4,6 +4,7 @@ import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.values.ArrayValue; 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.engine.values.ObjectValue.PlaceholderProto; import me.topchetoeu.jscript.interop.InitType; @@ -35,7 +36,7 @@ import me.topchetoeu.jscript.interop.NativeInit; if (thisArg instanceof ObjectValue) { var stack = Values.getMember(ctx, thisArg, "stack"); if (!(stack instanceof ArrayValue)) stack = null; - var cause = Values.getMember(ctx, thisArg, ctx.environment().symbol("Symbol.cause")); + var cause = Values.getMember(ctx, thisArg, Symbol.get("Symbol.cause")); return toString(ctx, thisArg == cause, cause, diff --git a/src/me/topchetoeu/jscript/lib/Internals.java b/src/me/topchetoeu/jscript/lib/Internals.java index 2059e62..c1582a2 100644 --- a/src/me/topchetoeu/jscript/lib/Internals.java +++ b/src/me/topchetoeu/jscript/lib/Internals.java @@ -10,6 +10,7 @@ import me.topchetoeu.jscript.engine.DataKey; import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.scope.GlobalScope; import me.topchetoeu.jscript.engine.values.FunctionValue; +import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.interop.Native; @@ -17,9 +18,6 @@ import me.topchetoeu.jscript.interop.NativeGetter; import me.topchetoeu.jscript.parsing.Parsing; public class Internals { - private static final DataKey> THREADS = new DataKey<>(); - private static final DataKey I = new DataKey<>(); - @Native public static Object require(Context ctx, String name) { var env = ctx.environment(); var res = env.modules.getModule(ctx, env.moduleCwd, name); @@ -47,7 +45,7 @@ public class Internals { } } - @Native public static int setTimeout(Context ctx, FunctionValue func, int delay, Object ...args) { + @Native public static Thread setTimeout(Context ctx, FunctionValue func, int delay, Object ...args) { var thread = new Thread(() -> { var ms = (long)delay; var ns = (int)((delay - ms) * 10000000); @@ -61,12 +59,9 @@ public class Internals { }); thread.start(); - int i = ctx.environment().data.increase(I, 1, 0); - var threads = ctx.environment().data.get(THREADS, new HashMap<>()); - threads.put(++i, thread); - return i; + return thread; } - @Native public static int setInterval(Context ctx, FunctionValue func, int delay, Object ...args) { + @Native public static Thread setInterval(Context ctx, FunctionValue func, int delay, Object ...args) { var thread = new Thread(() -> { var ms = (long)delay; var ns = (int)((delay - ms) * 10000000); @@ -76,26 +71,19 @@ public class Internals { Thread.sleep(ms, ns); } catch (InterruptedException e) { return; } - + ctx.engine.pushMsg(false, ctx.environment(), func, null, args); } }); - thread.start(); - int i = ctx.environment().data.increase(I, 1, 0); - var threads = ctx.environment().data.get(THREADS, new HashMap<>()); - threads.put(++i, thread); - return i; + return thread; } - @Native public static void clearTimeout(Context ctx, int i) { - var threads = ctx.environment().data.get(THREADS, new HashMap<>()); - - var thread = threads.remove(i); - if (thread != null) thread.interrupt(); + @Native public static void clearTimeout(Context ctx, Thread t) { + t.interrupt(); } - @Native public static void clearInterval(Context ctx, int i) { - clearTimeout(ctx, i); + @Native public static void clearInterval(Context ctx, Thread t) { + t.interrupt(); } @Native public static double parseInt(Context ctx, String val) { @@ -205,22 +193,22 @@ public class Internals { glob.define(false, wp.getConstr(TypeErrorLib.class)); glob.define(false, wp.getConstr(RangeErrorLib.class)); - env.setProto("object", wp.getProto(ObjectLib.class)); - env.setProto("function", wp.getProto(FunctionLib.class)); - env.setProto("array", wp.getProto(ArrayLib.class)); + env.add(Environment.OBJECT_PROTO, wp.getProto(ObjectLib.class)); + env.add(Environment.FUNCTION_PROTO, wp.getProto(FunctionLib.class)); + env.add(Environment.ARRAY_PROTO, wp.getProto(ArrayLib.class)); - env.setProto("bool", wp.getProto(BooleanLib.class)); - env.setProto("number", wp.getProto(NumberLib.class)); - env.setProto("string", wp.getProto(StringLib.class)); - env.setProto("symbol", wp.getProto(SymbolLib.class)); + env.add(Environment.BOOL_PROTO, wp.getProto(BooleanLib.class)); + env.add(Environment.NUMBER_PROTO, wp.getProto(NumberLib.class)); + env.add(Environment.STRING_PROTO, wp.getProto(StringLib.class)); + env.add(Environment.SYMBOL_PROTO, wp.getProto(SymbolLib.class)); - env.setProto("error", wp.getProto(ErrorLib.class)); - env.setProto("syntaxErr", wp.getProto(SyntaxErrorLib.class)); - env.setProto("typeErr", wp.getProto(TypeErrorLib.class)); - env.setProto("rangeErr", wp.getProto(RangeErrorLib.class)); + env.add(Environment.ERROR_PROTO, wp.getProto(ErrorLib.class)); + env.add(Environment.SYNTAX_ERR_PROTO, wp.getProto(SyntaxErrorLib.class)); + env.add(Environment.TYPE_ERR_PROTO, wp.getProto(TypeErrorLib.class)); + env.add(Environment.RANGE_ERR_PROTO, wp.getProto(RangeErrorLib.class)); wp.getProto(ObjectLib.class).setPrototype(null, null); - env.regexConstructor = wp.getConstr(RegExpLib.class); + env.add(Environment.REGEX_CONSTR, wp.getConstr(RegExpLib.class)); return env; } diff --git a/src/me/topchetoeu/jscript/lib/ObjectLib.java b/src/me/topchetoeu/jscript/lib/ObjectLib.java index 83457fa..2114f20 100644 --- a/src/me/topchetoeu/jscript/lib/ObjectLib.java +++ b/src/me/topchetoeu/jscript/lib/ObjectLib.java @@ -191,7 +191,7 @@ import me.topchetoeu.jscript.interop.NativeConstructor; return thisArg; } @Native(thisArg = true) public static String toString(Context ctx, Object thisArg) { - var name = Values.getMember(ctx, thisArg, ctx.environment().symbol("Symbol.typeName")); + var name = Values.getMember(ctx, thisArg, Symbol.get("Symbol.typeName")); if (name == null) name = "Unknown"; else name = Values.toString(ctx, name); diff --git a/src/me/topchetoeu/jscript/lib/StringLib.java b/src/me/topchetoeu/jscript/lib/StringLib.java index 23a5359..232c2f0 100644 --- a/src/me/topchetoeu/jscript/lib/StringLib.java +++ b/src/me/topchetoeu/jscript/lib/StringLib.java @@ -3,9 +3,11 @@ package me.topchetoeu.jscript.lib; import java.util.regex.Pattern; import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.Environment; 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; @@ -80,7 +82,7 @@ import me.topchetoeu.jscript.interop.NativeGetter; var val = passThis(ctx, "indexOf", thisArg); if (term != null && term != Values.NULL && !(term instanceof String)) { - var search = Values.getMember(ctx, term, ctx.environment().symbol("Symbol.search")); + var search = Values.getMember(ctx, term, Symbol.get("Symbol.search")); if (search instanceof FunctionValue) { return (int)Values.toNumber(ctx, ((FunctionValue)search).call(ctx, term, val, false, start)); } @@ -92,7 +94,7 @@ import me.topchetoeu.jscript.interop.NativeGetter; var val = passThis(ctx, "lastIndexOf", thisArg); if (term != null && term != Values.NULL && !(term instanceof String)) { - var search = Values.getMember(ctx, term, ctx.environment().symbol("Symbol.search")); + var search = Values.getMember(ctx, term, Symbol.get("Symbol.search")); if (search instanceof FunctionValue) { return (int)Values.toNumber(ctx, ((FunctionValue)search).call(ctx, term, val, true, pos)); } @@ -109,7 +111,7 @@ import me.topchetoeu.jscript.interop.NativeGetter; var val = passThis(ctx, "replace", thisArg); if (term != null && term != Values.NULL && !(term instanceof String)) { - var replace = Values.getMember(ctx, term, ctx.environment().symbol("Symbol.replace")); + var replace = Values.getMember(ctx, term, Symbol.get("Symbol.replace")); if (replace instanceof FunctionValue) { return Values.toString(ctx, ((FunctionValue)replace).call(ctx, term, val, replacement)); } @@ -121,7 +123,7 @@ import me.topchetoeu.jscript.interop.NativeGetter; var val = passThis(ctx, "replaceAll", thisArg); if (term != null && term != Values.NULL && !(term instanceof String)) { - var replace = Values.getMember(ctx, term, ctx.environment().symbol("Symbol.replace")); + var replace = Values.getMember(ctx, term, Symbol.get("Symbol.replace")); if (replace instanceof FunctionValue) { return Values.toString(ctx, ((FunctionValue)replace).call(ctx, term, val, replacement)); } @@ -136,11 +138,11 @@ import me.topchetoeu.jscript.interop.NativeGetter; FunctionValue match; try { - var _match = Values.getMember(ctx, term, ctx.environment().symbol("Symbol.match")); + var _match = Values.getMember(ctx, term, Symbol.get("Symbol.match")); if (_match instanceof FunctionValue) match = (FunctionValue)_match; - else if (ctx.environment().regexConstructor != null) { - var regex = Values.callNew(ctx, ctx.environment().regexConstructor, Values.toString(ctx, term), ""); - _match = Values.getMember(ctx, regex, ctx.environment().symbol("Symbol.match")); + else if (ctx.environment().has(Environment.REGEX_CONSTR)) { + var regex = Values.callNew(ctx, ctx.environment().get(Environment.REGEX_CONSTR), Values.toString(ctx, term), ""); + _match = Values.getMember(ctx, regex, Symbol.get("Symbol.match")); if (_match instanceof FunctionValue) match = (FunctionValue)_match; else throw EngineException.ofError("Regular expressions don't support matching."); } @@ -158,14 +160,14 @@ import me.topchetoeu.jscript.interop.NativeGetter; FunctionValue match = null; try { - var _match = Values.getMember(ctx, term, ctx.environment().symbol("Symbol.matchAll")); + var _match = Values.getMember(ctx, term, Symbol.get("Symbol.matchAll")); if (_match instanceof FunctionValue) match = (FunctionValue)_match; } catch (IllegalArgumentException e) { } - if (match == null && ctx.environment().regexConstructor != null) { - var regex = Values.callNew(ctx, ctx.environment().regexConstructor, Values.toString(ctx, term), "g"); - var _match = Values.getMember(ctx, regex, ctx.environment().symbol("Symbol.matchAll")); + if (match == null && ctx.environment().has(Environment.REGEX_CONSTR)) { + var regex = Values.callNew(ctx, ctx.environment().get(Environment.REGEX_CONSTR), Values.toString(ctx, term), "g"); + var _match = Values.getMember(ctx, regex, Symbol.get("Symbol.matchAll")); if (_match instanceof FunctionValue) match = (FunctionValue)_match; else throw EngineException.ofError("Regular expressions don't support matching."); } @@ -180,7 +182,7 @@ import me.topchetoeu.jscript.interop.NativeGetter; if (lim != null) lim = Values.toNumber(ctx, lim); if (term != null && term != Values.NULL && !(term instanceof String)) { - var replace = Values.getMember(ctx, term, ctx.environment().symbol("Symbol.replace")); + var replace = Values.getMember(ctx, term, Symbol.get("Symbol.replace")); if (replace instanceof FunctionValue) { var tmp = ((FunctionValue)replace).call(ctx, term, val, lim, sensible); diff --git a/src/me/topchetoeu/jscript/lib/SymbolLib.java b/src/me/topchetoeu/jscript/lib/SymbolLib.java index f8bb132..900fb20 100644 --- a/src/me/topchetoeu/jscript/lib/SymbolLib.java +++ b/src/me/topchetoeu/jscript/lib/SymbolLib.java @@ -10,20 +10,19 @@ 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; -import me.topchetoeu.jscript.interop.NativeGetter; @Native("Symbol") public class SymbolLib { private static final Map symbols = new HashMap<>(); - @NativeGetter public static Symbol typeName(Context ctx) { return ctx.environment().symbol("Symbol.typeName"); } - @NativeGetter public static Symbol replace(Context ctx) { return ctx.environment().symbol("Symbol.replace"); } - @NativeGetter public static Symbol match(Context ctx) { return ctx.environment().symbol("Symbol.match"); } - @NativeGetter public static Symbol matchAll(Context ctx) { return ctx.environment().symbol("Symbol.matchAll"); } - @NativeGetter public static Symbol split(Context ctx) { return ctx.environment().symbol("Symbol.split"); } - @NativeGetter public static Symbol search(Context ctx) { return ctx.environment().symbol("Symbol.search"); } - @NativeGetter public static Symbol iterator(Context ctx) { return ctx.environment().symbol("Symbol.iterator"); } - @NativeGetter public static Symbol asyncIterator(Context ctx) { return ctx.environment().symbol("Symbol.asyncIterator"); } - @NativeGetter public static Symbol cause(Context ctx) { return ctx.environment().symbol("Symbol.cause"); } + @Native public static final Symbol typeName = Symbol.get("Symbol.typeName"); + @Native public static final Symbol replace = Symbol.get("Symbol.replace"); + @Native public static final Symbol match = Symbol.get("Symbol.match"); + @Native public static final Symbol matchAll = Symbol.get("Symbol.matchAll"); + @Native public static final Symbol split = Symbol.get("Symbol.split"); + @Native public static final Symbol search = Symbol.get("Symbol.search"); + @Native public static final Symbol iterator = Symbol.get("Symbol.iterator"); + @Native public static final Symbol asyncIterator = Symbol.get("Symbol.asyncIterator"); + @Native public static final Symbol cause = Symbol.get("Symbol.cause"); public final Symbol value; From 802f2f3f5265f9ccd6b18d243eefd746b5f3a7a7 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Wed, 27 Dec 2023 13:14:09 +0200 Subject: [PATCH 02/35] fix: access env via context --- src/me/topchetoeu/jscript/engine/Engine.java | 7 ----- .../jscript/engine/debug/SimpleDebugger.java | 2 +- .../jscript/engine/frame/Runners.java | 5 ++-- .../jscript/engine/values/ObjectValue.java | 30 +++++++++---------- .../jscript/engine/values/Values.java | 12 ++++---- .../jscript/exceptions/EngineException.java | 2 +- src/me/topchetoeu/jscript/lib/StringLib.java | 8 ++--- 7 files changed, 29 insertions(+), 37 deletions(-) diff --git a/src/me/topchetoeu/jscript/engine/Engine.java b/src/me/topchetoeu/jscript/engine/Engine.java index 9066e83..2a02e2f 100644 --- a/src/me/topchetoeu/jscript/engine/Engine.java +++ b/src/me/topchetoeu/jscript/engine/Engine.java @@ -1,21 +1,14 @@ package me.topchetoeu.jscript.engine; import java.util.HashMap; -import java.util.TreeSet; import java.util.concurrent.PriorityBlockingQueue; import me.topchetoeu.jscript.Filename; -import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.compilation.FunctionBody; -import me.topchetoeu.jscript.compilation.Instruction; -import me.topchetoeu.jscript.engine.debug.DebugController; -import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.events.Awaitable; import me.topchetoeu.jscript.events.DataNotifier; -import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.InterruptException; -import me.topchetoeu.jscript.mapping.SourceMap; public class Engine { private class UncompiledFunction extends FunctionValue { diff --git a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java index 1ac5307..fdcd510 100644 --- a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java +++ b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java @@ -342,7 +342,7 @@ public class SimpleDebugger implements Debugger { try { defaultToString = Values.getMember(ctx, obj, "toString") == - Values.getMember(ctx, ctx.environment().get(Environment.OBJECT_PROTO), "toString"); + Values.getMember(ctx, ctx.get(Environment.OBJECT_PROTO), "toString"); } catch (Exception e) { } diff --git a/src/me/topchetoeu/jscript/engine/frame/Runners.java b/src/me/topchetoeu/jscript/engine/frame/Runners.java index 1d795b6..51416d7 100644 --- a/src/me/topchetoeu/jscript/engine/frame/Runners.java +++ b/src/me/topchetoeu/jscript/engine/frame/Runners.java @@ -198,9 +198,8 @@ public class Runners { return execLoadMember(ctx, instr, frame); } public static Object execLoadRegEx(Context ctx, Instruction instr, CodeFrame frame) { - var env = ctx.environment(); - if (env.has(Environment.REGEX_CONSTR)) { - frame.push(ctx, Values.callNew(ctx, env.get(Environment.REGEX_CONSTR))); + if (ctx.has(Environment.REGEX_CONSTR)) { + frame.push(ctx, Values.callNew(ctx, ctx.get(Environment.REGEX_CONSTR))); } else { throw EngineException.ofSyntax("Regex is not supported."); diff --git a/src/me/topchetoeu/jscript/engine/values/ObjectValue.java b/src/me/topchetoeu/jscript/engine/values/ObjectValue.java index ea2fe38..1f71fda 100644 --- a/src/me/topchetoeu/jscript/engine/values/ObjectValue.java +++ b/src/me/topchetoeu/jscript/engine/values/ObjectValue.java @@ -147,13 +147,13 @@ public class ObjectValue { public ObjectValue getPrototype(Context ctx) { try { - if (prototype == OBJ_PROTO) return ctx.environment().get(Environment.OBJECT_PROTO); - if (prototype == ARR_PROTO) return ctx.environment().get(Environment.ARRAY_PROTO); - if (prototype == FUNC_PROTO) return ctx.environment().get(Environment.FUNCTION_PROTO); - if (prototype == ERR_PROTO) return ctx.environment().get(Environment.ERROR_PROTO); - if (prototype == RANGE_ERR_PROTO) return ctx.environment().get(Environment.RANGE_ERR_PROTO); - if (prototype == SYNTAX_ERR_PROTO) return ctx.environment().get(Environment.SYNTAX_ERR_PROTO); - if (prototype == TYPE_ERR_PROTO) return ctx.environment().get(Environment.TYPE_ERR_PROTO); + if (prototype == OBJ_PROTO) return ctx.get(Environment.OBJECT_PROTO); + if (prototype == ARR_PROTO) return ctx.get(Environment.ARRAY_PROTO); + if (prototype == FUNC_PROTO) return ctx.get(Environment.FUNCTION_PROTO); + if (prototype == ERR_PROTO) return ctx.get(Environment.ERROR_PROTO); + if (prototype == RANGE_ERR_PROTO) return ctx.get(Environment.RANGE_ERR_PROTO); + if (prototype == SYNTAX_ERR_PROTO) return ctx.get(Environment.SYNTAX_ERR_PROTO); + if (prototype == TYPE_ERR_PROTO) return ctx.get(Environment.TYPE_ERR_PROTO); } catch (NullPointerException e) { return null; } @@ -170,14 +170,14 @@ public class ObjectValue { else if (Values.isObject(val)) { var obj = Values.object(val); - if (ctx != null && ctx.environment() != null) { - if (obj == ctx.environment().get(Environment.OBJECT_PROTO)) prototype = OBJ_PROTO; - else if (obj == ctx.environment().get(Environment.ARRAY_PROTO)) prototype = ARR_PROTO; - else if (obj == ctx.environment().get(Environment.FUNCTION_PROTO)) prototype = FUNC_PROTO; - else if (obj == ctx.environment().get(Environment.ERROR_PROTO)) prototype = ERR_PROTO; - else if (obj == ctx.environment().get(Environment.SYNTAX_ERR_PROTO)) prototype = SYNTAX_ERR_PROTO; - else if (obj == ctx.environment().get(Environment.TYPE_ERR_PROTO)) prototype = TYPE_ERR_PROTO; - else if (obj == ctx.environment().get(Environment.RANGE_ERR_PROTO)) prototype = RANGE_ERR_PROTO; + if (ctx != null) { + if (obj == ctx.get(Environment.OBJECT_PROTO)) prototype = OBJ_PROTO; + else if (obj == ctx.get(Environment.ARRAY_PROTO)) prototype = ARR_PROTO; + else if (obj == ctx.get(Environment.FUNCTION_PROTO)) prototype = FUNC_PROTO; + else if (obj == ctx.get(Environment.ERROR_PROTO)) prototype = ERR_PROTO; + else if (obj == ctx.get(Environment.SYNTAX_ERR_PROTO)) prototype = SYNTAX_ERR_PROTO; + else if (obj == ctx.get(Environment.TYPE_ERR_PROTO)) prototype = TYPE_ERR_PROTO; + else if (obj == ctx.get(Environment.RANGE_ERR_PROTO)) prototype = RANGE_ERR_PROTO; else prototype = obj; } else prototype = obj; diff --git a/src/me/topchetoeu/jscript/engine/values/Values.java b/src/me/topchetoeu/jscript/engine/values/Values.java index d8b07e3..851bdfb 100644 --- a/src/me/topchetoeu/jscript/engine/values/Values.java +++ b/src/me/topchetoeu/jscript/engine/values/Values.java @@ -347,10 +347,10 @@ public class Values { if (isObject(obj)) return object(obj).getPrototype(ctx); if (ctx == null) return null; - if (obj instanceof String) return ctx.environment().get(Environment.STRING_PROTO); - else if (obj instanceof Number) return ctx.environment().get(Environment.NUMBER_PROTO); - else if (obj instanceof Boolean) return ctx.environment().get(Environment.BOOL_PROTO); - else if (obj instanceof Symbol) return ctx.environment().get(Environment.SYMBOL_PROTO); + if (obj instanceof String) return ctx.get(Environment.STRING_PROTO); + else if (obj instanceof Number) return ctx.get(Environment.NUMBER_PROTO); + else if (obj instanceof Boolean) return ctx.get(Environment.BOOL_PROTO); + else if (obj instanceof Symbol) return ctx.get(Environment.SYMBOL_PROTO); return null; } @@ -605,7 +605,7 @@ public class Values { var res = new ObjectValue(); try { - var key = getMember(ctx, getMember(ctx, ctx.environment().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)); } catch (IllegalArgumentException | NullPointerException e) { } @@ -630,7 +630,7 @@ public class Values { var res = new ObjectValue(); try { - var key = getMemberPath(ctx, ctx.environment().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)); } catch (IllegalArgumentException | NullPointerException e) { } diff --git a/src/me/topchetoeu/jscript/exceptions/EngineException.java b/src/me/topchetoeu/jscript/exceptions/EngineException.java index 1b42c95..e5b619f 100644 --- a/src/me/topchetoeu/jscript/exceptions/EngineException.java +++ b/src/me/topchetoeu/jscript/exceptions/EngineException.java @@ -19,7 +19,7 @@ public class EngineException extends RuntimeException { public final Context ctx; public boolean visible() { - return ctx == null || ctx.environment() == null || !ctx.environment().get(Environment.HIDE_STACK, false); + return ctx == null || !ctx.get(Environment.HIDE_STACK, false); } public String toString() { var res = ""; diff --git a/src/me/topchetoeu/jscript/lib/StringLib.java b/src/me/topchetoeu/jscript/lib/StringLib.java index 232c2f0..620bf1d 100644 --- a/src/me/topchetoeu/jscript/lib/StringLib.java +++ b/src/me/topchetoeu/jscript/lib/StringLib.java @@ -140,8 +140,8 @@ import me.topchetoeu.jscript.interop.NativeGetter; try { var _match = Values.getMember(ctx, term, Symbol.get("Symbol.match")); if (_match instanceof FunctionValue) match = (FunctionValue)_match; - else if (ctx.environment().has(Environment.REGEX_CONSTR)) { - var regex = Values.callNew(ctx, ctx.environment().get(Environment.REGEX_CONSTR), Values.toString(ctx, term), ""); + else if (ctx.has(Environment.REGEX_CONSTR)) { + var regex = Values.callNew(ctx, ctx.get(Environment.REGEX_CONSTR), Values.toString(ctx, term), ""); _match = Values.getMember(ctx, regex, Symbol.get("Symbol.match")); if (_match instanceof FunctionValue) match = (FunctionValue)_match; else throw EngineException.ofError("Regular expressions don't support matching."); @@ -165,8 +165,8 @@ import me.topchetoeu.jscript.interop.NativeGetter; } catch (IllegalArgumentException e) { } - if (match == null && ctx.environment().has(Environment.REGEX_CONSTR)) { - var regex = Values.callNew(ctx, ctx.environment().get(Environment.REGEX_CONSTR), Values.toString(ctx, term), "g"); + if (match == null && ctx.has(Environment.REGEX_CONSTR)) { + var regex = Values.callNew(ctx, ctx.get(Environment.REGEX_CONSTR), Values.toString(ctx, term), "g"); var _match = Values.getMember(ctx, regex, Symbol.get("Symbol.matchAll")); if (_match instanceof FunctionValue) match = (FunctionValue)_match; else throw EngineException.ofError("Regular expressions don't support matching."); From 21534efd60e7ccf2d815599438a0e2b72b2d1cae Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Wed, 27 Dec 2023 13:14:46 +0200 Subject: [PATCH 03/35] feat: readd FS API via new env API --- src/me/topchetoeu/jscript/filesystem/Filesystem.java | 9 +++++++++ src/me/topchetoeu/jscript/lib/FilesystemLib.java | 7 ++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/me/topchetoeu/jscript/filesystem/Filesystem.java b/src/me/topchetoeu/jscript/filesystem/Filesystem.java index c21fb9e..2ac5e75 100644 --- a/src/me/topchetoeu/jscript/filesystem/Filesystem.java +++ b/src/me/topchetoeu/jscript/filesystem/Filesystem.java @@ -1,8 +1,17 @@ package me.topchetoeu.jscript.filesystem; +import me.topchetoeu.jscript.engine.Extensions; +import me.topchetoeu.jscript.engine.values.Symbol; + public interface Filesystem { + public static final Symbol ENV_KEY = Symbol.get("Environment.fs"); + String normalize(String... path); File open(String path, Mode mode) throws FilesystemException; void create(String path, EntryType type) throws FilesystemException; FileStat stat(String path) throws FilesystemException; + + public static Filesystem get(Extensions exts) { + return exts.get(ENV_KEY); + } } \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/lib/FilesystemLib.java b/src/me/topchetoeu/jscript/lib/FilesystemLib.java index 881f5ad..2d3f62e 100644 --- a/src/me/topchetoeu/jscript/lib/FilesystemLib.java +++ b/src/me/topchetoeu/jscript/lib/FilesystemLib.java @@ -25,11 +25,8 @@ public class FilesystemLib { @Native public static final int SEEK_END = 2; private static Filesystem fs(Context ctx) { - var env = ctx.environment(); - if (env != null) { - var fs = ctx.environment().filesystem; - if (fs != null) return fs; - } + var fs = Filesystem.get(ctx); + if (fs != null) return fs; throw EngineException.ofError("Current environment doesn't have a file system."); } From bf38587271e4cd216729990f7560becb2da54125 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Wed, 27 Dec 2023 13:15:19 +0200 Subject: [PATCH 04/35] feat: readd module API via env API --- src/me/topchetoeu/jscript/engine/Context.java | 2 +- .../jscript/engine/debug/DebugContext.java | 4 ++-- src/me/topchetoeu/jscript/lib/Internals.java | 17 ++++++++++------- .../topchetoeu/jscript/modules/ModuleRepo.java | 14 +++++++++++++- 4 files changed, 26 insertions(+), 11 deletions(-) diff --git a/src/me/topchetoeu/jscript/engine/Context.java b/src/me/topchetoeu/jscript/engine/Context.java index bd991a8..4924971 100644 --- a/src/me/topchetoeu/jscript/engine/Context.java +++ b/src/me/topchetoeu/jscript/engine/Context.java @@ -45,7 +45,7 @@ public class Context extends ExtensionStack { var result = Environment.compileFunc(this).call(this, null, raw, filename.toString(), env); var function = (FunctionValue)Values.getMember(this, result, "function"); - if (!engine.debugging) return function; + if (!has(DebugContext.ENV_KEY)) return function; var rawMapChain = ((ArrayValue)Values.getMember(this, result, "mapChain")).toArray(); var breakpoints = new TreeSet<>( diff --git a/src/me/topchetoeu/jscript/engine/debug/DebugContext.java b/src/me/topchetoeu/jscript/engine/debug/DebugContext.java index 6baae19..1906c65 100644 --- a/src/me/topchetoeu/jscript/engine/debug/DebugContext.java +++ b/src/me/topchetoeu/jscript/engine/debug/DebugContext.java @@ -14,7 +14,7 @@ import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.mapping.SourceMap; public class DebugContext implements DebugController { - public static final Symbol DEBUG_CTX = Symbol.get("Engine.debug"); + public static final Symbol ENV_KEY = Symbol.get("Engine.debug"); private HashMap sources; private HashMap> bpts; @@ -90,7 +90,7 @@ public class DebugContext implements DebugController { } public static DebugContext get(Extensions exts) { - if (exts.has(DEBUG_CTX)) return exts.get(DEBUG_CTX); + if (exts.has(ENV_KEY)) return exts.get(ENV_KEY); else return new DebugContext(false); } } diff --git a/src/me/topchetoeu/jscript/lib/Internals.java b/src/me/topchetoeu/jscript/lib/Internals.java index c1582a2..c36df08 100644 --- a/src/me/topchetoeu/jscript/lib/Internals.java +++ b/src/me/topchetoeu/jscript/lib/Internals.java @@ -1,28 +1,31 @@ package me.topchetoeu.jscript.lib; import java.io.IOException; -import java.util.HashMap; import me.topchetoeu.jscript.Buffer; import me.topchetoeu.jscript.Reading; import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.engine.DataKey; import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.scope.GlobalScope; import me.topchetoeu.jscript.engine.values.FunctionValue; -import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.NativeGetter; +import me.topchetoeu.jscript.modules.ModuleRepo; import me.topchetoeu.jscript.parsing.Parsing; public class Internals { @Native public static Object require(Context ctx, String name) { - var env = ctx.environment(); - var res = env.modules.getModule(ctx, env.moduleCwd, name); - res.load(ctx); - return res.value(); + var repo = ModuleRepo.get(ctx); + + if (repo != null) { + var res = repo.getModule(ctx, ModuleRepo.cwd(ctx), name); + res.load(ctx); + return res.value(); + } + + else throw EngineException.ofError("Modules are not supported."); } @Native public static Object log(Context ctx, Object ...args) { diff --git a/src/me/topchetoeu/jscript/modules/ModuleRepo.java b/src/me/topchetoeu/jscript/modules/ModuleRepo.java index 54a98e1..9a14d23 100644 --- a/src/me/topchetoeu/jscript/modules/ModuleRepo.java +++ b/src/me/topchetoeu/jscript/modules/ModuleRepo.java @@ -4,10 +4,15 @@ import java.util.HashMap; import me.topchetoeu.jscript.Filename; import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.Extensions; +import me.topchetoeu.jscript.engine.values.Symbol; import me.topchetoeu.jscript.filesystem.Filesystem; import me.topchetoeu.jscript.filesystem.Mode; public interface ModuleRepo { + public static final Symbol ENV_KEY = Symbol.get("Environment.modules"); + public static final Symbol CWD = Symbol.get("Environment.moduleCwd"); + public Module getModule(Context ctx, String cwd, String name); public static ModuleRepo ofFilesystem(Filesystem fs) { @@ -21,7 +26,7 @@ public interface ModuleRepo { if (modules.containsKey(name)) return modules.get(name); var env = ctx.environment().child(); - env.moduleCwd = fs.normalize(name, ".."); + env.add(CWD, fs.normalize(name, "..")); var mod = new SourceModule(filename, src, env); modules.put(name, mod); @@ -29,4 +34,11 @@ public interface ModuleRepo { return mod; }; } + + public static String cwd(Extensions exts) { + return exts.init(CWD, "/"); + } + public static ModuleRepo get(Extensions exts) { + return exts.get(ENV_KEY); + } } From 153a1a9a492a423e9304b33ff70b6e77d7e7a2b5 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Wed, 27 Dec 2023 13:16:12 +0200 Subject: [PATCH 05/35] feat: readd permissions API via new env API --- .../permissions/PermissionsProvider.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/me/topchetoeu/jscript/permissions/PermissionsProvider.java b/src/me/topchetoeu/jscript/permissions/PermissionsProvider.java index 73c5ee6..352f2c3 100644 --- a/src/me/topchetoeu/jscript/permissions/PermissionsProvider.java +++ b/src/me/topchetoeu/jscript/permissions/PermissionsProvider.java @@ -1,6 +1,11 @@ package me.topchetoeu.jscript.permissions; +import me.topchetoeu.jscript.engine.Extensions; +import me.topchetoeu.jscript.engine.values.Symbol; + public interface PermissionsProvider { + public static final Symbol ENV_KEY = new Symbol("Environment.perms"); + boolean hasPermission(Permission perm, char delim); boolean hasPermission(Permission perm); @@ -10,4 +15,17 @@ public interface PermissionsProvider { default boolean hasPermission(String perm) { return hasPermission(new Permission(perm)); } + + 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); + 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); + else return true; + } + }; + } } \ No newline at end of file From 3343262e724db3941be3abc3d7480d1048fccef8 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Wed, 27 Dec 2023 13:16:21 +0200 Subject: [PATCH 06/35] fix: main now uses new env API --- src/me/topchetoeu/jscript/Main.java | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/me/topchetoeu/jscript/Main.java b/src/me/topchetoeu/jscript/Main.java index 399f5aa..7d12cac 100644 --- a/src/me/topchetoeu/jscript/Main.java +++ b/src/me/topchetoeu/jscript/Main.java @@ -18,11 +18,15 @@ import me.topchetoeu.jscript.events.Observer; import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.InterruptException; import me.topchetoeu.jscript.exceptions.SyntaxException; +import me.topchetoeu.jscript.filesystem.Filesystem; import me.topchetoeu.jscript.filesystem.MemoryFilesystem; import me.topchetoeu.jscript.filesystem.Mode; import me.topchetoeu.jscript.filesystem.PhysicalFilesystem; +import me.topchetoeu.jscript.filesystem.RootFilesystem; import me.topchetoeu.jscript.lib.Internals; import me.topchetoeu.jscript.modules.ModuleRepo; +import me.topchetoeu.jscript.permissions.PermissionsManager; +import me.topchetoeu.jscript.permissions.PermissionsProvider; public class Main { public static class Printer implements Observer { @@ -43,7 +47,7 @@ public class Main { static Thread engineTask, debugTask; static Engine engine = new Engine(true); static DebugServer debugServer = new DebugServer(); - static Environment environment = new Environment(null, null, null); + static Environment environment = new Environment(); static int j = 0; static boolean exited = false; @@ -119,9 +123,13 @@ public class Main { } })); - environment.filesystem.protocols.put("temp", new MemoryFilesystem(Mode.READ_WRITE)); - environment.filesystem.protocols.put("file", new PhysicalFilesystem(".")); - environment.modules.repos.put("file", ModuleRepo.ofFilesystem(environment.filesystem)); + var fs = new RootFilesystem(PermissionsProvider.get(environment)); + fs.protocols.put("temp", new MemoryFilesystem(Mode.READ_WRITE)); + fs.protocols.put("file", new PhysicalFilesystem(".")); + + environment.add(PermissionsProvider.ENV_KEY, PermissionsManager.ALL_PERMS); + environment.add(Filesystem.ENV_KEY, fs); + environment.add(ModuleRepo.ENV_KEY, ModuleRepo.ofFilesystem(fs)); } private static void initEngine() { debugServer.targets.put("target", (ws, req) -> new SimpleDebugger(ws, engine)); @@ -130,11 +138,11 @@ public class Main { } private static void initTypescript() { try { - var tsEnv = Internals.apply(new Environment(null, null, null)); - tsEnv.stackHidden = false; + 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(null, null, null)); - bsEnv.stackHidden = false; + var bsEnv = Internals.apply(new Environment()); + bsEnv.add(Environment.HIDE_STACK, true); engine.pushMsg( false, tsEnv, From 579f09c8379a00cecd7ad54962718c02a159a114 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Wed, 27 Dec 2023 13:28:17 +0200 Subject: [PATCH 07/35] fix: pass arguments to regex constructor in LOAD_REGEX --- src/me/topchetoeu/jscript/engine/frame/Runners.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/me/topchetoeu/jscript/engine/frame/Runners.java b/src/me/topchetoeu/jscript/engine/frame/Runners.java index 51416d7..c9f0bb1 100644 --- a/src/me/topchetoeu/jscript/engine/frame/Runners.java +++ b/src/me/topchetoeu/jscript/engine/frame/Runners.java @@ -199,7 +199,7 @@ public class Runners { } public static Object execLoadRegEx(Context ctx, Instruction instr, CodeFrame frame) { if (ctx.has(Environment.REGEX_CONSTR)) { - frame.push(ctx, Values.callNew(ctx, ctx.get(Environment.REGEX_CONSTR))); + frame.push(ctx, Values.callNew(ctx, ctx.get(Environment.REGEX_CONSTR), instr.get(0), instr.get(1))); } else { throw EngineException.ofSyntax("Regex is not supported."); From aaf9a6fa4589d9a1e87db6c39c6986eca50d2c96 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Wed, 27 Dec 2023 13:58:58 +0200 Subject: [PATCH 08/35] fix: pass environment to compiler via simple environment wrapper --- src/me/topchetoeu/jscript/Main.java | 6 +++- src/me/topchetoeu/jscript/engine/Context.java | 3 +- .../jscript/engine/Environment.java | 6 +--- .../interop/NativeWrapperProvider.java | 8 ++--- .../jscript/lib/EnvironmentLib.java | 33 +++++++++++++++++++ 5 files changed, 45 insertions(+), 11 deletions(-) create mode 100644 src/me/topchetoeu/jscript/lib/EnvironmentLib.java diff --git a/src/me/topchetoeu/jscript/Main.java b/src/me/topchetoeu/jscript/Main.java index 7d12cac..252114f 100644 --- a/src/me/topchetoeu/jscript/Main.java +++ b/src/me/topchetoeu/jscript/Main.java @@ -23,6 +23,7 @@ import me.topchetoeu.jscript.filesystem.MemoryFilesystem; import me.topchetoeu.jscript.filesystem.Mode; import me.topchetoeu.jscript.filesystem.PhysicalFilesystem; import me.topchetoeu.jscript.filesystem.RootFilesystem; +import me.topchetoeu.jscript.lib.EnvironmentLib; import me.topchetoeu.jscript.lib.Internals; import me.topchetoeu.jscript.modules.ModuleRepo; import me.topchetoeu.jscript.permissions.PermissionsManager; @@ -151,10 +152,13 @@ public class Main { ).await(); System.out.println("Loaded typescript!"); + var typescript = tsEnv.global.get(new Context(engine, bsEnv), "ts"); + var libs = new ArrayValue(null, Reading.resourceToString("js/lib.d.ts")); + engine.pushMsg( false, bsEnv, new Filename("jscript", "bootstrap.js"), Reading.resourceToString("js/bootstrap.js"), null, - tsEnv.global.get(new Context(engine, bsEnv), "ts"), environment, new ArrayValue(null, Reading.resourceToString("js/lib.d.ts")) + typescript, new EnvironmentLib(environment), libs ).await(); } catch (EngineException e) { diff --git a/src/me/topchetoeu/jscript/engine/Context.java b/src/me/topchetoeu/jscript/engine/Context.java index 4924971..361d9e2 100644 --- a/src/me/topchetoeu/jscript/engine/Context.java +++ b/src/me/topchetoeu/jscript/engine/Context.java @@ -16,6 +16,7 @@ import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.exceptions.EngineException; +import me.topchetoeu.jscript.lib.EnvironmentLib; import me.topchetoeu.jscript.mapping.SourceMap; public class Context extends ExtensionStack { @@ -42,7 +43,7 @@ public class Context extends ExtensionStack { public FunctionValue compile(Filename filename, String raw) { var env = environment(); - var result = Environment.compileFunc(this).call(this, null, raw, filename.toString(), env); + 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; diff --git a/src/me/topchetoeu/jscript/engine/Environment.java b/src/me/topchetoeu/jscript/engine/Environment.java index 5a58248..a8202f3 100644 --- a/src/me/topchetoeu/jscript/engine/Environment.java +++ b/src/me/topchetoeu/jscript/engine/Environment.java @@ -13,14 +13,12 @@ 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.NativeWrapperProvider; import me.topchetoeu.jscript.parsing.Parsing; // TODO: Remove hardcoded extensions form environment @SuppressWarnings("unchecked") public class Environment implements Extensions { - private static int nextId = 0; public static final HashMap symbols = new HashMap<>(); @@ -48,8 +46,6 @@ public class Environment implements Extensions { public GlobalScope global; public WrappersProvider wrappers; - @Native public int id = ++nextId; - @Override public void add(Symbol key, T obj) { data.put(key, obj); } @@ -73,7 +69,7 @@ public class Environment implements Extensions { var filename = Values.toString(ctx, args[1]); var isDebug = Values.toBoolean(args[2]); - var env = Values.wrapper(args[2], Environment.class); + var env = Values.wrapper(Values.getMember(ctx, args[2], Symbol.get("env")), Environment.class); var res = new ObjectValue(); var target = Parsing.compile(env, Filename.parse(filename), source); diff --git a/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java b/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java index fa7be76..4aa409f 100644 --- a/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java +++ b/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java @@ -43,7 +43,7 @@ public class NativeWrapperProvider implements WrappersProvider { if (get.thisArg() && !member || !get.thisArg() && !memberMatch) continue; Object name = get.value(); - if (((String)name).startsWith("@@")) name = Symbol.get(((String)name).substring(2)); + if (name.toString().startsWith("@@")) name = Symbol.get(name.toString().substring(2)); else if (name.equals("")) name = method.getName(); var prop = target.properties.get(name); @@ -60,7 +60,7 @@ public class NativeWrapperProvider implements WrappersProvider { if (set.thisArg() && !member || !set.thisArg() && !memberMatch) continue; Object name = set.value(); - if (((String)name).startsWith("@@")) name = Symbol.get(((String)name).substring(2)); + if (name.toString().startsWith("@@")) name = Symbol.get(name.toString().substring(2)); else if (name.equals("")) name = method.getName(); var prop = target.properties.get(name); @@ -83,7 +83,7 @@ public class NativeWrapperProvider implements WrappersProvider { if (nat != null) { Object name = nat.value(); - if (((String)name).startsWith("@@")) name = Symbol.get(((String)name).substring(2)); + if (name.toString().startsWith("@@")) name = Symbol.get(name.toString().substring(2)); else if (name.equals("")) name = field.getName(); var getter = OverloadFunction.of("get " + name, Overload.getterFromField(field)); @@ -99,7 +99,7 @@ public class NativeWrapperProvider implements WrappersProvider { if (nat != null) { Object name = nat.value(); - if (((String)name).startsWith("@@")) name = Symbol.get(((String)name).substring(2)); + 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); diff --git a/src/me/topchetoeu/jscript/lib/EnvironmentLib.java b/src/me/topchetoeu/jscript/lib/EnvironmentLib.java new file mode 100644 index 0000000..aa15d7d --- /dev/null +++ b/src/me/topchetoeu/jscript/lib/EnvironmentLib.java @@ -0,0 +1,33 @@ +package me.topchetoeu.jscript.lib; + +import me.topchetoeu.jscript.engine.Environment; +import me.topchetoeu.jscript.engine.values.FunctionValue; +import me.topchetoeu.jscript.engine.values.ObjectValue; +import me.topchetoeu.jscript.interop.Native; +import me.topchetoeu.jscript.interop.NativeGetter; +import me.topchetoeu.jscript.interop.NativeSetter; + +@Native("Environment") +public class EnvironmentLib { + private Environment env; + + @NativeGetter("@@env") public Environment env() { return env; } + + @NativeGetter public int id() { + return env.hashCode(); + } + @NativeGetter public ObjectValue global() { + return env.global.obj; + } + + @NativeGetter public FunctionValue compile() { + return Environment.compileFunc(env); + } + @NativeSetter public void compile(FunctionValue func) { + env.add(Environment.COMPILE_FUNC, func); + } + + public EnvironmentLib(Environment env) { + this.env = env; + } +} From 9ea5cd9277cff2b5f89b8342f18ee49fbdd78c17 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Wed, 27 Dec 2023 14:21:52 +0200 Subject: [PATCH 09/35] refactor: remove old Data API --- src/me/topchetoeu/jscript/engine/Data.java | 56 ------------------- src/me/topchetoeu/jscript/engine/DataKey.java | 3 - 2 files changed, 59 deletions(-) delete mode 100644 src/me/topchetoeu/jscript/engine/Data.java delete mode 100644 src/me/topchetoeu/jscript/engine/DataKey.java diff --git a/src/me/topchetoeu/jscript/engine/Data.java b/src/me/topchetoeu/jscript/engine/Data.java deleted file mode 100644 index 54b78a2..0000000 --- a/src/me/topchetoeu/jscript/engine/Data.java +++ /dev/null @@ -1,56 +0,0 @@ -package me.topchetoeu.jscript.engine; - -import java.util.HashMap; -import java.util.Map; - -@SuppressWarnings("unchecked") -public class Data { - private HashMap, Object> data = new HashMap<>(); - - public Data copy() { - return new Data().addAll(this); - } - - public Data addAll(Map, ?> data) { - for (var el : data.entrySet()) { - get((DataKey)el.getKey(), (Object)el.getValue()); - } - return this; - } - public Data addAll(Data data) { - for (var el : data.data.entrySet()) { - get((DataKey)el.getKey(), (Object)el.getValue()); - } - return this; - } - - public T remove(DataKey key) { - return (T)data.remove(key); - } - public Data set(DataKey key, T val) { - data.put((DataKey)key, (Object)val); - return this; - } - public T get(DataKey key, T val) { - if (data.containsKey(key)) return (T)data.get((DataKey)key); - set(key, val); - return val; - } - public T get(DataKey key) { - if (data.containsKey(key)) return (T)data.get((DataKey)key); - return null; - } - public boolean has(DataKey key) { return data.containsKey(key); } - - public int increase(DataKey key, int n, int start) { - int res; - set(key, res = get(key, start) + n); - return res; - } - public int increase(DataKey key, int n) { - return increase(key, n, 0); - } - public int increase(DataKey key) { - return increase(key, 1, 0); - } -} diff --git a/src/me/topchetoeu/jscript/engine/DataKey.java b/src/me/topchetoeu/jscript/engine/DataKey.java deleted file mode 100644 index 017fa2a..0000000 --- a/src/me/topchetoeu/jscript/engine/DataKey.java +++ /dev/null @@ -1,3 +0,0 @@ -package me.topchetoeu.jscript.engine; - -public class DataKey { } \ No newline at end of file From c0b895e00ac3b320fdc678e5c98f73496af47ca0 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Wed, 27 Dec 2023 14:22:18 +0200 Subject: [PATCH 10/35] feat: greatly improve Context API --- src/me/topchetoeu/jscript/engine/Context.java | 105 +++++++++++------- .../jscript/engine/Environment.java | 2 +- .../jscript/engine/debug/SimpleDebugger.java | 13 +-- .../jscript/engine/frame/CodeFrame.java | 4 +- .../jscript/engine/frame/Runners.java | 14 +-- .../jscript/engine/values/CodeFunction.java | 12 +- .../jscript/engine/values/NativeWrapper.java | 2 +- .../jscript/engine/values/Values.java | 2 +- .../jscript/exceptions/EngineException.java | 4 +- .../jscript/lib/AsyncFunctionLib.java | 5 +- .../jscript/lib/AsyncGeneratorLib.java | 5 +- .../topchetoeu/jscript/lib/GeneratorLib.java | 4 +- src/me/topchetoeu/jscript/lib/Internals.java | 4 +- src/me/topchetoeu/jscript/lib/PromiseLib.java | 10 +- .../jscript/modules/ModuleRepo.java | 2 +- 15 files changed, 98 insertions(+), 90 deletions(-) diff --git a/src/me/topchetoeu/jscript/engine/Context.java b/src/me/topchetoeu/jscript/engine/Context.java index 361d9e2..e5c6138 100644 --- a/src/me/topchetoeu/jscript/engine/Context.java +++ b/src/me/topchetoeu/jscript/engine/Context.java @@ -2,9 +2,8 @@ package me.topchetoeu.jscript.engine; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; +import java.util.Iterator; import java.util.List; -import java.util.Stack; import java.util.TreeSet; import java.util.stream.Collectors; @@ -14,35 +13,44 @@ import me.topchetoeu.jscript.engine.debug.DebugContext; import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.FunctionValue; +import me.topchetoeu.jscript.engine.values.Symbol; import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.lib.EnvironmentLib; import me.topchetoeu.jscript.mapping.SourceMap; -public class Context extends ExtensionStack { - private final Stack env = new Stack<>(); - private final ArrayList frames = new ArrayList<>(); +public class Context implements Extensions { + public final Context parent; + public final Environment environment; + public final CodeFrame frame; public final Engine engine; + public final int stackSize; - @Override - protected Extensions[] extensionStack() { - return new Extensions[] { environment(), engine.globalEnvironment }; + @Override public void add(Symbol key, T obj) { + if (environment != null) environment.add(key, obj); + else if (engine != null) engine.globalEnvironment.add(key, obj); } + @Override public 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); + return null; + } + @Override public boolean has(Symbol key) { + return + environment != null && environment.has(key) || + engine != null && engine.globalEnvironment.has(key); + } + @Override public boolean remove(Symbol key) { + var res = false; - public Environment environment() { - return env.empty() ? null : env.peek(); - } + if (environment != null) res |= environment.remove(key); + else if (engine != null) res |= engine.globalEnvironment.remove(key); - private Context pushEnv(Environment env) { - this.env.push(env); - return this; - } - private void popEnv() { - if (!env.empty()) this.env.pop(); + return res; } public FunctionValue compile(Filename filename, String raw) { - var env = environment(); + 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"); @@ -74,33 +82,37 @@ public class Context extends ExtensionStack { return function; } - public void pushFrame(CodeFrame frame) { - frames.add(frame); - if (frames.size() > engine.maxStackFrames) throw EngineException.ofRange("Stack overflow!"); - pushEnv(frame.function.environment); - DebugContext.get(this).onFramePush(this, frame); - } - public boolean popFrame(CodeFrame frame) { - if (frames.size() == 0) return false; - if (frames.get(frames.size() - 1) != frame) return false; - frames.remove(frames.size() - 1); - popEnv(); - DebugContext.get(this).onFramePop(this, frame); - return true; - } - public CodeFrame peekFrame() { - if (frames.size() == 0) return null; - return frames.get(frames.size() - 1); + public Context pushFrame(CodeFrame frame) { + var res = new Context(this, frame.function.environment, frame, engine, stackSize + 1); + DebugContext.get(res).onFramePush(res, frame); + return res; } - public List frames() { - return Collections.unmodifiableList(frames); + public Iterable frames() { + var self = this; + return () -> new Iterator() { + private Context curr = self; + + private void update() { + while (curr.frame == null && curr != null) curr = curr.parent; + } + + @Override public boolean hasNext() { + update(); + return curr != null; + } + @Override public CodeFrame next() { + update(); + var res = curr.frame; + curr = curr.parent; + return res; + } + }; } public List stackTrace() { var res = new ArrayList(); - for (var i = frames.size() - 1; i >= 0; i--) { - var el = frames.get(i); + for (var el : frames()) { var name = el.function.name; Location loc = null; @@ -120,11 +132,20 @@ public class Context extends ExtensionStack { return res; } - public Context(Engine engine) { + private Context(Context parent, Environment environment, CodeFrame frame, Engine engine, int stackSize) { + this.parent = parent; + this.environment = environment; + this.frame = frame; this.engine = engine; + this.stackSize = stackSize; + + if (engine != null && stackSize > engine.maxStackFrames) throw EngineException.ofRange("Stack overflow!"); + } + + public Context(Engine engine) { + this(null, null, null, engine, 0); } public Context(Engine engine, Environment env) { - this(engine); - if (env != null) this.pushEnv(env); + this(null, env, null, engine, 0); } } diff --git a/src/me/topchetoeu/jscript/engine/Environment.java b/src/me/topchetoeu/jscript/engine/Environment.java index a8202f3..4a0c0a5 100644 --- a/src/me/topchetoeu/jscript/engine/Environment.java +++ b/src/me/topchetoeu/jscript/engine/Environment.java @@ -88,7 +88,7 @@ public class Environment implements Extensions { } public static FunctionValue regexConstructor(Extensions ext) { return ext.init(COMPILE_FUNC, new NativeFunction("RegExp", (ctx, thisArg, args) -> { - throw EngineException.ofError("Regular expressions not supported.").setCtx(ctx.environment(), ctx.engine); + throw EngineException.ofError("Regular expressions not supported.").setCtx(ctx.environment, ctx.engine); })); } diff --git a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java index fdcd510..eb16afd 100644 --- a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java +++ b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java @@ -234,7 +234,7 @@ public class SimpleDebugger implements Debugger { } private synchronized void updateFrames(Context ctx) { - var frame = ctx.peekFrame(); + var frame = ctx.frame; if (frame == null) return; if (!codeFrameToFrame.containsKey(frame)) { @@ -249,10 +249,9 @@ public class SimpleDebugger implements Debugger { } private JSONList serializeFrames(Context ctx) { var res = new JSONList(); - var frames = ctx.frames(); - for (var i = frames.size() - 1; i >= 0; i--) { - var frame = codeFrameToFrame.get(frames.get(i)); + for (var el : ctx.frames()) { + var frame = codeFrameToFrame.get(el); if (frame.location == null) continue; frame.serialized.set("location", serializeLocation(frame.location)); if (frame.location != null) res.add(frame.serialized); @@ -484,7 +483,7 @@ public class SimpleDebugger implements Debugger { env.global = new GlobalScope(codeFrame.local); var ctx = new Context(engine, env); - var awaiter = engine.pushMsg(false, ctx.environment(), new Filename("jscript", "eval"), code, codeFrame.frame.thisArg, codeFrame.frame.args); + var awaiter = engine.pushMsg(false, ctx.environment, new Filename("jscript", "eval"), code, codeFrame.frame.thisArg, codeFrame.frame.args); engine.run(true); @@ -496,7 +495,7 @@ public class SimpleDebugger implements Debugger { var res = new ArrayValue(); var passed = new HashSet(); var tildas = "~"; - if (target == null) target = ctx.environment().global; + if (target == null) target = ctx.environment.global; for (var proto = target; proto != null && proto != Values.NULL; proto = Values.getPrototype(ctx, proto)) { for (var el : Values.getMembers(ctx, proto, true, true)) { @@ -944,7 +943,7 @@ public class SimpleDebugger implements Debugger { try { idToFrame.remove(codeFrameToFrame.remove(frame).id); } catch (NullPointerException e) { } - if (ctx.frames().size() == 0) { + if (ctx.stackSize == 0) { if (state == State.PAUSED_EXCEPTION || state == State.PAUSED_NORMAL) resume(State.RESUMED); } else if (stepOutFrame != null && stepOutFrame.frame == frame && state == State.STEPPING_OUT) { diff --git a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java index bc2d032..9cecef2 100644 --- a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java +++ b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java @@ -94,6 +94,7 @@ public class CodeFrame { public final Object[] args; public final Stack tryStack = new Stack<>(); public final CodeFunction function; + public final Context ctx; public Object[] stack = new Object[32]; public int stackPtr = 0; public int codePtr = 0; @@ -187,7 +188,7 @@ public class CodeFrame { stack[stackPtr++] = Values.normalize(ctx, val); } - public Object next(Context ctx, Object value, Object returnValue, EngineException error) { + public Object next(Object value, Object returnValue, EngineException error) { if (value != Runners.NO_RETURN) push(ctx, value); Instruction instr = null; @@ -314,5 +315,6 @@ public class CodeFrame { this.thisArg = thisArg; this.function = func; + this.ctx = ctx.pushFrame(this); } } diff --git a/src/me/topchetoeu/jscript/engine/frame/Runners.java b/src/me/topchetoeu/jscript/engine/frame/Runners.java index c9f0bb1..7a5d59c 100644 --- a/src/me/topchetoeu/jscript/engine/frame/Runners.java +++ b/src/me/topchetoeu/jscript/engine/frame/Runners.java @@ -50,7 +50,7 @@ public class Runners { public static Object execMakeVar(Context ctx, Instruction instr, CodeFrame frame) { var name = (String)instr.get(0); - ctx.environment().global.define(name); + ctx.environment.global.define(name); frame.codePtr++; return NO_RETURN; } @@ -142,7 +142,7 @@ public class Runners { public static Object execLoadVar(Context ctx, Instruction instr, CodeFrame frame) { var i = instr.get(0); - if (i instanceof String) frame.push(ctx, ctx.environment().global.get(ctx, (String)i)); + if (i instanceof String) frame.push(ctx, ctx.environment.global.get(ctx, (String)i)); else frame.push(ctx, frame.scope.get((int)i).get(ctx)); frame.codePtr++; @@ -154,7 +154,7 @@ public class Runners { return NO_RETURN; } public static Object execLoadGlob(Context ctx, Instruction instr, CodeFrame frame) { - frame.push(ctx, ctx.environment().global.obj); + frame.push(ctx, ctx.environment.global.obj); frame.codePtr++; return NO_RETURN; } @@ -173,7 +173,7 @@ public class Runners { captures[i - 1] = frame.scope.get(instr.get(i)); } - var func = new CodeFunction(ctx.environment(), "", Engine.functions.get(id), captures); + var func = new CodeFunction(ctx.environment, "", Engine.functions.get(id), captures); frame.push(ctx, func); @@ -227,7 +227,7 @@ public class Runners { var val = (boolean)instr.get(1) ? frame.peek() : frame.pop(); var i = instr.get(0); - if (i instanceof String) ctx.environment().global.set(ctx, (String)i, val); + if (i instanceof String) ctx.environment.global.set(ctx, (String)i, val); else frame.scope.get((int)i).set(ctx, val); frame.codePtr++; @@ -274,8 +274,8 @@ public class Runners { Object obj; if (name != null) { - if (ctx.environment().global.has(ctx, name)) { - obj = ctx.environment().global.get(ctx, name); + if (ctx.environment.global.has(ctx, name)) { + obj = ctx.environment.global.get(ctx, name); } else obj = null; } diff --git a/src/me/topchetoeu/jscript/engine/values/CodeFunction.java b/src/me/topchetoeu/jscript/engine/values/CodeFunction.java index d506b81..deaa0de 100644 --- a/src/me/topchetoeu/jscript/engine/values/CodeFunction.java +++ b/src/me/topchetoeu/jscript/engine/values/CodeFunction.java @@ -32,16 +32,10 @@ public class CodeFunction extends FunctionValue { @Override public Object call(Context ctx, Object thisArg, Object ...args) { var frame = new CodeFrame(ctx, thisArg, args, this); - try { - ctx.pushFrame(frame); - while (true) { - var res = frame.next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, null); - if (res != Runners.NO_RETURN) return res; - } - } - finally { - ctx.popFrame(frame); + while (true) { + var res = frame.next(Runners.NO_RETURN, Runners.NO_RETURN, null); + if (res != Runners.NO_RETURN) return res; } } diff --git a/src/me/topchetoeu/jscript/engine/values/NativeWrapper.java b/src/me/topchetoeu/jscript/engine/values/NativeWrapper.java index f0b847c..f76c7bc 100644 --- a/src/me/topchetoeu/jscript/engine/values/NativeWrapper.java +++ b/src/me/topchetoeu/jscript/engine/values/NativeWrapper.java @@ -8,7 +8,7 @@ public class NativeWrapper extends ObjectValue { @Override public ObjectValue getPrototype(Context ctx) { - if (prototype == NATIVE_PROTO) return ctx.environment().wrappers.getProto(wrapped.getClass()); + if (prototype == NATIVE_PROTO) return ctx.environment.wrappers.getProto(wrapped.getClass()); else return super.getPrototype(ctx); } diff --git a/src/me/topchetoeu/jscript/engine/values/Values.java b/src/me/topchetoeu/jscript/engine/values/Values.java index 851bdfb..7107513 100644 --- a/src/me/topchetoeu/jscript/engine/values/Values.java +++ b/src/me/topchetoeu/jscript/engine/values/Values.java @@ -477,7 +477,7 @@ public class Values { if (val instanceof Class) { if (ctx == null) return null; - else return ctx.environment().wrappers.getConstr((Class)val); + else return ctx.environment.wrappers.getConstr((Class)val); } return new NativeWrapper(val); diff --git a/src/me/topchetoeu/jscript/exceptions/EngineException.java b/src/me/topchetoeu/jscript/exceptions/EngineException.java index e5b619f..3b784d1 100644 --- a/src/me/topchetoeu/jscript/exceptions/EngineException.java +++ b/src/me/topchetoeu/jscript/exceptions/EngineException.java @@ -38,7 +38,7 @@ public class EngineException extends RuntimeException { if (function.equals("")) function = null; if (ctx == null) this.ctx = null; - else this.ctx = new Context(ctx.engine, ctx.environment()); + else this.ctx = new Context(ctx.engine, ctx.environment); this.location = location; this.function = function; } @@ -53,7 +53,7 @@ public class EngineException extends RuntimeException { public EngineException add(Context ctx, String name, Location location) { var el = new StackElement(ctx, location, name); if (el.function == null && el.location == null) return this; - setCtx(ctx.environment(), ctx.engine); + setCtx(ctx.environment, ctx.engine); stackTrace.add(el); return this; } diff --git a/src/me/topchetoeu/jscript/lib/AsyncFunctionLib.java b/src/me/topchetoeu/jscript/lib/AsyncFunctionLib.java index 385a9b4..e8b65cb 100644 --- a/src/me/topchetoeu/jscript/lib/AsyncFunctionLib.java +++ b/src/me/topchetoeu/jscript/lib/AsyncFunctionLib.java @@ -20,12 +20,11 @@ import me.topchetoeu.jscript.interop.Native; private void next(Context ctx, Object inducedValue, Object inducedError) { Object res = null; - ctx.pushFrame(frame); awaiting = false; while (!awaiting) { try { - res = frame.next(ctx, inducedValue, Runners.NO_RETURN, inducedError == Runners.NO_RETURN ? null : new EngineException(inducedError)); + res = frame.next(inducedValue, Runners.NO_RETURN, inducedError == Runners.NO_RETURN ? null : new EngineException(inducedError)); inducedValue = inducedError = Runners.NO_RETURN; if (res != Runners.NO_RETURN) { promise.fulfill(ctx, res); @@ -38,8 +37,6 @@ import me.topchetoeu.jscript.interop.Native; } } - ctx.popFrame(frame); - if (awaiting) { PromiseLib.then(ctx, frame.pop(), new NativeFunction(this::fulfill), new NativeFunction(this::reject)); } diff --git a/src/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java b/src/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java index 0a2dd47..23688cc 100644 --- a/src/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java +++ b/src/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java @@ -28,12 +28,11 @@ import me.topchetoeu.jscript.interop.Native; } Object res = null; - ctx.pushFrame(frame); state = 0; while (state == 0) { try { - res = frame.next(ctx, inducedValue, inducedReturn, inducedError == Runners.NO_RETURN ? null : new EngineException(inducedError)); + res = frame.next(inducedValue, inducedReturn, inducedError == Runners.NO_RETURN ? null : new EngineException(inducedError)); inducedValue = inducedReturn = inducedError = Runners.NO_RETURN; if (res != Runners.NO_RETURN) { var obj = new ObjectValue(); @@ -49,8 +48,6 @@ import me.topchetoeu.jscript.interop.Native; } } - ctx.popFrame(frame); - if (state == 1) { PromiseLib.then(ctx, frame.pop(), new NativeFunction(this::fulfill), new NativeFunction(this::reject)); } diff --git a/src/me/topchetoeu/jscript/lib/GeneratorLib.java b/src/me/topchetoeu/jscript/lib/GeneratorLib.java index 9df2175..101f10c 100644 --- a/src/me/topchetoeu/jscript/lib/GeneratorLib.java +++ b/src/me/topchetoeu/jscript/lib/GeneratorLib.java @@ -24,12 +24,11 @@ import me.topchetoeu.jscript.interop.Native; } Object res = null; - ctx.pushFrame(frame); yielding = false; while (!yielding) { try { - res = frame.next(ctx, inducedValue, inducedReturn, inducedError == Runners.NO_RETURN ? null : new EngineException(inducedError)); + res = frame.next(inducedValue, inducedReturn, inducedError == Runners.NO_RETURN ? null : new EngineException(inducedError)); inducedReturn = inducedError = Runners.NO_RETURN; if (res != Runners.NO_RETURN) { done = true; @@ -42,7 +41,6 @@ import me.topchetoeu.jscript.interop.Native; } } - ctx.popFrame(frame); if (done) frame = null; else res = frame.pop(); diff --git a/src/me/topchetoeu/jscript/lib/Internals.java b/src/me/topchetoeu/jscript/lib/Internals.java index c36df08..4ad447e 100644 --- a/src/me/topchetoeu/jscript/lib/Internals.java +++ b/src/me/topchetoeu/jscript/lib/Internals.java @@ -58,7 +58,7 @@ public class Internals { } catch (InterruptedException e) { return; } - ctx.engine.pushMsg(false, ctx.environment(), func, null, args); + ctx.engine.pushMsg(false, ctx.environment, func, null, args); }); thread.start(); @@ -75,7 +75,7 @@ public class Internals { } catch (InterruptedException e) { return; } - ctx.engine.pushMsg(false, ctx.environment(), func, null, args); + ctx.engine.pushMsg(false, ctx.environment, func, null, args); } }); diff --git a/src/me/topchetoeu/jscript/lib/PromiseLib.java b/src/me/topchetoeu/jscript/lib/PromiseLib.java index 66f5191..3adac3a 100644 --- a/src/me/topchetoeu/jscript/lib/PromiseLib.java +++ b/src/me/topchetoeu/jscript/lib/PromiseLib.java @@ -253,7 +253,7 @@ import me.topchetoeu.jscript.interop.Native; this.val = val; this.state = STATE_FULFILLED; - ctx.engine.pushMsg(true, ctx.environment(), new NativeFunction((_ctx, _thisArg, _args) -> { + ctx.engine.pushMsg(true, ctx.environment, new NativeFunction((_ctx, _thisArg, _args) -> { for (var handle : handles) { handle.fulfilled.call(handle.ctx, null, val); } @@ -288,10 +288,10 @@ import me.topchetoeu.jscript.interop.Native; this.val = val; this.state = STATE_REJECTED; - ctx.engine.pushMsg(true, ctx.environment(), new NativeFunction((_ctx, _thisArg, _args) -> { + ctx.engine.pushMsg(true, ctx.environment, new NativeFunction((_ctx, _thisArg, _args) -> { for (var handle : handles) handle.rejected.call(handle.ctx, null, val); if (!handled) { - Values.printError(new EngineException(val).setCtx(ctx.environment(), ctx.engine), "(in promise)"); + Values.printError(new EngineException(val).setCtx(ctx.environment, ctx.engine), "(in promise)"); } handles = null; return null; @@ -305,9 +305,9 @@ import me.topchetoeu.jscript.interop.Native; } private void handle(Context ctx, FunctionValue fulfill, FunctionValue reject) { - if (state == STATE_FULFILLED) ctx.engine.pushMsg(true, ctx.environment(), fulfill, null, val); + if (state == STATE_FULFILLED) ctx.engine.pushMsg(true, ctx.environment, fulfill, null, val); else if (state == STATE_REJECTED) { - ctx.engine.pushMsg(true, ctx.environment(), reject, null, val); + ctx.engine.pushMsg(true, ctx.environment, reject, null, val); handled = true; } else handles.add(new Handle(ctx, fulfill, reject)); diff --git a/src/me/topchetoeu/jscript/modules/ModuleRepo.java b/src/me/topchetoeu/jscript/modules/ModuleRepo.java index 9a14d23..90d1d8d 100644 --- a/src/me/topchetoeu/jscript/modules/ModuleRepo.java +++ b/src/me/topchetoeu/jscript/modules/ModuleRepo.java @@ -25,7 +25,7 @@ public interface ModuleRepo { if (modules.containsKey(name)) return modules.get(name); - var env = ctx.environment().child(); + var env = ctx.environment.child(); env.add(CWD, fs.normalize(name, "..")); var mod = new SourceModule(filename, src, env); From d5fd6e650eed57fd8a7140bd7b24f10a1b3c7447 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Wed, 27 Dec 2023 20:02:45 +0200 Subject: [PATCH 11/35] fix: clean up debugger API --- src/me/topchetoeu/jscript/Main.java | 8 +++- src/me/topchetoeu/jscript/engine/Context.java | 2 +- src/me/topchetoeu/jscript/engine/Engine.java | 4 +- .../jscript/engine/debug/DebugController.java | 2 +- .../jscript/engine/debug/DebugHandler.java | 3 -- .../jscript/engine/debug/DebugServer.java | 11 ++--- .../jscript/engine/debug/Debugger.java | 3 +- .../jscript/engine/debug/SimpleDebugger.java | 48 +++++++++++++------ .../jscript/engine/frame/CodeFrame.java | 7 +++ .../jscript/engine/values/CodeFunction.java | 13 +++-- .../jscript/lib/AsyncFunctionLib.java | 2 + .../jscript/lib/AsyncGeneratorLib.java | 2 + .../topchetoeu/jscript/lib/GeneratorLib.java | 2 + 13 files changed, 72 insertions(+), 35 deletions(-) diff --git a/src/me/topchetoeu/jscript/Main.java b/src/me/topchetoeu/jscript/Main.java index 252114f..afbc58e 100644 --- a/src/me/topchetoeu/jscript/Main.java +++ b/src/me/topchetoeu/jscript/Main.java @@ -8,6 +8,7 @@ import java.nio.file.Path; import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Engine; import me.topchetoeu.jscript.engine.Environment; +import me.topchetoeu.jscript.engine.debug.DebugContext; import me.topchetoeu.jscript.engine.debug.DebugServer; import me.topchetoeu.jscript.engine.debug.SimpleDebugger; import me.topchetoeu.jscript.engine.values.ArrayValue; @@ -46,7 +47,7 @@ public class Main { } static Thread engineTask, debugTask; - static Engine engine = new Engine(true); + static Engine engine = new Engine(); static DebugServer debugServer = new DebugServer(); static Environment environment = new Environment(); @@ -133,7 +134,10 @@ public class Main { environment.add(ModuleRepo.ENV_KEY, ModuleRepo.ofFilesystem(fs)); } private static void initEngine() { - debugServer.targets.put("target", (ws, req) -> new SimpleDebugger(ws, engine)); + var ctx = new DebugContext(); + // engine.globalEnvironment.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); } diff --git a/src/me/topchetoeu/jscript/engine/Context.java b/src/me/topchetoeu/jscript/engine/Context.java index e5c6138..0b08d6d 100644 --- a/src/me/topchetoeu/jscript/engine/Context.java +++ b/src/me/topchetoeu/jscript/engine/Context.java @@ -94,7 +94,7 @@ public class Context implements Extensions { private Context curr = self; private void update() { - while (curr.frame == null && curr != null) curr = curr.parent; + while (curr != null && curr.frame == null) curr = curr.parent; } @Override public boolean hasNext() { diff --git a/src/me/topchetoeu/jscript/engine/Engine.java b/src/me/topchetoeu/jscript/engine/Engine.java index 2a02e2f..9ee6498 100644 --- a/src/me/topchetoeu/jscript/engine/Engine.java +++ b/src/me/topchetoeu/jscript/engine/Engine.java @@ -57,7 +57,6 @@ public class Engine { public final Environment globalEnvironment = new Environment(); public final int id = ++nextId; - public final boolean debugging; public int maxStackFrames = 10000; private Thread thread; @@ -111,7 +110,6 @@ public class Engine { return pushMsg(micro, env, new UncompiledFunction(filename, raw), thisArg, args); } - public Engine(boolean debugging) { - this.debugging = debugging; + public Engine() { } } diff --git a/src/me/topchetoeu/jscript/engine/debug/DebugController.java b/src/me/topchetoeu/jscript/engine/debug/DebugController.java index d03d4f6..f52a7b7 100644 --- a/src/me/topchetoeu/jscript/engine/debug/DebugController.java +++ b/src/me/topchetoeu/jscript/engine/debug/DebugController.java @@ -50,7 +50,7 @@ public interface DebugController { void onFramePop(Context ctx, CodeFrame frame); public static DebugController empty() { - return new DebugController() { + return new DebugController () { @Override public void onFramePop(Context ctx, CodeFrame frame) { } @Override public void onFramePush(Context ctx, CodeFrame frame) { } @Override public boolean onInstruction(Context ctx, CodeFrame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught) { diff --git a/src/me/topchetoeu/jscript/engine/debug/DebugHandler.java b/src/me/topchetoeu/jscript/engine/debug/DebugHandler.java index 1392e10..a2ed59f 100644 --- a/src/me/topchetoeu/jscript/engine/debug/DebugHandler.java +++ b/src/me/topchetoeu/jscript/engine/debug/DebugHandler.java @@ -25,9 +25,6 @@ public interface DebugHandler { void getProperties(V8Message msg); void releaseObjectGroup(V8Message msg); void releaseObject(V8Message msg); - /** - * This method might not execute the actual code for well-known requests - */ void callFunctionOn(V8Message msg); void runtimeEnable(V8Message msg); diff --git a/src/me/topchetoeu/jscript/engine/debug/DebugServer.java b/src/me/topchetoeu/jscript/engine/debug/DebugServer.java index 0884618..18f9899 100644 --- a/src/me/topchetoeu/jscript/engine/debug/DebugServer.java +++ b/src/me/topchetoeu/jscript/engine/debug/DebugServer.java @@ -50,8 +50,6 @@ public class DebugServer { private void handle(WebSocket ws, Debugger debugger) { WebSocketMessage raw; - debugger.connect(); - while ((raw = ws.receive()) != null) { if (raw.type != Type.Text) { ws.send(new V8Error("Expected a text message.")); @@ -72,8 +70,9 @@ public class DebugServer { switch (msg.name) { case "Debugger.enable": connNotifier.next(); - debugger.enable(msg); continue; - case "Debugger.disable": debugger.disable(msg); continue; + debugger.enable(msg); + continue; + case "Debugger.disable": debugger.close(); continue; case "Debugger.setBreakpointByUrl": debugger.setBreakpointByUrl(msg); continue; case "Debugger.removeBreakpoint": debugger.removeBreakpoint(msg); continue; @@ -116,7 +115,7 @@ public class DebugServer { } } - debugger.disconnect(); + debugger.close(); } private void onWsConnect(HttpRequest req, Socket socket, DebuggerProvider debuggerProvider) { var key = req.headers.get("sec-websocket-key"); @@ -151,7 +150,7 @@ public class DebugServer { catch (RuntimeException e) { ws.send(new V8Error(e.getMessage())); } - finally { ws.close(); debugger.disconnect(); } + finally { ws.close(); debugger.close(); } }, "Debug Handler"); } diff --git a/src/me/topchetoeu/jscript/engine/debug/Debugger.java b/src/me/topchetoeu/jscript/engine/debug/Debugger.java index a38e1f7..dcafc83 100644 --- a/src/me/topchetoeu/jscript/engine/debug/Debugger.java +++ b/src/me/topchetoeu/jscript/engine/debug/Debugger.java @@ -1,6 +1,5 @@ package me.topchetoeu.jscript.engine.debug; public interface Debugger extends DebugHandler, DebugController { - void connect(); - void disconnect(); + void close(); } diff --git a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java index eb16afd..d72de6d 100644 --- a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java +++ b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java @@ -174,7 +174,6 @@ public class SimpleDebugger implements Debugger { public State state = State.RESUMED; public final WebSocket ws; - public final Engine target; private ObjectValue emptyObject = new ObjectValue(); @@ -477,7 +476,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(false); + var engine = new Engine(); var env = codeFrame.func.environment.fork(); env.global = new GlobalScope(codeFrame.local); @@ -574,8 +573,36 @@ public class SimpleDebugger implements Debugger { updateNotifier.next(); } @Override public synchronized void disable(V8Message msg) { - enabled = false; + close(); ws.send(msg.respond()); + } + public synchronized void close() { + enabled = false; + execptionType = CatchType.NONE; + state = State.RESUMED; + + idToBptCand.clear(); + + idToBreakpoint.clear(); + locToBreakpoint.clear(); + tmpBreakpts.clear(); + + filenameToId.clear(); + idToSource.clear(); + pendingSources.clear(); + + idToFrame.clear(); + codeFrameToFrame.clear(); + + idToObject.clear(); + objectToId.clear(); + objectGroups.clear(); + + pendingPause = false; + + stepOutFrame = currFrame = null; + stepOutPtr = 0; + updateNotifier.next(); } @@ -952,19 +979,12 @@ public class SimpleDebugger implements Debugger { } } - @Override public synchronized void connect() { - if (!DebugContext.get(target.globalEnvironment).attachDebugger(this)) { - ws.send(new V8Error("A debugger is already attached to this engine.")); - } - } - @Override public synchronized void disconnect() { - DebugContext.get(target.globalEnvironment).detachDebugger(); - enabled = false; - updateNotifier.next(); + public SimpleDebugger attach(DebugContext ctx) { + ctx.attachDebugger(this); + return this; } - public SimpleDebugger(WebSocket ws, Engine target) { + public SimpleDebugger(WebSocket ws) { this.ws = ws; - this.target = target; } } diff --git a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java index 9cecef2..23671cf 100644 --- a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java +++ b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java @@ -303,6 +303,13 @@ public class CodeFrame { return Runners.NO_RETURN; } + public void onPush() { + DebugContext.get(ctx).onFramePush(ctx, this); + } + public void onPop() { + DebugContext.get(ctx.parent).onFramePop(ctx.parent, this); + } + public CodeFrame(Context ctx, Object thisArg, Object[] args, CodeFunction func) { this.args = args.clone(); this.scope = new LocalScope(func.localsN, func.captures); diff --git a/src/me/topchetoeu/jscript/engine/values/CodeFunction.java b/src/me/topchetoeu/jscript/engine/values/CodeFunction.java index deaa0de..ac41137 100644 --- a/src/me/topchetoeu/jscript/engine/values/CodeFunction.java +++ b/src/me/topchetoeu/jscript/engine/values/CodeFunction.java @@ -33,9 +33,16 @@ public class CodeFunction extends FunctionValue { public Object call(Context ctx, Object thisArg, Object ...args) { var frame = new CodeFrame(ctx, thisArg, args, this); - while (true) { - var res = frame.next(Runners.NO_RETURN, Runners.NO_RETURN, null); - if (res != Runners.NO_RETURN) return res; + frame.onPush(); + + try { + while (true) { + var res = frame.next(Runners.NO_RETURN, Runners.NO_RETURN, null); + if (res != Runners.NO_RETURN) return res; + } + } + finally { + frame.onPop(); } } diff --git a/src/me/topchetoeu/jscript/lib/AsyncFunctionLib.java b/src/me/topchetoeu/jscript/lib/AsyncFunctionLib.java index e8b65cb..597c93d 100644 --- a/src/me/topchetoeu/jscript/lib/AsyncFunctionLib.java +++ b/src/me/topchetoeu/jscript/lib/AsyncFunctionLib.java @@ -21,6 +21,7 @@ import me.topchetoeu.jscript.interop.Native; private void next(Context ctx, Object inducedValue, Object inducedError) { Object res = null; + frame.onPush(); awaiting = false; while (!awaiting) { try { @@ -36,6 +37,7 @@ import me.topchetoeu.jscript.interop.Native; break; } } + frame.onPop(); if (awaiting) { PromiseLib.then(ctx, frame.pop(), new NativeFunction(this::fulfill), new NativeFunction(this::reject)); diff --git a/src/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java b/src/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java index 23688cc..53181ca 100644 --- a/src/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java +++ b/src/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java @@ -30,6 +30,7 @@ import me.topchetoeu.jscript.interop.Native; Object res = null; state = 0; + frame.onPush(); while (state == 0) { try { res = frame.next(inducedValue, inducedReturn, inducedError == Runners.NO_RETURN ? null : new EngineException(inducedError)); @@ -47,6 +48,7 @@ import me.topchetoeu.jscript.interop.Native; break; } } + frame.onPop(); if (state == 1) { PromiseLib.then(ctx, frame.pop(), new NativeFunction(this::fulfill), new NativeFunction(this::reject)); diff --git a/src/me/topchetoeu/jscript/lib/GeneratorLib.java b/src/me/topchetoeu/jscript/lib/GeneratorLib.java index 101f10c..a21f828 100644 --- a/src/me/topchetoeu/jscript/lib/GeneratorLib.java +++ b/src/me/topchetoeu/jscript/lib/GeneratorLib.java @@ -26,6 +26,7 @@ import me.topchetoeu.jscript.interop.Native; Object res = null; yielding = false; + frame.onPush(); while (!yielding) { try { res = frame.next(inducedValue, inducedReturn, inducedError == Runners.NO_RETURN ? null : new EngineException(inducedError)); @@ -40,6 +41,7 @@ import me.topchetoeu.jscript.interop.Native; throw e; } } + frame.onPop(); if (done) frame = null; else res = frame.pop(); From d6ee59363fcc445d78bcb8cb1b7a392164635bdd Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Wed, 27 Dec 2023 20:10:11 +0200 Subject: [PATCH 12/35] refactor: remove unneeded event system --- src/me/topchetoeu/jscript/Main.java | 18 +---- .../topchetoeu/jscript/events/Awaitable.java | 38 +++++----- src/me/topchetoeu/jscript/events/Event.java | 49 ------------ .../jscript/events/FinishedException.java | 7 -- src/me/topchetoeu/jscript/events/Handle.java | 5 -- .../topchetoeu/jscript/events/Observable.java | 75 ------------------- .../topchetoeu/jscript/events/Observer.java | 7 -- src/me/topchetoeu/jscript/events/Pipe.java | 59 --------------- .../jscript/events/WarmObservable.java | 46 ------------ 9 files changed, 21 insertions(+), 283 deletions(-) delete mode 100644 src/me/topchetoeu/jscript/events/Event.java delete mode 100644 src/me/topchetoeu/jscript/events/FinishedException.java delete mode 100644 src/me/topchetoeu/jscript/events/Handle.java delete mode 100644 src/me/topchetoeu/jscript/events/Observable.java delete mode 100644 src/me/topchetoeu/jscript/events/Observer.java delete mode 100644 src/me/topchetoeu/jscript/events/Pipe.java delete mode 100644 src/me/topchetoeu/jscript/events/WarmObservable.java diff --git a/src/me/topchetoeu/jscript/Main.java b/src/me/topchetoeu/jscript/Main.java index afbc58e..abcc2a8 100644 --- a/src/me/topchetoeu/jscript/Main.java +++ b/src/me/topchetoeu/jscript/Main.java @@ -15,7 +15,6 @@ import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.NativeFunction; import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.Values; -import me.topchetoeu.jscript.events.Observer; import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.InterruptException; import me.topchetoeu.jscript.exceptions.SyntaxException; @@ -30,22 +29,7 @@ import me.topchetoeu.jscript.modules.ModuleRepo; import me.topchetoeu.jscript.permissions.PermissionsManager; import me.topchetoeu.jscript.permissions.PermissionsProvider; -public class Main { - public static class Printer implements Observer { - public void next(Object data) { - Values.printValue(null, data); - System.out.println(); - } - - public void error(RuntimeException err) { - Values.printError(err, null); - } - - public void finish() { - engineTask.interrupt(); - } - } - +public class Main { static Thread engineTask, debugTask; static Engine engine = new Engine(); static DebugServer debugServer = new DebugServer(); diff --git a/src/me/topchetoeu/jscript/events/Awaitable.java b/src/me/topchetoeu/jscript/events/Awaitable.java index cb0b58a..f72b020 100644 --- a/src/me/topchetoeu/jscript/events/Awaitable.java +++ b/src/me/topchetoeu/jscript/events/Awaitable.java @@ -1,25 +1,27 @@ package me.topchetoeu.jscript.events; -import me.topchetoeu.jscript.exceptions.InterruptException; - public interface Awaitable { - T await() throws FinishedException; + public static interface ResultHandler { + public void onResult(T data); + } + public static interface ErrorHandler { + public void onError(RuntimeException error); + } - default Observable toObservable() { - return sub -> { - var thread = new Thread(() -> { - try { - sub.next(await()); - sub.finish(); - } - catch (InterruptException | FinishedException e) { sub.finish(); } - catch (RuntimeException e) { - sub.error(e); - } - }, "Awaiter"); - thread.start(); + T await(); - return () -> thread.interrupt(); - }; + default void handle(ResultHandler onResult, ErrorHandler onError) { + var thread = new Thread(() -> { + try { + onResult.onResult(await()); + } + catch (RuntimeException e) { + onError.onError(e); + } + }, "Awaiter"); + thread.start(); + } + default void handle(ResultHandler onResult) { + handle(onResult, err -> {}); } } diff --git a/src/me/topchetoeu/jscript/events/Event.java b/src/me/topchetoeu/jscript/events/Event.java deleted file mode 100644 index 225d7ec..0000000 --- a/src/me/topchetoeu/jscript/events/Event.java +++ /dev/null @@ -1,49 +0,0 @@ -package me.topchetoeu.jscript.events; - -import java.util.HashSet; - -public class Event implements Observer, Observable { - private HashSet> handlers = new HashSet<>(); - - public Handle on(Observer handler) { - if (handlers == null) { - handler.finish(); - return () -> {}; - } - - handlers.add(handler); - return () -> { - if (handlers == null) return; - handlers.remove(handler); - }; - } - - public boolean isFinished() { - return handlers == null; - } - - public void next(T value) { - if (handlers == null) throw new IllegalStateException("Cannot use a finished event."); - for (var handler : handlers) { - handler.next(value); - } - } - public void error(RuntimeException value) { - if (handlers == null) throw new IllegalStateException("Cannot use a finished event."); - for (var handler : handlers) { - handler.error(value); - } - - handlers.clear(); - handlers = null; - } - public void finish() { - if (handlers == null) throw new IllegalStateException("Cannot use a finished event."); - for (var handler : handlers) { - handler.finish(); - } - - handlers.clear(); - handlers = null; - } -} diff --git a/src/me/topchetoeu/jscript/events/FinishedException.java b/src/me/topchetoeu/jscript/events/FinishedException.java deleted file mode 100644 index 569aecb..0000000 --- a/src/me/topchetoeu/jscript/events/FinishedException.java +++ /dev/null @@ -1,7 +0,0 @@ -package me.topchetoeu.jscript.events; - -public class FinishedException extends RuntimeException { - public FinishedException() { - super("The observable has ended."); - } -} diff --git a/src/me/topchetoeu/jscript/events/Handle.java b/src/me/topchetoeu/jscript/events/Handle.java deleted file mode 100644 index 4b0edf2..0000000 --- a/src/me/topchetoeu/jscript/events/Handle.java +++ /dev/null @@ -1,5 +0,0 @@ -package me.topchetoeu.jscript.events; - -public interface Handle { - void free(); -} \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/events/Observable.java b/src/me/topchetoeu/jscript/events/Observable.java deleted file mode 100644 index cc72a41..0000000 --- a/src/me/topchetoeu/jscript/events/Observable.java +++ /dev/null @@ -1,75 +0,0 @@ -package me.topchetoeu.jscript.events; - -public interface Observable { - Handle on(Observer val); - - default Handle once(Observer observer) { - // Java is fucking retarded - var unhandler = new Handle[1]; - var shouldUnsub = new boolean[1]; - - unhandler[0] = on(new Observer<>() { - public void next(T data) { - observer.next(data); - if (unhandler[0] == null) shouldUnsub[0] = true; - else unhandler[0].free(); - } - public void error(RuntimeException err) { - observer.error(err); - if (unhandler[0] == null) shouldUnsub[0] = true; - else unhandler[0].free(); - } - public void finish() { - observer.finish(); - if (unhandler[0] == null) shouldUnsub[0] = true; - else unhandler[0].free(); - } - }); - - if (shouldUnsub[0]) { - unhandler[0].free(); - return () -> {}; - } - else return unhandler[0]; - } - @SuppressWarnings("unchecked") - default Awaitable toAwaitable() { - return () -> { - var notifier = new Notifier(); - var valRef = new Object[1]; - var isErrRef = new boolean[1]; - - once(new Observer<>() { - public void next(T data) { - valRef[0] = data; - notifier.next(); - } - public void error(RuntimeException err) { - isErrRef[0] = true; - valRef[0] = err; - notifier.next(); - } - public void finish() { - isErrRef[0] = true; - valRef[0] = new FinishedException(); - notifier.next(); - } - }); - - notifier.await(); - - if (isErrRef[0]) throw (RuntimeException)valRef[0]; - else return (T)valRef[0]; - }; - } - default Observable encapsulate() { - return val -> on(val); - } - - default Observable pipe(Pipe pipe) { - return sub -> on(pipe.apply(sub)); - } - default WarmObservable warmUp() { - return new WarmObservable<>(this); - } -} diff --git a/src/me/topchetoeu/jscript/events/Observer.java b/src/me/topchetoeu/jscript/events/Observer.java deleted file mode 100644 index a687c05..0000000 --- a/src/me/topchetoeu/jscript/events/Observer.java +++ /dev/null @@ -1,7 +0,0 @@ -package me.topchetoeu.jscript.events; - -public interface Observer { - public void next(T data); - public default void error(RuntimeException err) {} - public default void finish() { } -} \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/events/Pipe.java b/src/me/topchetoeu/jscript/events/Pipe.java deleted file mode 100644 index 3199142..0000000 --- a/src/me/topchetoeu/jscript/events/Pipe.java +++ /dev/null @@ -1,59 +0,0 @@ -package me.topchetoeu.jscript.events; - -public interface Pipe { - Observer apply(Observer obs); - // void next(T val, Observer target); - // default void error(RuntimeException err, Observer target) { - // target.error(err); - // } - // default void finish(Observer target) { - // target.finish(); - // } - - public static interface MapFunc { - T2 map(T1 val); - } - - public static Pipe map(MapFunc func) { - return o -> val -> o.next(func.map(val)); - } - public static Pipe filter(MapFunc func) { - return o -> val -> { - if (func.map(val)) o.next(val); - }; - } - public static Pipe skip(int n) { - var i = new int[1]; - - return target -> val -> { - if (i[0] >= n) target.next(val); - else i[0]++; - }; - } - public static Pipe limit(int n) { - return target -> new Observer() { - private int i; - - public void next(T val) { - if (i >= n) target.finish(); - else { - target.next(val); - i++; - } - } - public void error(RuntimeException err) { - if (i < n) target.error(err); - } - public void finish() { - if (i < n) target.finish(); - } - }; - } - public static Pipe first() { - return limit(1); - } - - public static Pipe, T> merge() { - return target -> val -> val.on(target); - } -} \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/events/WarmObservable.java b/src/me/topchetoeu/jscript/events/WarmObservable.java deleted file mode 100644 index 1d98cd7..0000000 --- a/src/me/topchetoeu/jscript/events/WarmObservable.java +++ /dev/null @@ -1,46 +0,0 @@ -package me.topchetoeu.jscript.events; - -import java.util.HashSet; - -public class WarmObservable implements Observable, Handle { - private HashSet> observers = new HashSet<>(); - private Handle handle; - - @Override - public Handle on(Observer val) { - if (observers == null) return () -> {}; - observers.add(val); - return () -> observers.remove(val); - } - - @Override - public void free() { - if (observers == null) return; - handle.free(); - handle = null; - observers = null; - } - - public WarmObservable(Observable observable) { - observable.on(new Observer<>() { - public void next(T data) { - for (var obs : observers) obs.next(data); - } - public void error(RuntimeException err) { - for (var obs : observers) obs.error(err); - handle = null; - observers = null; - } - public void finish() { - for (var obs : observers) obs.finish(); - handle = null; - observers = null; - } - }); - } - - @Override - public WarmObservable warmUp() { - return this; - } -} From c36a0db8604dc40410de9bed6209a8a167d41ac6 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Wed, 27 Dec 2023 20:18:23 +0200 Subject: [PATCH 13/35] refactor: remove more dead code --- src/me/topchetoeu/jscript/MessageReceiver.java | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 src/me/topchetoeu/jscript/MessageReceiver.java diff --git a/src/me/topchetoeu/jscript/MessageReceiver.java b/src/me/topchetoeu/jscript/MessageReceiver.java deleted file mode 100644 index dcf6057..0000000 --- a/src/me/topchetoeu/jscript/MessageReceiver.java +++ /dev/null @@ -1,6 +0,0 @@ -package me.topchetoeu.jscript; - -public interface MessageReceiver { - void sendMessage(String msg); - void sendError(String msg); -} \ No newline at end of file From e372941e997587fea872ea6f8d5c38abecc3c11b Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Wed, 27 Dec 2023 20:18:41 +0200 Subject: [PATCH 14/35] refactor: generalize Reading class --- src/me/topchetoeu/jscript/Main.java | 8 ++++---- src/me/topchetoeu/jscript/Reading.java | 4 ++-- src/me/topchetoeu/jscript/engine/debug/DebugServer.java | 6 +++--- src/me/topchetoeu/jscript/lib/Internals.java | 2 +- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/me/topchetoeu/jscript/Main.java b/src/me/topchetoeu/jscript/Main.java index abcc2a8..a1aa22b 100644 --- a/src/me/topchetoeu/jscript/Main.java +++ b/src/me/topchetoeu/jscript/Main.java @@ -60,7 +60,7 @@ public class Main { } for (var i = 0; ; i++) { try { - var raw = Reading.read(); + var raw = Reading.readline(); if (raw == null) break; var res = engine.pushMsg( @@ -136,16 +136,16 @@ public class Main { engine.pushMsg( false, tsEnv, new Filename("jscript", "ts.js"), - Reading.resourceToString("js/ts.js"), null + Reading.resourceToString("assets/js/ts.js"), null ).await(); System.out.println("Loaded typescript!"); var typescript = tsEnv.global.get(new Context(engine, bsEnv), "ts"); - var libs = new ArrayValue(null, Reading.resourceToString("js/lib.d.ts")); + var libs = new ArrayValue(null, Reading.resourceToString("assets/js/lib.d.ts")); engine.pushMsg( false, bsEnv, - new Filename("jscript", "bootstrap.js"), Reading.resourceToString("js/bootstrap.js"), null, + new Filename("jscript", "bootstrap.js"), Reading.resourceToString("assets/js/bootstrap.js"), null, typescript, new EnvironmentLib(environment), libs ).await(); } diff --git a/src/me/topchetoeu/jscript/Reading.java b/src/me/topchetoeu/jscript/Reading.java index c076f53..7665ffb 100644 --- a/src/me/topchetoeu/jscript/Reading.java +++ b/src/me/topchetoeu/jscript/Reading.java @@ -10,7 +10,7 @@ import me.topchetoeu.jscript.exceptions.UncheckedException; public class Reading { private static final BufferedReader reader = new BufferedReader(new InputStreamReader(System.in)); - public static synchronized String read() throws IOException { + public static synchronized String readline() throws IOException { return reader.readLine(); } @@ -19,7 +19,7 @@ public class Reading { catch (Throwable e) { throw new UncheckedException(e); } } public static InputStream resourceToStream(String name) { - return Reading.class.getResourceAsStream("/assets/" + name); + return Reading.class.getResourceAsStream("/" + name); } public static String resourceToString(String name) { return streamToString(resourceToStream(name)); diff --git a/src/me/topchetoeu/jscript/engine/debug/DebugServer.java b/src/me/topchetoeu/jscript/engine/debug/DebugServer.java index 18f9899..65247c7 100644 --- a/src/me/topchetoeu/jscript/engine/debug/DebugServer.java +++ b/src/me/topchetoeu/jscript/engine/debug/DebugServer.java @@ -231,9 +231,9 @@ public class DebugServer { public DebugServer() { try { - this.favicon = Reading.resourceToStream("debugger/favicon.png").readAllBytes(); - this.protocol = Reading.resourceToStream("debugger/protocol.json").readAllBytes(); - this.index = Reading.resourceToString("debugger/index.html") + this.favicon = Reading.resourceToStream("assets/debugger/favicon.png").readAllBytes(); + this.protocol = Reading.resourceToStream("assets/debugger/protocol.json").readAllBytes(); + this.index = Reading.resourceToString("assets/debugger/index.html") .replace("${NAME}", Metadata.name()) .replace("${VERSION}", Metadata.version()) .replace("${AUTHOR}", Metadata.author()) diff --git a/src/me/topchetoeu/jscript/lib/Internals.java b/src/me/topchetoeu/jscript/lib/Internals.java index 4ad447e..03116e8 100644 --- a/src/me/topchetoeu/jscript/lib/Internals.java +++ b/src/me/topchetoeu/jscript/lib/Internals.java @@ -40,7 +40,7 @@ public class Internals { } @Native public static String readline(Context ctx) { try { - return Reading.read(); + return Reading.readline(); } catch (IOException e) { e.printStackTrace(); From 978ee8db795125b6b0d4717506e6242a6c188918 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Thu, 28 Dec 2023 16:55:57 +0200 Subject: [PATCH 15/35] feat: make better native wrapper API --- src/me/topchetoeu/jscript/Main.java | 10 +- .../jscript/engine/Environment.java | 28 +- .../jscript/engine/scope/GlobalScope.java | 4 +- .../jscript/engine/values/NativeFunction.java | 5 +- .../jscript/engine/values/Values.java | 15 +- .../topchetoeu/jscript/interop/Arguments.java | 95 ++++++ .../{NativeSetter.java => Expose.java} | 5 +- ...onstructor.java => ExposeConstructor.java} | 5 +- .../jscript/interop/ExposeTarget.java | 28 ++ .../jscript/interop/ExposeType.java | 9 + .../topchetoeu/jscript/interop/InitType.java | 7 - src/me/topchetoeu/jscript/interop/Native.java | 13 - .../interop/NativeWrapperProvider.java | 295 +++++++++--------- .../{NativeInit.java => OnWrapperInit.java} | 4 +- .../topchetoeu/jscript/interop/Overload.java | 70 ----- .../jscript/interop/OverloadFunction.java | 127 -------- .../{NativeGetter.java => WrapperName.java} | 7 +- 17 files changed, 323 insertions(+), 404 deletions(-) create mode 100644 src/me/topchetoeu/jscript/interop/Arguments.java rename src/me/topchetoeu/jscript/interop/{NativeSetter.java => Expose.java} (68%) rename src/me/topchetoeu/jscript/interop/{NativeConstructor.java => ExposeConstructor.java} (76%) create mode 100644 src/me/topchetoeu/jscript/interop/ExposeTarget.java create mode 100644 src/me/topchetoeu/jscript/interop/ExposeType.java delete mode 100644 src/me/topchetoeu/jscript/interop/InitType.java delete mode 100644 src/me/topchetoeu/jscript/interop/Native.java rename src/me/topchetoeu/jscript/interop/{NativeInit.java => OnWrapperInit.java} (83%) delete mode 100644 src/me/topchetoeu/jscript/interop/Overload.java delete mode 100644 src/me/topchetoeu/jscript/interop/OverloadFunction.java rename src/me/topchetoeu/jscript/interop/{NativeGetter.java => WrapperName.java} (62%) diff --git a/src/me/topchetoeu/jscript/Main.java b/src/me/topchetoeu/jscript/Main.java index a1aa22b..cfa303b 100644 --- a/src/me/topchetoeu/jscript/Main.java +++ b/src/me/topchetoeu/jscript/Main.java @@ -94,15 +94,15 @@ public class Main { private static void initEnv() { environment = Internals.apply(environment); - environment.global.define(false, new NativeFunction("exit", (_ctx, th, args) -> { + environment.global.define(false, new NativeFunction("exit", args -> { exited = true; throw new InterruptException(); })); - environment.global.define(false, new NativeFunction("go", (_ctx, th, args) -> { + environment.global.define(false, new NativeFunction("go", args -> { try { var f = Path.of("do.js"); - var func = _ctx.compile(new Filename("do", "do/" + j++ + ".js"), new String(Files.readAllBytes(f))); - return func.call(_ctx); + var func = args.ctx.compile(new Filename("do", "do/" + j++ + ".js"), new String(Files.readAllBytes(f))); + return func.call(args.ctx); } catch (IOException e) { throw new EngineException("Couldn't open do.js"); @@ -119,7 +119,7 @@ public class Main { } private static void initEngine() { 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)); engineTask = engine.start(); diff --git a/src/me/topchetoeu/jscript/engine/Environment.java b/src/me/topchetoeu/jscript/engine/Environment.java index 4a0c0a5..a8c9cf5 100644 --- a/src/me/topchetoeu/jscript/engine/Environment.java +++ b/src/me/topchetoeu/jscript/engine/Environment.java @@ -5,6 +5,7 @@ import java.util.stream.Collectors; import me.topchetoeu.jscript.Filename; import me.topchetoeu.jscript.Location; +import me.topchetoeu.jscript.engine.debug.DebugContext; import me.topchetoeu.jscript.engine.scope.GlobalScope; import me.topchetoeu.jscript.engine.values.ArrayValue; 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.parsing.Parsing; -// TODO: Remove hardcoded extensions form environment @SuppressWarnings("unchecked") public class Environment implements Extensions { @@ -64,31 +64,31 @@ public class Environment implements Extensions { } public static FunctionValue compileFunc(Extensions ext) { - return ext.init(COMPILE_FUNC, new NativeFunction("compile", (ctx, thisArg, args) -> { - var source = Values.toString(ctx, args[0]); - var filename = Values.toString(ctx, args[1]); - var isDebug = Values.toBoolean(args[2]); - - var env = Values.wrapper(Values.getMember(ctx, args[2], Symbol.get("env")), Environment.class); + 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 res = new ObjectValue(); var target = Parsing.compile(env, Filename.parse(filename), source); Engine.functions.putAll(target.functions); Engine.functions.remove(0l); - res.defineProperty(ctx, "function", target.func(env)); - res.defineProperty(ctx, "mapChain", new ArrayValue()); + res.defineProperty(args.ctx, "function", target.func(env)); + res.defineProperty(args.ctx, "mapChain", new ArrayValue()); - if (isDebug) { - res.defineProperty(ctx, "breakpoints", ArrayValue.of(ctx, target.breakpoints.stream().map(Location::toString).collect(Collectors.toList()))); - } + if (isDebug) res.defineProperty( + args.ctx, "breakpoints", + ArrayValue.of(args.ctx, target.breakpoints.stream().map(Location::toString).collect(Collectors.toList())) + ); return res; })); } public static FunctionValue regexConstructor(Extensions ext) { - return ext.init(COMPILE_FUNC, new NativeFunction("RegExp", (ctx, thisArg, args) -> { - throw EngineException.ofError("Regular expressions not supported.").setCtx(ctx.environment, ctx.engine); + return ext.init(COMPILE_FUNC, new NativeFunction("RegExp", args -> { + throw EngineException.ofError("Regular expressions not supported.").setCtx(args.ctx.environment, args.ctx.engine); })); } diff --git a/src/me/topchetoeu/jscript/engine/scope/GlobalScope.java b/src/me/topchetoeu/jscript/engine/scope/GlobalScope.java index 3590362..4d78e3a 100644 --- a/src/me/topchetoeu/jscript/engine/scope/GlobalScope.java +++ b/src/me/topchetoeu/jscript/engine/scope/GlobalScope.java @@ -35,8 +35,8 @@ public class GlobalScope implements ScopeRecord { } public void define(String name, Variable val) { obj.defineProperty(null, name, - new NativeFunction("get " + name, (ctx, th, a) -> val.get(ctx)), - new NativeFunction("set " + name, (ctx, th, args) -> { val.set(ctx, args.length > 0 ? args[0] : null); return null; }), + new NativeFunction("get " + name, args -> val.get(args.ctx)), + new NativeFunction("set " + name, args -> { val.set(args.ctx, args.get(0)); return null; }), true, true ); } diff --git a/src/me/topchetoeu/jscript/engine/values/NativeFunction.java b/src/me/topchetoeu/jscript/engine/values/NativeFunction.java index 64bd502..f2cf2a1 100644 --- a/src/me/topchetoeu/jscript/engine/values/NativeFunction.java +++ b/src/me/topchetoeu/jscript/engine/values/NativeFunction.java @@ -1,17 +1,18 @@ package me.topchetoeu.jscript.engine.values; import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.interop.Arguments; public class NativeFunction extends FunctionValue { public static interface NativeFunctionRunner { - Object run(Context ctx, Object thisArg, Object[] args); + Object run(Arguments args); } public final NativeFunctionRunner action; @Override 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) { diff --git a/src/me/topchetoeu/jscript/engine/values/Values.java b/src/me/topchetoeu/jscript/engine/values/Values.java index 7107513..4e94819 100644 --- a/src/me/topchetoeu/jscript/engine/values/Values.java +++ b/src/me/topchetoeu/jscript/engine/values/Values.java @@ -523,6 +523,9 @@ 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); @@ -606,15 +609,15 @@ public class Values { try { 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) { } - 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)); else { var obj = new ObjectValue(); - obj.defineProperty(_ctx, "value", it.next()); + obj.defineProperty(args.ctx, "value", it.next()); return obj; } })); @@ -631,16 +634,16 @@ public class Values { try { 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) { } - res.defineProperty(ctx, "next", new NativeFunction("", (_ctx, _th, _args) -> { + res.defineProperty(ctx, "next", new NativeFunction("", args -> { return PromiseLib.await(ctx, () -> { if (!it.hasNext()) return new ObjectValue(ctx, Map.of("done", true)); else { var obj = new ObjectValue(); - obj.defineProperty(_ctx, "value", it.next()); + obj.defineProperty(args.ctx, "value", it.next()); return obj; } }); diff --git a/src/me/topchetoeu/jscript/interop/Arguments.java b/src/me/topchetoeu/jscript/interop/Arguments.java new file mode 100644 index 0000000..796e96a --- /dev/null +++ b/src/me/topchetoeu/jscript/interop/Arguments.java @@ -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 get(int i, Class 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[] slice(int i, Class 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; + } +} diff --git a/src/me/topchetoeu/jscript/interop/NativeSetter.java b/src/me/topchetoeu/jscript/interop/Expose.java similarity index 68% rename from src/me/topchetoeu/jscript/interop/NativeSetter.java rename to src/me/topchetoeu/jscript/interop/Expose.java index f74c7a7..c9cf82b 100644 --- a/src/me/topchetoeu/jscript/interop/NativeSetter.java +++ b/src/me/topchetoeu/jscript/interop/Expose.java @@ -7,7 +7,8 @@ import java.lang.annotation.Target; @Target({ ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) -public @interface NativeSetter { +public @interface Expose { public String value() default ""; - public boolean thisArg() default false; + public ExposeType type() default ExposeType.METHOD; + public ExposeTarget target() default ExposeTarget.MEMBER; } diff --git a/src/me/topchetoeu/jscript/interop/NativeConstructor.java b/src/me/topchetoeu/jscript/interop/ExposeConstructor.java similarity index 76% rename from src/me/topchetoeu/jscript/interop/NativeConstructor.java rename to src/me/topchetoeu/jscript/interop/ExposeConstructor.java index e9aa462..8c9d8fa 100644 --- a/src/me/topchetoeu/jscript/interop/NativeConstructor.java +++ b/src/me/topchetoeu/jscript/interop/ExposeConstructor.java @@ -7,7 +7,4 @@ import java.lang.annotation.Target; @Target({ ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) -public @interface NativeConstructor { - public boolean thisArg() default false; -} - +public @interface ExposeConstructor { } diff --git a/src/me/topchetoeu/jscript/interop/ExposeTarget.java b/src/me/topchetoeu/jscript/interop/ExposeTarget.java new file mode 100644 index 0000000..66f2e32 --- /dev/null +++ b/src/me/topchetoeu/jscript/interop/ExposeTarget.java @@ -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; + } +} \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/interop/ExposeType.java b/src/me/topchetoeu/jscript/interop/ExposeType.java new file mode 100644 index 0000000..1b48f6a --- /dev/null +++ b/src/me/topchetoeu/jscript/interop/ExposeType.java @@ -0,0 +1,9 @@ +package me.topchetoeu.jscript.interop; + +public enum ExposeType { + INIT, + METHOD, + FIELD, + GETTER, + SETTER, +} \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/interop/InitType.java b/src/me/topchetoeu/jscript/interop/InitType.java deleted file mode 100644 index 89793f8..0000000 --- a/src/me/topchetoeu/jscript/interop/InitType.java +++ /dev/null @@ -1,7 +0,0 @@ -package me.topchetoeu.jscript.interop; - -public enum InitType { - CONSTRUCTOR, - PROTOTYPE, - NAMESPACE, -} \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/interop/Native.java b/src/me/topchetoeu/jscript/interop/Native.java deleted file mode 100644 index 0448c2d..0000000 --- a/src/me/topchetoeu/jscript/interop/Native.java +++ /dev/null @@ -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; -} diff --git a/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java b/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java index 4aa409f..7544adf 100644 --- a/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java +++ b/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java @@ -1,16 +1,23 @@ package me.topchetoeu.jscript.interop; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import java.util.Arrays; 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.WrappersProvider; import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.NativeFunction; import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.Symbol; +import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.exceptions.EngineException; -import me.topchetoeu.jscript.exceptions.UncheckedException; +import me.topchetoeu.jscript.exceptions.InterruptException; public class NativeWrapperProvider implements WrappersProvider { private final HashMap, FunctionValue> constructors = new HashMap<>(); @@ -18,125 +25,153 @@ public class NativeWrapperProvider implements WrappersProvider { private final HashMap, ObjectValue> namespaces = new HashMap<>(); private final Environment env; - private static void applyMethods(Environment env, boolean member, ObjectValue target, Class clazz) { - for (var method : clazz.getDeclaredMethods()) { - var nat = method.getAnnotation(Native.class); - var get = method.getAnnotation(NativeGetter.class); - var set = method.getAnnotation(NativeSetter.class); - var memberMatch = !Modifier.isStatic(method.getModifiers()) == member; - - if (nat != null) { - if (nat.thisArg() && !member || !nat.thisArg() && !memberMatch) continue; - - 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())); + private static Object call(Context ctx, String name, Method method, Object thisArg, Object... args) { + try { + var realArgs = new Object[method.getParameterCount()]; + System.arraycopy(args, 0, realArgs, 0, realArgs.length); + if (Modifier.isStatic(method.getModifiers())) thisArg = null; + return Values.normalize(ctx, method.invoke(Values.convert(ctx, thisArg, method.getDeclaringClass()), realArgs)); + } + catch (InvocationTargetException e) { + if (e.getTargetException() instanceof EngineException) { + throw ((EngineException)e.getTargetException()).add(ctx, name, Location.INTERNAL); } - else { - if (get != null) { - if (get.thisArg() && !member || !get.thisArg() && !memberMatch) continue; - - 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 NullPointerException) { + e.printStackTrace(); + throw EngineException.ofType("Unexpected value of 'undefined'.").add(ctx, name, Location.INTERNAL); } + 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) { - for (var field : clazz.getDeclaredFields()) { - if (!Modifier.isStatic(field.getModifiers()) != member) continue; - var nat = field.getAnnotation(Native.class); + private static FunctionValue create(String name, Method method) { + return new NativeFunction(name, args -> call(args.ctx, name, method, args.thisArg, args)); + } + 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) { - Object name = nat.value(); - if (name.toString().startsWith("@@")) name = Symbol.get(name.toString().substring(2)); - else if (name.equals("")) name = field.getName(); - - var getter = OverloadFunction.of("get " + name, Overload.getterFromField(field)); - var setter = OverloadFunction.of("set " + name, Overload.setterFromField(field)); - target.defineProperty(null, name, getter, setter, true, false); + var actual = method.getParameterTypes(); + + boolean failed = actual.length > params.length; + + if (!failed) for (var i = 0; i < actual.length; i++) { + if (!actual[i].isAssignableFrom(params[i])) { + failed = true; + 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) { - for (var cl : clazz.getDeclaredClasses()) { - 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); + private static String getName(Class clazz) { + var classNat = clazz.getAnnotation(WrapperName.class); if (classNat != null && !classNat.value().trim().equals("")) return classNat.value().trim(); else return clazz.getSimpleName(); } + private static void apply(ObjectValue obj, Environment env, ExposeTarget target, Class clazz) { + var getters = new HashMap(); + var setters = new HashMap(); + var props = new HashSet(); + var nonProps = new HashSet(); + + 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. * 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. * @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(); - res.defineProperty(null, Symbol.get("Symbol.typeName"), getName(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); - + apply(res, env, ExposeTarget.PROTOTYPE, clazz); return res; } /** @@ -146,36 +181,15 @@ public class NativeWrapperProvider implements WrappersProvider { * @param clazz The class for which a constructor should be generated */ public static FunctionValue makeConstructor(Environment ctx, Class clazz) { - FunctionValue func = new OverloadFunction(getName(clazz)); + var constr = getConstructor(ctx, clazz); - for (var overload : clazz.getDeclaredConstructors()) { - var nat = overload.getAnnotation(Native.class); - if (nat == null) continue; - ((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); } - } + FunctionValue res = constr == null ? + new NativeFunction(getName(clazz), args -> { throw EngineException.ofError("This constructor is not invokable."); }) : + create(getName(clazz), constr); - if (((OverloadFunction)func).overloads.size() == 0) { - func = new NativeFunction(getName(clazz), (a, b, c) -> { throw EngineException.ofError("This constructor is not invokable."); }); - } + apply(res, ctx, ExposeTarget.CONSTRUCTOR, clazz); - applyMethods(ctx, false, func, clazz); - applyFields(ctx, false, func, clazz); - applyClasses(ctx, false, func, clazz); - - func.special = true; - - return func; + return res; } /** * 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 */ public static ObjectValue makeNamespace(Environment ctx, Class clazz) { - ObjectValue res = new ObjectValue(); - - for (var overload : clazz.getDeclaredMethods()) { - 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); - + var res = new ObjectValue(); + res.defineProperty(null, Symbol.get("Symbol.typeName"), getName(clazz)); + apply(res, ctx, ExposeTarget.NAMESPACE, clazz); return res; } @@ -249,8 +253,7 @@ public class NativeWrapperProvider implements WrappersProvider { return constructors.get(clazz); } - @Override - public WrappersProvider fork(Environment env) { + @Override public WrappersProvider fork(Environment env) { return new NativeWrapperProvider(env); } @@ -263,12 +266,12 @@ public class NativeWrapperProvider implements WrappersProvider { private void initError() { var proto = new ObjectValue(); - proto.defineProperty(null, "message", new NativeFunction("message", (ctx, thisArg, args) -> { - if (thisArg instanceof Throwable) return ((Throwable)thisArg).getMessage(); + proto.defineProperty(null, "message", new NativeFunction("message", args -> { + if (args.thisArg instanceof Throwable) return ((Throwable)args.thisArg).getMessage(); else return null; })); - proto.defineProperty(null, "name", new NativeFunction("name", (ctx, thisArg, args) -> getName(thisArg.getClass()))); - proto.defineProperty(null, "toString", new NativeFunction("toString", (ctx, thisArg, args) -> thisArg.toString())); + proto.defineProperty(null, "name", new NativeFunction("name", args -> getName(args.thisArg.getClass()))); + proto.defineProperty(null, "toString", new NativeFunction("toString", args -> args.thisArg.toString())); var constr = makeConstructor(null, Throwable.class); proto.defineProperty(null, "constructor", constr, true, false, false); diff --git a/src/me/topchetoeu/jscript/interop/NativeInit.java b/src/me/topchetoeu/jscript/interop/OnWrapperInit.java similarity index 83% rename from src/me/topchetoeu/jscript/interop/NativeInit.java rename to src/me/topchetoeu/jscript/interop/OnWrapperInit.java index 66d13fe..ab75db3 100644 --- a/src/me/topchetoeu/jscript/interop/NativeInit.java +++ b/src/me/topchetoeu/jscript/interop/OnWrapperInit.java @@ -7,6 +7,6 @@ import java.lang.annotation.Target; @Target({ ElementType.METHOD }) @Retention(RetentionPolicy.RUNTIME) -public @interface NativeInit { - InitType value(); +public @interface OnWrapperInit { + } diff --git a/src/me/topchetoeu/jscript/interop/Overload.java b/src/me/topchetoeu/jscript/interop/Overload.java deleted file mode 100644 index 34871e0..0000000 --- a/src/me/topchetoeu/jscript/interop/Overload.java +++ /dev/null @@ -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; - } -} \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/interop/OverloadFunction.java b/src/me/topchetoeu/jscript/interop/OverloadFunction.java deleted file mode 100644 index c896d1b..0000000 --- a/src/me/topchetoeu/jscript/interop/OverloadFunction.java +++ /dev/null @@ -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 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); - } -} diff --git a/src/me/topchetoeu/jscript/interop/NativeGetter.java b/src/me/topchetoeu/jscript/interop/WrapperName.java similarity index 62% rename from src/me/topchetoeu/jscript/interop/NativeGetter.java rename to src/me/topchetoeu/jscript/interop/WrapperName.java index d7704c2..8898105 100644 --- a/src/me/topchetoeu/jscript/interop/NativeGetter.java +++ b/src/me/topchetoeu/jscript/interop/WrapperName.java @@ -5,9 +5,8 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -@Target({ ElementType.METHOD }) +@Target({ ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) -public @interface NativeGetter { - public String value() default ""; - public boolean thisArg() default false; +public @interface WrapperName { + String value(); } From a61c6a494e1014e486e954ee00f5fcd1cad7284d Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Thu, 4 Jan 2024 10:02:01 +0200 Subject: [PATCH 16/35] fix: some Argument and Engine API improvements, --- src/me/topchetoeu/jscript/Main.java | 12 +- src/me/topchetoeu/jscript/ResultRunnable.java | 5 + src/me/topchetoeu/jscript/engine/Context.java | 26 +++- src/me/topchetoeu/jscript/engine/Engine.java | 122 +++++------------- .../jscript/engine/Environment.java | 12 +- .../topchetoeu/jscript/engine/EventLoop.java | 81 ++++++++++++ .../topchetoeu/jscript/engine/Extensions.java | 10 ++ .../jscript/engine/debug/DebugContext.java | 6 +- .../jscript/engine/debug/SimpleDebugger.java | 4 +- .../jscript/engine/frame/Runners.java | 2 +- .../jscript/engine/values/Values.java | 12 +- .../jscript/exceptions/EngineException.java | 5 + .../topchetoeu/jscript/interop/Arguments.java | 108 ++++++++++------ .../jscript/interop/ExposeField.java | 13 ++ .../jscript/interop/ExposeType.java | 1 - .../interop/NativeWrapperProvider.java | 99 +++++++++++--- .../permissions/PermissionsProvider.java | 4 +- 17 files changed, 345 insertions(+), 177 deletions(-) create mode 100644 src/me/topchetoeu/jscript/ResultRunnable.java create mode 100644 src/me/topchetoeu/jscript/engine/EventLoop.java create mode 100644 src/me/topchetoeu/jscript/interop/ExposeField.java diff --git a/src/me/topchetoeu/jscript/Main.java b/src/me/topchetoeu/jscript/Main.java index cfa303b..a603117 100644 --- a/src/me/topchetoeu/jscript/Main.java +++ b/src/me/topchetoeu/jscript/Main.java @@ -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() { + var tsEnv = Internals.apply(new Environment()); + var bsEnv = Internals.apply(new Environment()); + 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); 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[]) { diff --git a/src/me/topchetoeu/jscript/ResultRunnable.java b/src/me/topchetoeu/jscript/ResultRunnable.java new file mode 100644 index 0000000..7fa559d --- /dev/null +++ b/src/me/topchetoeu/jscript/ResultRunnable.java @@ -0,0 +1,5 @@ +package me.topchetoeu.jscript; + +public interface ResultRunnable { + T run(); +} diff --git a/src/me/topchetoeu/jscript/engine/Context.java b/src/me/topchetoeu/jscript/engine/Context.java index 0b08d6d..33f3b07 100644 --- a/src/me/topchetoeu/jscript/engine/Context.java +++ b/src/me/topchetoeu/jscript/engine/Context.java @@ -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 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 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 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) { diff --git a/src/me/topchetoeu/jscript/engine/Engine.java b/src/me/topchetoeu/jscript/engine/Engine.java index 9ee6498..847609d 100644 --- a/src/me/topchetoeu/jscript/engine/Engine.java +++ b/src/me/topchetoeu/jscript/engine/Engine.java @@ -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 { - public final FunctionValue func; - public final Object thisArg; - public final Object[] args; - public final DataNotifier 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 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 tasks = new PriorityBlockingQueue<>(); - - private void runTask(Task task) { - try { - task.notifier.next(task.func.call(task.ctx, task.thisArg, task.args)); - } - catch (RuntimeException e) { - if (e instanceof InterruptException) throw e; - task.notifier.error(e); - } + @Override + public void add(Symbol key, T obj) { + this.env.add(key, obj); } - 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 T get(Symbol key) { + return this.env.get(key); + } + @Override + public boolean has(Symbol key) { + return this.env.has(key); + } + @Override + public boolean remove(Symbol key) { + return this.env.remove(key); + } + @Override + public Iterable 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 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 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() { diff --git a/src/me/topchetoeu/jscript/engine/Environment.java b/src/me/topchetoeu/jscript/engine/Environment.java index a8c9cf5..db7252c 100644 --- a/src/me/topchetoeu/jscript/engine/Environment.java +++ b/src/me/topchetoeu/jscript/engine/Environment.java @@ -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 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; } diff --git a/src/me/topchetoeu/jscript/engine/EventLoop.java b/src/me/topchetoeu/jscript/engine/EventLoop.java new file mode 100644 index 0000000..412e264 --- /dev/null +++ b/src/me/topchetoeu/jscript/engine/EventLoop.java @@ -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 { + public final ResultRunnable runnable; + public final DataNotifier 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 tasks = new PriorityBlockingQueue<>(); + private Thread thread; + + @SuppressWarnings("unchecked") + public Awaitable pushMsg(ResultRunnable runnable, boolean micro) { + var msg = new Task(runnable, micro); + tasks.add(msg); + return (Awaitable)msg.notifier; + } + public Awaitable 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; + } +} diff --git a/src/me/topchetoeu/jscript/engine/Extensions.java b/src/me/topchetoeu/jscript/engine/Extensions.java index 83072c9..05d248a 100644 --- a/src/me/topchetoeu/jscript/engine/Extensions.java +++ b/src/me/topchetoeu/jscript/engine/Extensions.java @@ -5,10 +5,15 @@ import me.topchetoeu.jscript.engine.values.Symbol; public interface Extensions { T get(Symbol key); void add(Symbol key, T obj); + Iterable keys(); boolean has(Symbol key); boolean remove(Symbol key); + default boolean hasNotNull(Symbol key) { + return has(key) && get(key) != null; + } + default 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)); + } + } } diff --git a/src/me/topchetoeu/jscript/engine/debug/DebugContext.java b/src/me/topchetoeu/jscript/engine/debug/DebugContext.java index 1906c65..58cac6b 100644 --- a/src/me/topchetoeu/jscript/engine/debug/DebugContext.java +++ b/src/me/topchetoeu/jscript/engine/debug/DebugContext.java @@ -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 sources; private HashMap> 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); } } diff --git a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java index d72de6d..3b16b70 100644 --- a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java +++ b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java @@ -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); diff --git a/src/me/topchetoeu/jscript/engine/frame/Runners.java b/src/me/topchetoeu/jscript/engine/frame/Runners.java index 7a5d59c..1eb1845 100644 --- a/src/me/topchetoeu/jscript/engine/frame/Runners.java +++ b/src/me/topchetoeu/jscript/engine/frame/Runners.java @@ -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 { diff --git a/src/me/topchetoeu/jscript/engine/values/Values.java b/src/me/topchetoeu/jscript/engine/values/Values.java index 4e94819..cdc1244 100644 --- a/src/me/topchetoeu/jscript/engine/values/Values.java +++ b/src/me/topchetoeu/jscript/engine/values/Values.java @@ -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(); diff --git a/src/me/topchetoeu/jscript/exceptions/EngineException.java b/src/me/topchetoeu/jscript/exceptions/EngineException.java index 3b784d1..7ecd386 100644 --- a/src/me/topchetoeu/jscript/exceptions/EngineException.java +++ b/src/me/topchetoeu/jscript/exceptions/EngineException.java @@ -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(); diff --git a/src/me/topchetoeu/jscript/interop/Arguments.java b/src/me/topchetoeu/jscript/interop/Arguments.java index 796e96a..952ec4f 100644 --- a/src/me/topchetoeu/jscript/interop/Arguments.java +++ b/src/me/topchetoeu/jscript/interop/Arguments.java @@ -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 get(int i, Class type) { + public int n() { + return args.length; + } + + public boolean has(int i) { + return i == -1 || i >= 0 && i < args.length; + } + + public T self(Class type) { + return convert(-1, type); + } + public T convert(int i, Class 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[] slice(int i, Class 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[] convert(Class 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; } } diff --git a/src/me/topchetoeu/jscript/interop/ExposeField.java b/src/me/topchetoeu/jscript/interop/ExposeField.java new file mode 100644 index 0000000..57db817 --- /dev/null +++ b/src/me/topchetoeu/jscript/interop/ExposeField.java @@ -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; +} diff --git a/src/me/topchetoeu/jscript/interop/ExposeType.java b/src/me/topchetoeu/jscript/interop/ExposeType.java index 1b48f6a..f7f5a48 100644 --- a/src/me/topchetoeu/jscript/interop/ExposeType.java +++ b/src/me/topchetoeu/jscript/interop/ExposeType.java @@ -3,7 +3,6 @@ package me.topchetoeu.jscript.interop; public enum ExposeType { INIT, METHOD, - FIELD, GETTER, SETTER, } \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java b/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java index 7544adf..0c98107 100644 --- a/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java +++ b/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java @@ -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(); var setters = new HashMap(); @@ -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; @@ -141,6 +154,52 @@ public class NativeWrapperProvider implements WrappersProvider { 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 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); + } + + 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.", @@ -149,7 +208,7 @@ public class NativeWrapperProvider implements WrappersProvider { } } - for (var key : props) obj.defineProperty(null, key, getters.get(key), setters.get(key), true, true); + 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); diff --git a/src/me/topchetoeu/jscript/permissions/PermissionsProvider.java b/src/me/topchetoeu/jscript/permissions/PermissionsProvider.java index 352f2c3..2cf84d7 100644 --- a/src/me/topchetoeu/jscript/permissions/PermissionsProvider.java +++ b/src/me/topchetoeu/jscript/permissions/PermissionsProvider.java @@ -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; } }; From 4fa5f5a8157e704ac2b8b4ec349c571ecf2bb275 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Thu, 4 Jan 2024 10:02:14 +0200 Subject: [PATCH 17/35] feat: use new wrapper API in libs --- build.js | 4 +- src/me/topchetoeu/jscript/lib/ArrayLib.java | 425 +++++++----- .../jscript/lib/AsyncFunctionLib.java | 45 +- .../lib/AsyncGeneratorFunctionLib.java | 5 +- .../jscript/lib/AsyncGeneratorLib.java | 86 ++- src/me/topchetoeu/jscript/lib/BooleanLib.java | 35 +- src/me/topchetoeu/jscript/lib/DateLib.java | 195 +++--- .../topchetoeu/jscript/lib/EncodingLib.java | 83 ++- .../jscript/lib/EnvironmentLib.java | 26 +- src/me/topchetoeu/jscript/lib/ErrorLib.java | 59 +- src/me/topchetoeu/jscript/lib/FileLib.java | 41 +- .../topchetoeu/jscript/lib/FilesystemLib.java | 89 +-- .../topchetoeu/jscript/lib/FunctionLib.java | 60 +- .../jscript/lib/GeneratorFunctionLib.java | 8 +- .../topchetoeu/jscript/lib/GeneratorLib.java | 42 +- src/me/topchetoeu/jscript/lib/Internals.java | 182 +++--- src/me/topchetoeu/jscript/lib/JSONLib.java | 21 +- src/me/topchetoeu/jscript/lib/MapLib.java | 65 +- src/me/topchetoeu/jscript/lib/MathLib.java | 210 ++++-- src/me/topchetoeu/jscript/lib/NumberLib.java | 77 ++- src/me/topchetoeu/jscript/lib/ObjectLib.java | 230 ++++--- src/me/topchetoeu/jscript/lib/PromiseLib.java | 617 +++++++++--------- .../topchetoeu/jscript/lib/RangeErrorLib.java | 28 +- src/me/topchetoeu/jscript/lib/RegExpLib.java | 229 ++++--- src/me/topchetoeu/jscript/lib/SetLib.java | 59 +- src/me/topchetoeu/jscript/lib/StringLib.java | 239 +++---- src/me/topchetoeu/jscript/lib/SymbolLib.java | 82 ++- .../jscript/lib/SyntaxErrorLib.java | 25 +- .../topchetoeu/jscript/lib/TypeErrorLib.java | 27 +- 29 files changed, 1867 insertions(+), 1427 deletions(-) diff --git a/build.js b/build.js index 208d4a1..7586811 100644 --- a/build.js +++ b/build.js @@ -76,9 +76,11 @@ async function downloadTypescript(outFile) { console.log('Minifying typescript...'); - const minified = minify((await fs.readFile('tmp/typescript-es5.js')).toString()); + // const minified = minify((await fs.readFile('tmp/typescript-es5.js')).toString()); + const minified = { code: (await fs.readFile('tmp/typescript-es5.js')).toString() }; if (minified.error) throw minified.error; + // Patch unsupported regex syntax minified.code = minified.code.replaceAll('[-/\\\\^$*+?.()|[\\]{}]', '[-/\\\\^$*+?.()|\\[\\]{}]'); diff --git a/src/me/topchetoeu/jscript/lib/ArrayLib.java b/src/me/topchetoeu/jscript/lib/ArrayLib.java index b9b1065..370fe51 100644 --- a/src/me/topchetoeu/jscript/lib/ArrayLib.java +++ b/src/me/topchetoeu/jscript/lib/ArrayLib.java @@ -3,107 +3,20 @@ package me.topchetoeu.jscript.lib; import java.util.Iterator; import java.util.Stack; -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.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.Values; -import me.topchetoeu.jscript.interop.Native; -import me.topchetoeu.jscript.interop.NativeConstructor; -import me.topchetoeu.jscript.interop.NativeGetter; -import me.topchetoeu.jscript.interop.NativeSetter; - -@Native("Array") public class ArrayLib { - @NativeGetter(thisArg = true) public static int length(Context ctx, ArrayValue thisArg) { - return thisArg.size(); - } - @NativeSetter(thisArg = true) public static void length(Context ctx, ArrayValue thisArg, int len) { - thisArg.setSize(len); - } - - @Native(thisArg = true) public static ObjectValue values(Context ctx, ArrayValue thisArg) { - return Values.toJSIterator(ctx, thisArg); - } - @Native(thisArg = true) public static ObjectValue keys(Context ctx, ArrayValue thisArg) { - return Values.toJSIterator(ctx, () -> new Iterator() { - private int i = 0; - - @Override - public boolean hasNext() { - return i < thisArg.size(); - } - @Override - public Object next() { - if (!hasNext()) return null; - return i++; - } - }); - } - @Native(thisArg = true) public static ObjectValue entries(Context ctx, ArrayValue thisArg) { - return Values.toJSIterator(ctx, () -> new Iterator() { - private int i = 0; - - @Override - public boolean hasNext() { - return i < thisArg.size(); - } - @Override - public Object next() { - if (!hasNext()) return null; - return new ArrayValue(ctx, i, thisArg.get(i++)); - } - }); - } - - @Native(value = "@@Symbol.iterator", thisArg = true) - public static ObjectValue iterator(Context ctx, ArrayValue thisArg) { - return values(ctx, thisArg); - } - @Native(value = "@@Symbol.asyncIterator", thisArg = true) - public static ObjectValue asyncIterator(Context ctx, ArrayValue thisArg) { - return values(ctx, thisArg); - } - - @Native(thisArg = true) public static ArrayValue concat(Context ctx, ArrayValue thisArg, Object ...others) { - // TODO: Fully implement with non-array spreadable objects - var size = thisArg.size(); - - for (int i = 0; i < others.length; i++) { - if (others[i] instanceof ArrayValue) size += ((ArrayValue)others[i]).size(); - else i++; - } - - var res = new ArrayValue(size); - thisArg.copyTo(ctx, res, 0, 0, thisArg.size()); - - for (int i = 0, j = thisArg.size(); i < others.length; i++) { - if (others[i] instanceof ArrayValue) { - int n = ((ArrayValue)others[i]).size(); - ((ArrayValue)others[i]).copyTo(ctx, res, 0, j, n); - j += n; - } - else { - res.set(ctx, j++, others[i]); - } - } - - return res; - } - - @Native(thisArg = true) public static ArrayValue sort(Context ctx, ArrayValue arr, FunctionValue cmp) { - var defaultCmp = new NativeFunction("", (_ctx, thisArg, args) -> { - return Values.toString(ctx, args[0]).compareTo(Values.toString(ctx, args[1])); - }); - arr.sort((a, b) -> { - var res = Values.toNumber(ctx, (cmp == null ? defaultCmp : cmp).call(ctx, null, a, b)); - if (res < 0) return -1; - if (res > 0) return 1; - return 0; - }); - return arr; - } +import me.topchetoeu.jscript.interop.Arguments; +import me.topchetoeu.jscript.interop.Expose; +import me.topchetoeu.jscript.interop.ExposeConstructor; +import me.topchetoeu.jscript.interop.ExposeTarget; +import me.topchetoeu.jscript.interop.ExposeType; +import me.topchetoeu.jscript.interop.WrapperName; +@WrapperName("Array") +public class ArrayLib { private static int normalizeI(int len, int i, boolean clamp) { if (i < 0) i += len; if (clamp) { @@ -113,91 +26,202 @@ import me.topchetoeu.jscript.interop.NativeSetter; return i; } - @Native(thisArg = true) public static ArrayValue fill(Context ctx, ArrayValue arr, Object val, int start, int end) { - start = normalizeI(arr.size(), start, true); - end = normalizeI(arr.size(), end, true); + @Expose(value = "length", type = ExposeType.GETTER) + public static int __getLength(Arguments args) { + return args.self(ArrayValue.class).size(); + } + @Expose(value = "length", type = ExposeType.SETTER) + public static void __setLength(Arguments args) { + args.self(ArrayValue.class).setSize(args.getInt(0)); + } - for (; start < end; start++) { - arr.set(ctx, start, val); + @Expose public static ObjectValue __values(Arguments args) { + return __iterator(args); + } + @Expose public static ObjectValue __keys(Arguments args) { + return Values.toJSIterator(args.ctx, () -> new Iterator() { + private int i = 0; + + @Override + public boolean hasNext() { + return i < args.self(ArrayValue.class).size(); + } + @Override + public Object next() { + if (!hasNext()) return null; + return i++; + } + }); + } + @Expose public static ObjectValue __entries(Arguments args) { + return Values.toJSIterator(args.ctx, () -> new Iterator() { + private int i = 0; + + @Override + public boolean hasNext() { + return i < args.self(ArrayValue.class).size(); + } + @Override + public Object next() { + if (!hasNext()) return null; + return new ArrayValue(args.ctx, i, args.self(ArrayValue.class).get(i++)); + } + }); + } + + @Expose(value = "@@Symbol.iterator") + public static ObjectValue __iterator(Arguments args) { + return Values.toJSIterator(args.ctx, args.self(ArrayValue.class)); + } + @Expose(value = "@@Symbol.asyncIterator") + public static ObjectValue __asyncIterator(Arguments args) { + return Values.toJSAsyncIterator(args.ctx, args.self(ArrayValue.class).iterator()); + } + + @Expose public static ArrayValue __concat(Arguments args) { + // TODO: Fully implement with non-array spreadable objects + var arrs = args.slice(-1); + var size = 0; + + for (int i = 0; i < arrs.n(); i++) { + if (arrs.get(i) instanceof ArrayValue) size += arrs.convert(i, ArrayValue.class).size(); + else i++; } + var res = new ArrayValue(size); + + for (int i = 0, j = 0; i < arrs.n(); i++) { + if (arrs.get(i) instanceof ArrayValue) { + var arrEl = arrs.convert(i, ArrayValue.class); + int n = arrEl.size(); + arrEl.copyTo(args.ctx, res, 0, j, n); + j += n; + } + else { + res.set(args.ctx, j++, arrs.get(i)); + } + } + + return res; + } + @Expose public static ArrayValue __sort(Arguments args) { + var arr = args.self(ArrayValue.class); + var cmp = args.convert(0, FunctionValue.class); + + var defaultCmp = new NativeFunction("", _args -> { + return _args.getString(0).compareTo(_args.getString(1)); + }); + + arr.sort((a, b) -> { + var res = Values.toNumber(args.ctx, (cmp == null ? defaultCmp : cmp).call(args.ctx, null, a, b)); + if (res < 0) return -1; + if (res > 0) return 1; + return 0; + }); + return arr; + } + + @Expose public static ArrayValue __fill(Arguments args) { + var arr = args.self(ArrayValue.class); + var val = args.get(0); + var start = normalizeI(arr.size(), args.getInt(1, 0), true); + var end = normalizeI(arr.size(), args.getInt(2, arr.size()), true); + + for (; start < end; start++) arr.set(args.ctx, start, val); + return arr; } - @Native(thisArg = true) public static ArrayValue fill(Context ctx, ArrayValue arr, Object val, int start) { - return fill(ctx, arr, val, start, arr.size()); - } - @Native(thisArg = true) public static ArrayValue fill(Context ctx, ArrayValue arr, Object val) { - return fill(ctx, arr, val, 0, arr.size()); - } + @Expose public static boolean __every(Arguments args) { + var arr = args.self(ArrayValue.class); + var func = args.convert(0, FunctionValue.class); + var thisArg = args.get(1); - @Native(thisArg = true) public static boolean every(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) { for (var i = 0; i < arr.size(); i++) { - if (!Values.toBoolean(func.call(ctx, thisArg, arr.get(i), i, arr))) return false; + if (!Values.toBoolean(func.call(args.ctx, thisArg, arr.get(i), i, arr))) return false; } return true; } - @Native(thisArg = true) public static boolean some(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) { + @Expose public static boolean __some(Arguments args) { + var arr = args.self(ArrayValue.class); + var func = args.convert(0, FunctionValue.class); + var thisArg = args.get(1); + for (var i = 0; i < arr.size(); i++) { - if (Values.toBoolean(func.call(ctx, thisArg, arr.get(i), i, arr))) return true; + if (Values.toBoolean(func.call(args.ctx, thisArg, arr.get(i), i, arr))) return true; } return false; } - - @Native(thisArg = true) public static ArrayValue filter(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) { + @Expose public static ArrayValue __filter(Arguments args) { + var arr = args.self(ArrayValue.class); + var func = args.convert(0, FunctionValue.class); + var thisArg = args.get(1); var res = new ArrayValue(arr.size()); for (int i = 0, j = 0; i < arr.size(); i++) { - if (arr.has(i) && Values.toBoolean(func.call(ctx, thisArg, arr.get(i), i, arr))) res.set(ctx, j++, arr.get(i)); + if (arr.has(i) && Values.toBoolean(func.call(args.ctx, thisArg, arr.get(i), i, arr))) res.set(args.ctx, j++, arr.get(i)); } return res; } - @Native(thisArg = true) public static ArrayValue map(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) { + @Expose public static ArrayValue __map(Arguments args) { + var arr = args.self(ArrayValue.class); + var func = args.convert(0, FunctionValue.class); + var thisArg = args.get(1); var res = new ArrayValue(arr.size()); + res.setSize(arr.size()); + for (int i = 0, j = 0; i < arr.size(); i++) { - if (arr.has(i)) res.set(ctx, j++, func.call(ctx, thisArg, arr.get(i), i, arr)); + if (arr.has(i)) res.set(args.ctx, j++, func.call(args.ctx, thisArg, arr.get(i), i, arr)); } return res; } - @Native(thisArg = true) public static void forEach(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) { + @Expose public static void __forEach(Arguments args) { + var arr = args.self(ArrayValue.class); + var func = args.convert(0, FunctionValue.class); + var thisArg = args.get(1); + for (int i = 0; i < arr.size(); i++) { - if (arr.has(i)) func.call(ctx, thisArg, arr.get(i), i, arr); + if (arr.has(i)) func.call(args.ctx, thisArg, arr.get(i), i, arr); } } - @Native(thisArg = true) public static Object reduce(Context ctx, ArrayValue arr, FunctionValue func, Object... args) { + @Expose public static Object __reduce(Arguments args) { + var arr = args.self(ArrayValue.class); + var func = args.convert(0, FunctionValue.class); + var res = args.get(1); var i = 0; - var res = arr.get(0); - if (args.length > 0) res = args[0]; - else for (; !arr.has(i) && i < arr.size(); i++) res = arr.get(i); + if (args.n() < 2) for (; !arr.has(i) && i < arr.size(); i++) res = arr.get(i); for (; i < arr.size(); i++) { if (arr.has(i)) { - res = func.call(ctx, null, res, arr.get(i), i, arr); + res = func.call(args.ctx, null, res, arr.get(i), i, arr); } } return res; } - @Native(thisArg = true) public static Object reduceRight(Context ctx, ArrayValue arr, FunctionValue func, Object... args) { + @Expose public static Object __reduceRight(Arguments args) { + var arr = args.self(ArrayValue.class); + var func = args.convert(0, FunctionValue.class); + var res = args.get(1); var i = arr.size(); - var res = arr.get(0); - if (args.length > 0) res = args[0]; - else while (!arr.has(i--) && i >= 0) res = arr.get(i); + if (args.n() < 1) while (!arr.has(i--) && i >= 0) res = arr.get(i); for (; i >= 0; i--) { if (arr.has(i)) { - res = func.call(ctx, null, res, arr.get(i), i, arr); + res = func.call(args.ctx, null, res, arr.get(i), i, arr); } } return res; } - @Native(thisArg = true) public static ArrayValue flat(Context ctx, ArrayValue arr, int depth) { + @Expose public static ArrayValue __flat(Arguments args) { + var arr = args.self(ArrayValue.class); + var depth = args.getInt(0, 1); var res = new ArrayValue(arr.size()); var stack = new Stack(); var depths = new Stack(); @@ -209,127 +233,165 @@ import me.topchetoeu.jscript.interop.NativeSetter; var el = stack.pop(); int d = depths.pop(); - if (d <= depth && el instanceof ArrayValue) { - for (int i = ((ArrayValue)el).size() - 1; i >= 0; i--) { - stack.push(((ArrayValue)el).get(i)); + if ((d == -1 || d < depth) && el instanceof ArrayValue) { + var arrEl = (ArrayValue)el; + for (int i = arrEl.size() - 1; i >= 0; i--) { + if (!arrEl.has(i)) continue; + stack.push(arrEl.get(i)); depths.push(d + 1); } } - else res.set(ctx, depth, arr); + else res.set(args.ctx, res.size(), el); } return res; } - @Native(thisArg = true) public static ArrayValue flatMap(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) { - return flat(ctx, map(ctx, arr, cmp, thisArg), 1); + @Expose public static ArrayValue __flatMap(Arguments args) { + return __flat(new Arguments(args.ctx, __map(args), 1)); } - @Native(thisArg = true) public static Object find(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) { + @Expose public static Object __find(Arguments args) { + var arr = args.self(ArrayValue.class); + var cmp = args.convert(0, FunctionValue.class); + var thisArg = args.get(1); + for (int i = 0; i < arr.size(); i++) { - if (arr.has(i) && Values.toBoolean(cmp.call(ctx, thisArg, arr.get(i), i, arr))) return arr.get(i); + if (arr.has(i) && Values.toBoolean(cmp.call(args.ctx, thisArg, arr.get(i), i, arr))) return arr.get(i); } return null; } - @Native(thisArg = true) public static Object findLast(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) { + @Expose public static Object __findLast(Arguments args) { + var arr = args.self(ArrayValue.class); + var cmp = args.convert(0, FunctionValue.class); + var thisArg = args.get(1); + for (var i = arr.size() - 1; i >= 0; i--) { - if (arr.has(i) && Values.toBoolean(cmp.call(ctx, thisArg, arr.get(i), i, arr))) return arr.get(i); + if (arr.has(i) && Values.toBoolean(cmp.call(args.ctx, thisArg, arr.get(i), i, arr))) return arr.get(i); } return null; } - @Native(thisArg = true) public static int findIndex(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) { + @Expose public static int __findIndex(Arguments args) { + var arr = args.self(ArrayValue.class); + var cmp = args.convert(0, FunctionValue.class); + var thisArg = args.get(1); + for (int i = 0; i < arr.size(); i++) { - if (arr.has(i) && Values.toBoolean(cmp.call(ctx, thisArg, arr.get(i), i, arr))) return i; + if (arr.has(i) && Values.toBoolean(cmp.call(args.ctx, thisArg, arr.get(i), i, arr))) return i; } return -1; } - @Native(thisArg = true) public static int findLastIndex(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) { + @Expose public static int __findLastIndex(Arguments args) { + var arr = args.self(ArrayValue.class); + var cmp = args.convert(0, FunctionValue.class); + var thisArg = args.get(1); + for (var i = arr.size() - 1; i >= 0; i--) { - if (arr.has(i) && Values.toBoolean(cmp.call(ctx, thisArg, arr.get(i), i, arr))) return i; + if (arr.has(i) && Values.toBoolean(cmp.call(args.ctx, thisArg, arr.get(i), i, arr))) return i; } return -1; } - @Native(thisArg = true) public static int indexOf(Context ctx, ArrayValue arr, Object val, int start) { - start = normalizeI(arr.size(), start, true); + @Expose public static int __indexOf(Arguments args) { + var arr = args.self(ArrayValue.class); + var val = args.get(0); + var start = normalizeI(arr.size(), args.getInt(1), true); for (int i = start; i < arr.size(); i++) { - if (Values.strictEquals(ctx, arr.get(i), val)) return i; + if (Values.strictEquals(args.ctx, arr.get(i), val)) return i; } return -1; } - @Native(thisArg = true) public static int lastIndexOf(Context ctx, ArrayValue arr, Object val, int start) { - start = normalizeI(arr.size(), start, true); + @Expose public static int __lastIndexOf(Arguments args) { + var arr = args.self(ArrayValue.class); + var val = args.get(0); + var start = normalizeI(arr.size(), args.getInt(1), true); for (int i = arr.size(); i >= start; i--) { - if (Values.strictEquals(ctx, arr.get(i), val)) return i; + if (Values.strictEquals(args.ctx, arr.get(i), val)) return i; } return -1; } - @Native(thisArg = true) public static boolean includes(Context ctx, ArrayValue arr, Object el, int start) { - return indexOf(ctx, arr, el, start) >= 0; + @Expose public static boolean __includes(Arguments args) { + return __indexOf(args) >= 0; } - @Native(thisArg = true) public static Object pop(Context ctx, ArrayValue arr) { + @Expose public static Object __pop(Arguments args) { + var arr = args.self(ArrayValue.class); if (arr.size() == 0) return null; + var val = arr.get(arr.size() - 1); arr.shrink(1); return val; } - @Native(thisArg = true) public static int push(Context ctx, ArrayValue arr, Object ...values) { - arr.copyFrom(ctx, values, 0, arr.size(), values.length); + @Expose public static int __push(Arguments args) { + var arr = args.self(ArrayValue.class); + var values = args.args; + + arr.copyFrom(args.ctx, values, 0, arr.size(), values.length); return arr.size(); } - @Native(thisArg = true) public static Object shift(Context ctx, ArrayValue arr) { + @Expose public static Object __shift(Arguments args) { + var arr = args.self(ArrayValue.class); + if (arr.size() == 0) return null; var val = arr.get(0); + arr.move(1, 0, arr.size()); arr.shrink(1); return val; } - @Native(thisArg = true) public static int unshift(Context ctx, ArrayValue arr, Object ...values) { + @Expose public static int __unshift(Arguments args) { + var arr = args.self(ArrayValue.class); + var values = args.slice(0).args; + arr.move(0, values.length, arr.size()); - arr.copyFrom(ctx, values, 0, 0, values.length); + arr.copyFrom(args.ctx, values, 0, 0, values.length); return arr.size(); } - @Native(thisArg = true) public static ArrayValue slice(Context ctx, ArrayValue arr, int start, Object _end) { - start = normalizeI(arr.size(), start, true); - int end = normalizeI(arr.size(), (int)(_end == null ? arr.size() : Values.toNumber(ctx, _end)), true); + @Expose public static ArrayValue __slice(Arguments args) { + var arr = args.self(ArrayValue.class); + var start = normalizeI(arr.size(), args.getInt(0), true); + var end = normalizeI(arr.size(), args.getInt(1, arr.size()), true); var res = new ArrayValue(end - start); - arr.copyTo(ctx, res, start, 0, end - start); + arr.copyTo(args.ctx, res, start, 0, end - start); return res; } - @Native(thisArg = true) public static ArrayValue splice(Context ctx, ArrayValue arr, int start, Object _deleteCount, Object ...items) { - start = normalizeI(arr.size(), start, true); - int deleteCount = _deleteCount == null ? arr.size() - 1 : (int)Values.toNumber(ctx, _deleteCount); - deleteCount = normalizeI(arr.size(), deleteCount, true); + @Expose public static ArrayValue __splice(Arguments args) { + var arr = args.self(ArrayValue.class); + var start = normalizeI(arr.size(), args.getInt(0), true); + var deleteCount = normalizeI(arr.size(), args.getInt(1, arr.size()), true); + var items = args.slice(2).args; + if (start + deleteCount >= arr.size()) deleteCount = arr.size() - start; var size = arr.size() - deleteCount + items.length; var res = new ArrayValue(deleteCount); - arr.copyTo(ctx, res, start, 0, deleteCount); + arr.copyTo(args.ctx, res, start, 0, deleteCount); arr.move(start + deleteCount, start + items.length, arr.size() - start - deleteCount); - arr.copyFrom(ctx, items, 0, start, items.length); + arr.copyFrom(args.ctx, items, 0, start, items.length); arr.setSize(size); return res; } - @Native(thisArg = true) public static String toString(Context ctx, ArrayValue arr) { - return join(ctx, arr, ","); + @Expose public static String __toString(Arguments args) { + return __join(new Arguments(args.ctx, args.self, ",")); } - @Native(thisArg = true) public static String join(Context ctx, ArrayValue arr, String sep) { + @Expose public static String __join(Arguments args) { + var arr = args.self(ArrayValue.class); + var sep = args.getString(0); var res = new StringBuilder(); var comma = false; @@ -342,30 +404,33 @@ import me.topchetoeu.jscript.interop.NativeSetter; var el = arr.get(i); if (el == null || el == Values.NULL) continue; - res.append(Values.toString(ctx, el)); + res.append(Values.toString(args.ctx, el)); } return res.toString(); } - @Native public static boolean isArray(Context ctx, Object val) { return val instanceof ArrayValue; } - @Native public static ArrayValue of(Context ctx, Object... args) { - var res = new ArrayValue(args.length); - res.copyFrom(ctx, args, 0, 0, args.length); - return res; + @Expose(target = ExposeTarget.STATIC) + public static boolean __isArray(Arguments args) { + return args.get(0) instanceof ArrayValue; + } + @Expose(target = ExposeTarget.STATIC) + public static ArrayValue __of(Arguments args) { + return new ArrayValue(args.ctx, args.slice(0).args); } - @NativeConstructor public static ArrayValue constructor(Context ctx, Object... args) { + @ExposeConstructor public static ArrayValue __constructor(Arguments args) { ArrayValue res; - if (args.length == 1 && args[0] instanceof Number) { - int len = ((Number)args[0]).intValue(); + if (args.n() == 1 && args.get(0) instanceof Number) { + var len = args.getInt(0); res = new ArrayValue(len); res.setSize(len); } else { - res = new ArrayValue(args.length); - res.copyFrom(ctx, args, 0, 0, args.length); + var val = args.slice(0).args; + res = new ArrayValue(val.length); + res.copyFrom(args.ctx, val, 0, 0, val.length); } return res; diff --git a/src/me/topchetoeu/jscript/lib/AsyncFunctionLib.java b/src/me/topchetoeu/jscript/lib/AsyncFunctionLib.java index 597c93d..b30fd5e 100644 --- a/src/me/topchetoeu/jscript/lib/AsyncFunctionLib.java +++ b/src/me/topchetoeu/jscript/lib/AsyncFunctionLib.java @@ -7,55 +7,60 @@ 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; -import me.topchetoeu.jscript.interop.Native; +import me.topchetoeu.jscript.interop.Arguments; +import me.topchetoeu.jscript.interop.WrapperName; +import me.topchetoeu.jscript.lib.PromiseLib.Handle; -@Native("AsyncFunction") public class AsyncFunctionLib extends FunctionValue { +@WrapperName("AsyncFunction") +public class AsyncFunctionLib extends FunctionValue { public final FunctionValue factory; - public static class AsyncHelper { + private static class AsyncHelper { public PromiseLib promise = new PromiseLib(); public CodeFrame frame; private boolean awaiting = false; - private void next(Context ctx, Object inducedValue, Object inducedError) { + private void next(Context ctx, Object inducedValue, EngineException inducedError) { Object res = null; frame.onPush(); awaiting = false; while (!awaiting) { try { - res = frame.next(inducedValue, Runners.NO_RETURN, inducedError == Runners.NO_RETURN ? null : new EngineException(inducedError)); - inducedValue = inducedError = Runners.NO_RETURN; + res = frame.next(inducedValue, Runners.NO_RETURN, inducedError); + inducedValue = Runners.NO_RETURN; + inducedError = null; + if (res != Runners.NO_RETURN) { promise.fulfill(ctx, res); break; } } catch (EngineException e) { - promise.reject(ctx, e.value); + promise.reject(ctx, e); break; } } frame.onPop(); if (awaiting) { - PromiseLib.then(ctx, frame.pop(), new NativeFunction(this::fulfill), new NativeFunction(this::reject)); + PromiseLib.handle(ctx, frame.pop(), new Handle() { + @Override + public void onFulfil(Object val) { + next(ctx, val, null); + } + @Override + public void onReject(EngineException err) { + next(ctx, Runners.NO_RETURN, err); + } + }); } } - public Object fulfill(Context ctx, Object thisArg, Object ...args) { - next(ctx, args.length > 0 ? args[0] : null, Runners.NO_RETURN); - return null; - } - public Object reject(Context ctx, Object thisArg, Object ...args) { - next(ctx, Runners.NO_RETURN, args.length > 0 ? args[0] : null); - return null; - } - - public Object await(Context ctx, Object thisArg, Object[] args) { + public Object await(Arguments args) { this.awaiting = true; - return args.length > 0 ? args[0] : null; + return args.get(0); } } @@ -65,7 +70,7 @@ import me.topchetoeu.jscript.interop.Native; 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); + handler.next(ctx, Runners.NO_RETURN, null); return handler.promise; } diff --git a/src/me/topchetoeu/jscript/lib/AsyncGeneratorFunctionLib.java b/src/me/topchetoeu/jscript/lib/AsyncGeneratorFunctionLib.java index 490905c..f4bdc46 100644 --- a/src/me/topchetoeu/jscript/lib/AsyncGeneratorFunctionLib.java +++ b/src/me/topchetoeu/jscript/lib/AsyncGeneratorFunctionLib.java @@ -6,9 +6,10 @@ 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; -import me.topchetoeu.jscript.interop.Native; +import me.topchetoeu.jscript.interop.WrapperName; -@Native("AsyncGeneratorFunction") public class AsyncGeneratorFunctionLib extends FunctionValue { +@WrapperName("AsyncGeneratorFunction") +public class AsyncGeneratorFunctionLib extends FunctionValue { public final FunctionValue factory; @Override diff --git a/src/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java b/src/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java index 53181ca..6e957ed 100644 --- a/src/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java +++ b/src/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java @@ -5,21 +5,23 @@ 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.NativeFunction; import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.exceptions.EngineException; -import me.topchetoeu.jscript.interop.Native; +import me.topchetoeu.jscript.interop.Arguments; +import me.topchetoeu.jscript.interop.Expose; +import me.topchetoeu.jscript.interop.WrapperName; +import me.topchetoeu.jscript.lib.PromiseLib.Handle; -@Native("AsyncGenerator") public class AsyncGeneratorLib { - @Native("@@Symbol.typeName") public final String name = "AsyncGenerator"; +@WrapperName("AsyncGenerator") +public class AsyncGeneratorLib { private int state = 0; private boolean done = false; private PromiseLib currPromise; public CodeFrame frame; - private void next(Context ctx, Object inducedValue, Object inducedReturn, Object inducedError) { + private void next(Context ctx, Object inducedValue, Object inducedReturn, EngineException inducedError) { if (done) { - if (inducedError != Runners.NO_RETURN) throw new EngineException(inducedError); + if (inducedError != null) throw inducedError; currPromise.fulfill(ctx, new ObjectValue(ctx, Map.of( "done", true, "value", inducedReturn == Runners.NO_RETURN ? null : inducedReturn @@ -33,8 +35,10 @@ import me.topchetoeu.jscript.interop.Native; frame.onPush(); while (state == 0) { try { - res = frame.next(inducedValue, inducedReturn, inducedError == Runners.NO_RETURN ? null : new EngineException(inducedError)); - inducedValue = inducedReturn = inducedError = Runners.NO_RETURN; + res = frame.next(inducedValue, inducedReturn, inducedError); + inducedValue = inducedReturn = Runners.NO_RETURN; + inducedError = null; + if (res != Runners.NO_RETURN) { var obj = new ObjectValue(); obj.defineProperty(ctx, "done", true); @@ -44,14 +48,21 @@ import me.topchetoeu.jscript.interop.Native; } } catch (EngineException e) { - currPromise.reject(ctx, e.value); + currPromise.reject(ctx, e); break; } } frame.onPop(); if (state == 1) { - PromiseLib.then(ctx, frame.pop(), new NativeFunction(this::fulfill), new NativeFunction(this::reject)); + PromiseLib.handle(ctx, frame.pop(), new Handle() { + @Override public void onFulfil(Object val) { + next(ctx, val, Runners.NO_RETURN, null); + } + @Override public void onReject(EngineException err) { + next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, err); + } + }); } else if (state == 2) { var obj = new ObjectValue(); @@ -68,42 +79,29 @@ import me.topchetoeu.jscript.interop.Native; return "Generator [running]"; } - public Object fulfill(Context ctx, Object thisArg, Object ...args) { - 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) { - next(ctx, Runners.NO_RETURN, args.length > 0 ? args[0] : null, Runners.NO_RETURN); - return null; - } - - @Native - public PromiseLib next(Context ctx, Object ...args) { - this.currPromise = new PromiseLib(); - 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 PromiseLib _throw(Context ctx, Object error) { - this.currPromise = new PromiseLib(); - next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, error); - return this.currPromise; - } - @Native("return") - public PromiseLib _return(Context ctx, Object value) { - this.currPromise = new PromiseLib(); - next(ctx, Runners.NO_RETURN, value, Runners.NO_RETURN); - return this.currPromise; - } - - - public Object await(Context ctx, Object thisArg, Object[] args) { + public Object await(Arguments args) { this.state = 1; - return args.length > 0 ? args[0] : null; + return args.get(0); } - public Object yield(Context ctx, Object thisArg, Object[] args) { + public Object yield(Arguments args) { this.state = 2; - return args.length > 0 ? args[0] : null; + return args.get(0); + } + + @Expose public PromiseLib __next(Arguments args) { + this.currPromise = new PromiseLib(); + if (args.has(0)) next(args.ctx, args.get(0), Runners.NO_RETURN, null); + else next(args.ctx, Runners.NO_RETURN, Runners.NO_RETURN, null); + return this.currPromise; + } + @Expose public PromiseLib __return(Arguments args) { + this.currPromise = new PromiseLib(); + next(args.ctx, Runners.NO_RETURN, args.get(0), null); + return this.currPromise; + } + @Expose public PromiseLib __throw(Arguments args) { + this.currPromise = new PromiseLib(); + next(args.ctx, Runners.NO_RETURN, Runners.NO_RETURN, new EngineException(args.get(0)).setCtx(args.ctx)); + return this.currPromise; } } \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/lib/BooleanLib.java b/src/me/topchetoeu/jscript/lib/BooleanLib.java index 2ac5e0d..0a77408 100644 --- a/src/me/topchetoeu/jscript/lib/BooleanLib.java +++ b/src/me/topchetoeu/jscript/lib/BooleanLib.java @@ -1,30 +1,31 @@ package me.topchetoeu.jscript.lib; -import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.values.ObjectValue; -import me.topchetoeu.jscript.engine.values.Values; -import me.topchetoeu.jscript.interop.Native; -import me.topchetoeu.jscript.interop.NativeConstructor; +import me.topchetoeu.jscript.interop.Arguments; +import me.topchetoeu.jscript.interop.Expose; +import me.topchetoeu.jscript.interop.ExposeConstructor; +import me.topchetoeu.jscript.interop.WrapperName; -@Native("Boolean") public class BooleanLib { +@WrapperName("Boolean") +public class BooleanLib { public static final BooleanLib TRUE = new BooleanLib(true); public static final BooleanLib FALSE = new BooleanLib(false); public final boolean value; - @NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object val) { - val = Values.toBoolean(val); - if (thisArg instanceof ObjectValue) return (boolean)val ? TRUE : FALSE; - else return val; - } - @Native(thisArg = true) public static String toString(Context ctx, Object thisArg) { - return Values.toBoolean(thisArg) ? "true" : "false"; - } - @Native(thisArg = true) public static boolean valueOf(Context ctx, Object thisArg) { - return Values.toBoolean(thisArg); - } - public BooleanLib(boolean val) { this.value = val; } + + @ExposeConstructor public static Object __constructor(Arguments args) { + var val = args.getBoolean(0); + if (args.self instanceof ObjectValue) return val ? TRUE : FALSE; + else return val; + } + @Expose public static String __toString(Arguments args) { + return args.self(Boolean.class) ? "true" : "false"; + } + @Expose public static boolean __valueOf(Arguments args) { + return args.self(Boolean.class); + } } diff --git a/src/me/topchetoeu/jscript/lib/DateLib.java b/src/me/topchetoeu/jscript/lib/DateLib.java index 339d961..599c19e 100644 --- a/src/me/topchetoeu/jscript/lib/DateLib.java +++ b/src/me/topchetoeu/jscript/lib/DateLib.java @@ -1,12 +1,17 @@ package me.topchetoeu.jscript.lib; import java.util.Calendar; +import java.util.Date; import java.util.TimeZone; -import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.interop.Native; +import me.topchetoeu.jscript.interop.Arguments; +import me.topchetoeu.jscript.interop.Expose; +import me.topchetoeu.jscript.interop.ExposeConstructor; +import me.topchetoeu.jscript.interop.ExposeTarget; +import me.topchetoeu.jscript.interop.WrapperName; -@Native("Date") public class DateLib { +@WrapperName("Date") +public class DateLib { private Calendar normal; private Calendar utc; @@ -22,244 +27,217 @@ import me.topchetoeu.jscript.interop.Native; normal = utc = null; } - @Native - public static double now() { - return new DateLib().getTime(); - } - - @Native - public double getYear() { + @Expose public double __getYear() { if (normal == null) return Double.NaN; return normal.get(Calendar.YEAR) - 1900; } - @Native - public double setYear(Context ctx, double real) { + @Expose public double __setYeard(Arguments args) { + var real = args.getDouble(0); if (real >= 0 && real <= 99) real = real + 1900; if (Double.isNaN(real)) invalidate(); else normal.set(Calendar.YEAR, (int)real); updateUTC(); - return getTime(); + return __getTime(); } - @Native - public double getFullYear() { + @Expose public double __getFullYear() { if (normal == null) return Double.NaN; return normal.get(Calendar.YEAR); } - @Native - public double getMonth() { + @Expose public double __getMonth() { if (normal == null) return Double.NaN; return normal.get(Calendar.MONTH); } - @Native - public double getDate() { + @Expose public double __getDate() { if (normal == null) return Double.NaN; return normal.get(Calendar.DAY_OF_MONTH); } - @Native - public double getDay() { + @Expose public double __getDay() { if (normal == null) return Double.NaN; return normal.get(Calendar.DAY_OF_WEEK); } - @Native - public double getHours() { + @Expose public double __getHours() { if (normal == null) return Double.NaN; return normal.get(Calendar.HOUR_OF_DAY); } - @Native - public double getMinutes() { + @Expose public double __getMinutes() { if (normal == null) return Double.NaN; return normal.get(Calendar.MINUTE); } - @Native - public double getSeconds() { + @Expose public double __getSeconds() { if (normal == null) return Double.NaN; return normal.get(Calendar.SECOND); } - @Native - public double getMilliseconds() { + @Expose public double __getMilliseconds() { if (normal == null) return Double.NaN; return normal.get(Calendar.MILLISECOND); } - @Native - public double getUTCFullYear() { + @Expose public double __getUTCFullYear() { if (utc == null) return Double.NaN; return utc.get(Calendar.YEAR); } - @Native - public double getUTCMonth() { + @Expose public double __getUTCMonth() { if (utc == null) return Double.NaN; return utc.get(Calendar.MONTH); } - @Native - public double getUTCDate() { + @Expose public double __getUTCDate() { if (utc == null) return Double.NaN; return utc.get(Calendar.DAY_OF_MONTH); } - @Native - public double getUTCDay() { + @Expose public double __getUTCDay() { if (utc == null) return Double.NaN; return utc.get(Calendar.DAY_OF_WEEK); } - @Native - public double getUTCHours() { + @Expose public double __getUTCHours() { if (utc == null) return Double.NaN; return utc.get(Calendar.HOUR_OF_DAY); } - @Native - public double getUTCMinutes() { + @Expose public double __getUTCMinutes() { if (utc == null) return Double.NaN; return utc.get(Calendar.MINUTE); } - @Native - public double getUTCSeconds() { + @Expose public double __getUTCSeconds() { if (utc == null) return Double.NaN; return utc.get(Calendar.SECOND); } - @Native - public double getUTCMilliseconds() { + @Expose public double __getUTCMilliseconds() { if (utc == null) return Double.NaN; return utc.get(Calendar.MILLISECOND); } - @Native - public double setFullYear(Context ctx, double real) { + @Expose public double __setFullYear(Arguments args) { + var real = args.getDouble(0); if (Double.isNaN(real)) invalidate(); else normal.set(Calendar.YEAR, (int)real); updateUTC(); - return getTime(); + return __getTime(); } - @Native - public double setMonth(Context ctx, double real) { + @Expose public double __setMonthd(Arguments args) { + var real = args.getDouble(0); if (Double.isNaN(real)) invalidate(); else normal.set(Calendar.MONTH, (int)real); updateUTC(); - return getTime(); + return __getTime(); } - @Native - public double setDate(Context ctx, double real) { + @Expose public double __setDated(Arguments args) { + var real = args.getDouble(0); if (Double.isNaN(real)) invalidate(); else normal.set(Calendar.DAY_OF_MONTH, (int)real); updateUTC(); - return getTime(); + return __getTime(); } - @Native - public double setDay(Context ctx, double real) { + @Expose public double __setDayd(Arguments args) { + var real = args.getDouble(0); if (Double.isNaN(real)) invalidate(); else normal.set(Calendar.DAY_OF_WEEK, (int)real); updateUTC(); - return getTime(); + return __getTime(); } - @Native - public double setHours(Context ctx, double real) { + @Expose public double __setHoursd(Arguments args) { + var real = args.getDouble(0); if (Double.isNaN(real)) invalidate(); else normal.set(Calendar.HOUR_OF_DAY, (int)real); updateUTC(); - return getTime(); + return __getTime(); } - @Native - public double setMinutes(Context ctx, double real) { + @Expose public double __setMinutesd(Arguments args) { + var real = args.getDouble(0); if (Double.isNaN(real)) invalidate(); else normal.set(Calendar.MINUTE, (int)real); updateUTC(); - return getTime(); + return __getTime(); } - @Native - public double setSeconds(Context ctx, double real) { + @Expose public double __setSecondsd(Arguments args) { + var real = args.getDouble(0); if (Double.isNaN(real)) invalidate(); else normal.set(Calendar.SECOND, (int)real); updateUTC(); - return getTime(); + return __getTime(); } - @Native - public double setMilliseconds(Context ctx, double real) { + @Expose public double __setMillisecondsd(Arguments args) { + var real = args.getDouble(0); if (Double.isNaN(real)) invalidate(); else normal.set(Calendar.MILLISECOND, (int)real); updateUTC(); - return getTime(); + return __getTime(); } - @Native - public double setUTCFullYear(Context ctx, double real) { + @Expose public double __setUTCFullYeard(Arguments args) { + var real = args.getDouble(0); if (Double.isNaN(real)) invalidate(); else utc.set(Calendar.YEAR, (int)real); updateNormal(); - return getTime(); + return __getTime(); } - @Native - public double setUTCMonth(Context ctx, double real) { + @Expose public double __setUTCMonthd(Arguments args) { + var real = args.getDouble(0); if (Double.isNaN(real)) invalidate(); else utc.set(Calendar.MONTH, (int)real); updateNormal(); - return getTime(); + return __getTime(); } - @Native - public double setUTCDate(Context ctx, double real) { + @Expose public double __setUTCDated(Arguments args) { + var real = args.getDouble(0); if (Double.isNaN(real)) invalidate(); else utc.set(Calendar.DAY_OF_MONTH, (int)real); updateNormal(); - return getTime(); + return __getTime(); } - @Native - public double setUTCDay(Context ctx, double real) { + @Expose public double __setUTCDayd(Arguments args) { + var real = args.getDouble(0); if (Double.isNaN(real)) invalidate(); else utc.set(Calendar.DAY_OF_WEEK, (int)real); updateNormal(); - return getTime(); + return __getTime(); } - @Native - public double setUTCHours(Context ctx, double real) { + @Expose public double __setUTCHoursd(Arguments args) { + var real = args.getDouble(0); if (Double.isNaN(real)) invalidate(); else utc.set(Calendar.HOUR_OF_DAY, (int)real); updateNormal(); - return getTime(); + return __getTime(); } - @Native - public double setUTCMinutes(Context ctx, double real) { + @Expose public double __setUTCMinutesd(Arguments args) { + var real = args.getDouble(0); if (Double.isNaN(real)) invalidate(); else utc.set(Calendar.MINUTE, (int)real); updateNormal(); - return getTime(); + return __getTime(); } - @Native - public double setUTCSeconds(Context ctx, double real) { + @Expose public double __setUTCSecondsd(Arguments args) { + var real = args.getDouble(0); if (Double.isNaN(real)) invalidate(); else utc.set(Calendar.SECOND, (int)real); updateNormal(); - return getTime(); + return __getTime(); } - @Native - public double setUTCMilliseconds(Context ctx, double real) { + @Expose public double __setUTCMillisecondsd(Arguments args) { + var real = args.getDouble(0); if (Double.isNaN(real)) invalidate(); else utc.set(Calendar.MILLISECOND, (int)real); updateNormal(); - return getTime(); + return __getTime(); } - @Native - public double getTime() { + @Expose public double __getTime() { if (utc == null) return Double.NaN; return utc.getTimeInMillis(); } - @Native - public double getTimezoneOffset() { + @Expose public double __getTimezoneOffset() { if (normal == null) return Double.NaN; return normal.getTimeZone().getRawOffset() / 60000; } - @Native - public double valueOf() { + @Expose public double __valueOf() { if (normal == null) return Double.NaN; else return normal.getTimeInMillis(); } - @Native - public String toString() { + @Expose public String __toString() { return normal.getTime().toString(); } - @Native public DateLib(long timestamp) { normal = Calendar.getInstance(); utc = Calendar.getInstance(); @@ -268,8 +246,17 @@ import me.topchetoeu.jscript.interop.Native; utc.setTimeInMillis(timestamp); } - @Native public DateLib() { - this(new java.util.Date().getTime()); + this(new Date().getTime()); + } + + @ExposeConstructor public static DateLib init(Arguments args) { + if (args.has(0)) return new DateLib(args.getLong(0)); + else return new DateLib(); + } + + @Expose(target = ExposeTarget.STATIC) + public static double __now() { + return new DateLib().__getTime(); } } diff --git a/src/me/topchetoeu/jscript/lib/EncodingLib.java b/src/me/topchetoeu/jscript/lib/EncodingLib.java index 3f7a495..2bbbcb6 100644 --- a/src/me/topchetoeu/jscript/lib/EncodingLib.java +++ b/src/me/topchetoeu/jscript/lib/EncodingLib.java @@ -1,20 +1,89 @@ package me.topchetoeu.jscript.lib; -import me.topchetoeu.jscript.engine.Context; +import java.util.ArrayList; + +import me.topchetoeu.jscript.Buffer; import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.Values; -import me.topchetoeu.jscript.interop.Native; +import me.topchetoeu.jscript.exceptions.EngineException; +import me.topchetoeu.jscript.interop.Arguments; +import me.topchetoeu.jscript.interop.Expose; +import me.topchetoeu.jscript.interop.ExposeTarget; +import me.topchetoeu.jscript.interop.WrapperName; +import me.topchetoeu.jscript.parsing.Parsing; -@Native("Encoding") +@WrapperName("Encoding") public class EncodingLib { - @Native public static ArrayValue encode(String value) { + private static final String HEX = "0123456789ABCDEF"; + + public static String encodeUriAny(String str, String keepAlphabet) { + if (str == null) str = "undefined"; + + var bytes = str.getBytes(); + var sb = new StringBuilder(bytes.length); + + for (byte c : bytes) { + if (Parsing.isAlphanumeric((char)c) || Parsing.isAny((char)c, keepAlphabet)) sb.append((char)c); + else { + sb.append('%'); + sb.append(HEX.charAt(c / 16)); + sb.append(HEX.charAt(c % 16)); + } + } + + return sb.toString(); + } + public static String decodeUriAny(String str, String keepAlphabet) { + if (str == null) str = "undefined"; + + var res = new Buffer(); + var bytes = str.getBytes(); + + for (var i = 0; i < bytes.length; i++) { + var c = bytes[i]; + if (c == '%') { + if (i >= bytes.length - 2) throw EngineException.ofError("URIError", "URI malformed."); + var b = Parsing.fromHex((char)bytes[i + 1]) * 16 | Parsing.fromHex((char)bytes[i + 2]); + if (!Parsing.isAny((char)b, keepAlphabet)) { + i += 2; + res.append((byte)b); + continue; + } + } + res.append(c); + } + + return new String(res.data()); + } + + @Expose(target = ExposeTarget.STATIC) + public static ArrayValue __encode(Arguments args) { var res = new ArrayValue(); - for (var el : value.getBytes()) res.set(null, res.size(), (int)el); + for (var el : args.getString(0).getBytes()) res.set(null, res.size(), (int)el); return res; } - @Native public static String decode(Context ctx, ArrayValue raw) { + @Expose(target = ExposeTarget.STATIC) + public static String __decode(Arguments args) { + var raw = args.convert(0, ArrayList.class); var res = new byte[raw.size()]; - for (var i = 0; i < raw.size(); i++) res[i] = (byte)Values.toNumber(ctx, raw.get(i)); + for (var i = 0; i < raw.size(); i++) res[i] = (byte)Values.toNumber(args.ctx, raw.get(i)); return new String(res); } + + @Expose(target = ExposeTarget.STATIC) + public static String __encodeURIComponent(Arguments args) { + return EncodingLib.encodeUriAny(args.getString(0), ".-_!~*'()"); + } + @Expose(target = ExposeTarget.STATIC) + public static String __decodeURIComponent(Arguments args) { + return decodeUriAny(args.getString(0), ""); + } + @Expose(target = ExposeTarget.STATIC) + public static String __encodeURI(Arguments args) { + return encodeUriAny(args.getString(0), ";,/?:@&=+$#.-_!~*'()"); + } + @Expose(target = ExposeTarget.STATIC) + public static String __decodeURI(Arguments args) { + return decodeUriAny(args.getString(0), ",/?:@&=+$#."); + } } diff --git a/src/me/topchetoeu/jscript/lib/EnvironmentLib.java b/src/me/topchetoeu/jscript/lib/EnvironmentLib.java index aa15d7d..d524529 100644 --- a/src/me/topchetoeu/jscript/lib/EnvironmentLib.java +++ b/src/me/topchetoeu/jscript/lib/EnvironmentLib.java @@ -3,28 +3,34 @@ package me.topchetoeu.jscript.lib; import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.ObjectValue; -import me.topchetoeu.jscript.interop.Native; -import me.topchetoeu.jscript.interop.NativeGetter; -import me.topchetoeu.jscript.interop.NativeSetter; +import me.topchetoeu.jscript.interop.Arguments; +import me.topchetoeu.jscript.interop.Expose; +import me.topchetoeu.jscript.interop.ExposeType; +import me.topchetoeu.jscript.interop.WrapperName; -@Native("Environment") +@WrapperName("Environment") public class EnvironmentLib { private Environment env; - @NativeGetter("@@env") public Environment env() { return env; } + @Expose(value = "@@env", type = ExposeType.GETTER) + public Environment __env() { return env; } - @NativeGetter public int id() { + @Expose(type = ExposeType.GETTER) + public int __id(Arguments args) { return env.hashCode(); } - @NativeGetter public ObjectValue global() { + @Expose(type = ExposeType.GETTER) + public ObjectValue __global(Arguments args) { return env.global.obj; } - @NativeGetter public FunctionValue compile() { + @Expose(type = ExposeType.GETTER) + public FunctionValue __compile() { return Environment.compileFunc(env); } - @NativeSetter public void compile(FunctionValue func) { - env.add(Environment.COMPILE_FUNC, func); + @Expose(type = ExposeType.SETTER) + public void __compile(Arguments args) { + env.add(Environment.COMPILE_FUNC, args.convert(0, FunctionValue.class)); } public EnvironmentLib(Environment env) { diff --git a/src/me/topchetoeu/jscript/lib/ErrorLib.java b/src/me/topchetoeu/jscript/lib/ErrorLib.java index e3cc945..3d9479a 100644 --- a/src/me/topchetoeu/jscript/lib/ErrorLib.java +++ b/src/me/topchetoeu/jscript/lib/ErrorLib.java @@ -1,19 +1,18 @@ package me.topchetoeu.jscript.lib; import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.engine.Environment; -import me.topchetoeu.jscript.engine.values.ArrayValue; 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.engine.values.ObjectValue.PlaceholderProto; -import me.topchetoeu.jscript.interop.InitType; -import me.topchetoeu.jscript.interop.Native; -import me.topchetoeu.jscript.interop.NativeConstructor; -import me.topchetoeu.jscript.interop.NativeInit; +import me.topchetoeu.jscript.exceptions.ConvertException; +import me.topchetoeu.jscript.interop.WrapperName; +import me.topchetoeu.jscript.interop.Arguments; +import me.topchetoeu.jscript.interop.Expose; +import me.topchetoeu.jscript.interop.ExposeField; -@Native("Error") public class ErrorLib { - private static String toString(Context ctx, boolean rethrown, Object cause, Object name, Object message, ArrayValue stack) { +@WrapperName("Error") +public class ErrorLib { + private static String toString(Context ctx, Object name, Object message) { if (name == null) name = ""; else name = Values.toString(ctx, name).trim(); if (message == null) message = ""; @@ -24,43 +23,31 @@ import me.topchetoeu.jscript.interop.NativeInit; if (!message.equals("") && !name.equals("")) res.append(": "); if (!message.equals("")) res.append(message); - if (cause instanceof ObjectValue) { - if (rethrown) res.append("\n (rethrown)"); - else res.append("\nCaused by ").append(toString(ctx, cause)); - } - return res.toString(); } - @Native(thisArg = true) public static String toString(Context ctx, Object thisArg) { - if (thisArg instanceof ObjectValue) { - var stack = Values.getMember(ctx, thisArg, "stack"); - if (!(stack instanceof ArrayValue)) stack = null; - var cause = Values.getMember(ctx, thisArg, Symbol.get("Symbol.cause")); - return toString(ctx, - thisArg == cause, - cause, - Values.getMember(ctx, thisArg, "name"), - Values.getMember(ctx, thisArg, "message"), - (ArrayValue)stack - ); - } + @ExposeField public static final String __name = "Error"; + + @Expose public static String __toString(Arguments args) { + if (args.self instanceof ObjectValue) return toString(args.ctx, + Values.getMember(args.ctx, args.self, "name"), + Values.getMember(args.ctx, args.self, "message") + ); else return "[Invalid error]"; } - @NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) { + @Expose public static ObjectValue __constructor(Arguments args) { var target = new ObjectValue(); - if (thisArg instanceof ObjectValue) target = (ObjectValue)thisArg; + var message = args.getString(0, ""); + + try { + target = args.self(ObjectValue.class); + } + catch (ConvertException e) {} target.setPrototype(PlaceholderProto.ERROR); - target.defineProperty(ctx, "stack", ArrayValue.of(ctx, ctx.stackTrace())); - if (message == null) target.defineProperty(ctx, "message", ""); - else target.defineProperty(ctx, "message", Values.toString(ctx, message)); + target.defineProperty(args.ctx, "message", Values.toString(args.ctx, message)); return target; } - - @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { - target.defineProperty(null, "name", "Error"); - } } diff --git a/src/me/topchetoeu/jscript/lib/FileLib.java b/src/me/topchetoeu/jscript/lib/FileLib.java index 536e03c..08d52b2 100644 --- a/src/me/topchetoeu/jscript/lib/FileLib.java +++ b/src/me/topchetoeu/jscript/lib/FileLib.java @@ -1,27 +1,27 @@ package me.topchetoeu.jscript.lib; -import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.filesystem.File; import me.topchetoeu.jscript.filesystem.FilesystemException; -import me.topchetoeu.jscript.interop.Native; -import me.topchetoeu.jscript.interop.NativeGetter; +import me.topchetoeu.jscript.interop.Arguments; +import me.topchetoeu.jscript.interop.Expose; +import me.topchetoeu.jscript.interop.WrapperName; -@Native("File") +@WrapperName("File") public class FileLib { public final File file; - @NativeGetter public PromiseLib pointer(Context ctx) { - return PromiseLib.await(ctx, () -> { + @Expose public PromiseLib __pointer(Arguments args) { + return PromiseLib.await(args.ctx, () -> { try { return file.seek(0, 1); } catch (FilesystemException e) { throw e.toEngineException(); } }); } - @NativeGetter public PromiseLib length(Context ctx) { - return PromiseLib.await(ctx, () -> { + @Expose public PromiseLib __length(Arguments args) { + return PromiseLib.await(args.ctx, () -> { try { long curr = file.seek(0, 1); long res = file.seek(0, 2); @@ -32,25 +32,27 @@ public class FileLib { }); } - @Native public PromiseLib read(Context ctx, int n) { - return PromiseLib.await(ctx, () -> { + @Expose public PromiseLib __read(Arguments args) { + return PromiseLib.await(args.ctx, () -> { + var n = args.getInt(0); try { var buff = new byte[n]; var res = new ArrayValue(); int resI = file.read(buff); - for (var i = resI - 1; i >= 0; i--) res.set(ctx, i, (int)buff[i]); + for (var i = resI - 1; i >= 0; i--) res.set(args.ctx, i, (int)buff[i]); return res; } catch (FilesystemException e) { throw e.toEngineException(); } }); } - @Native public PromiseLib write(Context ctx, ArrayValue val) { - return PromiseLib.await(ctx, () -> { + @Expose public PromiseLib __write(Arguments args) { + return PromiseLib.await(args.ctx, () -> { + var val = args.convert(0, ArrayValue.class); try { var res = new byte[val.size()]; - for (var i = 0; i < val.size(); i++) res[i] = (byte)Values.toNumber(ctx, val.get(i)); + for (var i = 0; i < val.size(); i++) res[i] = (byte)Values.toNumber(args.ctx, val.get(i)); file.write(res); return null; @@ -58,14 +60,17 @@ public class FileLib { catch (FilesystemException e) { throw e.toEngineException(); } }); } - @Native public PromiseLib close(Context ctx) { - return PromiseLib.await(ctx, () -> { + @Expose public PromiseLib __close(Arguments args) { + return PromiseLib.await(args.ctx, () -> { file.close(); return null; }); } - @Native public PromiseLib seek(Context ctx, long ptr, int whence) { - return PromiseLib.await(ctx, () -> { + @Expose public PromiseLib __seek(Arguments args) { + return PromiseLib.await(args.ctx, () -> { + var ptr = args.getLong(0); + var whence = args.getInt(1); + try { return file.seek(ptr, whence); } diff --git a/src/me/topchetoeu/jscript/lib/FilesystemLib.java b/src/me/topchetoeu/jscript/lib/FilesystemLib.java index 2d3f62e..aac8370 100644 --- a/src/me/topchetoeu/jscript/lib/FilesystemLib.java +++ b/src/me/topchetoeu/jscript/lib/FilesystemLib.java @@ -4,7 +4,6 @@ import java.io.IOException; import java.util.Iterator; import java.util.Stack; -import me.topchetoeu.jscript.Filename; import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.Values; @@ -16,13 +15,16 @@ import me.topchetoeu.jscript.filesystem.Filesystem; import me.topchetoeu.jscript.filesystem.FilesystemException; import me.topchetoeu.jscript.filesystem.Mode; import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode; -import me.topchetoeu.jscript.interop.Native; +import me.topchetoeu.jscript.interop.Arguments; +import me.topchetoeu.jscript.interop.Expose; +import me.topchetoeu.jscript.interop.ExposeField; +import me.topchetoeu.jscript.interop.WrapperName; -@Native("Filesystem") +@WrapperName("Filesystem") public class FilesystemLib { - @Native public static final int SEEK_SET = 0; - @Native public static final int SEEK_CUR = 1; - @Native public static final int SEEK_END = 2; + @ExposeField public static final int __SEEK_SET = 0; + @ExposeField public static final int __SEEK_CUR = 1; + @ExposeField public static final int __SEEK_END = 2; private static Filesystem fs(Context ctx) { var fs = Filesystem.get(ctx); @@ -30,30 +32,30 @@ public class FilesystemLib { throw EngineException.ofError("Current environment doesn't have a file system."); } - @Native public static String normalize(Context ctx, String... paths) { - return fs(ctx).normalize(paths); + @Expose public static String __normalize(Arguments args) { + return fs(args.ctx).normalize(args.convert(String.class)); } - @Native public static PromiseLib open(Context ctx, String _path, String mode) { - var path = fs(ctx).normalize(_path); - var _mode = Mode.parse(mode); + @Expose public static PromiseLib __open(Arguments args) { + return PromiseLib.await(args.ctx, () -> { + var fs = fs(args.ctx); + var path = fs.normalize(args.getString(0)); + var _mode = Mode.parse(args.getString(1)); - return PromiseLib.await(ctx, () -> { try { - if (fs(ctx).stat(path).type != EntryType.FILE) { + if (fs.stat(path).type != EntryType.FILE) { throw new FilesystemException(path, FSCode.NOT_FILE); } - var file = fs(ctx).open(path, _mode); + var file = fs.open(path, _mode); return new FileLib(file); } catch (FilesystemException e) { throw e.toEngineException(); } }); } - @Native public static ObjectValue ls(Context ctx, String _path) throws IOException { - var path = fs(ctx).normalize(_path); + @Expose public static ObjectValue __ls(Arguments args) { - return Values.toJSAsyncIterator(ctx, new Iterator<>() { + return Values.toJSAsyncIterator(args.ctx, new Iterator<>() { private boolean failed, done; private File file; private String nextLine; @@ -62,11 +64,14 @@ public class FilesystemLib { if (done) return; if (!failed) { if (file == null) { - if (fs(ctx).stat(path).type != EntryType.FOLDER) { + var fs = fs(args.ctx); + var path = fs.normalize(args.getString(0)); + + if (fs.stat(path).type != EntryType.FOLDER) { throw new FilesystemException(path, FSCode.NOT_FOLDER); } - file = fs(ctx).open(path, Mode.READ); + file = fs.open(path, Mode.READ); } if (nextLine == null) { @@ -103,29 +108,33 @@ public class FilesystemLib { } }); } - @Native public static PromiseLib mkdir(Context ctx, String _path) throws IOException { - return PromiseLib.await(ctx, () -> { + @Expose public static PromiseLib __mkdir(Arguments args) throws IOException { + return PromiseLib.await(args.ctx, () -> { try { - fs(ctx).create(Filename.parse(_path).toString(), EntryType.FOLDER); + fs(args.ctx).create(args.getString(0), EntryType.FOLDER); return null; } catch (FilesystemException e) { throw e.toEngineException(); } }); } - @Native public static PromiseLib mkfile(Context ctx, String path) throws IOException { - return PromiseLib.await(ctx, () -> { + @Expose public static PromiseLib __mkfile(Arguments args) throws IOException { + return PromiseLib.await(args.ctx, () -> { try { - fs(ctx).create(path, EntryType.FILE); + fs(args.ctx).create(args.getString(0), EntryType.FILE); return null; } catch (FilesystemException e) { throw e.toEngineException(); } }); } - @Native public static PromiseLib rm(Context ctx, String path, boolean recursive) throws IOException { - return PromiseLib.await(ctx, () -> { + @Expose public static PromiseLib __rm(Arguments args) throws IOException { + return PromiseLib.await(args.ctx, () -> { try { - if (!recursive) fs(ctx).create(path, EntryType.NONE); + var fs = fs(args.ctx); + var path = fs.normalize(args.getString(0)); + var recursive = args.getBoolean(1); + + if (!recursive) fs.create(path, EntryType.NONE); else { var stack = new Stack(); stack.push(path); @@ -134,13 +143,13 @@ public class FilesystemLib { var currPath = stack.pop(); FileStat stat; - try { stat = fs(ctx).stat(currPath); } + try { stat = fs.stat(currPath); } catch (FilesystemException e) { continue; } if (stat.type == EntryType.FOLDER) { - for (var el : fs(ctx).open(currPath, Mode.READ).readToString().split("\n")) stack.push(el); + for (var el : fs.open(currPath, Mode.READ).readToString().split("\n")) stack.push(el); } - else fs(ctx).create(currPath, EntryType.NONE); + else fs.create(currPath, EntryType.NONE); } } return null; @@ -148,22 +157,24 @@ public class FilesystemLib { catch (FilesystemException e) { throw e.toEngineException(); } }); } - @Native public static PromiseLib stat(Context ctx, String path) throws IOException { - return PromiseLib.await(ctx, () -> { + @Expose public static PromiseLib __stat(Arguments args) throws IOException { + return PromiseLib.await(args.ctx, () -> { try { - var stat = fs(ctx).stat(path); + var fs = fs(args.ctx); + var path = fs.normalize(args.getString(0)); + var stat = fs.stat(path); var res = new ObjectValue(); - res.defineProperty(ctx, "type", stat.type.name); - res.defineProperty(ctx, "mode", stat.mode.name); + res.defineProperty(args.ctx, "type", stat.type.name); + res.defineProperty(args.ctx, "mode", stat.mode.name); return res; } catch (FilesystemException e) { throw e.toEngineException(); } }); } - @Native public static PromiseLib exists(Context ctx, String _path) throws IOException { - return PromiseLib.await(ctx, () -> { - try { fs(ctx).stat(_path); return true; } + @Expose public static PromiseLib __exists(Arguments args) throws IOException { + return PromiseLib.await(args.ctx, () -> { + try { fs(args.ctx).stat(args.getString(0)); return true; } catch (FilesystemException e) { return false; } }); } diff --git a/src/me/topchetoeu/jscript/lib/FunctionLib.java b/src/me/topchetoeu/jscript/lib/FunctionLib.java index 77f5068..f79e5d1 100644 --- a/src/me/topchetoeu/jscript/lib/FunctionLib.java +++ b/src/me/topchetoeu/jscript/lib/FunctionLib.java @@ -1,54 +1,56 @@ package me.topchetoeu.jscript.lib; import me.topchetoeu.jscript.Location; -import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.CodeFunction; import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.NativeFunction; -import me.topchetoeu.jscript.exceptions.EngineException; -import me.topchetoeu.jscript.interop.Native; +import me.topchetoeu.jscript.interop.Arguments; +import me.topchetoeu.jscript.interop.Expose; +import me.topchetoeu.jscript.interop.ExposeTarget; +import me.topchetoeu.jscript.interop.WrapperName; -@Native("Function") public class FunctionLib { - @Native(thisArg = true) public static Object location(Context ctx, FunctionValue func) { - if (func instanceof CodeFunction) return ((CodeFunction)func).loc().toString(); +@WrapperName("Function") +public class FunctionLib { + @Expose public static Object __location(Arguments args) { + if (args.self instanceof CodeFunction) return ((CodeFunction)args.self).loc().toString(); else return Location.INTERNAL.toString(); } - @Native(thisArg = true) public static Object apply(Context ctx, FunctionValue func, Object thisArg, ArrayValue args) { - return func.call(ctx, thisArg, args.toArray()); + @Expose public static Object __apply(Arguments args) { + return args.self(FunctionValue.class).call(args.ctx, args.get(0), args.convert(1, ArrayValue.class).toArray()); } - @Native(thisArg = true) public static Object call(Context ctx, FunctionValue func, Object thisArg, Object... args) { - if (!(func instanceof FunctionValue)) throw EngineException.ofError("Expected this to be a function."); - - return func.call(ctx, thisArg, args); + @Expose public static Object __call(Arguments args) { + return args.self(FunctionValue.class).call(args.ctx, args.get(0), args.slice(1).args); } - @Native(thisArg = true) public static FunctionValue bind(FunctionValue func, Object thisArg, Object... args) { - if (!(func instanceof FunctionValue)) throw EngineException.ofError("Expected this to be a function."); - - return new NativeFunction(func.name + " (bound)", (callCtx, _0, callArgs) -> { + @Expose public static FunctionValue __bind(Arguments args) { + var self = args.self(FunctionValue.class); + return new NativeFunction(self.name + " (bound)", callArgs -> { Object[] resArgs; - if (args.length == 0) resArgs = callArgs; + if (args.n() == 0) resArgs = callArgs.args; else { - resArgs = new Object[args.length + callArgs.length]; - System.arraycopy(args, 0, resArgs, 0, args.length); - System.arraycopy(callArgs, 0, resArgs, args.length, callArgs.length); + resArgs = new Object[args.n() + callArgs.n()]; + System.arraycopy(args.args, 0, resArgs, 0, args.n()); + System.arraycopy(callArgs.args, 0, resArgs, args.n(), callArgs.n()); } - return func.call(callCtx, thisArg, resArgs); + return self.call(callArgs.ctx, self, resArgs); }); } - @Native(thisArg = true) public static String toString(Context ctx, Object func) { - return func.toString(); + @Expose public static String __toString(Arguments args) { + return args.self.toString(); } - @Native public static FunctionValue async(FunctionValue func) { - return new AsyncFunctionLib(func); + @Expose(target = ExposeTarget.STATIC) + public static FunctionValue __async(Arguments args) { + return new AsyncFunctionLib(args.convert(0, FunctionValue.class)); } - @Native public static FunctionValue asyncGenerator(FunctionValue func) { - return new AsyncGeneratorFunctionLib(func); + @Expose(target = ExposeTarget.STATIC) + public static FunctionValue __asyncGenerator(Arguments args) { + return new AsyncGeneratorFunctionLib(args.convert(0, FunctionValue.class)); } - @Native public static FunctionValue generator(FunctionValue func) { - return new GeneratorFunctionLib(func); + @Expose(target = ExposeTarget.STATIC) + public static FunctionValue __generator(Arguments args) { + return new GeneratorFunctionLib(args.convert(0, FunctionValue.class)); } } diff --git a/src/me/topchetoeu/jscript/lib/GeneratorFunctionLib.java b/src/me/topchetoeu/jscript/lib/GeneratorFunctionLib.java index f08e1a5..e8a07f6 100644 --- a/src/me/topchetoeu/jscript/lib/GeneratorFunctionLib.java +++ b/src/me/topchetoeu/jscript/lib/GeneratorFunctionLib.java @@ -6,13 +6,13 @@ 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; -import me.topchetoeu.jscript.interop.Native; +import me.topchetoeu.jscript.interop.WrapperName; -@Native("GeneratorFunction") public class GeneratorFunctionLib extends FunctionValue { +@WrapperName("GeneratorFunction") +public class GeneratorFunctionLib extends FunctionValue { public final FunctionValue factory; - @Override - public Object call(Context ctx, Object thisArg, Object ...args) { + @Override public Object call(Context ctx, Object thisArg, Object ...args) { var handler = new GeneratorLib(); var func = factory.call(ctx, thisArg, new NativeFunction("yield", handler::yield)); if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function."); diff --git a/src/me/topchetoeu/jscript/lib/GeneratorLib.java b/src/me/topchetoeu/jscript/lib/GeneratorLib.java index a21f828..cf4b5a9 100644 --- a/src/me/topchetoeu/jscript/lib/GeneratorLib.java +++ b/src/me/topchetoeu/jscript/lib/GeneratorLib.java @@ -5,18 +5,19 @@ import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.frame.Runners; import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.exceptions.EngineException; -import me.topchetoeu.jscript.interop.Native; +import me.topchetoeu.jscript.interop.Arguments; +import me.topchetoeu.jscript.interop.Expose; +import me.topchetoeu.jscript.interop.WrapperName; -@Native("Generator") public class GeneratorLib { +@WrapperName("Generator") +public class GeneratorLib { private boolean yielding = true; private boolean done = false; public CodeFrame frame; - @Native("@@Symbol.typeName") public final String name = "Generator"; - - private ObjectValue next(Context ctx, Object inducedValue, Object inducedReturn, Object inducedError) { + private ObjectValue next(Context ctx, Object inducedValue, Object inducedReturn, EngineException inducedError) { if (done) { - if (inducedError != Runners.NO_RETURN) throw new EngineException(inducedError); + if (inducedError != Runners.NO_RETURN) throw inducedError; var res = new ObjectValue(); res.defineProperty(ctx, "done", true); res.defineProperty(ctx, "value", inducedReturn == Runners.NO_RETURN ? null : inducedReturn); @@ -29,8 +30,9 @@ import me.topchetoeu.jscript.interop.Native; frame.onPush(); while (!yielding) { try { - res = frame.next(inducedValue, inducedReturn, inducedError == Runners.NO_RETURN ? null : new EngineException(inducedError)); - inducedReturn = inducedError = Runners.NO_RETURN; + res = frame.next(inducedValue, inducedReturn, inducedError); + inducedReturn = Runners.NO_RETURN; + inducedError = null; if (res != Runners.NO_RETURN) { done = true; break; @@ -52,29 +54,25 @@ import me.topchetoeu.jscript.interop.Native; return obj; } - @Native - public ObjectValue next(Context ctx, Object ...args) { - if (args.length == 0) return next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, Runners.NO_RETURN); - else return next(ctx, args[0], Runners.NO_RETURN, Runners.NO_RETURN); + @Expose public ObjectValue __next(Arguments args) { + if (args.n() == 0) return next(args.ctx, Runners.NO_RETURN, Runners.NO_RETURN, null); + else return next(args.ctx, args.get(0), Runners.NO_RETURN, null); } - @Native("throw") - public ObjectValue _throw(Context ctx, Object error) { - return next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, error); + @Expose public ObjectValue __throw(Arguments args) { + return next(args.ctx, Runners.NO_RETURN, Runners.NO_RETURN, new EngineException(args.get(0)).setCtx(args.ctx)); } - @Native("return") - public ObjectValue _return(Context ctx, Object value) { - return next(ctx, Runners.NO_RETURN, value, Runners.NO_RETURN); + @Expose public ObjectValue __return(Arguments args) { + return next(args.ctx, Runners.NO_RETURN, args.get(0), null); } - @Override - public String toString() { + @Override public String toString() { if (done) return "Generator [closed]"; if (yielding) return "Generator [suspended]"; return "Generator [running]"; } - public Object yield(Context ctx, Object thisArg, Object[] args) { + public Object yield(Arguments args) { this.yielding = true; - return args.length > 0 ? args[0] : null; + return args.get(0); } } \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/lib/Internals.java b/src/me/topchetoeu/jscript/lib/Internals.java index 03116e8..88a220b 100644 --- a/src/me/topchetoeu/jscript/lib/Internals.java +++ b/src/me/topchetoeu/jscript/lib/Internals.java @@ -1,44 +1,50 @@ package me.topchetoeu.jscript.lib; import java.io.IOException; +import java.util.HashMap; -import me.topchetoeu.jscript.Buffer; import me.topchetoeu.jscript.Reading; -import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.scope.GlobalScope; import me.topchetoeu.jscript.engine.values.FunctionValue; +import me.topchetoeu.jscript.engine.values.Symbol; import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.exceptions.EngineException; -import me.topchetoeu.jscript.interop.Native; -import me.topchetoeu.jscript.interop.NativeGetter; +import me.topchetoeu.jscript.interop.Arguments; +import me.topchetoeu.jscript.interop.Expose; +import me.topchetoeu.jscript.interop.ExposeField; +import me.topchetoeu.jscript.interop.ExposeTarget; import me.topchetoeu.jscript.modules.ModuleRepo; -import me.topchetoeu.jscript.parsing.Parsing; public class Internals { - @Native public static Object require(Context ctx, String name) { - var repo = ModuleRepo.get(ctx); + private static final Symbol THREADS = new Symbol("Internals.threads"); + private static final Symbol I = new Symbol("Internals.i"); + + @Expose(target = ExposeTarget.STATIC) + public static Object __require(Arguments args) { + var repo = ModuleRepo.get(args.ctx); if (repo != null) { - var res = repo.getModule(ctx, ModuleRepo.cwd(ctx), name); - res.load(ctx); + var res = repo.getModule(args.ctx, ModuleRepo.cwd(args.ctx), args.getString(0)); + res.load(args.ctx); return res.value(); } else throw EngineException.ofError("Modules are not supported."); } - @Native public static Object log(Context ctx, Object ...args) { - for (var arg : args) { - Values.printValue(ctx, arg); + @Expose(target = ExposeTarget.STATIC) + public static Object __log(Arguments args) { + for (var arg : args.args) { + Values.printValue(args.ctx, arg); System.out.print(" "); } System.out.println(); - if (args.length == 0) return null; - else return args[0]; + return args.get(0); } - @Native public static String readline(Context ctx) { + @Expose(target = ExposeTarget.STATIC) + public static String __readline() { try { return Reading.readline(); } @@ -48,23 +54,35 @@ public class Internals { } } - @Native public static Thread setTimeout(Context ctx, FunctionValue func, int delay, Object ...args) { + @Expose(target = ExposeTarget.STATIC) + public static Thread __setTimeout(Arguments args) { + var func = args.convert(0, FunctionValue.class); + var delay = args.getDouble(1); + var arguments = args.slice(2).args; + var thread = new Thread(() -> { var ms = (long)delay; var ns = (int)((delay - ms) * 10000000); - try { - Thread.sleep(ms, ns); - } + try { Thread.sleep(ms, ns); } catch (InterruptedException e) { return; } - ctx.engine.pushMsg(false, ctx.environment, func, null, args); + args.ctx.engine.pushMsg(false, args.ctx.environment, func, null, arguments); }); + thread.start(); + var i = args.ctx.init(I, 1); + args.ctx.add(I, i + 1); + args.ctx.init(THREADS, new HashMap()).put(i, thread); return thread; } - @Native public static Thread setInterval(Context ctx, FunctionValue func, int delay, Object ...args) { + @Expose(target = ExposeTarget.STATIC) + public static Thread __setInterval(Arguments args) { + var func = args.convert(0, FunctionValue.class); + var delay = args.getDouble(1); + var arguments = args.slice(2).args; + var thread = new Thread(() -> { var ms = (long)delay; var ns = (int)((delay - ms) * 10000000); @@ -75,96 +93,76 @@ public class Internals { } catch (InterruptedException e) { return; } - ctx.engine.pushMsg(false, ctx.environment, func, null, args); + args.ctx.engine.pushMsg(false, args.ctx.environment, func, null, arguments); } }); + thread.start(); + var i = args.ctx.init(I, 1); + args.ctx.add(I, i + 1); + args.ctx.init(THREADS, new HashMap()).put(i, thread); return thread; } - @Native public static void clearTimeout(Context ctx, Thread t) { - t.interrupt(); + @Expose(target = ExposeTarget.STATIC) + public static void __clearTimeout(Arguments args) { + var i = args.getInt(0); + HashMap map = args.ctx.get(THREADS); + if (map == null) return; + + var thread = map.get(i); + if (thread == null) return; + + thread.interrupt(); + map.remove(i); } - @Native public static void clearInterval(Context ctx, Thread t) { - t.interrupt(); + @Expose(target = ExposeTarget.STATIC) + public static void __clearInterval(Arguments args) { + __clearTimeout(args); } - @Native public static double parseInt(Context ctx, String val) { - return NumberLib.parseInt(ctx, val); + @Expose(target = ExposeTarget.STATIC) + public static double __parseInt(Arguments args) { + return NumberLib.__parseInt(args); } - @Native public static double parseFloat(Context ctx, String val) { - return NumberLib.parseFloat(ctx, val); + @Expose(target = ExposeTarget.STATIC) + public static double __parseFloat(Arguments args) { + return NumberLib.__parseFloat(args); } - @Native public static boolean isNaN(Context ctx, double val) { - return NumberLib.isNaN(ctx, val); + @Expose(target = ExposeTarget.STATIC) + public static boolean __isNaN(Arguments args) { + return NumberLib.__isNaN(args); } - @Native public static boolean isFinite(Context ctx, double val) { - return NumberLib.isFinite(ctx, val); + @Expose(target = ExposeTarget.STATIC) + public static boolean __isFinite(Arguments args) { + return NumberLib.__isFinite(args); } - @Native public static boolean isInfinite(Context ctx, double val) { - return NumberLib.isInfinite(ctx, val); + @Expose(target = ExposeTarget.STATIC) + public static boolean __isInfinite(Arguments args) { + return NumberLib.__isInfinite(args); } - @NativeGetter public static double NaN(Context ctx) { - return Double.NaN; + @ExposeField(target = ExposeTarget.STATIC) + public static double __NaN = Double.NaN; + @ExposeField(target = ExposeTarget.STATIC) + public static double __Infinity = Double.POSITIVE_INFINITY; + + @Expose(target = ExposeTarget.STATIC) + public static String __encodeURIComponent(Arguments args) { + return EncodingLib.__encodeURIComponent(args); } - @NativeGetter public static double Infinity(Context ctx) { - return Double.POSITIVE_INFINITY; + @Expose(target = ExposeTarget.STATIC) + public static String __decodeURIComponent(Arguments args) { + return EncodingLib.__decodeURIComponent(args); } - private static final String HEX = "0123456789ABCDEF"; - - private static String encodeUriAny(String str, String keepAlphabet) { - if (str == null) str = "undefined"; - - var bytes = str.getBytes(); - var sb = new StringBuilder(bytes.length); - - for (byte c : bytes) { - if (Parsing.isAlphanumeric((char)c) || Parsing.isAny((char)c, keepAlphabet)) sb.append((char)c); - else { - sb.append('%'); - sb.append(HEX.charAt(c / 16)); - sb.append(HEX.charAt(c % 16)); - } - } - - return sb.toString(); + @Expose(target = ExposeTarget.STATIC) + public static String __encodeURI(Arguments args) { + return EncodingLib.__encodeURI(args); } - private static String decodeUriAny(String str, String keepAlphabet) { - if (str == null) str = "undefined"; - - var res = new Buffer(); - var bytes = str.getBytes(); - - for (var i = 0; i < bytes.length; i++) { - var c = bytes[i]; - if (c == '%') { - if (i >= bytes.length - 2) throw EngineException.ofError("URIError", "URI malformed."); - var b = Parsing.fromHex((char)bytes[i + 1]) * 16 | Parsing.fromHex((char)bytes[i + 2]); - if (!Parsing.isAny((char)b, keepAlphabet)) { - i += 2; - res.append((byte)b); - continue; - } - } - res.append(c); - } - - return new String(res.data()); - } - - @Native public static String encodeURIComponent(String str) { - return encodeUriAny(str, ".-_!~*'()"); - } - @Native public static String decodeURIComponent(String str) { - return decodeUriAny(str, ""); - } - @Native public static String encodeURI(String str) { - return encodeUriAny(str, ";,/?:@&=+$#.-_!~*'()"); - } - @Native public static String decodeURI(String str) { - return decodeUriAny(str, ",/?:@&=+$#."); + @Expose(target = ExposeTarget.STATIC) + public static String __decodeURI(Arguments args) { + return EncodingLib.__decodeURI(args); } public static Environment apply(Environment env) { diff --git a/src/me/topchetoeu/jscript/lib/JSONLib.java b/src/me/topchetoeu/jscript/lib/JSONLib.java index e9aa8ad..3f025e1 100644 --- a/src/me/topchetoeu/jscript/lib/JSONLib.java +++ b/src/me/topchetoeu/jscript/lib/JSONLib.java @@ -1,21 +1,24 @@ package me.topchetoeu.jscript.lib; -import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.SyntaxException; -import me.topchetoeu.jscript.interop.Native; +import me.topchetoeu.jscript.interop.Arguments; +import me.topchetoeu.jscript.interop.Expose; +import me.topchetoeu.jscript.interop.ExposeTarget; +import me.topchetoeu.jscript.interop.WrapperName; import me.topchetoeu.jscript.json.JSON; -@Native("JSON") public class JSONLib { - @Native - public static Object parse(Context ctx, String val) { +@WrapperName("JSON") +public class JSONLib { + @Expose(target = ExposeTarget.STATIC) + public static Object __parse(Arguments args) { try { - return JSON.toJs(JSON.parse(null, val)); + return JSON.toJs(JSON.parse(null, args.getString(0))); } catch (SyntaxException e) { throw EngineException.ofSyntax(e.msg); } } - @Native - public static String stringify(Context ctx, Object val) { - return me.topchetoeu.jscript.json.JSON.stringify(JSON.fromJs(ctx, val)); + @Expose(target = ExposeTarget.STATIC) + public static String __stringify(Arguments args) { + return me.topchetoeu.jscript.json.JSON.stringify(JSON.fromJs(args.ctx, args.get(0))); } } diff --git a/src/me/topchetoeu/jscript/lib/MapLib.java b/src/me/topchetoeu/jscript/lib/MapLib.java index b9d72ff..dd52eea 100644 --- a/src/me/topchetoeu/jscript/lib/MapLib.java +++ b/src/me/topchetoeu/jscript/lib/MapLib.java @@ -9,21 +9,26 @@ 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.Values; -import me.topchetoeu.jscript.interop.Native; -import me.topchetoeu.jscript.interop.NativeGetter; +import me.topchetoeu.jscript.interop.Arguments; +import me.topchetoeu.jscript.interop.Expose; +import me.topchetoeu.jscript.interop.ExposeConstructor; +import me.topchetoeu.jscript.interop.ExposeType; +import me.topchetoeu.jscript.interop.WrapperName; -@Native("Map") public class MapLib { +@WrapperName("Map") +public class MapLib { private LinkedHashMap map = new LinkedHashMap<>(); - @Native("@@Symbol.typeName") public final String name = "Map"; - @Native("@@Symbol.iterator") public ObjectValue iterator(Context ctx) { - return this.entries(ctx); + @Expose("@@Symbol.iterator") + public ObjectValue __iterator(Arguments args) { + return this.__entries(args); } - @Native public void clear() { + @Expose public void __clear() { map.clear(); } - @Native public boolean delete(Object key) { + @Expose public boolean __delete(Arguments args) { + var key = args.get(0); if (map.containsKey(key)) { map.remove(key); return true; @@ -31,48 +36,56 @@ import me.topchetoeu.jscript.interop.NativeGetter; return false; } - @Native public ObjectValue entries(Context ctx) { - return ArrayValue.of(ctx, map + @Expose public ObjectValue __entries(Arguments args) { + return ArrayValue.of(args.ctx, map .entrySet() .stream() - .map(v -> new ArrayValue(ctx, v.getKey(), v.getValue())) + .map(v -> new ArrayValue(args.ctx, v.getKey(), v.getValue())) .collect(Collectors.toList()) ); } - @Native public ObjectValue keys(Context ctx) { - return ArrayValue.of(ctx, map.keySet()); + @Expose public ObjectValue __keys(Arguments args) { + return ArrayValue.of(args.ctx, map.keySet()); } - @Native public ObjectValue values(Context ctx) { - return ArrayValue.of(ctx, map.values()); + @Expose public ObjectValue __values(Arguments args) { + return ArrayValue.of(args.ctx, map.values()); } - @Native public Object get(Object key) { - return map.get(key); + @Expose public Object __get(Arguments args) { + return map.get(args.get(0)); } - @Native public MapLib set(Object key, Object val) { - map.put(key, val); + @Expose public MapLib __set(Arguments args) { + map.put(args.get(0), args.get(1)); return this; } - @Native public boolean has(Object key) { - return map.containsKey(key); + @Expose public boolean __has(Arguments args) { + return map.containsKey(args.get(0)); } - @NativeGetter public int size() { + @Expose(type = ExposeType.GETTER) + public int __size() { return map.size(); } - @Native public void forEach(Context ctx, FunctionValue func, Object thisArg) { + @Expose public void __forEach(Arguments args) { + var func = args.convert(0, FunctionValue.class); + var thisArg = args.get(1); + var keys = new ArrayList<>(map.keySet()); - for (var el : keys) func.call(ctx, thisArg, map.get(el), el,this); + for (var el : keys) func.call(args.ctx, thisArg, map.get(el), el,this); } - @Native public MapLib(Context ctx, Object iterable) { + public MapLib(Context ctx, Object iterable) { for (var el : Values.fromJSIterator(ctx, iterable)) { try { - set(Values.getMember(ctx, el, 0), Values.getMember(ctx, el, 1)); + map.put(Values.getMember(ctx, el, 0), Values.getMember(ctx, el, 1)); } catch (IllegalArgumentException e) { } } } + + @ExposeConstructor public static MapLib __constructor(Arguments args) { + return new MapLib(args.ctx, args.get(0)); + } } diff --git a/src/me/topchetoeu/jscript/lib/MathLib.java b/src/me/topchetoeu/jscript/lib/MathLib.java index 381b5ae..d741127 100644 --- a/src/me/topchetoeu/jscript/lib/MathLib.java +++ b/src/me/topchetoeu/jscript/lib/MathLib.java @@ -1,21 +1,47 @@ package me.topchetoeu.jscript.lib; -import me.topchetoeu.jscript.interop.Native; +import me.topchetoeu.jscript.interop.Arguments; +import me.topchetoeu.jscript.interop.Expose; +import me.topchetoeu.jscript.interop.ExposeField; +import me.topchetoeu.jscript.interop.ExposeTarget; +import me.topchetoeu.jscript.interop.WrapperName; -@Native("Math") public class MathLib { - @Native public static final double E = Math.E; - @Native public static final double PI = Math.PI; - @Native public static final double SQRT2 = Math.sqrt(2); - @Native public static final double SQRT1_2 = Math.sqrt(.5); - @Native public static final double LN2 = Math.log(2); - @Native public static final double LN10 = Math.log(10); - @Native public static final double LOG2E = Math.log(Math.E) / LN2; - @Native public static final double LOG10E = Math.log10(Math.E); +@WrapperName("Math") +public class MathLib { + @ExposeField(target = ExposeTarget.STATIC) + public static final double __E = Math.E; + @ExposeField(target = ExposeTarget.STATIC) + public static final double __PI = Math.PI; + @ExposeField(target = ExposeTarget.STATIC) + public static final double __SQRT2 = Math.sqrt(2); + @ExposeField(target = ExposeTarget.STATIC) + public static final double __SQRT1_2 = Math.sqrt(.5); + @ExposeField(target = ExposeTarget.STATIC) + public static final double __LN2 = Math.log(2); + @ExposeField(target = ExposeTarget.STATIC) + public static final double __LN10 = Math.log(10); + @ExposeField(target = ExposeTarget.STATIC) + public static final double __LOG2E = Math.log(Math.E) / __LN2; + @ExposeField(target = ExposeTarget.STATIC) + public static final double __LOG10E = Math.log10(Math.E); + + @Expose(target = ExposeTarget.STATIC) + public static double __asin(Arguments args) { + return Math.asin(args.getDouble(0)); + } + @Expose(target = ExposeTarget.STATIC) + public static double __acos(Arguments args) { + return Math.acos(args.getDouble(0)); + } + @Expose(target = ExposeTarget.STATIC) + public static double __atan(Arguments args) { + return Math.atan(args.getDouble(0)); + } + @Expose(target = ExposeTarget.STATIC) + public static double __atan2(Arguments args) { + var x = args.getDouble(1); + var y = args.getDouble(0); - @Native public static double asin(double x) { return Math.asin(x); } - @Native public static double acos(double x) { return Math.acos(x); } - @Native public static double atan(double x) { return Math.atan(x); } - @Native public static double atan2(double y, double x) { if (x == 0) { if (y == 0) return Double.NaN; return Math.signum(y) * Math.PI / 2; @@ -29,71 +55,157 @@ import me.topchetoeu.jscript.interop.Native; } - @Native public static double asinh(double x) { return Math.log(x + Math.sqrt(x * x + 1)); } - @Native public static double acosh(double x) { return Math.log(x + Math.sqrt(x * x - 1)); } - @Native public static double atanh(double x) { + @Expose(target = ExposeTarget.STATIC) + public static double __asinh(Arguments args) { + var x = args.getDouble(0); + return Math.log(x + Math.sqrt(x * x + 1)); + } + @Expose(target = ExposeTarget.STATIC) + public static double __acosh(Arguments args) { + var x = args.getDouble(0); + return Math.log(x + Math.sqrt(x * x - 1)); + } + @Expose(target = ExposeTarget.STATIC) + public static double __atanh(Arguments args) { + var x = args.getDouble(0); + if (x <= -1 || x >= 1) return Double.NaN; return .5 * Math.log((1 + x) / (1 - x)); } - @Native public static double sin(double x) { return Math.sin(x); } - @Native public static double cos(double x) { return Math.cos(x); } - @Native public static double tan(double x) { return Math.tan(x); } + @Expose(target = ExposeTarget.STATIC) + public static double __sin(Arguments args) { + return Math.sin(args.getDouble(0)); + } + @Expose(target = ExposeTarget.STATIC) + public static double __cos(Arguments args) { + return Math.cos(args.getDouble(0)); + } + @Expose(target = ExposeTarget.STATIC) + public static double __tan(Arguments args) { + return Math.tan(args.getDouble(0)); + } - @Native public static double sinh(double x) { return Math.sinh(x); } - @Native public static double cosh(double x) { return Math.cosh(x); } - @Native public static double tanh(double x) { return Math.tanh(x); } + @Expose(target = ExposeTarget.STATIC) + public static double __sinh(Arguments args) { + return Math.sinh(args.getDouble(0)); + } + @Expose(target = ExposeTarget.STATIC) + public static double __cosh(Arguments args) { + return Math.cosh(args.getDouble(0)); + } + @Expose(target = ExposeTarget.STATIC) + public static double __tanh(Arguments args) { + return Math.tanh(args.getDouble(0)); + } - @Native public static double sqrt(double x) { return Math.sqrt(x); } - @Native public static double cbrt(double x) { return Math.cbrt(x); } + @Expose(target = ExposeTarget.STATIC) + public static double __sqrt(Arguments args) { + return Math.sqrt(args.getDouble(0)); + } + @Expose(target = ExposeTarget.STATIC) + public static double __cbrt(Arguments args) { + return Math.cbrt(args.getDouble(0)); + } - @Native public static double hypot(double ...vals) { + @Expose(target = ExposeTarget.STATIC) + public static double __hypot(Arguments args) { var res = 0.; - for (var el : vals) { - var val = el; + for (var i = 0; i < args.n(); i++) { + var val = args.getDouble(i); res += val * val; } return Math.sqrt(res); } - @Native public static int imul(double a, double b) { return (int)a * (int)b; } + @Expose(target = ExposeTarget.STATIC) + public static int __imul(Arguments args) { return args.getInt(0) * args.getInt(1); } - @Native public static double exp(double x) { return Math.exp(x); } - @Native public static double expm1(double x) { return Math.expm1(x); } - @Native public static double pow(double x, double y) { return Math.pow(x, y); } + @Expose(target = ExposeTarget.STATIC) + public static double __exp(Arguments args) { + return Math.exp(args.getDouble(0)); + } + @Expose(target = ExposeTarget.STATIC) + public static double __expm1(Arguments args) { + return Math.expm1(args.getDouble(0)); + } + @Expose(target = ExposeTarget.STATIC) + public static double __pow(Arguments args) { return Math.pow(args.getDouble(0), args.getDouble(1)); } - @Native public static double log(double x) { return Math.log(x); } - @Native public static double log10(double x) { return Math.log10(x); } - @Native public static double log1p(double x) { return Math.log1p(x); } - @Native public static double log2(double x) { return Math.log(x) / LN2; } + @Expose(target = ExposeTarget.STATIC) + public static double __log(Arguments args) { + return Math.log(args.getDouble(0)); + } + @Expose(target = ExposeTarget.STATIC) + public static double __log10(Arguments args) { + return Math.log10(args.getDouble(0)); + } + @Expose(target = ExposeTarget.STATIC) + public static double __log1p(Arguments args) { + return Math.log1p(args.getDouble(0)); + } + @Expose(target = ExposeTarget.STATIC) + public static double __log2(Arguments args) { + return Math.log(args.getDouble(0)) / __LN2; + } - @Native public static double ceil(double x) { return Math.ceil(x); } - @Native public static double floor(double x) { return Math.floor(x); } - @Native public static double round(double x) { return Math.round(x); } - @Native public static float fround(double x) { return (float)x; } - @Native public static double trunc(double x) { return Math.floor(Math.abs(x)) * Math.signum(x); } - @Native public static double abs(double x) { return Math.abs(x); } + @Expose(target = ExposeTarget.STATIC) + public static double __ceil(Arguments args) { + return Math.ceil(args.getDouble(0)); + } + @Expose(target = ExposeTarget.STATIC) + public static double __floor(Arguments args) { + return Math.floor(args.getDouble(0)); + } + @Expose(target = ExposeTarget.STATIC) + public static double __round(Arguments args) { + return Math.round(args.getDouble(0)); + } + @Expose(target = ExposeTarget.STATIC) + public static float __fround(Arguments args) { + return (float)args.getDouble(0); + } + @Expose(target = ExposeTarget.STATIC) + public static double __trunc(Arguments args) { + var x = args.getDouble(0); + return Math.floor(Math.abs(x)) * Math.signum(x); + } + @Expose(target = ExposeTarget.STATIC) + public static double __abs(Arguments args) { + return Math.abs(args.getDouble(0)); + } - @Native public static double max(double ...vals) { + @Expose(target = ExposeTarget.STATIC) + public static double __max(Arguments args) { var res = Double.NEGATIVE_INFINITY; - for (var el : vals) { + for (var i = 0; i < args.n(); i++) { + var el = args.getDouble(i); if (el > res) res = el; } return res; } - @Native public static double min(double ...vals) { + @Expose(target = ExposeTarget.STATIC) + public static double __min(Arguments args) { var res = Double.POSITIVE_INFINITY; - for (var el : vals) { + for (var i = 0; i < args.n(); i++) { + var el = args.getDouble(i); if (el < res) res = el; } return res; } - @Native public static double sign(double x) { return Math.signum(x); } + @Expose(target = ExposeTarget.STATIC) + public static double __sign(Arguments args) { + return Math.signum(args.getDouble(0)); + } - @Native public static double random() { return Math.random(); } - @Native public static int clz32(double x) { return Integer.numberOfLeadingZeros((int)x); } + @Expose(target = ExposeTarget.STATIC) + public static double __random() { return Math.random(); } + @Expose(target = ExposeTarget.STATIC) + public static int __clz32(Arguments args) { + return Integer.numberOfLeadingZeros(args.getInt(0)); + } } diff --git a/src/me/topchetoeu/jscript/lib/NumberLib.java b/src/me/topchetoeu/jscript/lib/NumberLib.java index 53998e5..5777fed 100644 --- a/src/me/topchetoeu/jscript/lib/NumberLib.java +++ b/src/me/topchetoeu/jscript/lib/NumberLib.java @@ -1,49 +1,66 @@ package me.topchetoeu.jscript.lib; -import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.Values; -import me.topchetoeu.jscript.interop.Native; -import me.topchetoeu.jscript.interop.NativeConstructor; +import me.topchetoeu.jscript.interop.Arguments; +import me.topchetoeu.jscript.interop.Expose; +import me.topchetoeu.jscript.interop.ExposeConstructor; +import me.topchetoeu.jscript.interop.ExposeField; +import me.topchetoeu.jscript.interop.ExposeTarget; +import me.topchetoeu.jscript.interop.WrapperName; -@Native("Number") public class NumberLib { - @Native public static final double EPSILON = java.lang.Math.ulp(1.0); - @Native public static final double MAX_SAFE_INTEGER = 9007199254740991.; - @Native public static final double MIN_SAFE_INTEGER = -MAX_SAFE_INTEGER; +@WrapperName("Number") +public class NumberLib { + @ExposeField(target = ExposeTarget.STATIC) + public static final double __EPSILON = Math.ulp(1.0); + @ExposeField(target = ExposeTarget.STATIC) + public static final double __MAX_SAFE_INTEGER = 9007199254740991.; + @ExposeField(target = ExposeTarget.STATIC) + public static final double __MIN_SAFE_INTEGER = -__MAX_SAFE_INTEGER; // lmao big number go brrr - @Native public static final double MAX_VALUE = 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.; - @Native public static final double MIN_VALUE = -MAX_VALUE; - @Native public static final double NaN = 0. / 0; - @Native public static final double NEGATIVE_INFINITY = -1. / 0; - @Native public static final double POSITIVE_INFINITY = 1. / 0; + @ExposeField(target = ExposeTarget.STATIC) + public static final double __MAX_VALUE = 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.; + @ExposeField(target = ExposeTarget.STATIC) + public static final double __MIN_VALUE = -__MAX_VALUE; + @ExposeField(target = ExposeTarget.STATIC) + public static final double __NaN = 0. / 0; + @ExposeField(target = ExposeTarget.STATIC) + public static final double __NEGATIVE_INFINITY = -1. / 0; + @ExposeField(target = ExposeTarget.STATIC) + public static final double __POSITIVE_INFINITY = 1. / 0; public final double value; - @Native public static boolean isFinite(Context ctx, double val) { return Double.isFinite(val); } - @Native public static boolean isInfinite(Context ctx, double val) { return Double.isInfinite(val); } - @Native public static boolean isNaN(Context ctx, double val) { return Double.isNaN(val); } - @Native public static boolean isSafeInteger(Context ctx, double val) { - return val > MIN_SAFE_INTEGER && val < MAX_SAFE_INTEGER; + @Expose(target = ExposeTarget.STATIC) + public static boolean __isFinite(Arguments args) { return Double.isFinite(args.getDouble(0)); } + @Expose(target = ExposeTarget.STATIC) + public static boolean __isInfinite(Arguments args) { return Double.isInfinite(args.getDouble(0)); } + @Expose(target = ExposeTarget.STATIC) + public static boolean __isNaN(Arguments args) { return Double.isNaN(args.getDouble(0)); } + @Expose(target = ExposeTarget.STATIC) + public static boolean __isSafeInteger(Arguments args) { + return args.getDouble(0) > __MIN_SAFE_INTEGER && args.getDouble(0) < __MAX_SAFE_INTEGER; } - @Native public static double parseFloat(Context ctx, String val) { - return Values.toNumber(ctx, val); + @Expose(target = ExposeTarget.STATIC) + public static double __parseFloat(Arguments args) { + return args.getDouble(0); } - @Native public static double parseInt(Context ctx, String val) { - return (long)Values.toNumber(ctx, val); + @Expose(target = ExposeTarget.STATIC) + public static double __parseInt(Arguments args) { + return args.getLong(0); } - @NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object val) { - val = Values.toNumber(ctx, val); - if (thisArg instanceof ObjectValue) return new NumberLib((double)val); - else return val; + @ExposeConstructor public static Object __constructor(Arguments args) { + if (args.self instanceof ObjectValue) return new NumberLib(args.getDouble(0)); + else return args.getDouble(0); } - @Native(thisArg = true) public static String toString(Context ctx, Object thisArg) { - return Values.toString(ctx, Values.toNumber(ctx, thisArg)); + @Expose public static String __toString(Arguments args) { + return Values.toString(args.ctx, args.getDouble(0)); } - @Native(thisArg = true) public static double valueOf(Context ctx, Object thisArg) { - if (thisArg instanceof NumberLib) return ((NumberLib)thisArg).value; - else return Values.toNumber(ctx, thisArg); + @Expose public static double __valueOf(Arguments args) { + if (args.self instanceof NumberLib) return args.self(NumberLib.class).value; + else return Values.toNumber(args.ctx, args.self); } public NumberLib(double val) { diff --git a/src/me/topchetoeu/jscript/lib/ObjectLib.java b/src/me/topchetoeu/jscript/lib/ObjectLib.java index 2114f20..ad14a7f 100644 --- a/src/me/topchetoeu/jscript/lib/ObjectLib.java +++ b/src/me/topchetoeu/jscript/lib/ObjectLib.java @@ -1,171 +1,219 @@ package me.topchetoeu.jscript.lib; -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; +import me.topchetoeu.jscript.interop.Arguments; +import me.topchetoeu.jscript.interop.Expose; +import me.topchetoeu.jscript.interop.ExposeConstructor; +import me.topchetoeu.jscript.interop.ExposeTarget; +import me.topchetoeu.jscript.interop.WrapperName; -@Native("Object") public class ObjectLib { - @Native public static ObjectValue assign(Context ctx, ObjectValue dst, Object... src) { - for (var obj : src) { - for (var key : Values.getMembers(ctx, obj, true, true)) { - Values.setMember(ctx, dst, key, Values.getMember(ctx, obj, key)); +@WrapperName("Object") +public class ObjectLib { + @Expose(target = ExposeTarget.STATIC) + public static Object __assign(Arguments args) { + for (var obj : args.slice(1).args) { + for (var key : Values.getMembers(args.ctx, obj, true, true)) { + Values.setMember(args.ctx, args.get(0), key, Values.getMember(args.ctx, obj, key)); } } - return dst; + return args.get(0); } - @Native public static ObjectValue create(Context ctx, ObjectValue proto, ObjectValue props) { + @Expose(target = ExposeTarget.STATIC) + public static ObjectValue __create(Arguments args) { var obj = new ObjectValue(); - obj.setPrototype(ctx, proto); - return defineProperties(ctx, obj, props); + obj.setPrototype(args.ctx, args.get(0)); + + if (args.n() >= 1) { + var newArgs = new Object[args.n()]; + System.arraycopy(args.args, 1, args, 1, args.n() - 1); + newArgs[0] = obj; + + __defineProperties(new Arguments(args.ctx, null, newArgs)); + } + + return obj; } - @Native public static ObjectValue defineProperty(Context ctx, ObjectValue obj, Object key, ObjectValue attrib) { - var hasVal = attrib.hasMember(ctx, "value", false); - var hasGet = attrib.hasMember(ctx, "get", false); - var hasSet = attrib.hasMember(ctx, "set", false); + @Expose(target = ExposeTarget.STATIC) + public static ObjectValue __defineProperty(Arguments args) { + var obj = args.convert(0, ObjectValue.class); + var key = args.get(1); + var attrib = args.convert(2, ObjectValue.class); + + var hasVal = attrib.hasMember(args.ctx, "value", false); + var hasGet = attrib.hasMember(args.ctx, "get", false); + var hasSet = attrib.hasMember(args.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")) + args.ctx, key, + attrib.getMember(args.ctx, "value"), + Values.toBoolean(attrib.getMember(args.ctx, "writable")), + Values.toBoolean(attrib.getMember(args.ctx, "configurable")), + Values.toBoolean(attrib.getMember(args.ctx, "enumerable")) )) throw EngineException.ofType("Can't define property '" + key + "'."); } else { - var get = attrib.getMember(ctx, "get"); - var set = attrib.getMember(ctx, "set"); + var get = attrib.getMember(args.ctx, "get"); + var set = attrib.getMember(args.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, + args.ctx, key, (FunctionValue)get, (FunctionValue)set, - Values.toBoolean(attrib.getMember(ctx, "configurable")), - Values.toBoolean(attrib.getMember(ctx, "enumerable")) + Values.toBoolean(attrib.getMember(args.ctx, "configurable")), + Values.toBoolean(attrib.getMember(args.ctx, "enumerable")) )) throw EngineException.ofType("Can't define property '" + key + "'."); } return obj; } - @Native public static ObjectValue defineProperties(Context ctx, ObjectValue obj, ObjectValue attrib) { + @Expose(target = ExposeTarget.STATIC) + public static ObjectValue __defineProperties(Arguments args) { + var obj = args.convert(0, ObjectValue.class); + var attrib = args.convert(1, ObjectValue.class); + for (var key : Values.getMembers(null, obj, false, false)) { - obj.defineProperty(ctx, key, attrib.getMember(ctx, key)); + obj.defineProperty(args.ctx, key, attrib.getMember(args.ctx, key)); } return obj; } - @Native public static ArrayValue keys(Context ctx, Object obj, Object all) { + @Expose(target = ExposeTarget.STATIC) + public static ArrayValue __keys(Arguments args) { + var obj = args.convert(0, ObjectValue.class); + var all = args.getBoolean(1); 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); + for (var key : Values.getMembers(args.ctx, obj, true, false)) { + if (all || !(key instanceof Symbol)) res.set(args.ctx, res.size(), key); } return res; } - @Native public static ArrayValue entries(Context ctx, Object obj, Object all) { + @Expose(target = ExposeTarget.STATIC) + public static ArrayValue __entries(Arguments args) { var res = new ArrayValue(); - var _all = Values.toBoolean(all); + var obj = args.convert(0, ObjectValue.class); + var all = args.getBoolean(1); - 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))); + for (var key : Values.getMembers(args.ctx, obj, true, false)) { + if (all || !(key instanceof Symbol)) res.set(args.ctx, res.size(), new ArrayValue(args.ctx, key, Values.getMember(args.ctx, obj, key))); } return res; } - @Native public static ArrayValue values(Context ctx, Object obj, Object all) { + @Expose(target = ExposeTarget.STATIC) + public static ArrayValue __values(Arguments args) { var res = new ArrayValue(); - var _all = Values.toBoolean(all); + var obj = args.convert(0, ObjectValue.class); + var all = args.getBoolean(1); - for (var key : Values.getMembers(ctx, obj, true, false)) { - if (_all || key instanceof String) res.set(ctx, res.size(), Values.getMember(ctx, obj, key)); + for (var key : Values.getMembers(args.ctx, obj, true, false)) { + if (all || key instanceof String) res.set(args.ctx, res.size(), Values.getMember(args.ctx, obj, key)); } return res; } - - @Native public static ObjectValue getOwnPropertyDescriptor(Context ctx, Object obj, Object key) { - return Values.getMemberDescriptor(ctx, obj, key); + + @Expose(target = ExposeTarget.STATIC) + public static ObjectValue __getOwnPropertyDescriptor(Arguments args) { + return Values.getMemberDescriptor(args.ctx, args.get(0), args.get(1)); } - @Native public static ObjectValue getOwnPropertyDescriptors(Context ctx, Object obj) { + @Expose(target = ExposeTarget.STATIC) + public static ObjectValue __getOwnPropertyDescriptors(Arguments args) { var res = new ObjectValue(); - for (var key : Values.getMembers(ctx, obj, true, true)) { - res.defineProperty(ctx, key, getOwnPropertyDescriptor(ctx, obj, key)); + var obj = args.get(0); + for (var key : Values.getMembers(args.ctx, obj, true, true)) { + res.defineProperty(args.ctx, key, __getOwnPropertyDescriptor(new Arguments(args.ctx, null, obj, key))); } return res; } - @Native public static ArrayValue getOwnPropertyNames(Context ctx, Object obj, Object all) { + @Expose(target = ExposeTarget.STATIC) + public static ArrayValue __getOwnPropertyNames(Arguments args) { var res = new ArrayValue(); - var _all = Values.toBoolean(all); + var obj = args.convert(0, ObjectValue.class); + var all = args.getBoolean(1); - for (var key : Values.getMembers(ctx, obj, true, true)) { - if (_all || !(key instanceof Symbol)) res.set(ctx, res.size(), key); + for (var key : Values.getMembers(args.ctx, obj, true, true)) { + if (all || !(key instanceof Symbol)) res.set(args.ctx, res.size(), key); } return res; } - @Native public static ArrayValue getOwnPropertySymbols(Context ctx, Object obj) { + @Expose(target = ExposeTarget.STATIC) + public static ArrayValue __getOwnPropertySymbols(Arguments args) { + var obj = args.convert(0, ObjectValue.class); var res = new ArrayValue(); - for (var key : Values.getMembers(ctx, obj, true, true)) { - if (key instanceof Symbol) res.set(ctx, res.size(), key); + for (var key : Values.getMembers(args.ctx, obj, true, true)) { + if (key instanceof Symbol) res.set(args.ctx, res.size(), key); } return res; } - @Native public static boolean hasOwn(Context ctx, Object obj, Object key) { - return Values.hasMember(ctx, obj, key, true); + @Expose(target = ExposeTarget.STATIC) + public static boolean __hasOwn(Arguments args) { + return Values.hasMember(args.ctx, args.get(0), args.get(1), true); } - @Native public static ObjectValue getPrototypeOf(Context ctx, Object obj) { - return Values.getPrototype(ctx, obj); + @Expose(target = ExposeTarget.STATIC) + public static ObjectValue __getPrototypeOf(Arguments args) { + return Values.getPrototype(args.ctx, args.get(0)); } - @Native public static Object setPrototypeOf(Context ctx, Object obj, Object proto) { - Values.setPrototype(ctx, obj, proto); - return obj; + @Expose(target = ExposeTarget.STATIC) + public static Object __setPrototypeOf(Arguments args) { + Values.setPrototype(args.ctx, args.get(0), args.get(1)); + return args.get(0); } - @Native public static ObjectValue fromEntries(Context ctx, Object iterable) { + @Expose(target = ExposeTarget.STATIC) + public static ObjectValue __fromEntries(Arguments args) { var res = new ObjectValue(); - for (var el : Values.fromJSIterator(ctx, iterable)) { + for (var el : Values.fromJSIterator(args.ctx, args.get(0))) { if (el instanceof ArrayValue) { - res.defineProperty(ctx, ((ArrayValue)el).get(0), ((ArrayValue)el).get(1)); + res.defineProperty(args.ctx, ((ArrayValue)el).get(0), ((ArrayValue)el).get(1)); } } return res; } - @Native public static Object preventExtensions(Context ctx, Object obj) { - if (obj instanceof ObjectValue) ((ObjectValue)obj).preventExtensions(); - return obj; + @Expose(target = ExposeTarget.STATIC) + public static Object __preventExtensions(Arguments args) { + if (args.get(0) instanceof ObjectValue) args.convert(0, ObjectValue.class).preventExtensions(); + return args.get(0); } - @Native public static Object seal(Context ctx, Object obj) { - if (obj instanceof ObjectValue) ((ObjectValue)obj).seal(); - return obj; + @Expose(target = ExposeTarget.STATIC) + public static Object __seal(Arguments args) { + if (args.get(0) instanceof ObjectValue) args.convert(0, ObjectValue.class).seal(); + return args.get(0); } - @Native public static Object freeze(Context ctx, Object obj) { - if (obj instanceof ObjectValue) ((ObjectValue)obj).freeze(); - return obj; + @Expose(target = ExposeTarget.STATIC) + public static Object __freeze(Arguments args) { + if (args.get(0) instanceof ObjectValue) args.convert(0, ObjectValue.class).freeze(); + return args.get(0); } - @Native public static boolean isExtensible(Context ctx, Object obj) { + @Expose(target = ExposeTarget.STATIC) + public static boolean __isExtensible(Arguments args) { + var obj = args.get(0); return obj instanceof ObjectValue && ((ObjectValue)obj).extensible(); } - @Native public static boolean isSealed(Context ctx, Object obj) { + @Expose(target = ExposeTarget.STATIC) + public static boolean __isSealed(Arguments args) { + var obj = args.get(0); + if (obj instanceof ObjectValue && ((ObjectValue)obj).extensible()) { var _obj = (ObjectValue)obj; for (var key : _obj.keys(true)) { @@ -175,7 +223,10 @@ import me.topchetoeu.jscript.interop.NativeConstructor; return true; } - @Native public static boolean isFrozen(Context ctx, Object obj) { + @Expose(target = ExposeTarget.STATIC) + public static boolean __isFrozen(Arguments args) { + var obj = args.get(0); + if (obj instanceof ObjectValue && ((ObjectValue)obj).extensible()) { var _obj = (ObjectValue)obj; for (var key : _obj.keys(true)) { @@ -187,26 +238,31 @@ import me.topchetoeu.jscript.interop.NativeConstructor; return true; } - @Native(thisArg = true) public static Object valueOf(Context ctx, Object thisArg) { - return thisArg; + @Expose + public static Object __valueOf(Arguments args) { + return args.self; } - @Native(thisArg = true) public static String toString(Context ctx, Object thisArg) { - var name = Values.getMember(ctx, thisArg, Symbol.get("Symbol.typeName")); + @Expose + public static String __toString(Arguments args) { + var name = Values.getMember(args.ctx, args.self, Symbol.get("Symbol.typeName")); if (name == null) name = "Unknown"; - else name = Values.toString(ctx, name); + else name = Values.toString(args.ctx, name); return "[object " + name + "]"; } - @Native(thisArg = true) public static boolean hasOwnProperty(Context ctx, Object thisArg, Object key) { - return ObjectLib.hasOwn(ctx, thisArg, Values.convert(ctx, key, String.class)); + @Expose + public static boolean __hasOwnProperty(Arguments args) { + return Values.hasMember(args.ctx, args.self, args.get(0), true); } - @NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object arg) { + @ExposeConstructor + public static Object __constructor(Arguments args) { + var arg = args.get(0); if (arg == null || arg == Values.NULL) return new ObjectValue(); - else if (arg instanceof Boolean) return BooleanLib.constructor(ctx, thisArg, arg); - else if (arg instanceof Number) return NumberLib.constructor(ctx, thisArg, arg); - else if (arg instanceof String) return StringLib.constructor(ctx, thisArg, arg); - // else if (arg instanceof Symbol) return SymbolPolyfill.constructor(ctx, thisArg, arg); + else if (arg instanceof Boolean) return new BooleanLib((boolean)arg); + else if (arg instanceof Number) return new NumberLib(((Number)arg).doubleValue()); + else if (arg instanceof String) return new StringLib((String)arg); + else if (arg instanceof Symbol) return new SymbolLib((Symbol)arg); else return arg; } } diff --git a/src/me/topchetoeu/jscript/lib/PromiseLib.java b/src/me/topchetoeu/jscript/lib/PromiseLib.java index 3adac3a..e54bdd3 100644 --- a/src/me/topchetoeu/jscript/lib/PromiseLib.java +++ b/src/me/topchetoeu/jscript/lib/PromiseLib.java @@ -2,355 +2,314 @@ package me.topchetoeu.jscript.lib; import java.util.ArrayList; import java.util.List; -import java.util.Map; import me.topchetoeu.jscript.engine.Context; +import me.topchetoeu.jscript.engine.EventLoop; 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; +import me.topchetoeu.jscript.interop.Arguments; +import me.topchetoeu.jscript.interop.Expose; +import me.topchetoeu.jscript.interop.ExposeConstructor; +import me.topchetoeu.jscript.interop.ExposeTarget; +import me.topchetoeu.jscript.interop.WrapperName; +import me.topchetoeu.jscript.ResultRunnable; -@Native("Promise") public class PromiseLib { - public static interface PromiseRunner { - Object run(); - } - private static class Handle { - public final Context ctx; - public final FunctionValue fulfilled; - public final FunctionValue rejected; +@WrapperName("Promise") +public class PromiseLib { + public static interface Handle { + void onFulfil(Object val); + void onReject(EngineException err); - public Handle(Context ctx, FunctionValue fulfilled, FunctionValue rejected) { - this.ctx = ctx; - this.fulfilled = fulfilled; - this.rejected = rejected; + default Handle defer(EventLoop loop) { + var self = this; + return new Handle() { + @Override public void onFulfil(Object val) { + loop.pushMsg(() -> self.onFulfil(val), true); + } + @Override public void onReject(EngineException val) { + loop.pushMsg(() -> self.onReject(val), true); + } + }; } } - @Native("resolve") - public static PromiseLib ofResolved(Context ctx, Object val) { - var res = new PromiseLib(); - res.fulfill(ctx, val); - return res; - } - @Native("reject") - public static PromiseLib ofRejected(Context ctx, Object val) { - var res = new PromiseLib(); - res.reject(ctx, val); - return res; - } - - @Native public static PromiseLib any(Context ctx, Object _promises) { - if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); - var promises = Values.array(_promises); - if (promises.size() == 0) return ofResolved(ctx, new ArrayValue()); - var n = new int[] { promises.size() }; - var res = new PromiseLib(); - - 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 PromiseLib race(Context ctx, Object _promises) { - if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); - var promises = Values.array(_promises); - if (promises.size() == 0) return ofResolved(ctx, new ArrayValue()); - var res = new PromiseLib(); - - 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 PromiseLib all(Context ctx, Object _promises) { - if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); - var promises = Values.array(_promises); - if (promises.size() == 0) return ofResolved(ctx, new ArrayValue()); - var n = new int[] { promises.size() }; - var res = new PromiseLib(); - - 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 PromiseLib allSettled(Context ctx, Object _promises) { - if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); - var promises = Values.array(_promises); - if (promises.size() == 0) return ofResolved(ctx, new ArrayValue()); - var n = new int[] { promises.size() }; - var res = new PromiseLib(); - - 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(thisArg=true) public static Object then(Context ctx, Object _thisArg, Object _onFulfill, Object _onReject) { - var onFulfill = _onFulfill instanceof FunctionValue ? ((FunctionValue)_onFulfill) : null; - var onReject = _onReject instanceof FunctionValue ? ((FunctionValue)_onReject) : null; - - var res = new PromiseLib(); - - var fulfill = onFulfill == null ? new NativeFunction((_ctx, _0, _args) -> _args.length > 0 ? _args[0] : null) : (FunctionValue)onFulfill; - var reject = onReject == null ? new NativeFunction((_ctx, _0, _args) -> { - throw new EngineException(_args.length > 0 ? _args[0] : null); - }) : (FunctionValue)onReject; - - var thisArg = _thisArg instanceof NativeWrapper && ((NativeWrapper)_thisArg).wrapped instanceof PromiseLib ? - ((NativeWrapper)_thisArg).wrapped : - _thisArg; - - 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); } - if (thisArg instanceof PromiseLib) ((PromiseLib)thisArg).handled = true; - return null; - }); - - if (thisArg instanceof PromiseLib) ((PromiseLib)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", thisArg=true) public static Object _catch(Context ctx, Object thisArg, Object _onReject) { - return then(ctx, thisArg, null, _onReject); - } - /** - * Thread safe - you can call this from anywhere - * HOWEVER, it's strongly recommended to use this only in javascript - */ - @Native(value="finally", thisArg=true) public static Object _finally(Context ctx, Object thisArg, Object _handle) { - return then(ctx, thisArg, - new NativeFunction(null, (e, th, _args) -> { - if (_handle instanceof FunctionValue) ((FunctionValue)_handle).call(ctx); - return _args.length > 0 ? _args[0] : null; - }), - new NativeFunction(null, (e, th, _args) -> { - if (_handle instanceof FunctionValue) ((FunctionValue)_handle).call(ctx); - throw new EngineException(_args.length > 0 ? _args[0] : null); - }) - ); - } - - private List 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; + @Expose(value = "resolve", target = ExposeTarget.STATIC) + public static PromiseLib __ofResolved(Arguments args) { + return ofResolved(args.ctx, args.get(0)); + } + @Expose(value = "reject", target = ExposeTarget.STATIC) + public static PromiseLib __ofRejected(Arguments args) { + return ofRejected(args.ctx, new EngineException(args.get(0)).setCtx(args.ctx)); + } - public synchronized void fulfill(Context ctx, Object val) { - if (this.state != STATE_PENDING) return; + @Expose(target = ExposeTarget.STATIC) + private static PromiseLib __any(Arguments args) { + if (!(args.get(0) instanceof ArrayValue)) throw EngineException.ofType("Expected argument for any to be an array."); + var promises = args.convert(0, ArrayValue.class); - if (val instanceof PromiseLib) ((PromiseLib)val).handle(ctx, - new NativeFunction(null, (e, th, a) -> { this.fulfill(ctx, a[0]); return null; }), - new NativeFunction(null, (e, th, a) -> { this.reject(ctx, a[0]); return null; }) - ); - else { - Object then; - try { then = Values.getMember(ctx, val, "then"); } - catch (IllegalArgumentException e) { then = null; } + if (promises.size() == 0) return ofRejected(args.ctx, EngineException.ofError("No promises passed to 'Promise.any'.").setCtx(args.ctx)); + var n = new int[] { promises.size() }; + var res = new PromiseLib(); + var errors = new ArrayValue(); - try { - if (then instanceof FunctionValue) ((FunctionValue)then).call(ctx, val, - new NativeFunction((e, _thisArg, a) -> { this.fulfill(ctx, a.length > 0 ? a[0] : null); return null; }), - new NativeFunction((e, _thisArg, a) -> { this.reject(ctx, a.length > 0 ? a[0] : null); return null; }) - ); - else { - this.val = val; - this.state = STATE_FULFILLED; + for (var i = 0; i < promises.size(); i++) { + var index = i; + var val = promises.get(i); + if (res.state != STATE_PENDING) break; - ctx.engine.pushMsg(true, ctx.environment, new NativeFunction((_ctx, _thisArg, _args) -> { - for (var handle : handles) { - handle.fulfilled.call(handle.ctx, null, val); - } - handles = null; - return null; - }), null); + handle(args.ctx, val, new Handle() { + public void onFulfil(Object val) { res.fulfill(args.ctx, val); } + public void onReject(EngineException err) { + errors.set(args.ctx, index, err.value); + n[0]--; + if (n[0] <= 0) res.reject(args.ctx, new EngineException(errors).setCtx(args.ctx)); } - } - catch (EngineException err) { - this.reject(ctx, err.value); - } + }); } + + return res; } - public synchronized void reject(Context ctx, Object val) { - if (this.state != STATE_PENDING) return; + @Expose(target = ExposeTarget.STATIC) + private static PromiseLib __race(Arguments args) { + if (!(args.get(0) instanceof ArrayValue)) throw EngineException.ofType("Expected argument for any to be an array."); + var promises = args.convert(0, ArrayValue.class); + var res = new PromiseLib(); - if (val instanceof PromiseLib) ((PromiseLib)val).handle(ctx, - new NativeFunction(null, (e, th, a) -> { this.reject(ctx, a[0]); return null; }), - new NativeFunction(null, (e, th, a) -> { this.reject(ctx, a[0]); return null; }) - ); - else { - Object then; - try { then = Values.getMember(ctx, val, "then"); } - catch (IllegalArgumentException e) { then = null; } + for (var i = 0; i < promises.size(); i++) { + var val = promises.get(i); + if (res.state != STATE_PENDING) break; - try { - if (then instanceof FunctionValue) ((FunctionValue)then).call(ctx, val, - new NativeFunction((e, _thisArg, a) -> { this.reject(ctx, a.length > 0 ? a[0] : null); return null; }), - new NativeFunction((e, _thisArg, a) -> { this.reject(ctx, a.length > 0 ? a[0] : null); return null; }) - ); - else { - this.val = val; - this.state = STATE_REJECTED; + handle(args.ctx, val, new Handle() { + @Override public void onFulfil(Object val) { res.fulfill(args.ctx, val); } + @Override public void onReject(EngineException err) { res.reject(args.ctx, err); } + }); + } - ctx.engine.pushMsg(true, ctx.environment, new NativeFunction((_ctx, _thisArg, _args) -> { - for (var handle : handles) handle.rejected.call(handle.ctx, null, val); - if (!handled) { - Values.printError(new EngineException(val).setCtx(ctx.environment, ctx.engine), "(in promise)"); - } - handles = null; - return null; - }), null); + return res; + } + @Expose(target = ExposeTarget.STATIC) + private static PromiseLib __all(Arguments args) { + if (!(args.get(0) instanceof ArrayValue)) throw EngineException.ofType("Expected argument for any to be an array."); + var promises = args.convert(0, ArrayValue.class); + var n = new int[] { promises.size() }; + var res = new PromiseLib(); + var result = new ArrayValue(); + + for (var i = 0; i < promises.size(); i++) { + if (res.state != STATE_PENDING) break; + + var index = i; + var val = promises.get(i); + + handle(args.ctx, val, new Handle() { + @Override public void onFulfil(Object val) { + result.set(args.ctx, index, val); + n[0]--; + if (n[0] <= 0) res.fulfill(args.ctx, result); } - } - catch (EngineException err) { - this.reject(ctx, err.value); - } + @Override public void onReject(EngineException err) { + res.reject(args.ctx, err); + } + }); } - } - private void handle(Context ctx, FunctionValue fulfill, FunctionValue reject) { - if (state == STATE_FULFILLED) ctx.engine.pushMsg(true, ctx.environment, fulfill, null, val); - else if (state == STATE_REJECTED) { - ctx.engine.pushMsg(true, ctx.environment, reject, null, val); - handled = true; + if (n[0] <= 0) res.fulfill(args.ctx, result); + + return res; + } + @Expose(target = ExposeTarget.STATIC) + private static PromiseLib __allSettled(Arguments args) { + if (!(args.get(0) instanceof ArrayValue)) throw EngineException.ofType("Expected argument for any to be an array."); + var promises = args.convert(0, ArrayValue.class); + var n = new int[] { promises.size() }; + var res = new PromiseLib(); + var result = new ArrayValue(); + + for (var i = 0; i < promises.size(); i++) { + if (res.state != STATE_PENDING) break; + + var index = i; + + handle(args.ctx, promises.get(i), new Handle() { + @Override public void onFulfil(Object val) { + var desc = new ObjectValue(); + desc.defineProperty(args.ctx, "status", "fulfilled"); + desc.defineProperty(args.ctx, "value", val); + + result.set(args.ctx, index, desc); + + n[0]--; + if (n[0] <= 0) res.fulfill(args.ctx, res); + } + @Override public void onReject(EngineException err) { + var desc = new ObjectValue(); + desc.defineProperty(args.ctx, "status", "reject"); + desc.defineProperty(args.ctx, "value", err.value); + + result.set(args.ctx, index, desc); + + n[0]--; + if (n[0] <= 0) res.fulfill(args.ctx, res); + } + }); } - else handles.add(new Handle(ctx, fulfill, reject)); + + if (n[0] <= 0) res.fulfill(args.ctx, result); + + return res; } - @Override @Native public String toString() { - if (state == STATE_PENDING) return "Promise (pending)"; - else if (state == STATE_FULFILLED) return "Promise (fulfilled)"; - else return "Promise (rejected)"; + @Expose + private static Object __then(Arguments args) { + var onFulfill = args.get(0) instanceof FunctionValue ? args.convert(0, FunctionValue.class) : null; + var onReject = args.get(1) instanceof FunctionValue ? args.convert(1, FunctionValue.class) : null; + + var res = new PromiseLib(); + + handle(args.ctx, args.self, new Handle() { + @Override public void onFulfil(Object val) { + try { res.fulfill(args.ctx, onFulfill.call(args.ctx, null, val)); } + catch (EngineException e) { res.reject(args.ctx, e); } + } + @Override public void onReject(EngineException err) { + try { res.fulfill(args.ctx, onReject.call(args.ctx, null, err.value)); } + catch (EngineException e) { res.reject(args.ctx, e); } + } + }.defer(args.ctx.engine)); + + return res; + } + @Expose + private static Object __catch(Arguments args) { + return __then(new Arguments(args.ctx, args.self, null, args.get(0))); + } + @Expose + private static Object __finally(Arguments args) { + var func = args.get(0) instanceof FunctionValue ? args.convert(0, FunctionValue.class) : null; + + var res = new PromiseLib(); + + handle(args.ctx, args.self, new Handle() { + @Override public void onFulfil(Object val) { + try { + func.call(args.ctx); + res.fulfill(args.ctx, val); + } + catch (EngineException e) { res.reject(args.ctx, e); } + } + @Override public void onReject(EngineException err) { + try { + func.call(args.ctx); + res.reject(args.ctx, err); + } + catch (EngineException e) { res.reject(args.ctx, e); } + } + }.defer(args.ctx.engine)); + + return res; } - /** - * NOT THREAD SAFE - must be called from the engine executor thread - */ - @Native public PromiseLib(Context ctx, FunctionValue func) { - if (!(func instanceof FunctionValue)) throw EngineException.ofType("A function must be passed to the promise constructor."); + @ExposeConstructor + private static PromiseLib __constructor(Arguments args) { + var func = args.convert(0, FunctionValue.class); + var res = new PromiseLib(); + try { func.call( - ctx, null, - new NativeFunction(null, (e, th, args) -> { - fulfill(e, args.length > 0 ? args[0] : null); + args.ctx, null, + new NativeFunction(null, _args -> { + res.fulfill(_args.ctx, _args.get(0)); return null; }), - new NativeFunction(null, (e, th, args) -> { - reject(e, args.length > 0 ? args[0] : null); + new NativeFunction(null, _args -> { + res.reject(_args.ctx, new EngineException(_args.get(0)).setCtx(_args.ctx)); return null; }) ); } catch (EngineException e) { - reject(ctx, e.value); + res.reject(args.ctx, e); } + + return res; } - private PromiseLib(int state, Object val) { - this.state = state; - this.val = val; + private List handles = new ArrayList<>(); + + private int state = STATE_PENDING; + private boolean handled = false; + private Object val; + + private void resolveSynchronized(Context ctx, Object val, int newState) { + ctx.engine.pushMsg(() -> { + this.val = val; + this.state = newState; + + for (var handle : handles) { + if (newState == STATE_FULFILLED) handle.onFulfil(val); + if (newState == STATE_REJECTED) { + handle.onReject((EngineException)val); + handled = true; + } + } + + if (state == STATE_REJECTED && !handled) { + Values.printError(new EngineException(val).setCtx(ctx.environment, ctx.engine), "(in promise)"); + } + + handles = null; + }, true); + } + private synchronized void resolve(Context ctx, Object val, int newState) { + if (this.state != STATE_PENDING || newState == STATE_PENDING) return; + + handle(ctx, val, new Handle() { + @Override public void onFulfil(Object val) { + resolveSynchronized(ctx, val, newState); + } + @Override public void onReject(EngineException err) { + resolveSynchronized(ctx, val, STATE_REJECTED); + } + }); + } + + public synchronized void fulfill(Context ctx, Object val) { + resolve(ctx, val, STATE_FULFILLED); + } + public synchronized void reject(Context ctx, EngineException val) { + resolve(ctx, val, STATE_REJECTED); + } + + private void handle(Handle handle) { + if (state == STATE_FULFILLED) handle.onFulfil(val); + else if (state == STATE_REJECTED) { + handle.onReject((EngineException)val); + handled = true; + } + else handles.add(handle); + } + + @Override public String toString() { + if (state == STATE_PENDING) return "Promise (pending)"; + else if (state == STATE_FULFILLED) return "Promise (fulfilled)"; + else return "Promise (rejected)"; + } + public PromiseLib() { - this(STATE_PENDING, null); + this.state = STATE_PENDING; + this.val = null; } - public static PromiseLib await(Context ctx, PromiseRunner runner) { + public static PromiseLib await(Context ctx, ResultRunnable runner) { var res = new PromiseLib(); new Thread(() -> { @@ -358,10 +317,70 @@ import me.topchetoeu.jscript.interop.Native; res.fulfill(ctx, runner.run()); } catch (EngineException e) { - res.reject(ctx, e.value); + res.reject(ctx, e); } }, "Promisifier").start(); return res; } + public static PromiseLib await(Context ctx, Runnable runner) { + return await(ctx, () -> { + runner.run(); + return null; + }); + } + + public static void handle(Context ctx, Object obj, Handle handle) { + if (Values.isWrapper(obj, PromiseLib.class)) { + var promise = Values.wrapper(obj, PromiseLib.class); + handle(ctx, promise, handle); + return; + } + if (obj instanceof PromiseLib) { + ((PromiseLib)obj).handle(handle); + return; + } + + var rethrow = new boolean[1]; + + try { + var then = Values.getMember(ctx, obj, "then"); + Values.call(ctx, then, obj, + new NativeFunction(args -> { + try { handle.onFulfil(args.get(0)); } + catch (Exception e) { + rethrow[0] = true; + throw e; + } + return null; + }), + new NativeFunction(args -> { + try { handle.onReject(new EngineException(args.get(0))); } + catch (Exception e) { + rethrow[0] = true; + throw e; + } + return null; + }) + ); + + return; + } + catch (Exception e) { + if (rethrow[0]) throw e; + } + + handle.onFulfil(obj); + } + + public static PromiseLib ofResolved(Context ctx, Object value) { + var res = new PromiseLib(); + res.fulfill(ctx, value); + return res; + } + public static PromiseLib ofRejected(Context ctx, EngineException value) { + var res = new PromiseLib(); + res.reject(ctx, value); + return res; + } } diff --git a/src/me/topchetoeu/jscript/lib/RangeErrorLib.java b/src/me/topchetoeu/jscript/lib/RangeErrorLib.java index 3c48d42..86b10ad 100644 --- a/src/me/topchetoeu/jscript/lib/RangeErrorLib.java +++ b/src/me/topchetoeu/jscript/lib/RangeErrorLib.java @@ -1,22 +1,22 @@ package me.topchetoeu.jscript.lib; -import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto; -import me.topchetoeu.jscript.interop.InitType; -import me.topchetoeu.jscript.interop.Native; -import me.topchetoeu.jscript.interop.NativeConstructor; -import me.topchetoeu.jscript.interop.NativeInit; +import me.topchetoeu.jscript.interop.WrapperName; +import me.topchetoeu.jscript.interop.Arguments; +import me.topchetoeu.jscript.interop.ExposeConstructor; +import me.topchetoeu.jscript.interop.ExposeField; +import me.topchetoeu.jscript.interop.ExposeTarget; -@Native("RangeError") public class RangeErrorLib extends ErrorLib { - @NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) { - var target = ErrorLib.constructor(ctx, thisArg, message); - target.setPrototype(PlaceholderProto.SYNTAX_ERROR); - target.defineProperty(ctx, "name", "RangeError"); +@WrapperName("RangeError") +public class RangeErrorLib extends ErrorLib { + @ExposeField(target = ExposeTarget.STATIC) + public static final String __name = "RangeError"; + + @ExposeConstructor + public static ObjectValue constructor(Arguments args) { + var target = ErrorLib.__constructor(args); + target.setPrototype(PlaceholderProto.RANGE_ERROR); return target; } - @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { - target.defineProperty(null, "name", "RangeError"); - } } \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/lib/RegExpLib.java b/src/me/topchetoeu/jscript/lib/RegExpLib.java index cea6970..64c5b1d 100644 --- a/src/me/topchetoeu/jscript/lib/RegExpLib.java +++ b/src/me/topchetoeu/jscript/lib/RegExpLib.java @@ -10,79 +10,63 @@ import me.topchetoeu.jscript.engine.values.FunctionValue; 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.interop.Native; -import me.topchetoeu.jscript.interop.NativeGetter; +import me.topchetoeu.jscript.interop.Arguments; +import me.topchetoeu.jscript.interop.Expose; +import me.topchetoeu.jscript.interop.ExposeConstructor; +import me.topchetoeu.jscript.interop.ExposeTarget; +import me.topchetoeu.jscript.interop.ExposeType; +import me.topchetoeu.jscript.interop.WrapperName; -@Native("RegExp") public class RegExpLib { +@WrapperName("RegExp") +public class RegExpLib { // I used Regex to analyze Regex private static final Pattern NAMED_PATTERN = Pattern.compile("\\(\\?<([^=!].*?)>", Pattern.DOTALL); private static final Pattern ESCAPE_PATTERN = Pattern.compile("[/\\-\\\\^$*+?.()|\\[\\]{}]"); - private static String cleanupPattern(Context ctx, Object val) { - if (val == null) return "(?:)"; - if (val instanceof RegExpLib) return ((RegExpLib)val).source; - if (val instanceof NativeWrapper && ((NativeWrapper)val).wrapped instanceof RegExpLib) { - return ((RegExpLib)((NativeWrapper)val).wrapped).source; - } - var res = Values.toString(ctx, val); - if (res.equals("")) return "(?:)"; - return res; - } - private static String cleanupFlags(Context ctx, Object val) { - if (val == null) return ""; - return Values.toString(ctx, val); - } - - private static boolean checkEscaped(String s, int pos) { - int n = 0; - - while (true) { - if (pos <= 0) break; - if (s.charAt(pos) != '\\') break; - n++; - pos--; - } - - return (n % 2) != 0; - } - - @Native - public static RegExpLib escape(Context ctx, Object raw, Object flags) { - return escape(Values.toString(ctx, raw), cleanupFlags(ctx, flags)); - } - public static RegExpLib escape(String raw, String flags) { - return new RegExpLib(ESCAPE_PATTERN.matcher(raw).replaceAll("\\\\$0"), flags); - } - private Pattern pattern; private String[] namedGroups; private int flags; - - @Native public int lastI = 0; - @Native public final String source; - @Native public final boolean hasIndices; - @Native public final boolean global; - @Native public final boolean sticky; - @Native("@@Symbol.typeName") public final String name = "RegExp"; - @NativeGetter public boolean ignoreCase() { return (flags & Pattern.CASE_INSENSITIVE) != 0; } - @NativeGetter public boolean multiline() { return (flags & Pattern.MULTILINE) != 0; } - @NativeGetter public boolean unicode() { return (flags & Pattern.UNICODE_CHARACTER_CLASS) != 0; } - @NativeGetter public boolean dotAll() { return (flags & Pattern.DOTALL) != 0; } + public int lastI = 0; + public final String source; + public final boolean hasIndices; + public final boolean global; + public final boolean sticky; - @NativeGetter("flags") public final String flags() { + @Expose(type = ExposeType.GETTER) + public int __lastIndex() { return lastI; } + @Expose(type = ExposeType.SETTER) + public void __setLastIndex(Arguments args) { lastI = args.getInt(0); } + @Expose(type = ExposeType.GETTER) + public String __source() { return source; } + + @Expose(type = ExposeType.GETTER) + public boolean __ignoreCase() { return (flags & Pattern.CASE_INSENSITIVE) != 0; } + @Expose(type = ExposeType.GETTER) + public boolean __multiline() { return (flags & Pattern.MULTILINE) != 0; } + @Expose(type = ExposeType.GETTER) + public boolean __unicode() { return (flags & Pattern.UNICODE_CHARACTER_CLASS) != 0; } + @Expose(type = ExposeType.GETTER) + public boolean __dotAll() { return (flags & Pattern.DOTALL) != 0; } + @Expose(type = ExposeType.GETTER) + public boolean __global() { return global; } + @Expose(type = ExposeType.GETTER) + public boolean __sticky() { return sticky; } + @Expose(type = ExposeType.GETTER) + public final String __flags() { String res = ""; if (hasIndices) res += 'd'; if (global) res += 'g'; - if (ignoreCase()) res += 'i'; - if (multiline()) res += 'm'; - if (dotAll()) res += 's'; - if (unicode()) res += 'u'; + if (__ignoreCase()) res += 'i'; + if (__multiline()) res += 'm'; + if (__dotAll()) res += 's'; + if (__unicode()) res += 'u'; if (sticky) res += 'y'; return res; } - @Native public Object exec(String str) { + @Expose public Object __exec(Arguments args) { + var str = args.getString(0); var matcher = pattern.matcher(str); if (lastI > str.length() || !matcher.find(lastI) || sticky && matcher.start() != lastI) { lastI = 0; @@ -126,39 +110,36 @@ import me.topchetoeu.jscript.interop.NativeGetter; return obj; } - @Native public boolean test(String str) { - return this.exec(str) != Values.NULL; - } - @Native public String toString() { - return "/" + source + "/" + flags(); + @Expose public boolean __test(Arguments args) { + return this.__exec(args) != Values.NULL; } - @Native("@@Symbol.match") public Object match(Context ctx, String target) { + @Expose("@@Symbol.match") public Object __match(Arguments args) { if (this.global) { var res = new ArrayValue(); Object val; - while ((val = this.exec(target)) != Values.NULL) { - res.set(ctx, res.size(), Values.getMember(ctx, val, 0)); + while ((val = this.__exec(args)) != Values.NULL) { + res.set(args.ctx, res.size(), Values.getMember(args.ctx, val, 0)); } lastI = 0; return res; } else { - var res = this.exec(target); + var res = this.__exec(args); if (!this.sticky) this.lastI = 0; return res; } } - @Native("@@Symbol.matchAll") public Object matchAll(Context ctx, String target) { - var pattern = new RegExpLib(this.source, this.flags() + "g"); + @Expose("@@Symbol.matchAll") public Object __matchAll(Arguments args) { + var pattern = this.toGlobal(); - return Values.toJSIterator(ctx, new Iterator() { + return Values.toJSIterator(args.ctx, new Iterator() { private Object val = null; private boolean updated = false; private void update() { - if (!updated) val = pattern.exec(target); + if (!updated) val = pattern.__exec(args); } @Override public boolean hasNext() { update(); @@ -172,17 +153,21 @@ import me.topchetoeu.jscript.interop.NativeGetter; }); } - @Native("@@Symbol.split") public ArrayValue split(Context ctx, String target, Object limit, boolean sensible) { - var pattern = new RegExpLib(this.source, this.flags() + "g"); + @Expose("@@Symbol.split") public ArrayValue __split(Arguments args) { + var pattern = this.toGlobal(); + var target = args.getString(0); + var hasLimit = args.get(1) != null; + var lim = args.getInt(1); + var sensible = args.getBoolean(2); + Object match; int lastEnd = 0; var res = new ArrayValue(); - var lim = limit == null ? 0 : Values.toNumber(ctx, limit); - while ((match = pattern.exec(target)) != Values.NULL) { + while ((match = pattern.__exec(args)) != Values.NULL) { var added = new ArrayList(); var arrMatch = (ArrayValue)match; - int index = (int)Values.toNumber(ctx, Values.getMember(ctx, match, "index")); + int index = (int)Values.toNumber(args.ctx, Values.getMember(args.ctx, match, "index")); var matchVal = (String)arrMatch.get(0); if (index >= target.length()) break; @@ -198,31 +183,33 @@ import me.topchetoeu.jscript.interop.NativeGetter; } if (sensible) { - if (limit != null && res.size() + added.size() >= lim) break; - else for (var i = 0; i < added.size(); i++) res.set(ctx, res.size(), added.get(i)); + if (hasLimit && res.size() + added.size() >= lim) break; + else for (var i = 0; i < added.size(); i++) res.set(args.ctx, res.size(), added.get(i)); } else { for (var i = 0; i < added.size(); i++) { - if (limit != null && res.size() >= lim) return res; - else res.set(ctx, res.size(), added.get(i)); + if (hasLimit && res.size() >= lim) return res; + else res.set(args.ctx, res.size(), added.get(i)); } } lastEnd = pattern.lastI; } if (lastEnd < target.length()) { - res.set(ctx, res.size(), target.substring(lastEnd)); + res.set(args.ctx, res.size(), target.substring(lastEnd)); } return res; } - @Native("@@Symbol.replace") public String replace(Context ctx, String target, Object replacement) { - var pattern = new RegExpLib(this.source, this.flags() + "d"); + @Expose("@@Symbol.replace") public String __replace(Arguments args) { + var pattern = this.toIndexed(); + var target = args.getString(0); + var replacement = args.get(1); Object match; var lastEnd = 0; var res = new StringBuilder(); - while ((match = pattern.exec(target)) != Values.NULL) { - var indices = (ArrayValue)((ArrayValue)Values.getMember(ctx, match, "indices")).get(0); + while ((match = pattern.__exec(args)) != Values.NULL) { + var indices = (ArrayValue)((ArrayValue)Values.getMember(args.ctx, match, "indices")).get(0); var arrMatch = (ArrayValue)match; var start = ((Number)indices.get(0)).intValue(); @@ -230,15 +217,15 @@ import me.topchetoeu.jscript.interop.NativeGetter; res.append(target.substring(lastEnd, start)); if (replacement instanceof FunctionValue) { - var args = new Object[arrMatch.size() + 2]; - args[0] = target.substring(start, end); - arrMatch.copyTo(args, 1, 1, arrMatch.size() - 1); - args[args.length - 2] = start; - args[args.length - 1] = target; - res.append(Values.toString(ctx, ((FunctionValue)replacement).call(ctx, null, args))); + var callArgs = new Object[arrMatch.size() + 2]; + callArgs[0] = target.substring(start, end); + arrMatch.copyTo(callArgs, 1, 1, arrMatch.size() - 1); + callArgs[callArgs.length - 2] = start; + callArgs[callArgs.length - 1] = target; + res.append(Values.toString(args.ctx, ((FunctionValue)replacement).call(args.ctx, null, callArgs))); } else { - res.append(Values.toString(ctx, replacement)); + res.append(Values.toString(args.ctx, replacement)); } lastEnd = end; if (!pattern.global) break; @@ -271,9 +258,17 @@ import me.topchetoeu.jscript.interop.NativeGetter; // } // }, - @Native public RegExpLib(Context ctx, Object pattern, Object flags) { - this(cleanupPattern(ctx, pattern), cleanupFlags(ctx, flags)); + public RegExpLib toGlobal() { + return new RegExpLib(pattern, namedGroups, flags, source, hasIndices, true, sticky); } + public RegExpLib toIndexed() { + return new RegExpLib(pattern, namedGroups, flags, source, true, global, sticky); + } + + public String toString() { + return "/" + source + "/" + __flags(); + } + public RegExpLib(String pattern, String flags) { if (pattern == null || pattern.equals("")) pattern = "(?:)"; if (flags == null || flags.equals("")) flags = ""; @@ -304,6 +299,56 @@ import me.topchetoeu.jscript.interop.NativeGetter; namedGroups = groups.toArray(String[]::new); } + private RegExpLib(Pattern pattern, String[] namedGroups, int flags, String source, boolean hasIndices, boolean global, boolean sticky) { + this.pattern = pattern; + this.namedGroups = namedGroups; + this.flags = flags; + this.source = source; + this.hasIndices = hasIndices; + this.global = global; + this.sticky = sticky; + } public RegExpLib(String pattern) { this(pattern, null); } public RegExpLib() { this(null, null); } + + @ExposeConstructor + public static RegExpLib __constructor(Arguments args) { + return new RegExpLib(cleanupPattern(args.ctx, args.get(0)), cleanupFlags(args.ctx, args.get(1))); + } + @Expose(target = ExposeTarget.STATIC) + public static RegExpLib __escape(Arguments args) { + return escape(Values.toString(args.ctx, args.get(0)), cleanupFlags(args.ctx, args.get(1))); + } + + private static String cleanupPattern(Context ctx, Object val) { + if (val == null) return "(?:)"; + if (val instanceof RegExpLib) return ((RegExpLib)val).source; + if (val instanceof NativeWrapper && ((NativeWrapper)val).wrapped instanceof RegExpLib) { + return ((RegExpLib)((NativeWrapper)val).wrapped).source; + } + var res = Values.toString(ctx, val); + if (res.equals("")) return "(?:)"; + return res; + } + private static String cleanupFlags(Context ctx, Object val) { + if (val == null) return ""; + return Values.toString(ctx, val); + } + + private static boolean checkEscaped(String s, int pos) { + int n = 0; + + while (true) { + if (pos <= 0) break; + if (s.charAt(pos) != '\\') break; + n++; + pos--; + } + + return (n % 2) != 0; + } + + public static RegExpLib escape(String raw, String flags) { + return new RegExpLib(ESCAPE_PATTERN.matcher(raw).replaceAll("\\\\$0"), flags); + } } diff --git a/src/me/topchetoeu/jscript/lib/SetLib.java b/src/me/topchetoeu/jscript/lib/SetLib.java index 2cc35c2..bda9149 100644 --- a/src/me/topchetoeu/jscript/lib/SetLib.java +++ b/src/me/topchetoeu/jscript/lib/SetLib.java @@ -6,55 +6,64 @@ import java.util.stream.Collectors; 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.Values; -import me.topchetoeu.jscript.interop.Native; -import me.topchetoeu.jscript.interop.NativeGetter; +import me.topchetoeu.jscript.interop.Arguments; +import me.topchetoeu.jscript.interop.Expose; +import me.topchetoeu.jscript.interop.ExposeConstructor; +import me.topchetoeu.jscript.interop.ExposeType; +import me.topchetoeu.jscript.interop.WrapperName; -@Native("Set") public class SetLib { +@WrapperName("Set") +public class SetLib { private LinkedHashSet set = new LinkedHashSet<>(); - @Native("@@Symbol.typeName") public final String name = "Set"; - @Native("@@Symbol.iterator") public ObjectValue iterator(Context ctx) { - return this.values(ctx); + @Expose("@@Symbol.iterator") + public ObjectValue __iterator(Arguments args) { + return this.__values(args); } - @Native public ObjectValue entries(Context ctx) { - return ArrayValue.of(ctx, set.stream().map(v -> new ArrayValue(ctx, v, v)).collect(Collectors.toList())); + @Expose public ObjectValue __entries(Arguments args) { + return ArrayValue.of(args.ctx, set.stream().map(v -> new ArrayValue(args.ctx, v, v)).collect(Collectors.toList())); } - @Native public ObjectValue keys(Context ctx) { - return ArrayValue.of(ctx, set); + @Expose public ObjectValue __keys(Arguments args) { + return ArrayValue.of(args.ctx, set); } - @Native public ObjectValue values(Context ctx) { - return ArrayValue.of(ctx, set); + @Expose public ObjectValue __values(Arguments args) { + return ArrayValue.of(args.ctx, set); } - @Native public Object add(Object key) { - return set.add(key); + @Expose public Object __add(Arguments args) { + return set.add(args.get(0)); } - @Native public boolean delete(Object key) { - return set.remove(key); + @Expose public boolean __delete(Arguments args) { + return set.remove(args.get(0)); } - @Native public boolean has(Object key) { - return set.contains(key); + @Expose public boolean __has(Arguments args) { + return set.contains(args.get(0)); } - @Native public void clear() { + @Expose public void __clear() { set.clear(); } - @NativeGetter public int size() { + @Expose(type = ExposeType.GETTER) + public int __size() { return set.size(); } - @Native public void forEach(Context ctx, FunctionValue func, Object thisArg) { + @Expose public void __forEach(Arguments args) { var keys = new ArrayList<>(set); - for (var el : keys) func.call(ctx, thisArg, el, el, this); + for (var el : keys) Values.call(args.ctx, args.get(0), args.get(1), el, el, this); } - @Native public SetLib(Context ctx, Object iterable) { - for (var el : Values.fromJSIterator(ctx, iterable)) add(el); + public SetLib(Context ctx, Object iterable) { + for (var el : Values.fromJSIterator(ctx, iterable)) set.add(el); + } + + @ExposeConstructor + public static SetLib __constructor(Arguments args) { + return new SetLib(args.ctx, args.get(0)); } } diff --git a/src/me/topchetoeu/jscript/lib/StringLib.java b/src/me/topchetoeu/jscript/lib/StringLib.java index 620bf1d..c377a38 100644 --- a/src/me/topchetoeu/jscript/lib/StringLib.java +++ b/src/me/topchetoeu/jscript/lib/StringLib.java @@ -2,7 +2,6 @@ package me.topchetoeu.jscript.lib; import java.util.regex.Pattern; -import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.FunctionValue; @@ -10,15 +9,20 @@ 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; -import me.topchetoeu.jscript.interop.NativeGetter; +import me.topchetoeu.jscript.interop.Arguments; +import me.topchetoeu.jscript.interop.Expose; +import me.topchetoeu.jscript.interop.ExposeConstructor; +import me.topchetoeu.jscript.interop.ExposeTarget; +import me.topchetoeu.jscript.interop.ExposeType; +import me.topchetoeu.jscript.interop.WrapperName; // TODO: implement index wrapping properly -@Native("String") public class StringLib { +@WrapperName("String") +public class StringLib { public final String value; - private static String passThis(Context ctx, String funcName, Object val) { + private static String passThis(Arguments args, String funcName) { + var val = args.self; if (val instanceof StringLib) return ((StringLib)val).value; else if (val instanceof String) return (String)val; else throw EngineException.ofType(String.format("'%s' may only be called upon object and primitve strings.", funcName)); @@ -32,170 +36,174 @@ import me.topchetoeu.jscript.interop.NativeGetter; return i; } - @NativeGetter(thisArg = true) public static int length(Context ctx, Object thisArg) { - return passThis(ctx, "substring", thisArg).length(); + @Expose(type = ExposeType.GETTER) + public static int __length(Arguments args) { + return passThis(args, "substring").length(); } - @Native(thisArg = true) public static String substring(Context ctx, Object thisArg, int start, Object _end) { - var val = passThis(ctx, "substring", thisArg); - start = normalizeI(start, val.length(), true); - int end = normalizeI(_end == null ? val.length() : (int)Values.toNumber(ctx, _end), val.length(), true); + @Expose public static String __substring(Arguments args) { + var val = passThis(args, "substring"); + var start = normalizeI(args.getInt(0), val.length(), true); + var end = normalizeI(args.getInt(1, val.length()), val.length(), true); return val.substring(start, end); } - @Native(thisArg = true) public static String substr(Context ctx, Object thisArg, int start, Object _len) { - var val = passThis(ctx, "substr", thisArg); - int len = _len == null ? val.length() - start : (int)Values.toNumber(ctx, _len); - return substring(ctx, val, start, start + len); + @Expose public static String __substr(Arguments args) { + var val = passThis(args, "substr"); + var start = normalizeI(args.getInt(0), val.length(), true); + int len = normalizeI(args.getInt(0), val.length() - start, true); + return val.substring(start, start + len); } - @Native(thisArg = true) public static String toLowerCase(Context ctx, Object thisArg) { - return passThis(ctx, "toLowerCase", thisArg).toLowerCase(); + @Expose public static String __toLowerCase(Arguments args) { + return passThis(args, "toLowerCase").toLowerCase(); } - @Native(thisArg = true) public static String toUpperCase(Context ctx, Object thisArg) { - return passThis(ctx, "toUpperCase", thisArg).toUpperCase(); + @Expose public static String __toUpperCase(Arguments args) { + return passThis(args, "toUpperCase").toUpperCase(); } - @Native(thisArg = true) public static String charAt(Context ctx, Object thisArg, int i) { - return passThis(ctx, "charAt", thisArg).charAt(i) + ""; + @Expose public static String __charAt(Arguments args) { + return passThis(args, "charAt").charAt(args.getInt(0)) + ""; } - @Native(thisArg = true) public static double charCodeAt(Context ctx, Object thisArg, int i) { - var str = passThis(ctx, "charCodeAt", thisArg); + @Expose public static double __charCodeAt(Arguments args) { + var str = passThis(args, "charCodeAt"); + var i = args.getInt(0); if (i < 0 || i >= str.length()) return Double.NaN; else return str.charAt(i); } - @Native(thisArg = true) public static double codePointAt(Context ctx, Object thisArg, int i) { - var str = passThis(ctx, "codePointAt", thisArg); + @Expose public static double __codePointAt(Arguments args) { + var str = passThis(args, "codePointAt"); + var i = args.getInt(0); if (i < 0 || i >= str.length()) return Double.NaN; else return str.codePointAt(i); } - @Native(thisArg = true) public static boolean startsWith(Context ctx, Object thisArg, String term, int pos) { - return passThis(ctx, "startsWith", thisArg).startsWith(term, pos); + @Expose public static boolean __startsWith(Arguments args) { + return passThis(args, "startsWith").startsWith(args.getString(0), args.getInt(1)); } - @Native(thisArg = true) public static boolean endsWith(Context ctx, Object thisArg, String term, int pos) { - var val = passThis(ctx, "endsWith", thisArg); - return val.lastIndexOf(term, pos) >= 0; + @Expose public static boolean __endsWith(Arguments args) { + return passThis(args, "endsWith").lastIndexOf(args.getString(0), args.getInt(1)) >= 0; } - @Native(thisArg = true) public static int indexOf(Context ctx, Object thisArg, Object term, int start) { - var val = passThis(ctx, "indexOf", thisArg); + @Expose public static int __indexOf(Arguments args) { + var val = passThis(args, "indexOf"); + var term = args.get(0); + var start = args.getInt(1); + var search = Values.getMember(args.ctx, term, Symbol.get("Symbol.search")); - if (term != null && term != Values.NULL && !(term instanceof String)) { - var search = Values.getMember(ctx, term, Symbol.get("Symbol.search")); - if (search instanceof FunctionValue) { - return (int)Values.toNumber(ctx, ((FunctionValue)search).call(ctx, term, val, false, start)); - } + if (search instanceof FunctionValue) { + return (int)Values.toNumber(args.ctx, Values.call(args.ctx, search, term, val, false, start)); } - - return val.indexOf(Values.toString(ctx, term), start); + else return val.indexOf(Values.toString(args.ctx, term), start); } - @Native(thisArg = true) public static int lastIndexOf(Context ctx, Object thisArg, Object term, int pos) { - var val = passThis(ctx, "lastIndexOf", thisArg); + @Expose public static int __lastIndexOf(Arguments args) { + var val = passThis(args, "lastIndexOf"); + var term = args.get(0); + var start = args.getInt(1); + var search = Values.getMember(args.ctx, term, Symbol.get("Symbol.search")); - if (term != null && term != Values.NULL && !(term instanceof String)) { - var search = Values.getMember(ctx, term, Symbol.get("Symbol.search")); - if (search instanceof FunctionValue) { - return (int)Values.toNumber(ctx, ((FunctionValue)search).call(ctx, term, val, true, pos)); - } + if (search instanceof FunctionValue) { + return (int)Values.toNumber(args.ctx, Values.call(args.ctx, search, term, val, true, start)); } - - return val.lastIndexOf(Values.toString(ctx, term), pos); + else return val.lastIndexOf(Values.toString(args.ctx, term), start); } - @Native(thisArg = true) public static boolean includes(Context ctx, Object thisArg, Object term, int pos) { - return indexOf(ctx, passThis(ctx, "includes", thisArg), term, pos) >= 0; + @Expose public static boolean __includes(Arguments args) { + return __indexOf(args) >= 0; } - @Native(thisArg = true) public static String replace(Context ctx, Object thisArg, Object term, Object replacement) { - var val = passThis(ctx, "replace", thisArg); + @Expose public static String __replace(Arguments args) { + var val = passThis(args, "replace"); + var term = args.get(0); + var replacement = args.get(1); + var replace = Values.getMember(args.ctx, term, Symbol.get("Symbol.replace")); - if (term != null && term != Values.NULL && !(term instanceof String)) { - var replace = Values.getMember(ctx, term, Symbol.get("Symbol.replace")); - if (replace instanceof FunctionValue) { - return Values.toString(ctx, ((FunctionValue)replace).call(ctx, term, val, replacement)); - } + if (replace instanceof FunctionValue) { + return Values.toString(args.ctx, Values.call(args.ctx, replace, term, val, replacement)); } - - return val.replaceFirst(Pattern.quote(Values.toString(ctx, term)), Values.toString(ctx, replacement)); + else return val.replaceFirst(Pattern.quote(Values.toString(args.ctx, term)), Values.toString(args.ctx, replacement)); } - @Native(thisArg = true) public static String replaceAll(Context ctx, Object thisArg, Object term, Object replacement) { - var val = passThis(ctx, "replaceAll", thisArg); + @Expose public static String __replaceAll(Arguments args) { + var val = passThis(args, "replaceAll"); + var term = args.get(0); + var replacement = args.get(1); + var replace = Values.getMember(args.ctx, term, Symbol.get("Symbol.replace")); - if (term != null && term != Values.NULL && !(term instanceof String)) { - var replace = Values.getMember(ctx, term, Symbol.get("Symbol.replace")); - if (replace instanceof FunctionValue) { - return Values.toString(ctx, ((FunctionValue)replace).call(ctx, term, val, replacement)); - } + if (replace instanceof FunctionValue) { + return Values.toString(args.ctx, Values.call(args.ctx, replace, term, val, replacement)); } - - return val.replaceFirst(Pattern.quote(Values.toString(ctx, term)), Values.toString(ctx, replacement)); + else return val.replace(Values.toString(args.ctx, term), Values.toString(args.ctx, replacement)); } - @Native(thisArg = true) public static ArrayValue match(Context ctx, Object thisArg, Object term, String replacement) { - var val = passThis(ctx, "match", thisArg); + @Expose public static ArrayValue __match(Arguments args) { + var val = passThis(args, "match"); + var term = args.get(0); FunctionValue match; - + try { - var _match = Values.getMember(ctx, term, Symbol.get("Symbol.match")); + var _match = Values.getMember(args.ctx, term, Symbol.get("Symbol.match")); if (_match instanceof FunctionValue) match = (FunctionValue)_match; - else if (ctx.has(Environment.REGEX_CONSTR)) { - var regex = Values.callNew(ctx, ctx.get(Environment.REGEX_CONSTR), Values.toString(ctx, term), ""); - _match = Values.getMember(ctx, regex, Symbol.get("Symbol.match")); + else if (args.ctx.hasNotNull(Environment.REGEX_CONSTR)) { + var regex = Values.callNew(args.ctx, args.ctx.get(Environment.REGEX_CONSTR), Values.toString(args.ctx, term), ""); + _match = Values.getMember(args.ctx, regex, Symbol.get("Symbol.match")); if (_match instanceof FunctionValue) match = (FunctionValue)_match; else throw EngineException.ofError("Regular expressions don't support matching."); } else throw EngineException.ofError("Regular expressions not supported."); } - catch (IllegalArgumentException e) { return new ArrayValue(ctx, ""); } + catch (IllegalArgumentException e) { return new ArrayValue(args.ctx, ""); } - var res = match.call(ctx, term, val); + var res = match.call(args.ctx, term, val); if (res instanceof ArrayValue) return (ArrayValue)res; - else return new ArrayValue(ctx, ""); + else return new ArrayValue(args.ctx, ""); } - @Native(thisArg = true) public static Object matchAll(Context ctx, Object thisArg, Object term, String replacement) { - var val = passThis(ctx, "matchAll", thisArg); + @Expose public static Object __matchAll(Arguments args) { + var val = passThis(args, "matchAll"); + var term = args.get(0); FunctionValue match = null; try { - var _match = Values.getMember(ctx, term, Symbol.get("Symbol.matchAll")); + var _match = Values.getMember(args.ctx, term, Symbol.get("Symbol.matchAll")); if (_match instanceof FunctionValue) match = (FunctionValue)_match; } catch (IllegalArgumentException e) { } - if (match == null && ctx.has(Environment.REGEX_CONSTR)) { - var regex = Values.callNew(ctx, ctx.get(Environment.REGEX_CONSTR), Values.toString(ctx, term), "g"); - var _match = Values.getMember(ctx, regex, Symbol.get("Symbol.matchAll")); + if (match == null && args.ctx.hasNotNull(Environment.REGEX_CONSTR)) { + var regex = Values.callNew(args.ctx, args.ctx.get(Environment.REGEX_CONSTR), Values.toString(args.ctx, term), "g"); + var _match = Values.getMember(args.ctx, regex, Symbol.get("Symbol.matchAll")); if (_match instanceof FunctionValue) match = (FunctionValue)_match; else throw EngineException.ofError("Regular expressions don't support matching."); } else throw EngineException.ofError("Regular expressions not supported."); - return match.call(ctx, term, val); + return match.call(args.ctx, term, val); } - @Native(thisArg = true) public static ArrayValue split(Context ctx, Object thisArg, Object term, Object lim, boolean sensible) { - var val = passThis(ctx, "split", thisArg); + @Expose public static ArrayValue __split(Arguments args) { + var val = passThis(args, "split"); + var term = args.get(0); + var lim = args.get(1); + var sensible = args.getBoolean(2); - if (lim != null) lim = Values.toNumber(ctx, lim); + if (lim != null) lim = Values.toNumber(args.ctx, lim); if (term != null && term != Values.NULL && !(term instanceof String)) { - var replace = Values.getMember(ctx, term, Symbol.get("Symbol.replace")); + var replace = Values.getMember(args.ctx, term, Symbol.get("Symbol.replace")); if (replace instanceof FunctionValue) { - var tmp = ((FunctionValue)replace).call(ctx, term, val, lim, sensible); + var tmp = ((FunctionValue)replace).call(args.ctx, term, val, lim, sensible); if (tmp instanceof ArrayValue) { var parts = new ArrayValue(((ArrayValue)tmp).size()); - for (int i = 0; i < parts.size(); i++) parts.set(ctx, i, Values.toString(ctx, ((ArrayValue)tmp).get(i))); + for (int i = 0; i < parts.size(); i++) parts.set(args.ctx, i, Values.toString(args.ctx, ((ArrayValue)tmp).get(i))); return parts; } } } String[] parts; - var pattern = Pattern.quote(Values.toString(ctx, term)); + var pattern = Pattern.quote(Values.toString(args.ctx, term)); if (lim == null) parts = val.split(pattern); else if (sensible) parts = val.split(pattern, (int)(double)lim); @@ -207,7 +215,7 @@ import me.topchetoeu.jscript.interop.NativeGetter; if (parts.length > limit) res = new ArrayValue(limit); else res = new ArrayValue(parts.length); - for (var i = 0; i < parts.length && i < limit; i++) res.set(ctx, i, parts[i]); + for (var i = 0; i < parts.length && i < limit; i++) res.set(args.ctx, i, parts[i]); return res; } @@ -217,46 +225,49 @@ import me.topchetoeu.jscript.interop.NativeGetter; for (; i < parts.length; i++) { if (lim != null && (double)lim <= i) break; - res.set(ctx, i, parts[i]); + res.set(args.ctx, i, parts[i]); } return res; } - @Native(thisArg = true) public static String slice(Context ctx, Object thisArg, int start, Object _end) { - return substring(ctx, passThis(ctx, "slice", thisArg), start, _end); + @Expose public static String __slice(Arguments args) { + passThis(args, "slice"); + return __substring(args); } - @Native(thisArg = true) public static String concat(Context ctx, Object thisArg, Object... args) { - var res = new StringBuilder(passThis(ctx, "concat", thisArg)); + @Expose public static String __concat(Arguments args) { + var res = new StringBuilder(passThis(args, "concat")); - for (var el : args) res.append(Values.toString(ctx, el)); + for (var el : args.convert(String.class)) res.append(el); return res.toString(); } - @Native(thisArg = true) public static String trim(Context ctx, Object thisArg) { - return passThis(ctx, "trim", thisArg).trim(); + @Expose public static String __trim(Arguments args) { + return passThis(args, "trim").trim(); } - @Native(thisArg = true) public static String trimStart(Context ctx, Object thisArg) { - return passThis(ctx, "trimStart", thisArg).replaceAll("^\\s+", ""); + @Expose public static String __trimStart(Arguments args) { + return passThis(args, "trimStart").replaceAll("^\\s+", ""); } - @Native(thisArg = true) public static String trimEnd(Context ctx, Object thisArg) { - return passThis(ctx, "trimEnd", thisArg).replaceAll("\\s+$", ""); + @Expose public static String __trimEnd(Arguments args) { + return passThis(args, "trimEnd").replaceAll("\\s+$", ""); } - @NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object val) { - val = Values.toString(ctx, val); - if (thisArg instanceof ObjectValue) return new StringLib((String)val); + @ExposeConstructor public static Object __constructor(Arguments args) { + var val = args.getString(0); + if (args.self instanceof ObjectValue) return new StringLib(val); else return val; } - @Native(thisArg = true) public static String toString(Context ctx, Object thisArg) { - return passThis(ctx, "toString", thisArg); + @Expose public static String __toString(Arguments args) { + return passThis(args, "toString"); } - @Native(thisArg = true) public static String valueOf(Context ctx, Object thisArg) { - return passThis(ctx, "valueOf", thisArg); + @Expose public static String __valueOf(Arguments args) { + return passThis(args, "valueOf"); } - @Native public static String fromCharCode(int ...val) { + @Expose(target = ExposeTarget.STATIC) + public static String __fromCharCode(Arguments args) { + var val = args.convertInt(); char[] arr = new char[val.length]; for (var i = 0; i < val.length; i++) arr[i] = (char)val[i]; return new String(arr); diff --git a/src/me/topchetoeu/jscript/lib/SymbolLib.java b/src/me/topchetoeu/jscript/lib/SymbolLib.java index 900fb20..7a56ca2 100644 --- a/src/me/topchetoeu/jscript/lib/SymbolLib.java +++ b/src/me/topchetoeu/jscript/lib/SymbolLib.java @@ -3,48 +3,69 @@ package me.topchetoeu.jscript.lib; import java.util.HashMap; import java.util.Map; -import me.topchetoeu.jscript.engine.Context; 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; +import me.topchetoeu.jscript.interop.Arguments; +import me.topchetoeu.jscript.interop.Expose; +import me.topchetoeu.jscript.interop.ExposeConstructor; +import me.topchetoeu.jscript.interop.ExposeField; +import me.topchetoeu.jscript.interop.ExposeTarget; +import me.topchetoeu.jscript.interop.WrapperName; -@Native("Symbol") public class SymbolLib { +@WrapperName("Symbol") +public class SymbolLib { private static final Map symbols = new HashMap<>(); - @Native public static final Symbol typeName = Symbol.get("Symbol.typeName"); - @Native public static final Symbol replace = Symbol.get("Symbol.replace"); - @Native public static final Symbol match = Symbol.get("Symbol.match"); - @Native public static final Symbol matchAll = Symbol.get("Symbol.matchAll"); - @Native public static final Symbol split = Symbol.get("Symbol.split"); - @Native public static final Symbol search = Symbol.get("Symbol.search"); - @Native public static final Symbol iterator = Symbol.get("Symbol.iterator"); - @Native public static final Symbol asyncIterator = Symbol.get("Symbol.asyncIterator"); - @Native public static final Symbol cause = Symbol.get("Symbol.cause"); + @ExposeField(target = ExposeTarget.STATIC) + public static final Symbol __typeName = Symbol.get("Symbol.typeName"); + @ExposeField(target = ExposeTarget.STATIC) + public static final Symbol __replace = Symbol.get("Symbol.replace"); + @ExposeField(target = ExposeTarget.STATIC) + public static final Symbol __match = Symbol.get("Symbol.match"); + @ExposeField(target = ExposeTarget.STATIC) + public static final Symbol __matchAll = Symbol.get("Symbol.matchAll"); + @ExposeField(target = ExposeTarget.STATIC) + public static final Symbol __split = Symbol.get("Symbol.split"); + @ExposeField(target = ExposeTarget.STATIC) + public static final Symbol __search = Symbol.get("Symbol.search"); + @ExposeField(target = ExposeTarget.STATIC) + public static final Symbol __iterator = Symbol.get("Symbol.iterator"); + @ExposeField(target = ExposeTarget.STATIC) + public static final Symbol __asyncIterator = Symbol.get("Symbol.asyncIterator"); + @ExposeField(target = ExposeTarget.STATIC) + public static final Symbol __cause = Symbol.get("Symbol.cause"); public final Symbol value; - private static Symbol passThis(Context ctx, String funcName, Object val) { + private static Symbol passThis(Arguments args, String funcName) { + var val = args.self; if (val instanceof SymbolLib) return ((SymbolLib)val).value; else if (val instanceof Symbol) return (Symbol)val; else throw EngineException.ofType(String.format("'%s' may only be called upon object and primitve symbols.", funcName)); } - @NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object val) { - if (thisArg instanceof ObjectValue) throw EngineException.ofType("Symbol constructor may not be called with new."); - if (val == null) return new Symbol(""); - else return new Symbol(Values.toString(ctx, val)); - } - @Native(thisArg = true) public static String toString(Context ctx, Object thisArg) { - return passThis(ctx, "toString", thisArg).value; - } - @Native(thisArg = true) public static Symbol valueOf(Context ctx, Object thisArg) { - return passThis(ctx, "valueOf", thisArg); + public SymbolLib(Symbol val) { + this.value = val; } - @Native("for") public static Symbol _for(String key) { + @Expose public static String __toString(Arguments args) { + return passThis(args, "toString").value; + } + @Expose public static Symbol __valueOf(Arguments args) { + return passThis(args, "valueOf"); + } + + @ExposeConstructor + public static Object __constructor(Arguments args) { + if (args.self instanceof ObjectValue) throw EngineException.ofType("Symbol constructor may not be called with new."); + if (args.get(0) == null) return new Symbol(""); + else return new Symbol(args.getString(0)); + } + + @Expose(target = ExposeTarget.STATIC) + public static Symbol __for(Arguments args) { + var key = args.getString(0); if (symbols.containsKey(key)) return symbols.get(key); else { var sym = new Symbol(key); @@ -52,11 +73,8 @@ import me.topchetoeu.jscript.interop.NativeConstructor; return sym; } } - @Native public static String keyFor(Symbol sym) { - return sym.value; - } - - public SymbolLib(Symbol val) { - this.value = val; + @Expose(target = ExposeTarget.STATIC) + public static String __keyFor(Arguments args) { + return passThis(args.slice(-1), "keyFor").value; } } diff --git a/src/me/topchetoeu/jscript/lib/SyntaxErrorLib.java b/src/me/topchetoeu/jscript/lib/SyntaxErrorLib.java index d47feb3..b457510 100644 --- a/src/me/topchetoeu/jscript/lib/SyntaxErrorLib.java +++ b/src/me/topchetoeu/jscript/lib/SyntaxErrorLib.java @@ -1,21 +1,22 @@ package me.topchetoeu.jscript.lib; -import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto; -import me.topchetoeu.jscript.interop.InitType; -import me.topchetoeu.jscript.interop.Native; -import me.topchetoeu.jscript.interop.NativeConstructor; -import me.topchetoeu.jscript.interop.NativeInit; +import me.topchetoeu.jscript.interop.WrapperName; +import me.topchetoeu.jscript.interop.Arguments; +import me.topchetoeu.jscript.interop.ExposeConstructor; +import me.topchetoeu.jscript.interop.ExposeField; +import me.topchetoeu.jscript.interop.ExposeTarget; -@Native("SyntaxError") public class SyntaxErrorLib extends ErrorLib { - @NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) { - var target = ErrorLib.constructor(ctx, thisArg, message); +@WrapperName("SyntaxError") +public class SyntaxErrorLib extends ErrorLib { + @ExposeField(target = ExposeTarget.STATIC) + public static final String __name = "SyntaxError"; + + @ExposeConstructor + public static ObjectValue __constructor(Arguments args) { + var target = ErrorLib.__constructor(args); target.setPrototype(PlaceholderProto.SYNTAX_ERROR); return target; } - @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { - target.defineProperty(null, "name", "SyntaxError"); - } } \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/lib/TypeErrorLib.java b/src/me/topchetoeu/jscript/lib/TypeErrorLib.java index 7e4179c..0fb1c17 100644 --- a/src/me/topchetoeu/jscript/lib/TypeErrorLib.java +++ b/src/me/topchetoeu/jscript/lib/TypeErrorLib.java @@ -1,21 +1,22 @@ package me.topchetoeu.jscript.lib; -import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto; -import me.topchetoeu.jscript.interop.InitType; -import me.topchetoeu.jscript.interop.Native; -import me.topchetoeu.jscript.interop.NativeConstructor; -import me.topchetoeu.jscript.interop.NativeInit; +import me.topchetoeu.jscript.interop.WrapperName; +import me.topchetoeu.jscript.interop.Arguments; +import me.topchetoeu.jscript.interop.ExposeConstructor; +import me.topchetoeu.jscript.interop.ExposeField; +import me.topchetoeu.jscript.interop.ExposeTarget; -@Native("TypeError") public class TypeErrorLib extends ErrorLib { - @NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) { - var target = ErrorLib.constructor(ctx, thisArg, message); - target.setPrototype(PlaceholderProto.SYNTAX_ERROR); +@WrapperName("TypeError") +public class TypeErrorLib extends ErrorLib { + @ExposeField(target = ExposeTarget.STATIC) + public static final String __name = "TypeError"; + + @ExposeConstructor + public static ObjectValue __constructor(Arguments args) { + var target = ErrorLib.__constructor(args); + target.setPrototype(PlaceholderProto.TYPE_ERROR); return target; } - @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { - target.defineProperty(null, "name", "TypeError"); - } } \ No newline at end of file From e575b3287e6bb9cb882ff710174a7ca095373b8b Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Thu, 4 Jan 2024 13:57:41 +0200 Subject: [PATCH 18/35] fix: try-catch-finally fix #457846982 --- .../jscript/engine/frame/CodeFrame.java | 23 +++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java index 23671cf..c85a589 100644 --- a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java +++ b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java @@ -38,8 +38,10 @@ public class CodeFrame { return ptr >= start && ptr < end; } + public void setCause(EngineException target) { + if (error != null) target.setCause(error); + } public TryCtx _catch(EngineException e) { - if (error != null) e.setCause(error); return new TryCtx(TryState.CATCH, e, result, restoreStackPtr, start, end, -1, finallyStart); } public TryCtx _finally(PendingResult res) { @@ -221,6 +223,7 @@ public class CodeFrame { TryCtx newCtx = null; if (error != null) { + tryCtx.setCause(error); if (tryCtx.hasCatch()) newCtx = tryCtx._catch(error); else if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofThrow(error, instr)); } @@ -269,15 +272,17 @@ public class CodeFrame { tryStack.pop(); codePtr = tryCtx.end; if (tryCtx.result.instruction != null) instr = tryCtx.result.instruction; - if (tryCtx.result.isJump) { - codePtr = tryCtx.result.ptr; - jumpFlag = true; + if (!jumpFlag && returnValue == Runners.NO_RETURN && error == null) { + if (tryCtx.result.isJump) { + codePtr = tryCtx.result.ptr; + jumpFlag = true; + } + if (tryCtx.result.isReturn) returnValue = tryCtx.result.value; + if (error == null && tryCtx.result.isThrow) { + error = tryCtx.result.error; + } } - if (tryCtx.result.isReturn) returnValue = tryCtx.result.value; - if (tryCtx.result.isThrow) { - error = tryCtx.result.error; - } - if (error != null) error.setCause(tryCtx.error); + if (error != null) tryCtx.setCause(error); continue; } } From 71f735b8127bbdb79b739d4d968543b74d286de0 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Thu, 4 Jan 2024 13:58:04 +0200 Subject: [PATCH 19/35] fix: some more libs fixes --- src/me/topchetoeu/jscript/lib/BooleanLib.java | 6 + src/me/topchetoeu/jscript/lib/DateLib.java | 4 + src/me/topchetoeu/jscript/lib/ErrorLib.java | 3 +- .../topchetoeu/jscript/lib/FilesystemLib.java | 34 +- src/me/topchetoeu/jscript/lib/JSONLib.java | 2 +- src/me/topchetoeu/jscript/lib/MapLib.java | 6 +- src/me/topchetoeu/jscript/lib/NumberLib.java | 12 +- src/me/topchetoeu/jscript/lib/ObjectLib.java | 47 +- src/me/topchetoeu/jscript/lib/PromiseLib.java | 404 +++++++++--------- .../topchetoeu/jscript/lib/RangeErrorLib.java | 7 +- src/me/topchetoeu/jscript/lib/SetLib.java | 6 +- src/me/topchetoeu/jscript/lib/StringLib.java | 36 +- src/me/topchetoeu/jscript/lib/SymbolLib.java | 5 +- .../jscript/lib/SyntaxErrorLib.java | 7 +- .../topchetoeu/jscript/lib/TypeErrorLib.java | 7 +- 15 files changed, 310 insertions(+), 276 deletions(-) diff --git a/src/me/topchetoeu/jscript/lib/BooleanLib.java b/src/me/topchetoeu/jscript/lib/BooleanLib.java index 0a77408..15b58fd 100644 --- a/src/me/topchetoeu/jscript/lib/BooleanLib.java +++ b/src/me/topchetoeu/jscript/lib/BooleanLib.java @@ -1,6 +1,7 @@ package me.topchetoeu.jscript.lib; import me.topchetoeu.jscript.engine.values.ObjectValue; +import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.interop.Arguments; import me.topchetoeu.jscript.interop.Expose; import me.topchetoeu.jscript.interop.ExposeConstructor; @@ -13,6 +14,10 @@ public class BooleanLib { public final boolean value; + @Override public String toString() { + return value + ""; + } + public BooleanLib(boolean val) { this.value = val; } @@ -26,6 +31,7 @@ public class BooleanLib { return args.self(Boolean.class) ? "true" : "false"; } @Expose public static boolean __valueOf(Arguments args) { + if (Values.isWrapper(args.self, BooleanLib.class)) return Values.wrapper(args.self, BooleanLib.class).value; return args.self(Boolean.class); } } diff --git a/src/me/topchetoeu/jscript/lib/DateLib.java b/src/me/topchetoeu/jscript/lib/DateLib.java index 599c19e..9acc8a2 100644 --- a/src/me/topchetoeu/jscript/lib/DateLib.java +++ b/src/me/topchetoeu/jscript/lib/DateLib.java @@ -238,6 +238,10 @@ public class DateLib { return normal.getTime().toString(); } + @Override public String toString() { + return __toString(); + } + public DateLib(long timestamp) { normal = Calendar.getInstance(); utc = Calendar.getInstance(); diff --git a/src/me/topchetoeu/jscript/lib/ErrorLib.java b/src/me/topchetoeu/jscript/lib/ErrorLib.java index 3d9479a..c21e8c2 100644 --- a/src/me/topchetoeu/jscript/lib/ErrorLib.java +++ b/src/me/topchetoeu/jscript/lib/ErrorLib.java @@ -8,6 +8,7 @@ import me.topchetoeu.jscript.exceptions.ConvertException; import me.topchetoeu.jscript.interop.WrapperName; import me.topchetoeu.jscript.interop.Arguments; import me.topchetoeu.jscript.interop.Expose; +import me.topchetoeu.jscript.interop.ExposeConstructor; import me.topchetoeu.jscript.interop.ExposeField; @WrapperName("Error") @@ -36,7 +37,7 @@ public class ErrorLib { else return "[Invalid error]"; } - @Expose public static ObjectValue __constructor(Arguments args) { + @ExposeConstructor public static ObjectValue __constructor(Arguments args) { var target = new ObjectValue(); var message = args.getString(0, ""); diff --git a/src/me/topchetoeu/jscript/lib/FilesystemLib.java b/src/me/topchetoeu/jscript/lib/FilesystemLib.java index aac8370..59f7b03 100644 --- a/src/me/topchetoeu/jscript/lib/FilesystemLib.java +++ b/src/me/topchetoeu/jscript/lib/FilesystemLib.java @@ -18,13 +18,17 @@ import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode; import me.topchetoeu.jscript.interop.Arguments; import me.topchetoeu.jscript.interop.Expose; import me.topchetoeu.jscript.interop.ExposeField; +import me.topchetoeu.jscript.interop.ExposeTarget; import me.topchetoeu.jscript.interop.WrapperName; @WrapperName("Filesystem") public class FilesystemLib { - @ExposeField public static final int __SEEK_SET = 0; - @ExposeField public static final int __SEEK_CUR = 1; - @ExposeField public static final int __SEEK_END = 2; + @ExposeField(target = ExposeTarget.STATIC) + public static final int __SEEK_SET = 0; + @ExposeField(target = ExposeTarget.STATIC) + public static final int __SEEK_CUR = 1; + @ExposeField(target = ExposeTarget.STATIC) + public static final int __SEEK_END = 2; private static Filesystem fs(Context ctx) { var fs = Filesystem.get(ctx); @@ -32,11 +36,13 @@ public class FilesystemLib { throw EngineException.ofError("Current environment doesn't have a file system."); } - @Expose public static String __normalize(Arguments args) { + @Expose(target = ExposeTarget.STATIC) + public static String __normalize(Arguments args) { return fs(args.ctx).normalize(args.convert(String.class)); } - @Expose public static PromiseLib __open(Arguments args) { + @Expose(target = ExposeTarget.STATIC) + public static PromiseLib __open(Arguments args) { return PromiseLib.await(args.ctx, () -> { var fs = fs(args.ctx); var path = fs.normalize(args.getString(0)); @@ -53,7 +59,8 @@ public class FilesystemLib { catch (FilesystemException e) { throw e.toEngineException(); } }); } - @Expose public static ObjectValue __ls(Arguments args) { + @Expose(target = ExposeTarget.STATIC) + public static ObjectValue __ls(Arguments args) { return Values.toJSAsyncIterator(args.ctx, new Iterator<>() { private boolean failed, done; @@ -108,7 +115,8 @@ public class FilesystemLib { } }); } - @Expose public static PromiseLib __mkdir(Arguments args) throws IOException { + @Expose(target = ExposeTarget.STATIC) + public static PromiseLib __mkdir(Arguments args) throws IOException { return PromiseLib.await(args.ctx, () -> { try { fs(args.ctx).create(args.getString(0), EntryType.FOLDER); @@ -118,7 +126,8 @@ public class FilesystemLib { }); } - @Expose public static PromiseLib __mkfile(Arguments args) throws IOException { + @Expose(target = ExposeTarget.STATIC) + public static PromiseLib __mkfile(Arguments args) throws IOException { return PromiseLib.await(args.ctx, () -> { try { fs(args.ctx).create(args.getString(0), EntryType.FILE); @@ -127,7 +136,8 @@ public class FilesystemLib { catch (FilesystemException e) { throw e.toEngineException(); } }); } - @Expose public static PromiseLib __rm(Arguments args) throws IOException { + @Expose(target = ExposeTarget.STATIC) + public static PromiseLib __rm(Arguments args) throws IOException { return PromiseLib.await(args.ctx, () -> { try { var fs = fs(args.ctx); @@ -157,7 +167,8 @@ public class FilesystemLib { catch (FilesystemException e) { throw e.toEngineException(); } }); } - @Expose public static PromiseLib __stat(Arguments args) throws IOException { + @Expose(target = ExposeTarget.STATIC) + public static PromiseLib __stat(Arguments args) throws IOException { return PromiseLib.await(args.ctx, () -> { try { var fs = fs(args.ctx); @@ -172,7 +183,8 @@ public class FilesystemLib { catch (FilesystemException e) { throw e.toEngineException(); } }); } - @Expose public static PromiseLib __exists(Arguments args) throws IOException { + @Expose(target = ExposeTarget.STATIC) + public static PromiseLib __exists(Arguments args) throws IOException { return PromiseLib.await(args.ctx, () -> { try { fs(args.ctx).stat(args.getString(0)); return true; } catch (FilesystemException e) { return false; } diff --git a/src/me/topchetoeu/jscript/lib/JSONLib.java b/src/me/topchetoeu/jscript/lib/JSONLib.java index 3f025e1..1cd0118 100644 --- a/src/me/topchetoeu/jscript/lib/JSONLib.java +++ b/src/me/topchetoeu/jscript/lib/JSONLib.java @@ -19,6 +19,6 @@ public class JSONLib { } @Expose(target = ExposeTarget.STATIC) public static String __stringify(Arguments args) { - return me.topchetoeu.jscript.json.JSON.stringify(JSON.fromJs(args.ctx, args.get(0))); + return JSON.stringify(JSON.fromJs(args.ctx, args.get(0))); } } diff --git a/src/me/topchetoeu/jscript/lib/MapLib.java b/src/me/topchetoeu/jscript/lib/MapLib.java index dd52eea..2452102 100644 --- a/src/me/topchetoeu/jscript/lib/MapLib.java +++ b/src/me/topchetoeu/jscript/lib/MapLib.java @@ -37,7 +37,7 @@ public class MapLib { } @Expose public ObjectValue __entries(Arguments args) { - return ArrayValue.of(args.ctx, map + return Values.toJSIterator(args.ctx, map .entrySet() .stream() .map(v -> new ArrayValue(args.ctx, v.getKey(), v.getValue())) @@ -45,10 +45,10 @@ public class MapLib { ); } @Expose public ObjectValue __keys(Arguments args) { - return ArrayValue.of(args.ctx, map.keySet()); + return Values.toJSIterator(args.ctx, map.keySet()); } @Expose public ObjectValue __values(Arguments args) { - return ArrayValue.of(args.ctx, map.values()); + return Values.toJSIterator(args.ctx, map.values()); } @Expose public Object __get(Arguments args) { diff --git a/src/me/topchetoeu/jscript/lib/NumberLib.java b/src/me/topchetoeu/jscript/lib/NumberLib.java index 5777fed..d312e82 100644 --- a/src/me/topchetoeu/jscript/lib/NumberLib.java +++ b/src/me/topchetoeu/jscript/lib/NumberLib.java @@ -31,6 +31,12 @@ public class NumberLib { public final double value; + @Override public String toString() { return value + ""; } + + public NumberLib(double val) { + this.value = val; + } + @Expose(target = ExposeTarget.STATIC) public static boolean __isFinite(Arguments args) { return Double.isFinite(args.getDouble(0)); } @Expose(target = ExposeTarget.STATIC) @@ -59,11 +65,7 @@ public class NumberLib { return Values.toString(args.ctx, args.getDouble(0)); } @Expose public static double __valueOf(Arguments args) { - if (args.self instanceof NumberLib) return args.self(NumberLib.class).value; + if (Values.isWrapper(args.self, NumberLib.class)) return Values.wrapper(args.self, NumberLib.class).value; else return Values.toNumber(args.ctx, args.self); } - - public NumberLib(double val) { - this.value = val; - } } diff --git a/src/me/topchetoeu/jscript/lib/ObjectLib.java b/src/me/topchetoeu/jscript/lib/ObjectLib.java index ad14a7f..5aefe2f 100644 --- a/src/me/topchetoeu/jscript/lib/ObjectLib.java +++ b/src/me/topchetoeu/jscript/lib/ObjectLib.java @@ -80,8 +80,8 @@ public class ObjectLib { var obj = args.convert(0, ObjectValue.class); var attrib = args.convert(1, ObjectValue.class); - for (var key : Values.getMembers(null, obj, false, false)) { - obj.defineProperty(args.ctx, key, attrib.getMember(args.ctx, key)); + for (var key : Values.getMembers(null, attrib, false, false)) { + __defineProperty(new Arguments(args.ctx, null, obj, key, attrib.getMember(args.ctx, key))); } return obj; @@ -89,7 +89,7 @@ public class ObjectLib { @Expose(target = ExposeTarget.STATIC) public static ArrayValue __keys(Arguments args) { - var obj = args.convert(0, ObjectValue.class); + var obj = args.get(0); var all = args.getBoolean(1); var res = new ArrayValue(); @@ -102,7 +102,7 @@ public class ObjectLib { @Expose(target = ExposeTarget.STATIC) public static ArrayValue __entries(Arguments args) { var res = new ArrayValue(); - var obj = args.convert(0, ObjectValue.class); + var obj = args.get(0); var all = args.getBoolean(1); for (var key : Values.getMembers(args.ctx, obj, true, false)) { @@ -114,11 +114,11 @@ public class ObjectLib { @Expose(target = ExposeTarget.STATIC) public static ArrayValue __values(Arguments args) { var res = new ArrayValue(); - var obj = args.convert(0, ObjectValue.class); + var obj = args.get(0); var all = args.getBoolean(1); for (var key : Values.getMembers(args.ctx, obj, true, false)) { - if (all || key instanceof String) res.set(args.ctx, res.size(), Values.getMember(args.ctx, obj, key)); + if (all || !(key instanceof Symbol)) res.set(args.ctx, res.size(), Values.getMember(args.ctx, obj, key)); } return res; @@ -139,9 +139,9 @@ public class ObjectLib { } @Expose(target = ExposeTarget.STATIC) - public static ArrayValue __getOwnPropertyNames(Arguments args) { + public static ArrayValue __getOwnPropertyNames(Arguments args) { var res = new ArrayValue(); - var obj = args.convert(0, ObjectValue.class); + var obj = args.get(0); var all = args.getBoolean(1); for (var key : Values.getMembers(args.ctx, obj, true, true)) { @@ -152,7 +152,7 @@ public class ObjectLib { } @Expose(target = ExposeTarget.STATIC) public static ArrayValue __getOwnPropertySymbols(Arguments args) { - var obj = args.convert(0, ObjectValue.class); + var obj = args.get(0); var res = new ArrayValue(); for (var key : Values.getMembers(args.ctx, obj, true, true)) { @@ -208,17 +208,20 @@ public class ObjectLib { @Expose(target = ExposeTarget.STATIC) public static boolean __isExtensible(Arguments args) { var obj = args.get(0); - return obj instanceof ObjectValue && ((ObjectValue)obj).extensible(); + if (!(obj instanceof ObjectValue)) return false; + return ((ObjectValue)obj).extensible(); } @Expose(target = ExposeTarget.STATIC) public static boolean __isSealed(Arguments args) { var obj = args.get(0); - 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 instanceof ObjectValue)) return true; + var _obj = (ObjectValue)obj; + + if (_obj.extensible()) return false; + + for (var key : _obj.keys(true)) { + if (_obj.memberConfigurable(key)) return false; } return true; @@ -227,12 +230,14 @@ public class ObjectLib { public static boolean __isFrozen(Arguments args) { var obj = args.get(0); - 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; - } + if (!(obj instanceof ObjectValue)) return true; + var _obj = (ObjectValue)obj; + + if (_obj.extensible()) return false; + + for (var key : _obj.keys(true)) { + if (_obj.memberConfigurable(key)) return false; + if (_obj.memberWritable(key)) return false; } return true; diff --git a/src/me/topchetoeu/jscript/lib/PromiseLib.java b/src/me/topchetoeu/jscript/lib/PromiseLib.java index e54bdd3..f9055a4 100644 --- a/src/me/topchetoeu/jscript/lib/PromiseLib.java +++ b/src/me/topchetoeu/jscript/lib/PromiseLib.java @@ -41,207 +41,6 @@ public class PromiseLib { private static final int STATE_FULFILLED = 1; private static final int STATE_REJECTED = 2; - @Expose(value = "resolve", target = ExposeTarget.STATIC) - public static PromiseLib __ofResolved(Arguments args) { - return ofResolved(args.ctx, args.get(0)); - } - @Expose(value = "reject", target = ExposeTarget.STATIC) - public static PromiseLib __ofRejected(Arguments args) { - return ofRejected(args.ctx, new EngineException(args.get(0)).setCtx(args.ctx)); - } - - @Expose(target = ExposeTarget.STATIC) - private static PromiseLib __any(Arguments args) { - if (!(args.get(0) instanceof ArrayValue)) throw EngineException.ofType("Expected argument for any to be an array."); - var promises = args.convert(0, ArrayValue.class); - - if (promises.size() == 0) return ofRejected(args.ctx, EngineException.ofError("No promises passed to 'Promise.any'.").setCtx(args.ctx)); - var n = new int[] { promises.size() }; - var res = new PromiseLib(); - var errors = new ArrayValue(); - - for (var i = 0; i < promises.size(); i++) { - var index = i; - var val = promises.get(i); - if (res.state != STATE_PENDING) break; - - handle(args.ctx, val, new Handle() { - public void onFulfil(Object val) { res.fulfill(args.ctx, val); } - public void onReject(EngineException err) { - errors.set(args.ctx, index, err.value); - n[0]--; - if (n[0] <= 0) res.reject(args.ctx, new EngineException(errors).setCtx(args.ctx)); - } - }); - } - - return res; - } - @Expose(target = ExposeTarget.STATIC) - private static PromiseLib __race(Arguments args) { - if (!(args.get(0) instanceof ArrayValue)) throw EngineException.ofType("Expected argument for any to be an array."); - var promises = args.convert(0, ArrayValue.class); - var res = new PromiseLib(); - - for (var i = 0; i < promises.size(); i++) { - var val = promises.get(i); - if (res.state != STATE_PENDING) break; - - handle(args.ctx, val, new Handle() { - @Override public void onFulfil(Object val) { res.fulfill(args.ctx, val); } - @Override public void onReject(EngineException err) { res.reject(args.ctx, err); } - }); - } - - return res; - } - @Expose(target = ExposeTarget.STATIC) - private static PromiseLib __all(Arguments args) { - if (!(args.get(0) instanceof ArrayValue)) throw EngineException.ofType("Expected argument for any to be an array."); - var promises = args.convert(0, ArrayValue.class); - var n = new int[] { promises.size() }; - var res = new PromiseLib(); - var result = new ArrayValue(); - - for (var i = 0; i < promises.size(); i++) { - if (res.state != STATE_PENDING) break; - - var index = i; - var val = promises.get(i); - - handle(args.ctx, val, new Handle() { - @Override public void onFulfil(Object val) { - result.set(args.ctx, index, val); - n[0]--; - if (n[0] <= 0) res.fulfill(args.ctx, result); - } - @Override public void onReject(EngineException err) { - res.reject(args.ctx, err); - } - }); - } - - if (n[0] <= 0) res.fulfill(args.ctx, result); - - return res; - } - @Expose(target = ExposeTarget.STATIC) - private static PromiseLib __allSettled(Arguments args) { - if (!(args.get(0) instanceof ArrayValue)) throw EngineException.ofType("Expected argument for any to be an array."); - var promises = args.convert(0, ArrayValue.class); - var n = new int[] { promises.size() }; - var res = new PromiseLib(); - var result = new ArrayValue(); - - for (var i = 0; i < promises.size(); i++) { - if (res.state != STATE_PENDING) break; - - var index = i; - - handle(args.ctx, promises.get(i), new Handle() { - @Override public void onFulfil(Object val) { - var desc = new ObjectValue(); - desc.defineProperty(args.ctx, "status", "fulfilled"); - desc.defineProperty(args.ctx, "value", val); - - result.set(args.ctx, index, desc); - - n[0]--; - if (n[0] <= 0) res.fulfill(args.ctx, res); - } - @Override public void onReject(EngineException err) { - var desc = new ObjectValue(); - desc.defineProperty(args.ctx, "status", "reject"); - desc.defineProperty(args.ctx, "value", err.value); - - result.set(args.ctx, index, desc); - - n[0]--; - if (n[0] <= 0) res.fulfill(args.ctx, res); - } - }); - } - - if (n[0] <= 0) res.fulfill(args.ctx, result); - - return res; - } - - @Expose - private static Object __then(Arguments args) { - var onFulfill = args.get(0) instanceof FunctionValue ? args.convert(0, FunctionValue.class) : null; - var onReject = args.get(1) instanceof FunctionValue ? args.convert(1, FunctionValue.class) : null; - - var res = new PromiseLib(); - - handle(args.ctx, args.self, new Handle() { - @Override public void onFulfil(Object val) { - try { res.fulfill(args.ctx, onFulfill.call(args.ctx, null, val)); } - catch (EngineException e) { res.reject(args.ctx, e); } - } - @Override public void onReject(EngineException err) { - try { res.fulfill(args.ctx, onReject.call(args.ctx, null, err.value)); } - catch (EngineException e) { res.reject(args.ctx, e); } - } - }.defer(args.ctx.engine)); - - return res; - } - @Expose - private static Object __catch(Arguments args) { - return __then(new Arguments(args.ctx, args.self, null, args.get(0))); - } - @Expose - private static Object __finally(Arguments args) { - var func = args.get(0) instanceof FunctionValue ? args.convert(0, FunctionValue.class) : null; - - var res = new PromiseLib(); - - handle(args.ctx, args.self, new Handle() { - @Override public void onFulfil(Object val) { - try { - func.call(args.ctx); - res.fulfill(args.ctx, val); - } - catch (EngineException e) { res.reject(args.ctx, e); } - } - @Override public void onReject(EngineException err) { - try { - func.call(args.ctx); - res.reject(args.ctx, err); - } - catch (EngineException e) { res.reject(args.ctx, e); } - } - }.defer(args.ctx.engine)); - - return res; - } - - @ExposeConstructor - private static PromiseLib __constructor(Arguments args) { - var func = args.convert(0, FunctionValue.class); - var res = new PromiseLib(); - - try { - func.call( - args.ctx, null, - new NativeFunction(null, _args -> { - res.fulfill(_args.ctx, _args.get(0)); - return null; - }), - new NativeFunction(null, _args -> { - res.reject(_args.ctx, new EngineException(_args.get(0)).setCtx(_args.ctx)); - return null; - }) - ); - } - catch (EngineException e) { - res.reject(args.ctx, e); - } - - return res; - } - private List handles = new ArrayList<>(); private int state = STATE_PENDING; @@ -262,7 +61,7 @@ public class PromiseLib { } if (state == STATE_REJECTED && !handled) { - Values.printError(new EngineException(val).setCtx(ctx.environment, ctx.engine), "(in promise)"); + Values.printError(((EngineException)val).setCtx(ctx.environment, ctx.engine), "(in promise)"); } handles = null; @@ -383,4 +182,205 @@ public class PromiseLib { res.reject(ctx, value); return res; } + + @Expose(value = "resolve", target = ExposeTarget.STATIC) + public static PromiseLib __ofResolved(Arguments args) { + return ofResolved(args.ctx, args.get(0)); + } + @Expose(value = "reject", target = ExposeTarget.STATIC) + public static PromiseLib __ofRejected(Arguments args) { + return ofRejected(args.ctx, new EngineException(args.get(0)).setCtx(args.ctx)); + } + + @Expose(target = ExposeTarget.STATIC) + public static PromiseLib __any(Arguments args) { + if (!(args.get(0) instanceof ArrayValue)) throw EngineException.ofType("Expected argument for any to be an array."); + var promises = args.convert(0, ArrayValue.class); + + if (promises.size() == 0) return ofRejected(args.ctx, EngineException.ofError("No promises passed to 'Promise.any'.").setCtx(args.ctx)); + var n = new int[] { promises.size() }; + var res = new PromiseLib(); + var errors = new ArrayValue(); + + for (var i = 0; i < promises.size(); i++) { + var index = i; + var val = promises.get(i); + if (res.state != STATE_PENDING) break; + + handle(args.ctx, val, new Handle() { + public void onFulfil(Object val) { res.fulfill(args.ctx, val); } + public void onReject(EngineException err) { + errors.set(args.ctx, index, err.value); + n[0]--; + if (n[0] <= 0) res.reject(args.ctx, new EngineException(errors).setCtx(args.ctx)); + } + }); + } + + return res; + } + @Expose(target = ExposeTarget.STATIC) + public static PromiseLib __race(Arguments args) { + if (!(args.get(0) instanceof ArrayValue)) throw EngineException.ofType("Expected argument for any to be an array."); + var promises = args.convert(0, ArrayValue.class); + var res = new PromiseLib(); + + for (var i = 0; i < promises.size(); i++) { + var val = promises.get(i); + if (res.state != STATE_PENDING) break; + + handle(args.ctx, val, new Handle() { + @Override public void onFulfil(Object val) { res.fulfill(args.ctx, val); } + @Override public void onReject(EngineException err) { res.reject(args.ctx, err); } + }); + } + + return res; + } + @Expose(target = ExposeTarget.STATIC) + public static PromiseLib __all(Arguments args) { + if (!(args.get(0) instanceof ArrayValue)) throw EngineException.ofType("Expected argument for any to be an array."); + var promises = args.convert(0, ArrayValue.class); + var n = new int[] { promises.size() }; + var res = new PromiseLib(); + var result = new ArrayValue(); + + for (var i = 0; i < promises.size(); i++) { + if (res.state != STATE_PENDING) break; + + var index = i; + var val = promises.get(i); + + handle(args.ctx, val, new Handle() { + @Override public void onFulfil(Object val) { + result.set(args.ctx, index, val); + n[0]--; + if (n[0] <= 0) res.fulfill(args.ctx, result); + } + @Override public void onReject(EngineException err) { + res.reject(args.ctx, err); + } + }); + } + + if (n[0] <= 0) res.fulfill(args.ctx, result); + + return res; + } + @Expose(target = ExposeTarget.STATIC) + public static PromiseLib __allSettled(Arguments args) { + if (!(args.get(0) instanceof ArrayValue)) throw EngineException.ofType("Expected argument for any to be an array."); + var promises = args.convert(0, ArrayValue.class); + var n = new int[] { promises.size() }; + var res = new PromiseLib(); + var result = new ArrayValue(); + + for (var i = 0; i < promises.size(); i++) { + if (res.state != STATE_PENDING) break; + + var index = i; + + handle(args.ctx, promises.get(i), new Handle() { + @Override public void onFulfil(Object val) { + var desc = new ObjectValue(); + desc.defineProperty(args.ctx, "status", "fulfilled"); + desc.defineProperty(args.ctx, "value", val); + + result.set(args.ctx, index, desc); + + n[0]--; + if (n[0] <= 0) res.fulfill(args.ctx, res); + } + @Override public void onReject(EngineException err) { + var desc = new ObjectValue(); + desc.defineProperty(args.ctx, "status", "reject"); + desc.defineProperty(args.ctx, "value", err.value); + + result.set(args.ctx, index, desc); + + n[0]--; + if (n[0] <= 0) res.fulfill(args.ctx, res); + } + }); + } + + if (n[0] <= 0) res.fulfill(args.ctx, result); + + return res; + } + + @Expose + public static Object __then(Arguments args) { + var onFulfill = args.get(0) instanceof FunctionValue ? args.convert(0, FunctionValue.class) : null; + var onReject = args.get(1) instanceof FunctionValue ? args.convert(1, FunctionValue.class) : null; + + var res = new PromiseLib(); + + handle(args.ctx, args.self, new Handle() { + @Override public void onFulfil(Object val) { + try { res.fulfill(args.ctx, onFulfill.call(args.ctx, null, val)); } + catch (EngineException e) { res.reject(args.ctx, e); } + } + @Override public void onReject(EngineException err) { + try { res.fulfill(args.ctx, onReject.call(args.ctx, null, err.value)); } + catch (EngineException e) { res.reject(args.ctx, e); } + } + }.defer(args.ctx.engine)); + + return res; + } + @Expose + public static Object __catch(Arguments args) { + return __then(new Arguments(args.ctx, args.self, null, args.get(0))); + } + @Expose + public static Object __finally(Arguments args) { + var func = args.get(0) instanceof FunctionValue ? args.convert(0, FunctionValue.class) : null; + + var res = new PromiseLib(); + + handle(args.ctx, args.self, new Handle() { + @Override public void onFulfil(Object val) { + try { + func.call(args.ctx); + res.fulfill(args.ctx, val); + } + catch (EngineException e) { res.reject(args.ctx, e); } + } + @Override public void onReject(EngineException err) { + try { + func.call(args.ctx); + res.reject(args.ctx, err); + } + catch (EngineException e) { res.reject(args.ctx, e); } + } + }.defer(args.ctx.engine)); + + return res; + } + + @ExposeConstructor + public static PromiseLib __constructor(Arguments args) { + var func = args.convert(0, FunctionValue.class); + var res = new PromiseLib(); + + try { + func.call( + args.ctx, null, + new NativeFunction(null, _args -> { + res.fulfill(_args.ctx, _args.get(0)); + return null; + }), + new NativeFunction(null, _args -> { + res.reject(_args.ctx, new EngineException(_args.get(0)).setCtx(_args.ctx)); + return null; + }) + ); + } + catch (EngineException e) { + res.reject(args.ctx, e); + } + + return res; + } } diff --git a/src/me/topchetoeu/jscript/lib/RangeErrorLib.java b/src/me/topchetoeu/jscript/lib/RangeErrorLib.java index 86b10ad..906c826 100644 --- a/src/me/topchetoeu/jscript/lib/RangeErrorLib.java +++ b/src/me/topchetoeu/jscript/lib/RangeErrorLib.java @@ -6,15 +6,12 @@ import me.topchetoeu.jscript.interop.WrapperName; import me.topchetoeu.jscript.interop.Arguments; import me.topchetoeu.jscript.interop.ExposeConstructor; import me.topchetoeu.jscript.interop.ExposeField; -import me.topchetoeu.jscript.interop.ExposeTarget; @WrapperName("RangeError") public class RangeErrorLib extends ErrorLib { - @ExposeField(target = ExposeTarget.STATIC) - public static final String __name = "RangeError"; + @ExposeField public static final String __name = "RangeError"; - @ExposeConstructor - public static ObjectValue constructor(Arguments args) { + @ExposeConstructor public static ObjectValue constructor(Arguments args) { var target = ErrorLib.__constructor(args); target.setPrototype(PlaceholderProto.RANGE_ERROR); return target; diff --git a/src/me/topchetoeu/jscript/lib/SetLib.java b/src/me/topchetoeu/jscript/lib/SetLib.java index bda9149..eade541 100644 --- a/src/me/topchetoeu/jscript/lib/SetLib.java +++ b/src/me/topchetoeu/jscript/lib/SetLib.java @@ -24,13 +24,13 @@ public class SetLib { } @Expose public ObjectValue __entries(Arguments args) { - return ArrayValue.of(args.ctx, set.stream().map(v -> new ArrayValue(args.ctx, v, v)).collect(Collectors.toList())); + return Values.toJSIterator(args.ctx, set.stream().map(v -> new ArrayValue(args.ctx, v, v)).collect(Collectors.toList())); } @Expose public ObjectValue __keys(Arguments args) { - return ArrayValue.of(args.ctx, set); + return Values.toJSIterator(args.ctx, set); } @Expose public ObjectValue __values(Arguments args) { - return ArrayValue.of(args.ctx, set); + return Values.toJSIterator(args.ctx, set); } @Expose public Object __add(Arguments args) { diff --git a/src/me/topchetoeu/jscript/lib/StringLib.java b/src/me/topchetoeu/jscript/lib/StringLib.java index c377a38..65311b3 100644 --- a/src/me/topchetoeu/jscript/lib/StringLib.java +++ b/src/me/topchetoeu/jscript/lib/StringLib.java @@ -21,9 +21,15 @@ import me.topchetoeu.jscript.interop.WrapperName; public class StringLib { public final String value; + @Override public String toString() { return value; } + + public StringLib(String val) { + this.value = val; + } + private static String passThis(Arguments args, String funcName) { var val = args.self; - if (val instanceof StringLib) return ((StringLib)val).value; + if (Values.isWrapper(val, StringLib.class)) return Values.wrapper(val, StringLib.class).value; else if (val instanceof String) return (String)val; else throw EngineException.ofType(String.format("'%s' may only be called upon object and primitve strings.", funcName)); } @@ -43,16 +49,22 @@ public class StringLib { @Expose public static String __substring(Arguments args) { var val = passThis(args, "substring"); - var start = normalizeI(args.getInt(0), val.length(), true); - var end = normalizeI(args.getInt(1, val.length()), val.length(), true); + var start = Math.max(0, Math.min(val.length(), args.getInt(0))); + var end = Math.max(0, Math.min(val.length(), args.getInt(1, val.length()))); + + if (end < start) { + var tmp = end; + end = start; + start = tmp; + } return val.substring(start, end); } @Expose public static String __substr(Arguments args) { var val = passThis(args, "substr"); var start = normalizeI(args.getInt(0), val.length(), true); - int len = normalizeI(args.getInt(0), val.length() - start, true); - return val.substring(start, start + len); + int end = normalizeI(args.getInt(1, val.length() - start) + start, val.length(), true); + return val.substring(start, end); } @Expose public static String __toLowerCase(Arguments args) { @@ -206,6 +218,7 @@ public class StringLib { var pattern = Pattern.quote(Values.toString(args.ctx, term)); if (lim == null) parts = val.split(pattern); + else if ((double)lim < 1) return new ArrayValue(); else if (sensible) parts = val.split(pattern, (int)(double)lim); else { var limit = (int)(double)lim; @@ -232,8 +245,11 @@ public class StringLib { } @Expose public static String __slice(Arguments args) { - passThis(args, "slice"); - return __substring(args); + var self = passThis(args, "slice"); + var start = normalizeI(args.getInt(0), self.length(), false); + var end = normalizeI(args.getInt(1, self.length()), self.length(), false); + + return __substring(new Arguments(args.ctx, self, start, end)); } @Expose public static String __concat(Arguments args) { @@ -254,7 +270,7 @@ public class StringLib { } @ExposeConstructor public static Object __constructor(Arguments args) { - var val = args.getString(0); + var val = args.getString(0, ""); if (args.self instanceof ObjectValue) return new StringLib(val); else return val; } @@ -272,8 +288,4 @@ public class StringLib { for (var i = 0; i < val.length; i++) arr[i] = (char)val[i]; return new String(arr); } - - public StringLib(String val) { - this.value = val; - } } diff --git a/src/me/topchetoeu/jscript/lib/SymbolLib.java b/src/me/topchetoeu/jscript/lib/SymbolLib.java index 7a56ca2..d182108 100644 --- a/src/me/topchetoeu/jscript/lib/SymbolLib.java +++ b/src/me/topchetoeu/jscript/lib/SymbolLib.java @@ -5,6 +5,7 @@ import java.util.Map; 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.Arguments; import me.topchetoeu.jscript.interop.Expose; @@ -40,7 +41,7 @@ public class SymbolLib { private static Symbol passThis(Arguments args, String funcName) { var val = args.self; - if (val instanceof SymbolLib) return ((SymbolLib)val).value; + if (Values.isWrapper(val, SymbolLib.class)) return Values.wrapper(val, SymbolLib.class).value; else if (val instanceof Symbol) return (Symbol)val; else throw EngineException.ofType(String.format("'%s' may only be called upon object and primitve symbols.", funcName)); } @@ -75,6 +76,6 @@ public class SymbolLib { } @Expose(target = ExposeTarget.STATIC) public static String __keyFor(Arguments args) { - return passThis(args.slice(-1), "keyFor").value; + return passThis(new Arguments(args.ctx, args.get(0)), "keyFor").value; } } diff --git a/src/me/topchetoeu/jscript/lib/SyntaxErrorLib.java b/src/me/topchetoeu/jscript/lib/SyntaxErrorLib.java index b457510..2ca77ee 100644 --- a/src/me/topchetoeu/jscript/lib/SyntaxErrorLib.java +++ b/src/me/topchetoeu/jscript/lib/SyntaxErrorLib.java @@ -6,15 +6,12 @@ import me.topchetoeu.jscript.interop.WrapperName; import me.topchetoeu.jscript.interop.Arguments; import me.topchetoeu.jscript.interop.ExposeConstructor; import me.topchetoeu.jscript.interop.ExposeField; -import me.topchetoeu.jscript.interop.ExposeTarget; @WrapperName("SyntaxError") public class SyntaxErrorLib extends ErrorLib { - @ExposeField(target = ExposeTarget.STATIC) - public static final String __name = "SyntaxError"; + @ExposeField public static final String __name = "SyntaxError"; - @ExposeConstructor - public static ObjectValue __constructor(Arguments args) { + @ExposeConstructor public static ObjectValue __constructor(Arguments args) { var target = ErrorLib.__constructor(args); target.setPrototype(PlaceholderProto.SYNTAX_ERROR); return target; diff --git a/src/me/topchetoeu/jscript/lib/TypeErrorLib.java b/src/me/topchetoeu/jscript/lib/TypeErrorLib.java index 0fb1c17..d8fd623 100644 --- a/src/me/topchetoeu/jscript/lib/TypeErrorLib.java +++ b/src/me/topchetoeu/jscript/lib/TypeErrorLib.java @@ -6,15 +6,12 @@ import me.topchetoeu.jscript.interop.WrapperName; import me.topchetoeu.jscript.interop.Arguments; import me.topchetoeu.jscript.interop.ExposeConstructor; import me.topchetoeu.jscript.interop.ExposeField; -import me.topchetoeu.jscript.interop.ExposeTarget; @WrapperName("TypeError") public class TypeErrorLib extends ErrorLib { - @ExposeField(target = ExposeTarget.STATIC) - public static final String __name = "TypeError"; + @ExposeField public static final String __name = "TypeError"; - @ExposeConstructor - public static ObjectValue __constructor(Arguments args) { + @ExposeConstructor public static ObjectValue __constructor(Arguments args) { var target = ErrorLib.__constructor(args); target.setPrototype(PlaceholderProto.TYPE_ERROR); return target; From 5f4011aa0cf8d69a6d7fcf287314cc0ba2927c1e Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 6 Jan 2024 17:43:47 +0200 Subject: [PATCH 20/35] refactor: move NO_RETURN to Values, remove some casters from Values --- .../jscript/engine/debug/DebugController.java | 2 +- .../jscript/engine/debug/SimpleDebugger.java | 5 +- .../jscript/engine/frame/CodeFrame.java | 16 ++--- .../jscript/engine/frame/Runners.java | 69 +++++++++---------- .../jscript/engine/values/CodeFunction.java | 5 +- .../jscript/engine/values/ObjectValue.java | 2 +- .../jscript/engine/values/Values.java | 28 ++++---- .../jscript/lib/AsyncFunctionLib.java | 12 ++-- .../jscript/lib/AsyncGeneratorLib.java | 20 +++--- .../topchetoeu/jscript/lib/GeneratorLib.java | 18 ++--- 10 files changed, 86 insertions(+), 91 deletions(-) diff --git a/src/me/topchetoeu/jscript/engine/debug/DebugController.java b/src/me/topchetoeu/jscript/engine/debug/DebugController.java index f52a7b7..2978ec1 100644 --- a/src/me/topchetoeu/jscript/engine/debug/DebugController.java +++ b/src/me/topchetoeu/jscript/engine/debug/DebugController.java @@ -27,7 +27,7 @@ public interface DebugController { * @param frame The frame in which execution is occuring * @param instruction The instruction which was or will be executed * @param loc The most recent location the code frame has been at - * @param returnVal The return value of the instruction, Runners.NO_RETURN if none + * @param returnVal The return value of the instruction, Values.NO_RETURN if none * @param error The error that the instruction threw, null if none * @param caught Whether or not the error has been caught * @return Whether or not the frame should restart diff --git a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java index 3b16b70..adee72d 100644 --- a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java +++ b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java @@ -17,7 +17,6 @@ import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Engine; import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.frame.CodeFrame; -import me.topchetoeu.jscript.engine.frame.Runners; import me.topchetoeu.jscript.engine.scope.GlobalScope; import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.CodeFunction; @@ -906,7 +905,7 @@ public class SimpleDebugger implements Debugger { if (error != null && (execptionType == CatchType.ALL || execptionType == CatchType.UNCAUGHT && !caught)) { pauseException(ctx); } - else if (loc != null && (state == State.STEPPING_IN || state == State.STEPPING_OVER) && returnVal != Runners.NO_RETURN && stepOutFrame == frame) { + else if (loc != null && (state == State.STEPPING_IN || state == State.STEPPING_OVER) && returnVal != Values.NO_RETURN && stepOutFrame == frame) { pauseDebug(ctx, null); } else if (isBreakpointable && locToBreakpoint.containsKey(loc)) { @@ -935,7 +934,7 @@ public class SimpleDebugger implements Debugger { case STEPPING_IN: case STEPPING_OVER: if (stepOutFrame.frame == frame.frame) { - if (returnVal != Runners.NO_RETURN || error != null) { + if (returnVal != Values.NO_RETURN || error != null) { state = State.STEPPING_OUT; continue; } diff --git a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java index c85a589..1b09cb9 100644 --- a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java +++ b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java @@ -191,18 +191,18 @@ public class CodeFrame { } public Object next(Object value, Object returnValue, EngineException error) { - if (value != Runners.NO_RETURN) push(ctx, value); + if (value != Values.NO_RETURN) push(ctx, value); Instruction instr = null; if (codePtr >= 0 && codePtr < function.body.length) instr = function.body[codePtr]; - if (returnValue == Runners.NO_RETURN && error == null) { + if (returnValue == Values.NO_RETURN && error == null) { try { if (Thread.currentThread().isInterrupted()) throw new InterruptException(); if (instr == null) returnValue = null; else { - DebugContext.get(ctx).onInstruction(ctx, this, instr, Runners.NO_RETURN, null, false); + DebugContext.get(ctx).onInstruction(ctx, this, instr, Values.NO_RETURN, null, false); if (instr.location != null) prevLoc = instr.location; @@ -227,7 +227,7 @@ public class CodeFrame { if (tryCtx.hasCatch()) newCtx = tryCtx._catch(error); else if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofThrow(error, instr)); } - else if (returnValue != Runners.NO_RETURN) { + else if (returnValue != Values.NO_RETURN) { if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofReturn(returnValue, instr)); } else if (jumpFlag && !tryCtx.inBounds(codePtr)) { @@ -254,7 +254,7 @@ public class CodeFrame { tryStack.push(newCtx); } error = null; - returnValue = Runners.NO_RETURN; + returnValue = Values.NO_RETURN; break; } else { @@ -272,7 +272,7 @@ public class CodeFrame { tryStack.pop(); codePtr = tryCtx.end; if (tryCtx.result.instruction != null) instr = tryCtx.result.instruction; - if (!jumpFlag && returnValue == Runners.NO_RETURN && error == null) { + if (!jumpFlag && returnValue == Values.NO_RETURN && error == null) { if (tryCtx.result.isJump) { codePtr = tryCtx.result.ptr; jumpFlag = true; @@ -300,12 +300,12 @@ public class CodeFrame { DebugContext.get(ctx).onInstruction(ctx, this, instr, null, error, caught); throw error; } - if (returnValue != Runners.NO_RETURN) { + if (returnValue != Values.NO_RETURN) { DebugContext.get(ctx).onInstruction(ctx, this, instr, returnValue, null, false); return returnValue; } - return Runners.NO_RETURN; + return Values.NO_RETURN; } public void onPush() { diff --git a/src/me/topchetoeu/jscript/engine/frame/Runners.java b/src/me/topchetoeu/jscript/engine/frame/Runners.java index 1eb1845..35f67db 100644 --- a/src/me/topchetoeu/jscript/engine/frame/Runners.java +++ b/src/me/topchetoeu/jscript/engine/frame/Runners.java @@ -10,14 +10,13 @@ import me.topchetoeu.jscript.engine.Operation; import me.topchetoeu.jscript.engine.scope.ValueVariable; import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.CodeFunction; +import me.topchetoeu.jscript.engine.values.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; public class Runners { - public static final Object NO_RETURN = new Object(); - public static Object execReturn(Context ctx, Instruction instr, CodeFrame frame) { return frame.pop(); } @@ -36,7 +35,7 @@ public class Runners { frame.push(ctx, Values.call(ctx, func, thisArg, callArgs)); frame.codePtr++; - return NO_RETURN; + return Values.NO_RETURN; } public static Object execCallNew(Context ctx, Instruction instr, CodeFrame frame) { var callArgs = frame.take(instr.get(0)); @@ -45,14 +44,14 @@ public class Runners { frame.push(ctx, Values.callNew(ctx, funcObj, callArgs)); frame.codePtr++; - return NO_RETURN; + return Values.NO_RETURN; } public static Object execMakeVar(Context ctx, Instruction instr, CodeFrame frame) { var name = (String)instr.get(0); ctx.environment.global.define(name); frame.codePtr++; - return NO_RETURN; + return Values.NO_RETURN; } public static Object execDefProp(Context ctx, Instruction instr, CodeFrame frame) { var setter = frame.pop(); @@ -60,14 +59,14 @@ public class Runners { var name = frame.pop(); var obj = frame.pop(); - if (getter != null && !Values.isFunction(getter)) throw EngineException.ofType("Getter must be a function or undefined."); - if (setter != null && !Values.isFunction(setter)) throw EngineException.ofType("Setter must be a function or undefined."); - if (!Values.isObject(obj)) throw EngineException.ofType("Property apply target must be an object."); + if (getter != null && !(getter instanceof FunctionValue)) throw EngineException.ofType("Getter must be a function or undefined."); + if (setter != null && !(setter instanceof FunctionValue)) throw EngineException.ofType("Setter must be a function or undefined."); + if (!(obj instanceof ObjectValue)) throw EngineException.ofType("Property apply target must be an object."); Values.object(obj).defineProperty(ctx, name, Values.function(getter), Values.function(setter), false, false); frame.push(ctx, obj); frame.codePtr++; - return NO_RETURN; + return Values.NO_RETURN; } public static Object execInstanceof(Context ctx, Instruction instr, CodeFrame frame) { var type = frame.pop(); @@ -82,7 +81,7 @@ public class Runners { } frame.codePtr++; - return NO_RETURN; + return Values.NO_RETURN; } public static Object execKeys(Context ctx, Instruction instr, CodeFrame frame) { var val = frame.pop(); @@ -100,7 +99,7 @@ public class Runners { } frame.codePtr++; - return NO_RETURN; + return Values.NO_RETURN; } public static Object execTryStart(Context ctx, Instruction instr, CodeFrame frame) { @@ -112,11 +111,11 @@ public class Runners { int end = (int)instr.get(2) + start; frame.addTry(start, end, catchStart, finallyStart); frame.codePtr++; - return NO_RETURN; + return Values.NO_RETURN; } public static Object execTryEnd(Context ctx, Instruction instr, CodeFrame frame) { frame.popTryFlag = true; - return NO_RETURN; + return Values.NO_RETURN; } public static Object execDup(Context ctx, Instruction instr, CodeFrame frame) { @@ -127,17 +126,17 @@ public class Runners { } frame.codePtr++; - return NO_RETURN; + return Values.NO_RETURN; } public static Object execLoadUndefined(Context ctx, Instruction instr, CodeFrame frame) { frame.push(ctx, null); frame.codePtr++; - return NO_RETURN; + return Values.NO_RETURN; } public static Object execLoadValue(Context ctx, Instruction instr, CodeFrame frame) { frame.push(ctx, instr.get(0)); frame.codePtr++; - return NO_RETURN; + return Values.NO_RETURN; } public static Object execLoadVar(Context ctx, Instruction instr, CodeFrame frame) { var i = instr.get(0); @@ -146,24 +145,24 @@ public class Runners { else frame.push(ctx, frame.scope.get((int)i).get(ctx)); frame.codePtr++; - return NO_RETURN; + return Values.NO_RETURN; } public static Object execLoadObj(Context ctx, Instruction instr, CodeFrame frame) { frame.push(ctx, new ObjectValue()); frame.codePtr++; - return NO_RETURN; + return Values.NO_RETURN; } public static Object execLoadGlob(Context ctx, Instruction instr, CodeFrame frame) { frame.push(ctx, ctx.environment.global.obj); frame.codePtr++; - return NO_RETURN; + return Values.NO_RETURN; } public static Object execLoadArr(Context ctx, Instruction instr, CodeFrame frame) { var res = new ArrayValue(); res.setSize(instr.get(0)); frame.push(ctx, res); frame.codePtr++; - return NO_RETURN; + return Values.NO_RETURN; } public static Object execLoadFunc(Context ctx, Instruction instr, CodeFrame frame) { long id = (Long)instr.get(0); @@ -178,7 +177,7 @@ public class Runners { frame.push(ctx, func); frame.codePtr++; - return NO_RETURN; + return Values.NO_RETURN; } public static Object execLoadMember(Context ctx, Instruction instr, CodeFrame frame) { var key = frame.pop(); @@ -191,7 +190,7 @@ public class Runners { throw EngineException.ofType(e.getMessage()); } frame.codePtr++; - return NO_RETURN; + return Values.NO_RETURN; } public static Object execLoadKeyMember(Context ctx, Instruction instr, CodeFrame frame) { frame.push(ctx, instr.get(0)); @@ -205,13 +204,13 @@ public class Runners { throw EngineException.ofSyntax("Regex is not supported."); } frame.codePtr++; - return NO_RETURN; + return Values.NO_RETURN; } public static Object execDiscard(Context ctx, Instruction instr, CodeFrame frame) { frame.pop(); frame.codePtr++; - return NO_RETURN; + return Values.NO_RETURN; } public static Object execStoreMember(Context ctx, Instruction instr, CodeFrame frame) { var val = frame.pop(); @@ -221,7 +220,7 @@ public class Runners { if (!Values.setMember(ctx, obj, key, val)) throw EngineException.ofSyntax("Can't set member '" + key + "'."); if ((boolean)instr.get(0)) frame.push(ctx, val); frame.codePtr++; - return NO_RETURN; + return Values.NO_RETURN; } public static Object execStoreVar(Context ctx, Instruction instr, CodeFrame frame) { var val = (boolean)instr.get(1) ? frame.peek() : frame.pop(); @@ -231,18 +230,18 @@ public class Runners { else frame.scope.get((int)i).set(ctx, val); frame.codePtr++; - return NO_RETURN; + return Values.NO_RETURN; } public static Object execStoreSelfFunc(Context ctx, Instruction instr, CodeFrame frame) { frame.scope.locals[(int)instr.get(0)].set(ctx, frame.function); frame.codePtr++; - return NO_RETURN; + return Values.NO_RETURN; } public static Object execJmp(Context ctx, Instruction instr, CodeFrame frame) { frame.codePtr += (int)instr.get(0); frame.jumpFlag = true; - return NO_RETURN; + return Values.NO_RETURN; } public static Object execJmpIf(Context ctx, Instruction instr, CodeFrame frame) { if (Values.toBoolean(frame.pop())) { @@ -250,7 +249,7 @@ public class Runners { frame.jumpFlag = true; } else frame.codePtr ++; - return NO_RETURN; + return Values.NO_RETURN; } public static Object execJmpIfNot(Context ctx, Instruction instr, CodeFrame frame) { if (Values.not(frame.pop())) { @@ -258,7 +257,7 @@ public class Runners { frame.jumpFlag = true; } else frame.codePtr ++; - return NO_RETURN; + return Values.NO_RETURN; } public static Object execIn(Context ctx, Instruction instr, CodeFrame frame) { @@ -267,7 +266,7 @@ public class Runners { frame.push(ctx, Values.hasMember(ctx, obj, index, false)); frame.codePtr++; - return NO_RETURN; + return Values.NO_RETURN; } public static Object execTypeof(Context ctx, Instruction instr, CodeFrame frame) { String name = instr.get(0); @@ -284,11 +283,11 @@ public class Runners { frame.push(ctx, Values.type(obj)); frame.codePtr++; - return NO_RETURN; + return Values.NO_RETURN; } public static Object execNop(Context ctx, Instruction instr, CodeFrame frame) { frame.codePtr++; - return NO_RETURN; + return Values.NO_RETURN; } public static Object execDelete(Context ctx, Instruction instr, CodeFrame frame) { @@ -297,7 +296,7 @@ public class Runners { if (!Values.deleteMember(ctx, val, key)) throw EngineException.ofSyntax("Can't delete member '" + key + "'."); frame.codePtr++; - return NO_RETURN; + return Values.NO_RETURN; } public static Object execOperation(Context ctx, Instruction instr, CodeFrame frame) { @@ -308,7 +307,7 @@ public class Runners { frame.push(ctx, Values.operation(ctx, op, args)); frame.codePtr++; - return NO_RETURN; + return Values.NO_RETURN; } public static Object exec(Context ctx, Instruction instr, CodeFrame frame) { diff --git a/src/me/topchetoeu/jscript/engine/values/CodeFunction.java b/src/me/topchetoeu/jscript/engine/values/CodeFunction.java index ac41137..01fd6a6 100644 --- a/src/me/topchetoeu/jscript/engine/values/CodeFunction.java +++ b/src/me/topchetoeu/jscript/engine/values/CodeFunction.java @@ -6,7 +6,6 @@ import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.frame.CodeFrame; -import me.topchetoeu.jscript.engine.frame.Runners; import me.topchetoeu.jscript.engine.scope.ValueVariable; public class CodeFunction extends FunctionValue { @@ -37,8 +36,8 @@ public class CodeFunction extends FunctionValue { try { while (true) { - var res = frame.next(Runners.NO_RETURN, Runners.NO_RETURN, null); - if (res != Runners.NO_RETURN) return res; + var res = frame.next(Values.NO_RETURN, Values.NO_RETURN, null); + if (res != Values.NO_RETURN) return res; } } finally { diff --git a/src/me/topchetoeu/jscript/engine/values/ObjectValue.java b/src/me/topchetoeu/jscript/engine/values/ObjectValue.java index 1f71fda..176078a 100644 --- a/src/me/topchetoeu/jscript/engine/values/ObjectValue.java +++ b/src/me/topchetoeu/jscript/engine/values/ObjectValue.java @@ -167,7 +167,7 @@ public class ObjectValue { prototype = null; return true; } - else if (Values.isObject(val)) { + else if (val instanceof ObjectValue) { var obj = Values.object(val); if (ctx != null) { diff --git a/src/me/topchetoeu/jscript/engine/values/Values.java b/src/me/topchetoeu/jscript/engine/values/Values.java index cdc1244..a6b3790 100644 --- a/src/me/topchetoeu/jscript/engine/values/Values.java +++ b/src/me/topchetoeu/jscript/engine/values/Values.java @@ -40,10 +40,8 @@ public class Values { } public static final Object NULL = new Object(); + public static final Object NO_RETURN = new Object(); - public static boolean isObject(Object val) { return val instanceof ObjectValue; } - public static boolean isFunction(Object val) { return val instanceof FunctionValue; } - public static boolean isArray(Object val) { return val instanceof ArrayValue; } public static boolean isWrapper(Object val) { return val instanceof NativeWrapper; } public static boolean isWrapper(Object val, Class clazz) { if (!isWrapper(val)) return false; @@ -90,8 +88,8 @@ public class Values { private static Object tryCallConvertFunc(Context ctx, Object obj, String name) { var func = getMember(ctx, obj, name); - if (func != null) { - var res = ((FunctionValue)func).call(ctx, obj); + if (func instanceof FunctionValue) { + var res = Values.call(ctx, func, obj); if (isPrimitive(res)) return res; } @@ -285,7 +283,7 @@ public class Values { obj = normalize(ctx, obj); key = normalize(ctx, key); if (obj == null) throw new IllegalArgumentException("Tried to access member of undefined."); if (obj == NULL) throw new IllegalArgumentException("Tried to access member of null."); - if (isObject(obj)) return object(obj).getMember(ctx, key); + if (obj instanceof ObjectValue) return object(obj).getMember(ctx, key); if (obj instanceof String && key instanceof Number) { var i = number(key); @@ -311,7 +309,7 @@ public class Values { if (obj == null) throw EngineException.ofType("Tried to access member of undefined."); if (obj == NULL) throw EngineException.ofType("Tried to access member of null."); if (key != null && "__proto__".equals(key)) return setPrototype(ctx, obj, val); - if (isObject(obj)) return object(obj).setMember(ctx, key, val, false); + if (obj instanceof ObjectValue) return object(obj).setMember(ctx, key, val, false); var proto = getPrototype(ctx, obj); return proto.setMember(ctx, key, val, obj, true); @@ -321,7 +319,7 @@ public class Values { obj = normalize(ctx, obj); key = normalize(ctx, key); if ("__proto__".equals(key)) return true; - if (isObject(obj)) return object(obj).hasMember(ctx, key, own); + if (obj instanceof ObjectValue) return object(obj).hasMember(ctx, key, own); if (obj instanceof String && key instanceof Number) { var i = number(key); @@ -338,13 +336,13 @@ public class Values { if (obj == null || obj == NULL) return false; obj = normalize(ctx, obj); key = normalize(ctx, key); - if (isObject(obj)) return object(obj).deleteMember(ctx, key); + if (obj instanceof ObjectValue) return object(obj).deleteMember(ctx, key); else return false; } public static ObjectValue getPrototype(Context ctx, Object obj) { if (obj == null || obj == NULL) return null; obj = normalize(ctx, obj); - if (isObject(obj)) return object(obj).getPrototype(ctx); + if (obj instanceof ObjectValue) return object(obj).getPrototype(ctx); if (ctx == null) return null; if (obj instanceof String) return ctx.get(Environment.STRING_PROTO); @@ -356,12 +354,12 @@ public class Values { } public static boolean setPrototype(Context ctx, Object obj, Object proto) { obj = normalize(ctx, obj); - return isObject(obj) && object(obj).setPrototype(ctx, proto); + return obj instanceof ObjectValue && object(obj).setPrototype(ctx, proto); } public static List getMembers(Context ctx, Object obj, boolean own, boolean includeNonEnumerable) { List res = new ArrayList<>(); - if (isObject(obj)) res = object(obj).keys(includeNonEnumerable); + if (obj instanceof ObjectValue) res = object(obj).keys(includeNonEnumerable); if (obj instanceof String) { for (var i = 0; i < ((String)obj).length(); i++) res.add((double)i); } @@ -397,7 +395,7 @@ public class Values { } public static Object call(Context ctx, Object func, Object thisArg, Object ...args) { - if (!isFunction(func)) throw EngineException.ofType("Tried to call a non-function value."); + if (!(func instanceof FunctionValue)) 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) { @@ -557,13 +555,13 @@ public class Values { var symbol = Symbol.get("Symbol.iterator"); var iteratorFunc = getMember(ctx, obj, symbol); - if (!isFunction(iteratorFunc)) return Collections.emptyIterator(); + if (!(iteratorFunc instanceof FunctionValue)) return Collections.emptyIterator(); var iterator = iteratorFunc instanceof FunctionValue ? ((FunctionValue)iteratorFunc).call(ctx, obj, obj) : iteratorFunc; var nextFunc = getMember(ctx, call(ctx, iteratorFunc, obj), "next"); - if (!isFunction(nextFunc)) return Collections.emptyIterator(); + if (!(nextFunc instanceof FunctionValue)) return Collections.emptyIterator(); return new Iterator() { private Object value = null; diff --git a/src/me/topchetoeu/jscript/lib/AsyncFunctionLib.java b/src/me/topchetoeu/jscript/lib/AsyncFunctionLib.java index b30fd5e..1814e5d 100644 --- a/src/me/topchetoeu/jscript/lib/AsyncFunctionLib.java +++ b/src/me/topchetoeu/jscript/lib/AsyncFunctionLib.java @@ -2,10 +2,10 @@ package me.topchetoeu.jscript.lib; 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.Values; import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.interop.Arguments; import me.topchetoeu.jscript.interop.WrapperName; @@ -28,11 +28,11 @@ public class AsyncFunctionLib extends FunctionValue { awaiting = false; while (!awaiting) { try { - res = frame.next(inducedValue, Runners.NO_RETURN, inducedError); - inducedValue = Runners.NO_RETURN; + res = frame.next(inducedValue, Values.NO_RETURN, inducedError); + inducedValue = Values.NO_RETURN; inducedError = null; - if (res != Runners.NO_RETURN) { + if (res != Values.NO_RETURN) { promise.fulfill(ctx, res); break; } @@ -52,7 +52,7 @@ public class AsyncFunctionLib extends FunctionValue { } @Override public void onReject(EngineException err) { - next(ctx, Runners.NO_RETURN, err); + next(ctx, Values.NO_RETURN, err); } }); } @@ -70,7 +70,7 @@ public class AsyncFunctionLib extends FunctionValue { 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, null); + handler.next(ctx, Values.NO_RETURN, null); return handler.promise; } diff --git a/src/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java b/src/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java index 6e957ed..d53ece1 100644 --- a/src/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java +++ b/src/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java @@ -4,8 +4,8 @@ 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.ObjectValue; +import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.interop.Arguments; import me.topchetoeu.jscript.interop.Expose; @@ -24,7 +24,7 @@ public class AsyncGeneratorLib { if (inducedError != null) throw inducedError; currPromise.fulfill(ctx, new ObjectValue(ctx, Map.of( "done", true, - "value", inducedReturn == Runners.NO_RETURN ? null : inducedReturn + "value", inducedReturn == Values.NO_RETURN ? null : inducedReturn ))); return; } @@ -36,10 +36,10 @@ public class AsyncGeneratorLib { while (state == 0) { try { res = frame.next(inducedValue, inducedReturn, inducedError); - inducedValue = inducedReturn = Runners.NO_RETURN; + inducedValue = inducedReturn = Values.NO_RETURN; inducedError = null; - if (res != Runners.NO_RETURN) { + if (res != Values.NO_RETURN) { var obj = new ObjectValue(); obj.defineProperty(ctx, "done", true); obj.defineProperty(ctx, "value", res); @@ -57,10 +57,10 @@ public class AsyncGeneratorLib { if (state == 1) { PromiseLib.handle(ctx, frame.pop(), new Handle() { @Override public void onFulfil(Object val) { - next(ctx, val, Runners.NO_RETURN, null); + next(ctx, val, Values.NO_RETURN, null); } @Override public void onReject(EngineException err) { - next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, err); + next(ctx, Values.NO_RETURN, Values.NO_RETURN, err); } }); } @@ -90,18 +90,18 @@ public class AsyncGeneratorLib { @Expose public PromiseLib __next(Arguments args) { this.currPromise = new PromiseLib(); - if (args.has(0)) next(args.ctx, args.get(0), Runners.NO_RETURN, null); - else next(args.ctx, Runners.NO_RETURN, Runners.NO_RETURN, null); + if (args.has(0)) next(args.ctx, args.get(0), Values.NO_RETURN, null); + else next(args.ctx, Values.NO_RETURN, Values.NO_RETURN, null); return this.currPromise; } @Expose public PromiseLib __return(Arguments args) { this.currPromise = new PromiseLib(); - next(args.ctx, Runners.NO_RETURN, args.get(0), null); + next(args.ctx, Values.NO_RETURN, args.get(0), null); return this.currPromise; } @Expose public PromiseLib __throw(Arguments args) { this.currPromise = new PromiseLib(); - next(args.ctx, Runners.NO_RETURN, Runners.NO_RETURN, new EngineException(args.get(0)).setCtx(args.ctx)); + next(args.ctx, Values.NO_RETURN, Values.NO_RETURN, new EngineException(args.get(0)).setCtx(args.ctx)); return this.currPromise; } } \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/lib/GeneratorLib.java b/src/me/topchetoeu/jscript/lib/GeneratorLib.java index cf4b5a9..3e3219b 100644 --- a/src/me/topchetoeu/jscript/lib/GeneratorLib.java +++ b/src/me/topchetoeu/jscript/lib/GeneratorLib.java @@ -2,8 +2,8 @@ package me.topchetoeu.jscript.lib; 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.ObjectValue; +import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.interop.Arguments; import me.topchetoeu.jscript.interop.Expose; @@ -17,10 +17,10 @@ public class GeneratorLib { private ObjectValue next(Context ctx, Object inducedValue, Object inducedReturn, EngineException inducedError) { if (done) { - if (inducedError != Runners.NO_RETURN) throw inducedError; + if (inducedError != Values.NO_RETURN) throw inducedError; var res = new ObjectValue(); res.defineProperty(ctx, "done", true); - res.defineProperty(ctx, "value", inducedReturn == Runners.NO_RETURN ? null : inducedReturn); + res.defineProperty(ctx, "value", inducedReturn == Values.NO_RETURN ? null : inducedReturn); return res; } @@ -31,9 +31,9 @@ public class GeneratorLib { while (!yielding) { try { res = frame.next(inducedValue, inducedReturn, inducedError); - inducedReturn = Runners.NO_RETURN; + inducedReturn = Values.NO_RETURN; inducedError = null; - if (res != Runners.NO_RETURN) { + if (res != Values.NO_RETURN) { done = true; break; } @@ -55,14 +55,14 @@ public class GeneratorLib { } @Expose public ObjectValue __next(Arguments args) { - if (args.n() == 0) return next(args.ctx, Runners.NO_RETURN, Runners.NO_RETURN, null); - else return next(args.ctx, args.get(0), Runners.NO_RETURN, null); + if (args.n() == 0) return next(args.ctx, Values.NO_RETURN, Values.NO_RETURN, null); + else return next(args.ctx, args.get(0), Values.NO_RETURN, null); } @Expose public ObjectValue __throw(Arguments args) { - return next(args.ctx, Runners.NO_RETURN, Runners.NO_RETURN, new EngineException(args.get(0)).setCtx(args.ctx)); + return next(args.ctx, Values.NO_RETURN, Values.NO_RETURN, new EngineException(args.get(0)).setCtx(args.ctx)); } @Expose public ObjectValue __return(Arguments args) { - return next(args.ctx, Runners.NO_RETURN, args.get(0), null); + return next(args.ctx, Values.NO_RETURN, args.get(0), null); } @Override public String toString() { From 07a6f18b16425737bef5c1a8e0be7e03c6bacd2b Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 6 Jan 2024 17:44:23 +0200 Subject: [PATCH 21/35] refactor: some spring cleaning in array lib, fix small issue with join --- src/me/topchetoeu/jscript/lib/ArrayLib.java | 78 +++++++++++++-------- 1 file changed, 48 insertions(+), 30 deletions(-) diff --git a/src/me/topchetoeu/jscript/lib/ArrayLib.java b/src/me/topchetoeu/jscript/lib/ArrayLib.java index 370fe51..ac9ff39 100644 --- a/src/me/topchetoeu/jscript/lib/ArrayLib.java +++ b/src/me/topchetoeu/jscript/lib/ArrayLib.java @@ -21,7 +21,7 @@ public class ArrayLib { if (i < 0) i += len; if (clamp) { if (i < 0) i = 0; - if (i >= len) i = len; + if (i > len) i = len; } return i; } @@ -133,46 +133,48 @@ public class ArrayLib { } @Expose public static boolean __every(Arguments args) { var arr = args.self(ArrayValue.class); - var func = args.convert(0, FunctionValue.class); - var thisArg = args.get(1); for (var i = 0; i < arr.size(); i++) { - if (!Values.toBoolean(func.call(args.ctx, thisArg, arr.get(i), i, arr))) return false; + if (arr.has(i) && !Values.toBoolean(Values.call( + args.ctx, args.get(0), args.get(1), + arr.get(i), i, arr + ))) return false; } return true; } @Expose public static boolean __some(Arguments args) { var arr = args.self(ArrayValue.class); - var func = args.convert(0, FunctionValue.class); - var thisArg = args.get(1); for (var i = 0; i < arr.size(); i++) { - if (Values.toBoolean(func.call(args.ctx, thisArg, arr.get(i), i, arr))) return true; + if (arr.has(i) && Values.toBoolean(Values.call( + args.ctx, args.get(0), args.get(1), + arr.get(i), i, arr + ))) return true; } return false; } @Expose public static ArrayValue __filter(Arguments args) { var arr = args.self(ArrayValue.class); - var func = args.convert(0, FunctionValue.class); - var thisArg = args.get(1); var res = new ArrayValue(arr.size()); for (int i = 0, j = 0; i < arr.size(); i++) { - if (arr.has(i) && Values.toBoolean(func.call(args.ctx, thisArg, arr.get(i), i, arr))) res.set(args.ctx, j++, arr.get(i)); + if (arr.has(i) && Values.toBoolean(Values.call( + args.ctx, args.get(0), args.get(1), + arr.get(i), i, arr + ))) res.set(args.ctx, j++, arr.get(i)); } + return res; } @Expose public static ArrayValue __map(Arguments args) { var arr = args.self(ArrayValue.class); - var func = args.convert(0, FunctionValue.class); - var thisArg = args.get(1); var res = new ArrayValue(arr.size()); res.setSize(arr.size()); - for (int i = 0, j = 0; i < arr.size(); i++) { - if (arr.has(i)) res.set(args.ctx, j++, func.call(args.ctx, thisArg, arr.get(i), i, arr)); + for (int i = 0; i < arr.size(); i++) { + if (arr.has(i)) res.set(args.ctx, i, Values.call(args.ctx, args.get(0), args.get(1), arr.get(i), i, arr)); } return res; } @@ -192,7 +194,14 @@ public class ArrayLib { var res = args.get(1); var i = 0; - if (args.n() < 2) for (; !arr.has(i) && i < arr.size(); i++) res = arr.get(i); + if (args.n() < 2) { + for (; i < arr.size(); i++) { + if (arr.has(i)){ + res = arr.get(i++); + break; + } + } + } for (; i < arr.size(); i++) { if (arr.has(i)) { @@ -208,7 +217,12 @@ public class ArrayLib { var res = args.get(1); var i = arr.size(); - if (args.n() < 1) while (!arr.has(i--) && i >= 0) res = arr.get(i); + if (args.n() < 2) { + while (!arr.has(i--) && i >= 0) { + res = arr.get(i); + } + } + else i--; for (; i >= 0; i--) { if (arr.has(i)) { @@ -252,22 +266,24 @@ public class ArrayLib { @Expose public static Object __find(Arguments args) { var arr = args.self(ArrayValue.class); - var cmp = args.convert(0, FunctionValue.class); - var thisArg = args.get(1); for (int i = 0; i < arr.size(); i++) { - if (arr.has(i) && Values.toBoolean(cmp.call(args.ctx, thisArg, arr.get(i), i, arr))) return arr.get(i); + if (arr.has(i) && Values.toBoolean(Values.call( + args.ctx, args.get(0), args.get(1), + arr.get(i), i, args.self + ))) return arr.get(i); } return null; } @Expose public static Object __findLast(Arguments args) { var arr = args.self(ArrayValue.class); - var cmp = args.convert(0, FunctionValue.class); - var thisArg = args.get(1); for (var i = arr.size() - 1; i >= 0; i--) { - if (arr.has(i) && Values.toBoolean(cmp.call(args.ctx, thisArg, arr.get(i), i, arr))) return arr.get(i); + if (arr.has(i) && Values.toBoolean(Values.call( + args.ctx, args.get(0), args.get(1), + arr.get(i), i, args.self + ))) return arr.get(i); } return null; @@ -275,22 +291,24 @@ public class ArrayLib { @Expose public static int __findIndex(Arguments args) { var arr = args.self(ArrayValue.class); - var cmp = args.convert(0, FunctionValue.class); - var thisArg = args.get(1); for (int i = 0; i < arr.size(); i++) { - if (arr.has(i) && Values.toBoolean(cmp.call(args.ctx, thisArg, arr.get(i), i, arr))) return i; + if (arr.has(i) && Values.toBoolean(Values.call( + args.ctx, args.get(0), args.get(1), + arr.get(i), i, args.self + ))) return i; } return -1; } @Expose public static int __findLastIndex(Arguments args) { var arr = args.self(ArrayValue.class); - var cmp = args.convert(0, FunctionValue.class); - var thisArg = args.get(1); for (var i = arr.size() - 1; i >= 0; i--) { - if (arr.has(i) && Values.toBoolean(cmp.call(args.ctx, thisArg, arr.get(i), i, arr))) return i; + if (arr.has(i) && Values.toBoolean(Values.call( + args.ctx, args.get(0), args.get(1), + arr.get(i), i, args.self + ))) return i; } return -1; @@ -391,7 +409,7 @@ public class ArrayLib { @Expose public static String __join(Arguments args) { var arr = args.self(ArrayValue.class); - var sep = args.getString(0); + var sep = args.getString(0, ", "); var res = new StringBuilder(); var comma = false; @@ -428,7 +446,7 @@ public class ArrayLib { res.setSize(len); } else { - var val = args.slice(0).args; + var val = args.args; res = new ArrayValue(val.length); res.copyFrom(args.ctx, val, 0, 0, val.length); } From a321fc14bc6b514b882271271db8bbe07d6c3953 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 6 Jan 2024 17:44:57 +0200 Subject: [PATCH 22/35] fix: wrong signature of Map.forEach --- src/assets/js/lib.d.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/assets/js/lib.d.ts b/src/assets/js/lib.d.ts index 1cc226b..07b8254 100644 --- a/src/assets/js/lib.d.ts +++ b/src/assets/js/lib.d.ts @@ -566,7 +566,7 @@ declare class Map { public get size(): number; - public forEach(func: (key: KeyT, val: ValueT, map: Map) => void, thisArg?: any): void; + public forEach(func: (val: ValueT, key: KeyT, map: Map) => void, thisArg?: any): void; public constructor(); } @@ -585,7 +585,7 @@ declare class Set { public get size(): number; - public forEach(func: (key: T, set: Set) => void, thisArg?: any): void; + public forEach(func: (key: T, value: T, set: Set) => void, thisArg?: any): void; public constructor(); } From 918f2623cd2055b2a34e60958b720c8cb741f83d Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 6 Jan 2024 17:46:13 +0200 Subject: [PATCH 23/35] fix: small issue with sparse arrays --- src/me/topchetoeu/jscript/engine/values/ArrayValue.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/me/topchetoeu/jscript/engine/values/ArrayValue.java b/src/me/topchetoeu/jscript/engine/values/ArrayValue.java index 7a99b76..fa43bf7 100644 --- a/src/me/topchetoeu/jscript/engine/values/ArrayValue.java +++ b/src/me/topchetoeu/jscript/engine/values/ArrayValue.java @@ -84,6 +84,8 @@ public class ArrayValue extends ObjectValue implements Iterable { } public void copyTo(Context ctx, ArrayValue arr, int sourceStart, int destStart, int count) { // Iterate in reverse to reallocate at most once + if (destStart + count > arr.size) arr.size = destStart + count; + for (var i = count - 1; i >= 0; i--) { if (i + sourceStart < 0 || i + sourceStart >= size) arr.remove(i + destStart); if (values[i + sourceStart] == UNDEFINED) arr.set(ctx, i + destStart, null); From 4aa757e625293e131c321df3843edb888f83ee64 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 6 Jan 2024 17:46:39 +0200 Subject: [PATCH 24/35] fix: Function.bind now passess this argument, instead of the function itself --- src/me/topchetoeu/jscript/lib/FunctionLib.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/me/topchetoeu/jscript/lib/FunctionLib.java b/src/me/topchetoeu/jscript/lib/FunctionLib.java index f79e5d1..7d42754 100644 --- a/src/me/topchetoeu/jscript/lib/FunctionLib.java +++ b/src/me/topchetoeu/jscript/lib/FunctionLib.java @@ -24,17 +24,20 @@ public class FunctionLib { } @Expose public static FunctionValue __bind(Arguments args) { var self = args.self(FunctionValue.class); + var thisArg = args.get(0); + var bindArgs = args.slice(1).args; + return new NativeFunction(self.name + " (bound)", callArgs -> { Object[] resArgs; - if (args.n() == 0) resArgs = callArgs.args; + if (args.n() == 0) resArgs = bindArgs; else { - resArgs = new Object[args.n() + callArgs.n()]; - System.arraycopy(args.args, 0, resArgs, 0, args.n()); - System.arraycopy(callArgs.args, 0, resArgs, args.n(), callArgs.n()); + resArgs = new Object[bindArgs.length + callArgs.n()]; + System.arraycopy(bindArgs, 0, resArgs, 0, bindArgs.length); + System.arraycopy(callArgs.args, 0, resArgs, bindArgs.length, callArgs.n()); } - return self.call(callArgs.ctx, self, resArgs); + return self.call(callArgs.ctx, thisArg, resArgs); }); } @Expose public static String __toString(Arguments args) { From 2c634778c3f6024dff47af6c7844e65af7ac060c Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 6 Jan 2024 17:47:07 +0200 Subject: [PATCH 25/35] fix: report proper function name in String.length errors --- src/me/topchetoeu/jscript/lib/StringLib.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/me/topchetoeu/jscript/lib/StringLib.java b/src/me/topchetoeu/jscript/lib/StringLib.java index 65311b3..40d5941 100644 --- a/src/me/topchetoeu/jscript/lib/StringLib.java +++ b/src/me/topchetoeu/jscript/lib/StringLib.java @@ -37,14 +37,14 @@ public class StringLib { if (i < 0) i += len; if (clamp) { if (i < 0) i = 0; - if (i >= len) i = len; + if (i > len) i = len; } return i; } @Expose(type = ExposeType.GETTER) public static int __length(Arguments args) { - return passThis(args, "substring").length(); + return passThis(args, "length").length(); } @Expose public static String __substring(Arguments args) { From 34276d720c8b7fc4394bbb6c2a559852a2446472 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 6 Jan 2024 17:47:38 +0200 Subject: [PATCH 26/35] fix: remove sparse call arguments --- src/me/topchetoeu/jscript/parsing/Parsing.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/me/topchetoeu/jscript/parsing/Parsing.java b/src/me/topchetoeu/jscript/parsing/Parsing.java index 6131677..1053225 100644 --- a/src/me/topchetoeu/jscript/parsing/Parsing.java +++ b/src/me/topchetoeu/jscript/parsing/Parsing.java @@ -1178,8 +1178,7 @@ public class Parsing { prevArg = true; } else if (argRes.isError()) return argRes.transform(); - else if (isOperator(tokens, i + n, Operator.COMMA)) { - if (!prevArg) args.add(null); + else if (prevArg && isOperator(tokens, i + n, Operator.COMMA)) { prevArg = false; n++; } @@ -1187,7 +1186,7 @@ public class Parsing { n++; break; } - else return ParseRes.failed(); + else return ParseRes.error(getLoc(filename, tokens, i + n), prevArg ? "Expected a comma or a closing paren." : "Expected an expression or a closing paren."); } return ParseRes.res(new CallStatement(loc, false, prev, args.toArray(Statement[]::new)), n); From 45f133c6b09b17c506d5c5a11e3dc74040909d3e Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 6 Jan 2024 17:48:10 +0200 Subject: [PATCH 27/35] fix: use Values.call instead of direct calling --- src/me/topchetoeu/jscript/lib/MapLib.java | 6 +----- src/me/topchetoeu/jscript/lib/SetLib.java | 2 +- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/src/me/topchetoeu/jscript/lib/MapLib.java b/src/me/topchetoeu/jscript/lib/MapLib.java index 2452102..144ee0a 100644 --- a/src/me/topchetoeu/jscript/lib/MapLib.java +++ b/src/me/topchetoeu/jscript/lib/MapLib.java @@ -6,7 +6,6 @@ import java.util.stream.Collectors; 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.Values; import me.topchetoeu.jscript.interop.Arguments; @@ -68,12 +67,9 @@ public class MapLib { } @Expose public void __forEach(Arguments args) { - var func = args.convert(0, FunctionValue.class); - var thisArg = args.get(1); - var keys = new ArrayList<>(map.keySet()); - for (var el : keys) func.call(args.ctx, thisArg, map.get(el), el,this); + for (var el : keys) Values.call(args.ctx, args.get(0), args.get(1), map.get(el), el, args.self); } public MapLib(Context ctx, Object iterable) { diff --git a/src/me/topchetoeu/jscript/lib/SetLib.java b/src/me/topchetoeu/jscript/lib/SetLib.java index eade541..5cbc2ea 100644 --- a/src/me/topchetoeu/jscript/lib/SetLib.java +++ b/src/me/topchetoeu/jscript/lib/SetLib.java @@ -55,7 +55,7 @@ public class SetLib { @Expose public void __forEach(Arguments args) { var keys = new ArrayList<>(set); - for (var el : keys) Values.call(args.ctx, args.get(0), args.get(1), el, el, this); + for (var el : keys) Values.call(args.ctx, args.get(0), args.get(1), el, el, args.self); } public SetLib(Context ctx, Object iterable) { From 3173919b49218fd41826119f3ebc4fae95ec3ce2 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 6 Jan 2024 17:48:35 +0200 Subject: [PATCH 28/35] fix: implement proper parseInt logic --- src/me/topchetoeu/jscript/lib/NumberLib.java | 23 +++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/me/topchetoeu/jscript/lib/NumberLib.java b/src/me/topchetoeu/jscript/lib/NumberLib.java index d312e82..355feec 100644 --- a/src/me/topchetoeu/jscript/lib/NumberLib.java +++ b/src/me/topchetoeu/jscript/lib/NumberLib.java @@ -54,7 +54,28 @@ public class NumberLib { } @Expose(target = ExposeTarget.STATIC) public static double __parseInt(Arguments args) { - return args.getLong(0); + var radix = args.getInt(1, 10); + + if (radix < 2 || radix > 36) return Double.NaN; + else { + long res = 0; + + for (var c : args.getString(0).toCharArray()) { + var digit = 0; + + if (c >= '0' && c <= '9') digit = c - '0'; + else if (c >= 'a' && c <= 'z') digit = c - 'a' + 10; + else if (c >= 'A' && c <= 'Z') digit = c - 'A' + 10; + else break; + + if (digit > radix) break; + + res *= radix; + res += digit; + } + + return res; + } } @ExposeConstructor public static Object __constructor(Arguments args) { From 0251c4689d7813a1161993bda9363269d11c3fd7 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 6 Jan 2024 17:49:27 +0200 Subject: [PATCH 29/35] fix: use Values to access members in ObjectLib, instead of direct access --- src/me/topchetoeu/jscript/lib/ObjectLib.java | 26 ++++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/me/topchetoeu/jscript/lib/ObjectLib.java b/src/me/topchetoeu/jscript/lib/ObjectLib.java index 5aefe2f..112da5a 100644 --- a/src/me/topchetoeu/jscript/lib/ObjectLib.java +++ b/src/me/topchetoeu/jscript/lib/ObjectLib.java @@ -45,31 +45,31 @@ public class ObjectLib { var key = args.get(1); var attrib = args.convert(2, ObjectValue.class); - var hasVal = attrib.hasMember(args.ctx, "value", false); - var hasGet = attrib.hasMember(args.ctx, "get", false); - var hasSet = attrib.hasMember(args.ctx, "set", false); + var hasVal = Values.hasMember(args.ctx, attrib, "value", false); + var hasGet = Values.hasMember(args.ctx, attrib, "get", false); + var hasSet = Values.hasMember(args.ctx, attrib, "set", false); if (hasVal) { if (hasGet || hasSet) throw EngineException.ofType("Cannot specify a value and accessors for a property."); if (!obj.defineProperty( args.ctx, key, attrib.getMember(args.ctx, "value"), - Values.toBoolean(attrib.getMember(args.ctx, "writable")), - Values.toBoolean(attrib.getMember(args.ctx, "configurable")), - Values.toBoolean(attrib.getMember(args.ctx, "enumerable")) + Values.toBoolean(Values.getMember(args.ctx, attrib, "writable")), + Values.toBoolean(Values.getMember(args.ctx, attrib, "configurable")), + Values.toBoolean(Values.getMember(args.ctx, attrib, "enumerable")) )) throw EngineException.ofType("Can't define property '" + key + "'."); } else { - var get = attrib.getMember(args.ctx, "get"); - var set = attrib.getMember(args.ctx, "set"); + var get = Values.getMember(args.ctx, attrib, "get"); + var set = Values.getMember(args.ctx, attrib, "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( args.ctx, key, (FunctionValue)get, (FunctionValue)set, - Values.toBoolean(attrib.getMember(args.ctx, "configurable")), - Values.toBoolean(attrib.getMember(args.ctx, "enumerable")) + Values.toBoolean(Values.getMember(args.ctx, attrib, "configurable")), + Values.toBoolean(Values.getMember(args.ctx, attrib, "enumerable")) )) throw EngineException.ofType("Can't define property '" + key + "'."); } @@ -78,10 +78,10 @@ public class ObjectLib { @Expose(target = ExposeTarget.STATIC) public static ObjectValue __defineProperties(Arguments args) { var obj = args.convert(0, ObjectValue.class); - var attrib = args.convert(1, ObjectValue.class); + var attrib = args.get(1); for (var key : Values.getMembers(null, attrib, false, false)) { - __defineProperty(new Arguments(args.ctx, null, obj, key, attrib.getMember(args.ctx, key))); + __defineProperty(new Arguments(args.ctx, null, obj, key, Values.getMember(args.ctx, attrib, key))); } return obj; @@ -133,7 +133,7 @@ public class ObjectLib { var res = new ObjectValue(); var obj = args.get(0); for (var key : Values.getMembers(args.ctx, obj, true, true)) { - res.defineProperty(args.ctx, key, __getOwnPropertyDescriptor(new Arguments(args.ctx, null, obj, key))); + res.defineProperty(args.ctx, key, Values.getMemberDescriptor(args.ctx, obj, key)); } return res; } From 4572db5c467254cc9c1bb1765fb9f45baa83d326 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 6 Jan 2024 17:49:36 +0200 Subject: [PATCH 30/35] feat: some array tests --- tests/arithmetics/counters.js | 4 +- tests/arithmetics/index.js | 2 +- tests/array/constructor.js | 12 ++++ tests/array/fill.js | 19 +++++++ tests/array/find.js | 19 +++++++ tests/array/index.js | 32 ++++------- tests/array/indexOf.js | 19 +++++++ tests/array/length.js | 6 +- tests/array/pop.js | 8 +++ tests/array/push.js | 16 ++++++ tests/array/reduce.js | 102 ++++++++++++++++++++-------------- tests/array/sort.js | 10 ++-- tests/entry.js | 1 + tests/index.js | 25 ++++++--- 14 files changed, 195 insertions(+), 80 deletions(-) create mode 100644 tests/array/constructor.js create mode 100644 tests/array/fill.js create mode 100644 tests/array/find.js create mode 100644 tests/array/indexOf.js create mode 100644 tests/array/pop.js create mode 100644 tests/array/push.js create mode 100644 tests/entry.js diff --git a/tests/arithmetics/counters.js b/tests/arithmetics/counters.js index 9bcbd63..0935dda 100644 --- a/tests/arithmetics/counters.js +++ b/tests/arithmetics/counters.js @@ -3,8 +3,8 @@ return new UnitTest('counters') .add('postfix decrement', function () { var i = 10; i-- === 10; }) .add('prefix decrement', function () { var i = 10; --i === 9; }) .add('prefix increment', function () { var i = 10; ++i === 11; }) - .add('ostfix increment of non-number', function () { var i = 'hi mom'; isNaN(i++); }) - .add('ostfix decrement of non-number', function () { var i = 'hi mom'; isNaN(i--); }) + .add('postfix increment of non-number', function () { var i = 'hi mom'; isNaN(i++); }) + .add('postfix decrement of non-number', function () { var i = 'hi mom'; isNaN(i--); }) .add('prefix increment of non-number', function () { var i = 'hi mom'; isNaN(++i); }) .add('prefix decrement of non-number', function () { var i = 'hi mom'; isNaN(--i); }) .add('postfix increment of convertible to number', function () { var i = '10'; i++; i === 11; }) \ No newline at end of file diff --git a/tests/arithmetics/index.js b/tests/arithmetics/index.js index a1cb82c..aabe7ba 100644 --- a/tests/arithmetics/index.js +++ b/tests/arithmetics/index.js @@ -1,2 +1,2 @@ return new UnitTest('Arithmetics') - .add(include('counters.js')) \ No newline at end of file + .add(require('counters.js')) \ No newline at end of file diff --git a/tests/array/constructor.js b/tests/array/constructor.js new file mode 100644 index 0000000..536719b --- /dev/null +++ b/tests/array/constructor.js @@ -0,0 +1,12 @@ +return new UnitTest('constructor', function() { return typeof Array === 'function'; }) + .add('no args', function () { return match(new Array(), []); }) + .add('length', function () { + var res = new Array(3); + return res.length === 3 && + !(0 in res) && + !(1 in res) && + !(2 in res); + }) + .add('elements', function () { + return match(new Array(1, 2, 3), [1, 2, 3]); + }) diff --git a/tests/array/fill.js b/tests/array/fill.js new file mode 100644 index 0000000..2b9dfc9 --- /dev/null +++ b/tests/array/fill.js @@ -0,0 +1,19 @@ +return new UnitTest('fill', function() { return typeof Array.prototype.push === 'function'; }) + .add('simple fill', function() { + return match([5, 5, 5, 5, 5], [1, 2, 3, 4, 5].fill(5)) + }) + .add('fill empty', function() { + return match([], [].fill(5)) + }) + .add('fill from', function() { + return match([1, 'a', 'a', 'a', 'a'], [1, 2, 3, 4, 5].fill('a', 1)) + }) + .add('fill range', function() { + return match([1, 'a', 'a', 'a', 5], [1, 2, 3, 4, 5].fill('a', 1, 4)) + }) + .add('fill wrap', function() { + return match([1, 'a', 'a', 4, 5], [1, 2, 3, 4, 5].fill('a', 1, -2)) + }) + .add('fill out of range', function() { + return match([1, 2, 'a', 'a', 'a'], [1, 2, 3, 4, 5].fill('a', 2, 8)) + }) \ No newline at end of file diff --git a/tests/array/find.js b/tests/array/find.js new file mode 100644 index 0000000..db0321a --- /dev/null +++ b/tests/array/find.js @@ -0,0 +1,19 @@ +var a = { id: 10, name: 'test1' }; +var b = { id: 15, name: 'test2' }; +var c = { id: 20, name: 'test3' }; + +return new UnitTest('find', function() { return typeof Array.prototype.find === 'function'; }) + .add('simple', function() { + return [ a, b, c ].find(function (v) { return v.id === 15; }) === b; + }) + .add('sparse', function() { + var n = 0; + [ a, b,,,, c ].find(function (v) { n++; return v === undefined; }); + return n === 3; + }) + .add('no occurence', function() { + return [ a, b, c ].find(function (v) { return v.id === 30 }) === undefined; + }) + .add('pass this', function() { + return [ a, b ].find(function (v) { return this === c; }, c) === a; + }) \ No newline at end of file diff --git a/tests/array/index.js b/tests/array/index.js index ee18fa2..ef94a2a 100644 --- a/tests/array/index.js +++ b/tests/array/index.js @@ -1,22 +1,12 @@ -function runIterator(arr, method, func, n) { - var res = []; - var j = 1; - var args = [ function() { - var pushed = []; - for (var i = 0; i < n; i++) pushed[i] = arguments[i]; - res[j++] = pushed; - return func.apply(this, arguments); - } ]; - - for (var i = 4; i < arguments.length; i++) args[i - 3] = arguments[i]; - - res[0] = method.apply(arr, args); - - return res; -} - return new UnitTest('Array', function() { []; }) - .add(include('length.js')) - .add(include('reduce.js')) - .add(include('sparse.js')) - .add(include('concat.js')) + .add(require('constructor.js')) + .add(require('length.js')) + .add(require('reduce.js')('reduce')) + .add(require('reduce.js')('reduceRight')) + .add(require('sparse.js')) + .add(require('concat.js')) + .add(require('sort.js')) + .add(require('push.js')) + .add(require('pop.js')) + .add(require('fill.js')) + .add(require('find.js')) diff --git a/tests/array/indexOf.js b/tests/array/indexOf.js new file mode 100644 index 0000000..2b9dfc9 --- /dev/null +++ b/tests/array/indexOf.js @@ -0,0 +1,19 @@ +return new UnitTest('fill', function() { return typeof Array.prototype.push === 'function'; }) + .add('simple fill', function() { + return match([5, 5, 5, 5, 5], [1, 2, 3, 4, 5].fill(5)) + }) + .add('fill empty', function() { + return match([], [].fill(5)) + }) + .add('fill from', function() { + return match([1, 'a', 'a', 'a', 'a'], [1, 2, 3, 4, 5].fill('a', 1)) + }) + .add('fill range', function() { + return match([1, 'a', 'a', 'a', 5], [1, 2, 3, 4, 5].fill('a', 1, 4)) + }) + .add('fill wrap', function() { + return match([1, 'a', 'a', 4, 5], [1, 2, 3, 4, 5].fill('a', 1, -2)) + }) + .add('fill out of range', function() { + return match([1, 2, 'a', 'a', 'a'], [1, 2, 3, 4, 5].fill('a', 2, 8)) + }) \ No newline at end of file diff --git a/tests/array/length.js b/tests/array/length.js index 9bee0e1..c107cea 100644 --- a/tests/array/length.js +++ b/tests/array/length.js @@ -8,12 +8,12 @@ return new UnitTest('length & capacity', function() { return 'length' in Array.p }) .add('length after set', function() { var a = []; - a [5]= 5; + a[5] = 5; return a.length === 6; }) - .add('length after set (big', function() { + .add('length after set (big)', function() { var a = [1, 2]; - a [5000]= 5; + a[5000] = 5; return a.length === 5001; }) .add('expand test', function() { diff --git a/tests/array/pop.js b/tests/array/pop.js new file mode 100644 index 0000000..41308cd --- /dev/null +++ b/tests/array/pop.js @@ -0,0 +1,8 @@ +return new UnitTest('pop', function() { return typeof Array.prototype.pop === 'function'; }) + .add('simple pop', function() { + var arr = [1, 2, 3]; + return match(3, arr.pop()) + }) + .add('pop from empty', function() { + return match(undefined, [].pop()) + }) \ No newline at end of file diff --git a/tests/array/push.js b/tests/array/push.js new file mode 100644 index 0000000..7f60e14 --- /dev/null +++ b/tests/array/push.js @@ -0,0 +1,16 @@ +return new UnitTest('push', function() { return typeof Array.prototype.push === 'function'; }) + .add('simple push', function() { + var arr = []; + arr.push(1, 2, 3); + return match(arr, [1, 2, 3]) + }) + .add('push array', function() { + var arr = []; + arr.push([1, 2, 3]); + return match(arr, [[1, 2, 3]]) + }) + .add('push as concat', function() { + var arr = [1, 2, 3]; + arr.push(4, 5, 6); + return match(arr, [1, 2, 3, 4, 5, 6]) + }) \ No newline at end of file diff --git a/tests/array/reduce.js b/tests/array/reduce.js index c1c2892..727de4c 100644 --- a/tests/array/reduce.js +++ b/tests/array/reduce.js @@ -1,44 +1,62 @@ -var res = []; +function runIterator(arr, method, func, n) { + var res = []; + var j = 1; + var args = [ function() { + var pushed = []; + for (var i = 0; i < n; i++) pushed[i] = arguments[i]; + res[j++] = pushed; + return func.apply(this, arguments); + } ]; -return new UnitTest('reduceRight', function () { return typeof Array.prototype.reduceRight === 'function' }) - .add('empty function', function () {match( - [ undefined, [4, 3, 2], [undefined, 2, 1], [undefined, 1, 0], ], - runIterator([1, 2, 3, 4], Array.prototype.reduceRight, function() { }, 3), 1 - )}) - .add('adder', function () {match( - [ 10, [4, 3, 2], [7, 2, 1], [9, 1, 0], ], - runIterator([1, 2, 3, 4], Array.prototype.reduceRight, function(a, b) { return a + b; }, 3), 1 - )}) - .add('sparse array', function () {match( - [ 10, [4, 3, 11], [7, 2, 7], [9, 1, 3], ], - runIterator([,,,1,,,, 2,,,, 3,,,, 4,,,,], Array.prototype.reduceRight, function(a, b) { return a + b }, 3), 1 - )}) - .add('sparse array with one element', function () {match( - [ 1 ], - runIterator([,,,1,,,,], Array.prototype.reduceRight, function(v) { return v; }, 3), 1 - )}) - .add('sparse array with no elements', function () {match( - [ undefined ], - runIterator([,,,,,,,], Array.prototype.reduceRight, function(v) { return v; }, 3), 1 - )}) + for (var i = 4; i < arguments.length; i++) args[i - 3] = arguments[i]; - .add('initial value and empty function', function () {match( - [ undefined, [0, 4, 3], [undefined, 3, 2], [undefined, 2, 1], [undefined, 1, 0] ], - runIterator([1, 2, 3, 4], Array.prototype.reduceRight, function() { }, 3, 0), 1 - )}) - .add('initial value and adder', function () {match( - [ 15, [5, 4, 3], [9, 3, 2], [12, 2, 1], [14, 1, 0] ], - runIterator([1, 2, 3, 4], Array.prototype.reduceRight, function(a, b) { return a + b; }, 3, 5), 1 - )}) - .add('initial value, sparce array and adder', function () {match( - [ 15, [5, 4, 15], [9, 3, 11], [12, 2, 7], [14, 1, 3] ], - runIterator([,,,1,,,, 2,,,, 3,,,, 4,,,,], Array.prototype.reduceRight, function(a, b) { return a + b; }, 3, 5), 1 - )}) - .add('initial value and sparse array with one element', function () {match( - [ 6, [5, 1, 3] ], - runIterator([,,,1,,,,], Array.prototype.reduceRight, function(a, b) { return a + b; }, 3, 5), 1 - )}) - .add('initial value and sparse array with no elements', function () {match( - [ 5 ], - runIterator([,,,,,,,], Array.prototype.reduceRight, function(v) { return v; }, 3, 5), 1 - )}); \ No newline at end of file + res[0] = method.apply(arr, args); + + return res; +} + +return function(name) { + var func = Array.prototype[name]; + return new UnitTest(name, function () { return typeof func === 'function' }) + .add('empty function', function () {match( + [ undefined, [4, 3, 2], [undefined, 2, 1], [undefined, 1, 0], ], + runIterator([1, 2, 3, 4], func, function() { }, 3), 1 + )}) + .add('adder', function () {match( + [ 10, [4, 3, 2], [7, 2, 1], [9, 1, 0], ], + runIterator([1, 2, 3, 4], func, function(a, b) { return a + b; }, 3), 1 + )}) + .add('sparse array', function () {match( + [ 10, [4, 3, 11], [7, 2, 7], [9, 1, 3], ], + runIterator([,,,1,,,, 2,,,, 3,,,, 4,,,,], func, function(a, b) { return a + b }, 3), 1 + )}) + .add('sparse array with one element', function () {match( + [ 1 ], + runIterator([,,,1,,,,], func, function(v) { return v; }, 3), 1 + )}) + .add('sparse array with no elements', function () {match( + [ undefined ], + runIterator([,,,,,,,], func, function(v) { return v; }, 3), 1 + )}) + + .add('initial value and empty function', function () {match( + [ undefined, [0, 4, 3], [undefined, 3, 2], [undefined, 2, 1], [undefined, 1, 0] ], + runIterator([1, 2, 3, 4], func, function() { }, 3, 0), 1 + )}) + .add('initial value and adder', function () {match( + [ 15, [5, 4, 3], [9, 3, 2], [12, 2, 1], [14, 1, 0] ], + runIterator([1, 2, 3, 4], func, function(a, b) { return a + b; }, 3, 5), 1 + )}) + .add('initial value, sparce array and adder', function () {match( + [ 15, [5, 4, 15], [9, 3, 11], [12, 2, 7], [14, 1, 3] ], + runIterator([,,,1,,,, 2,,,, 3,,,, 4,,,,], func, function(a, b) { return a + b; }, 3, 5), 1 + )}) + .add('initial value and sparse array with one element', function () {match( + [ 6, [5, 1, 3] ], + runIterator([,,,1,,,,], func, function(a, b) { return a + b; }, 3, 5), 1 + )}) + .add('initial value and sparse array with no elements', function () {match( + [ 5 ], + runIterator([,,,,,,,], func, function(v) { return v; }, 3, 5), 1 + )}); +} \ No newline at end of file diff --git a/tests/array/sort.js b/tests/array/sort.js index bab004f..965196e 100644 --- a/tests/array/sort.js +++ b/tests/array/sort.js @@ -1,4 +1,6 @@ -return new UnitTest('concat', function() { return typeof Array.prototype.concat === 'function'; }) - .add('two arrays', function() { return match([1, 2, 3], [1].concat([2], [3])) }) - .add('simple spread', function() { return match([1, 2, 3, 4, 5], [1].concat([2], 3, [4, 5])) }) - .add('sparse concat', function() { return match([1,, 2,,, 3,,, 4, 5], [1,,2].concat([,,3,,,4], 5)) }) \ No newline at end of file +return new UnitTest('sort', function() { return typeof Array.prototype.sort === 'function'; }) + .add('simple', function() { return match([4, 6, 2].sort(), [2, 4, 6]) }) + .add('stringify', function() { return match([4, 6, 2, 10].sort(), [10, 2, 4, 6]) }) + .add('undefined and empty', function() { return match([4, undefined, 6, , 2].sort(), [2, 4, 6, undefined,,]) }) + .add('function ascend', function() { return match([3, 1, 2].sort(function (a, b) { return a - b; }), [1, 2, 3]) }) + .add('function descend', function() { return match([3, 1, 2].sort(function (a, b) { return b - a; }), [3, 2, 1]) }) \ No newline at end of file diff --git a/tests/entry.js b/tests/entry.js new file mode 100644 index 0000000..9804afb --- /dev/null +++ b/tests/entry.js @@ -0,0 +1 @@ +require('tests/index.js')(); \ No newline at end of file diff --git a/tests/index.js b/tests/index.js index 6290b85..82bc508 100644 --- a/tests/index.js +++ b/tests/index.js @@ -2,19 +2,24 @@ function assert(cond, msg, locDepth) { if (locDepth < 0 || locDepth === undefined) locDepth = 0; if (!cond) { log('Assert failed', (typeof locDepth === 'string' ? locDepth : Error().stack[locDepth + 1]) + ': ', msg); + return false; } + return true; } function assertMatch(expected, actual, depth, msg) { if (!match(expected, actual, depth)) { log('Assert failed', Error().stack[1] + ': ', msg); log('Expected:', expected); log('Actual:', actual); + return false; } + return true; } function match(expected, actual, depth) { if (!Array.isArray(expected) || !Array.isArray(actual)) return expected === actual; else if (expected.length !== actual.length) return false; - else if (depth === undefined || depth < 0) depth = 0; + else if (depth === undefined) depth = Infinity; + else if (depth < 0) depth = 0; for (var i = 0; i < expected.length; i++) { if (!(i in expected) || !(i in actual)) return !(i in expected) && !(i in actual); @@ -43,6 +48,7 @@ UnitTest.prototype.run = function(path) { if (path === undefined) path = []; path.push(this.name); + var res = true; if (typeof this.exec === 'function') { var res = true, err = 'exec() returned false.'; @@ -50,13 +56,15 @@ UnitTest.prototype.run = function(path) { if (this.exec() === false) res = false; } catch (e) { res = false; err = e; } - assert(res, path.join('/') + ': ' + err, this.exec.location()); + res &= assert(res, path.join('/') + ': ' + err, this.exec.location()); } for (var i = 0; i < this.subtests.length; i++) { - this.subtests[i].run(path); + res &= this.subtests[i].run(path); } path.pop(); + + return res; } UnitTest.prototype.add = function(test, exec) { if (test instanceof UnitTest) this.subtests.push(test); @@ -64,7 +72,10 @@ UnitTest.prototype.add = function(test, exec) { return this; } -include('arithmetics/index.js').run(); -include('array/index.js').run(); - -log('Tests complete.'); \ No newline at end of file +return function() { + if ( + require('arithmetics/index.js').run() && + require('array/index.js').run() + ) log('All tests passed!'); + exit(); +} From 292ca64cb9871951decffd190d9ab83d8c57b536 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 6 Jan 2024 17:50:06 +0200 Subject: [PATCH 31/35] fix: wrong behavior in Number.toString (which somehow broke typescript) --- src/me/topchetoeu/jscript/lib/NumberLib.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/me/topchetoeu/jscript/lib/NumberLib.java b/src/me/topchetoeu/jscript/lib/NumberLib.java index 355feec..a3d7e21 100644 --- a/src/me/topchetoeu/jscript/lib/NumberLib.java +++ b/src/me/topchetoeu/jscript/lib/NumberLib.java @@ -83,7 +83,7 @@ public class NumberLib { else return args.getDouble(0); } @Expose public static String __toString(Arguments args) { - return Values.toString(args.ctx, args.getDouble(0)); + return Values.toString(args.ctx, args.self); } @Expose public static double __valueOf(Arguments args) { if (Values.isWrapper(args.self, NumberLib.class)) return Values.wrapper(args.self, NumberLib.class).value; From c6e6425c7e12572e2bee1941c76f045b276b21a6 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 6 Jan 2024 17:52:50 +0200 Subject: [PATCH 32/35] fix: bring back ts minification --- build.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/build.js b/build.js index 7586811..49d5116 100644 --- a/build.js +++ b/build.js @@ -76,11 +76,10 @@ async function downloadTypescript(outFile) { console.log('Minifying typescript...'); - // const minified = minify((await fs.readFile('tmp/typescript-es5.js')).toString()); - const minified = { code: (await fs.readFile('tmp/typescript-es5.js')).toString() }; + const minified = minify((await fs.readFile('tmp/typescript-es5.js')).toString()); + // const minified = { code: (await fs.readFile('tmp/typescript-es5.js')).toString() }; if (minified.error) throw minified.error; - // Patch unsupported regex syntax minified.code = minified.code.replaceAll('[-/\\\\^$*+?.()|[\\]{}]', '[-/\\\\^$*+?.()|\\[\\]{}]'); From e4c9a8756e28309fb4045cd116023f4c638c3867 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 6 Jan 2024 18:23:20 +0200 Subject: [PATCH 33/35] fix: debugger hanging sometimes --- src/me/topchetoeu/jscript/engine/Context.java | 1 - .../jscript/engine/debug/SimpleDebugger.java | 66 ++++++++++++------- 2 files changed, 41 insertions(+), 26 deletions(-) diff --git a/src/me/topchetoeu/jscript/engine/Context.java b/src/me/topchetoeu/jscript/engine/Context.java index 33f3b07..4e440e4 100644 --- a/src/me/topchetoeu/jscript/engine/Context.java +++ b/src/me/topchetoeu/jscript/engine/Context.java @@ -96,7 +96,6 @@ public class Context implements Extensions { public Context pushFrame(CodeFrame frame) { var res = new Context(this, frame.function.environment, frame, engine, stackSize + 1); - DebugContext.get(res).onFramePush(res, frame); return res; } diff --git a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java index adee72d..7abefa5 100644 --- a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java +++ b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java @@ -109,34 +109,48 @@ public class SimpleDebugger implements Debugger { public ObjectValue local, capture, global, valstack; public JSONMap serialized; public Location location; - public boolean debugData = false; public void updateLoc(Location loc) { if (loc == null) return; this.location = loc; } - public Frame(Context ctx, CodeFrame frame, int id) { + public Frame(CodeFrame frame, int id) { this.frame = frame; this.func = frame.function; this.id = id; this.global = frame.function.environment.global.obj; - this.local = frame.getLocalScope(ctx, true); - this.capture = frame.getCaptureScope(ctx, true); - this.local.setPrototype(ctx, capture); - this.capture.setPrototype(ctx, global); - this.valstack = frame.getValStackScope(ctx); - debugData = true; + this.local = frame.getLocalScope(true); + this.capture = frame.getCaptureScope(true); + this.local.setPrototype(frame.ctx, capture); + this.capture.setPrototype(frame.ctx, global); + this.valstack = frame.getValStackScope(); this.serialized = new JSONMap() .set("callFrameId", id + "") .set("functionName", func.name) .set("scopeChain", new JSONList() - .add(new JSONMap().set("type", "local").set("name", "Local Scope").set("object", serializeObj(ctx, local))) - .add(new JSONMap().set("type", "closure").set("name", "Closure").set("object", serializeObj(ctx, capture))) - .add(new JSONMap().set("type", "global").set("name", "Global Scope").set("object", serializeObj(ctx, global))) - .add(new JSONMap().set("type", "other").set("name", "Value Stack").set("object", serializeObj(ctx, valstack))) + .add(new JSONMap() + .set("type", "local") + .set("name", "Local Scope") + .set("object", serializeObj(frame.ctx, local)) + ) + .add(new JSONMap() + .set("type", "closure") + .set("name", "Closure") + .set("object", serializeObj(frame.ctx, capture)) + ) + .add(new JSONMap() + .set("type", "global") + .set("name", "Global Scope") + .set("object", serializeObj(frame.ctx, global)) + ) + .add(new JSONMap() + .set("type", "other") + .set("name", "Value Stack") + .set("object", serializeObj(frame.ctx, valstack)) + ) ); } } @@ -231,25 +245,29 @@ public class SimpleDebugger implements Debugger { return nextId++; } + private synchronized Frame getFrame(CodeFrame frame) { + if (!codeFrameToFrame.containsKey(frame)) { + var id = nextId(); + var fr = new Frame(frame, id); + + idToFrame.put(id, fr); + codeFrameToFrame.put(frame, fr); + + return fr; + } + else return codeFrameToFrame.get(frame); + } private synchronized void updateFrames(Context ctx) { var frame = ctx.frame; if (frame == null) return; - if (!codeFrameToFrame.containsKey(frame)) { - var id = nextId(); - var fr = new Frame(ctx, frame, id); - - idToFrame.put(id, fr); - codeFrameToFrame.put(frame, fr); - } - - currFrame = codeFrameToFrame.get(frame); + currFrame = getFrame(frame); } private JSONList serializeFrames(Context ctx) { var res = new JSONList(); for (var el : ctx.frames()) { - var frame = codeFrameToFrame.get(el); + var frame = getFrame(el); if (frame.location == null) continue; frame.serialized.set("location", serializeLocation(frame.location)); if (frame.location != null) res.add(frame.serialized); @@ -894,9 +912,7 @@ public class SimpleDebugger implements Debugger { Frame frame; synchronized (this) { - frame = codeFrameToFrame.get(cf); - - if (!frame.debugData) return false; + frame = getFrame(cf); if (instruction.location != null) frame.updateLoc(DebugContext.get(ctx).mapToCompiled(instruction.location)); loc = frame.location; From d8585a20bfb11341432c2bf33045686b56de4bdb Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 6 Jan 2024 18:23:34 +0200 Subject: [PATCH 34/35] refactor: don't require ctx in frame.push --- .../jscript/engine/frame/CodeFrame.java | 10 ++-- .../jscript/engine/frame/Runners.java | 46 +++++++++---------- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java index 1b09cb9..283286a 100644 --- a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java +++ b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java @@ -103,7 +103,7 @@ public class CodeFrame { public boolean jumpFlag = false, popTryFlag = false; private Location prevLoc = null; - public ObjectValue getLocalScope(Context ctx, boolean props) { + public ObjectValue getLocalScope(boolean props) { var names = new String[scope.locals.length]; for (int i = 0; i < scope.locals.length; i++) { @@ -118,7 +118,7 @@ public class CodeFrame { return new ScopeValue(scope.locals, names); } - public ObjectValue getCaptureScope(Context ctx, boolean props) { + public ObjectValue getCaptureScope(boolean props) { var names = new String[scope.captures.length]; for (int i = 0; i < scope.captures.length; i++) { @@ -129,7 +129,7 @@ public class CodeFrame { return new ScopeValue(scope.captures, names); } - public ObjectValue getValStackScope(Context ctx) { + public ObjectValue getValStackScope() { return new ObjectValue() { @Override protected Object getField(Context ctx, Object key) { @@ -181,7 +181,7 @@ public class CodeFrame { return res; } - public void push(Context ctx, Object val) { + public void push(Object val) { if (stack.length <= stackPtr) { var newStack = new Object[stack.length * 2]; System.arraycopy(stack, 0, newStack, 0, stack.length); @@ -191,7 +191,7 @@ public class CodeFrame { } public Object next(Object value, Object returnValue, EngineException error) { - if (value != Values.NO_RETURN) push(ctx, value); + if (value != Values.NO_RETURN) push(value); Instruction instr = null; if (codePtr >= 0 && codePtr < function.body.length) instr = function.body[codePtr]; diff --git a/src/me/topchetoeu/jscript/engine/frame/Runners.java b/src/me/topchetoeu/jscript/engine/frame/Runners.java index 35f67db..4607c17 100644 --- a/src/me/topchetoeu/jscript/engine/frame/Runners.java +++ b/src/me/topchetoeu/jscript/engine/frame/Runners.java @@ -32,7 +32,7 @@ public class Runners { var func = frame.pop(); var thisArg = frame.pop(); - frame.push(ctx, Values.call(ctx, func, thisArg, callArgs)); + frame.push(Values.call(ctx, func, thisArg, callArgs)); frame.codePtr++; return Values.NO_RETURN; @@ -41,7 +41,7 @@ public class Runners { var callArgs = frame.take(instr.get(0)); var funcObj = frame.pop(); - frame.push(ctx, Values.callNew(ctx, funcObj, callArgs)); + frame.push(Values.callNew(ctx, funcObj, callArgs)); frame.codePtr++; return Values.NO_RETURN; @@ -64,7 +64,7 @@ public class Runners { if (!(obj instanceof ObjectValue)) throw EngineException.ofType("Property apply target must be an object."); Values.object(obj).defineProperty(ctx, name, Values.function(getter), Values.function(setter), false, false); - frame.push(ctx, obj); + frame.push(obj); frame.codePtr++; return Values.NO_RETURN; } @@ -74,10 +74,10 @@ public class Runners { if (!Values.isPrimitive(type)) { var proto = Values.getMember(ctx, type, "prototype"); - frame.push(ctx, Values.isInstanceOf(ctx, obj, proto)); + frame.push(Values.isInstanceOf(ctx, obj, proto)); } else { - frame.push(ctx, false); + frame.push(false); } frame.codePtr++; @@ -89,13 +89,13 @@ public class Runners { var members = Values.getMembers(ctx, val, false, false); Collections.reverse(members); - frame.push(ctx, null); + frame.push(null); for (var el : members) { if (el instanceof Symbol) continue; var obj = new ObjectValue(); obj.defineProperty(ctx, "value", el); - frame.push(ctx, obj); + frame.push(obj); } frame.codePtr++; @@ -122,45 +122,45 @@ public class Runners { int count = instr.get(0); for (var i = 0; i < count; i++) { - frame.push(ctx, frame.peek(count - 1)); + frame.push(frame.peek(count - 1)); } frame.codePtr++; return Values.NO_RETURN; } public static Object execLoadUndefined(Context ctx, Instruction instr, CodeFrame frame) { - frame.push(ctx, null); + frame.push(null); frame.codePtr++; return Values.NO_RETURN; } public static Object execLoadValue(Context ctx, Instruction instr, CodeFrame frame) { - frame.push(ctx, instr.get(0)); + frame.push(instr.get(0)); frame.codePtr++; return Values.NO_RETURN; } public static Object execLoadVar(Context ctx, Instruction instr, CodeFrame frame) { var i = instr.get(0); - if (i instanceof String) frame.push(ctx, ctx.environment.global.get(ctx, (String)i)); - else frame.push(ctx, frame.scope.get((int)i).get(ctx)); + if (i instanceof String) frame.push(ctx.environment.global.get(ctx, (String)i)); + else frame.push(frame.scope.get((int)i).get(ctx)); frame.codePtr++; return Values.NO_RETURN; } public static Object execLoadObj(Context ctx, Instruction instr, CodeFrame frame) { - frame.push(ctx, new ObjectValue()); + frame.push(new ObjectValue()); frame.codePtr++; return Values.NO_RETURN; } public static Object execLoadGlob(Context ctx, Instruction instr, CodeFrame frame) { - frame.push(ctx, ctx.environment.global.obj); + frame.push(ctx.environment.global.obj); frame.codePtr++; return Values.NO_RETURN; } public static Object execLoadArr(Context ctx, Instruction instr, CodeFrame frame) { var res = new ArrayValue(); res.setSize(instr.get(0)); - frame.push(ctx, res); + frame.push(res); frame.codePtr++; return Values.NO_RETURN; } @@ -174,7 +174,7 @@ public class Runners { var func = new CodeFunction(ctx.environment, "", Engine.functions.get(id), captures); - frame.push(ctx, func); + frame.push(func); frame.codePtr++; return Values.NO_RETURN; @@ -184,7 +184,7 @@ public class Runners { var obj = frame.pop(); try { - frame.push(ctx, Values.getMember(ctx, obj, key)); + frame.push(Values.getMember(ctx, obj, key)); } catch (IllegalArgumentException e) { throw EngineException.ofType(e.getMessage()); @@ -193,12 +193,12 @@ public class Runners { return Values.NO_RETURN; } public static Object execLoadKeyMember(Context ctx, Instruction instr, CodeFrame frame) { - frame.push(ctx, instr.get(0)); + frame.push(instr.get(0)); return execLoadMember(ctx, instr, frame); } public static Object execLoadRegEx(Context ctx, Instruction instr, CodeFrame frame) { if (ctx.hasNotNull(Environment.REGEX_CONSTR)) { - frame.push(ctx, Values.callNew(ctx, ctx.get(Environment.REGEX_CONSTR), instr.get(0), instr.get(1))); + frame.push(Values.callNew(ctx, ctx.get(Environment.REGEX_CONSTR), instr.get(0), instr.get(1))); } else { throw EngineException.ofSyntax("Regex is not supported."); @@ -218,7 +218,7 @@ public class Runners { var obj = frame.pop(); if (!Values.setMember(ctx, obj, key, val)) throw EngineException.ofSyntax("Can't set member '" + key + "'."); - if ((boolean)instr.get(0)) frame.push(ctx, val); + if ((boolean)instr.get(0)) frame.push(val); frame.codePtr++; return Values.NO_RETURN; } @@ -264,7 +264,7 @@ public class Runners { var obj = frame.pop(); var index = frame.pop(); - frame.push(ctx, Values.hasMember(ctx, obj, index, false)); + frame.push(Values.hasMember(ctx, obj, index, false)); frame.codePtr++; return Values.NO_RETURN; } @@ -280,7 +280,7 @@ public class Runners { } else obj = frame.pop(); - frame.push(ctx, Values.type(obj)); + frame.push(Values.type(obj)); frame.codePtr++; return Values.NO_RETURN; @@ -305,7 +305,7 @@ public class Runners { for (var i = op.operands - 1; i >= 0; i--) args[i] = frame.pop(); - frame.push(ctx, Values.operation(ctx, op, args)); + frame.push(Values.operation(ctx, op, args)); frame.codePtr++; return Values.NO_RETURN; } From 72a0d39d0b0dcea81f17c6559d4f0c47928d993d Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 6 Jan 2024 18:27:36 +0200 Subject: [PATCH 35/35] fix: make java 11 compatible --- .../topchetoeu/jscript/interop/NativeWrapperProvider.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java b/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java index 0c98107..96c04ed 100644 --- a/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java +++ b/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java @@ -7,6 +7,7 @@ import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; +import java.util.stream.Collectors; import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.engine.Context; @@ -73,8 +74,8 @@ public class NativeWrapperProvider implements WrappersProvider { 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()) + String.join(", ", Arrays.stream(params).map(v -> v.getName()).collect(Collectors.toList())), + String.join(", ", Arrays.stream(actual).map(v -> v.getName()).collect(Collectors.toList())) )); } private static String getName(Class clazz) { @@ -85,7 +86,7 @@ public class NativeWrapperProvider implements WrappersProvider { 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( + System.out.println(String.format("WARNING: The name of the exposed member '%s.%s' doesn't start with '__'.", member.getDeclaringClass().getName(), member.getName() ));