From b47d1a7576ae26396a4e2747f134267223698e20 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Mon, 6 Nov 2023 13:53:36 +0200 Subject: [PATCH 01/36] refactor: remove StackData and Data usage --- src/me/topchetoeu/jscript/engine/Context.java | 62 +++++++++++++++-- src/me/topchetoeu/jscript/engine/Engine.java | 15 +++-- .../jscript/engine/Environment.java | 3 - .../topchetoeu/jscript/engine/StackData.java | 66 ------------------- .../jscript/engine/debug/SimpleDebugger.java | 7 +- .../jscript/engine/debug/WebSocket.java | 6 -- .../jscript/engine/values/CodeFunction.java | 5 +- .../jscript/lib/AsyncFunctionLib.java | 5 +- .../jscript/lib/AsyncGeneratorLib.java | 5 +- src/me/topchetoeu/jscript/lib/ErrorLib.java | 3 +- .../topchetoeu/jscript/lib/GeneratorLib.java | 5 +- 11 files changed, 75 insertions(+), 107 deletions(-) delete mode 100644 src/me/topchetoeu/jscript/engine/StackData.java diff --git a/src/me/topchetoeu/jscript/engine/Context.java b/src/me/topchetoeu/jscript/engine/Context.java index 0261737..fa46bbe 100644 --- a/src/me/topchetoeu/jscript/engine/Context.java +++ b/src/me/topchetoeu/jscript/engine/Context.java @@ -1,17 +1,23 @@ package me.topchetoeu.jscript.engine; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.Stack; import java.util.TreeSet; import me.topchetoeu.jscript.Filename; import me.topchetoeu.jscript.Location; +import me.topchetoeu.jscript.engine.frame.CodeFrame; 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.parsing.Parsing; public class Context { private final Stack env = new Stack<>(); + private final ArrayList frames = new ArrayList<>(); public final Data data; public final Engine engine; @@ -48,16 +54,58 @@ public class Context { return res; } - public Context(Engine engine, Data data) { - this.data = new Data(engine.data); - if (data != null) this.data.addAll(data); + + public void pushFrame(CodeFrame frame) { + frames.add(frame); + if (frames.size() > engine.maxStackFrames) throw EngineException.ofRange("Stack overflow!"); + pushEnv(frame.function.environment); + } + 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); + return true; + } + public CodeFrame peekFrame() { + if (frames.size() == 0) return null; + return frames.get(frames.size() - 1); + } + + public List frames() { + return Collections.unmodifiableList(frames); + } + public List stackTrace() { + var res = new ArrayList(); + + for (var i = frames.size() - 1; i >= 0; i--) { + var el = frames.get(i); + var name = el.function.name; + Location loc = null; + + for (var j = el.codePtr; j >= 0 && loc == null; j--) loc = el.function.body[j].location; + if (loc == null) loc = el.function.loc(); + + var trace = ""; + + if (loc != null) trace += "at " + loc.toString() + " "; + if (name != null && !name.equals("")) trace += "in " + name + " "; + + trace = trace.trim(); + + if (!trace.equals("")) res.add(trace); + } + + return res; + } + + public Context(Engine engine) { + this.data = new Data(); this.engine = engine; } - public Context(Engine engine) { - this(engine, (Data)null); - } public Context(Engine engine, Environment env) { - this(engine, (Data)null); + this(engine); this.pushEnv(env); } diff --git a/src/me/topchetoeu/jscript/engine/Engine.java b/src/me/topchetoeu/jscript/engine/Engine.java index 88a56ed..a48366f 100644 --- a/src/me/topchetoeu/jscript/engine/Engine.java +++ b/src/me/topchetoeu/jscript/engine/Engine.java @@ -53,17 +53,18 @@ public class Engine implements DebugController { private static int nextId = 0; public static final HashMap functions = new HashMap<>(); + 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 DebugController debugger; private Thread thread; private LinkedBlockingDeque macroTasks = new LinkedBlockingDeque<>(); private LinkedBlockingDeque microTasks = new LinkedBlockingDeque<>(); - public final int id = ++nextId; - public final Data data = new Data().set(StackData.MAX_FRAMES, 10000); - public final boolean debugging; - private final HashMap sources = new HashMap<>(); - private final HashMap> bpts = new HashMap<>(); - private DebugController debugger; - public boolean attachDebugger(DebugController debugger) { if (!debugging || this.debugger != null) return false; diff --git a/src/me/topchetoeu/jscript/engine/Environment.java b/src/me/topchetoeu/jscript/engine/Environment.java index 5c2c4e2..397ec78 100644 --- a/src/me/topchetoeu/jscript/engine/Environment.java +++ b/src/me/topchetoeu/jscript/engine/Environment.java @@ -68,9 +68,6 @@ public class Environment { return res; } - public Context context(Engine engine, Data data) { - return new Context(engine, data).pushEnv(this); - } public Context context(Engine engine) { return new Context(engine).pushEnv(this); } diff --git a/src/me/topchetoeu/jscript/engine/StackData.java b/src/me/topchetoeu/jscript/engine/StackData.java deleted file mode 100644 index 9e42eca..0000000 --- a/src/me/topchetoeu/jscript/engine/StackData.java +++ /dev/null @@ -1,66 +0,0 @@ -package me.topchetoeu.jscript.engine; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import me.topchetoeu.jscript.Location; -import me.topchetoeu.jscript.engine.debug.Debugger; -import me.topchetoeu.jscript.engine.frame.CodeFrame; -import me.topchetoeu.jscript.exceptions.EngineException; - -public class StackData { - public static final DataKey> FRAMES = new DataKey<>(); - public static final DataKey MAX_FRAMES = new DataKey<>(); - public static final DataKey DEBUGGER = new DataKey<>(); - - public static void pushFrame(Context ctx, CodeFrame frame) { - var frames = ctx.data.get(FRAMES, new ArrayList<>()); - frames.add(frame); - if (frames.size() > ctx.data.get(MAX_FRAMES, 10000)) - throw EngineException.ofRange("Stack overflow!"); - ctx.pushEnv(frame.function.environment); - } - public static boolean popFrame(Context ctx, CodeFrame frame) { - var frames = ctx.data.get(FRAMES, new ArrayList<>()); - if (frames.size() == 0) return false; - if (frames.get(frames.size() - 1) != frame) return false; - frames.remove(frames.size() - 1); - ctx.popEnv(); - ctx.engine.onFramePop(ctx, frame); - return true; - } - public static CodeFrame peekFrame(Context ctx) { - var frames = ctx.data.get(FRAMES, new ArrayList<>()); - if (frames.size() == 0) return null; - return frames.get(frames.size() - 1); - } - - public static List frames(Context ctx) { - return Collections.unmodifiableList(ctx.data.get(FRAMES, new ArrayList<>())); - } - public static List stackTrace(Context ctx) { - var res = new ArrayList(); - var frames = frames(ctx); - - for (var i = frames.size() - 1; i >= 0; i--) { - var el = frames.get(i); - var name = el.function.name; - Location loc = null; - - for (var j = el.codePtr; j >= 0 && loc == null; j--) loc = el.function.body[j].location; - if (loc == null) loc = el.function.loc(); - - var trace = ""; - - if (loc != null) trace += "at " + loc.toString() + " "; - if (name != null && !name.equals("")) trace += "in " + name + " "; - - trace = trace.trim(); - - if (!trace.equals("")) res.add(trace); - } - - return res; - } -} diff --git a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java index c2d7c4a..bee5428 100644 --- a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java +++ b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java @@ -15,7 +15,6 @@ 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.StackData; import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.frame.Runners; import me.topchetoeu.jscript.engine.scope.GlobalScope; @@ -187,7 +186,7 @@ public class SimpleDebugger implements Debugger { } private void updateFrames(Context ctx) { - var frame = StackData.peekFrame(ctx); + var frame = ctx.peekFrame(); if (frame == null) return; if (!codeFrameToFrame.containsKey(frame)) { @@ -202,7 +201,7 @@ public class SimpleDebugger implements Debugger { } private JSONList serializeFrames(Context ctx) { var res = new JSONList(); - var frames = StackData.frames(ctx); + var frames = ctx.frames(); for (var i = frames.size() - 1; i >= 0; i--) { res.add(codeFrameToFrame.get(frames.get(i)).serialized); @@ -782,7 +781,7 @@ public class SimpleDebugger implements Debugger { try { idToFrame.remove(codeFrameToFrame.remove(frame).id); } catch (NullPointerException e) { } - if (StackData.frames(ctx).size() == 0) resume(State.RESUMED); + if (ctx.frames().size() == 0) resume(State.RESUMED); else if (stepOutFrame != null && stepOutFrame.frame == frame && (state == State.STEPPING_OUT || state == State.STEPPING_IN || state == State.STEPPING_OVER) ) { diff --git a/src/me/topchetoeu/jscript/engine/debug/WebSocket.java b/src/me/topchetoeu/jscript/engine/debug/WebSocket.java index 253bf38..ea1eaef 100644 --- a/src/me/topchetoeu/jscript/engine/debug/WebSocket.java +++ b/src/me/topchetoeu/jscript/engine/debug/WebSocket.java @@ -118,8 +118,6 @@ public class WebSocket implements AutoCloseable { else send(msg.textData()); } public void send(Object data) { - // TODO: Remove - // System.out.println("SEND: " + data); if (closed) throw new IllegalStateException("Object is closed."); write(1, data.toString().getBytes()); } @@ -201,10 +199,6 @@ public class WebSocket implements AutoCloseable { if (!fin) continue; var raw = data.toByteArray(); - // TODO: Remove - // System.out.println("RECEIVED: " + new String(raw)); - - if (type == 1) return new WebSocketMessage(new String(raw)); else return new WebSocketMessage(raw); } diff --git a/src/me/topchetoeu/jscript/engine/values/CodeFunction.java b/src/me/topchetoeu/jscript/engine/values/CodeFunction.java index e2dc2bf..adc14d4 100644 --- a/src/me/topchetoeu/jscript/engine/values/CodeFunction.java +++ b/src/me/topchetoeu/jscript/engine/values/CodeFunction.java @@ -5,7 +5,6 @@ import me.topchetoeu.jscript.compilation.FunctionBody; import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Environment; -import me.topchetoeu.jscript.engine.StackData; import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.frame.Runners; import me.topchetoeu.jscript.engine.scope.ValueVariable; @@ -35,7 +34,7 @@ public class CodeFunction extends FunctionValue { public Object call(Context ctx, Object thisArg, Object ...args) { var frame = new CodeFrame(ctx, thisArg, args, this); try { - StackData.pushFrame(ctx, frame); + ctx.pushFrame(frame); while (true) { var res = frame.next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, null); @@ -43,7 +42,7 @@ public class CodeFunction extends FunctionValue { } } finally { - StackData.popFrame(ctx, frame); + ctx.popFrame(frame); } } diff --git a/src/me/topchetoeu/jscript/lib/AsyncFunctionLib.java b/src/me/topchetoeu/jscript/lib/AsyncFunctionLib.java index ff248e4..c352a20 100644 --- a/src/me/topchetoeu/jscript/lib/AsyncFunctionLib.java +++ b/src/me/topchetoeu/jscript/lib/AsyncFunctionLib.java @@ -1,7 +1,6 @@ package me.topchetoeu.jscript.lib; import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.engine.StackData; import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.frame.Runners; import me.topchetoeu.jscript.engine.values.CodeFunction; @@ -21,7 +20,7 @@ import me.topchetoeu.jscript.interop.Native; private void next(Context ctx, Object inducedValue, Object inducedError) { Object res = null; - StackData.pushFrame(ctx, frame); + ctx.pushFrame(frame); ctx.pushEnv(frame.function.environment); awaiting = false; @@ -40,7 +39,7 @@ import me.topchetoeu.jscript.interop.Native; } } - StackData.popFrame(ctx, frame); + 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 7f2e78c..0a2dd47 100644 --- a/src/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java +++ b/src/me/topchetoeu/jscript/lib/AsyncGeneratorLib.java @@ -3,7 +3,6 @@ package me.topchetoeu.jscript.lib; import java.util.Map; import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.engine.StackData; import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.frame.Runners; import me.topchetoeu.jscript.engine.values.NativeFunction; @@ -29,7 +28,7 @@ import me.topchetoeu.jscript.interop.Native; } Object res = null; - StackData.pushFrame(ctx, frame); + ctx.pushFrame(frame); state = 0; while (state == 0) { @@ -50,7 +49,7 @@ import me.topchetoeu.jscript.interop.Native; } } - StackData.popFrame(ctx, frame); + 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/ErrorLib.java b/src/me/topchetoeu/jscript/lib/ErrorLib.java index e33cf1a..6952e97 100644 --- a/src/me/topchetoeu/jscript/lib/ErrorLib.java +++ b/src/me/topchetoeu/jscript/lib/ErrorLib.java @@ -2,7 +2,6 @@ package me.topchetoeu.jscript.lib; import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Environment; -import me.topchetoeu.jscript.engine.StackData; import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.Values; @@ -51,7 +50,7 @@ import me.topchetoeu.jscript.interop.NativeInit; var target = new ObjectValue(); if (thisArg instanceof ObjectValue) target = (ObjectValue)thisArg; - target.defineProperty(ctx, "stack", ArrayValue.of(ctx, StackData.stackTrace(ctx))); + target.defineProperty(ctx, "stack", ArrayValue.of(ctx, ctx.stackTrace())); target.defineProperty(ctx, "name", "Error"); if (message == null) target.defineProperty(ctx, "message", ""); else target.defineProperty(ctx, "message", Values.toString(ctx, message)); diff --git a/src/me/topchetoeu/jscript/lib/GeneratorLib.java b/src/me/topchetoeu/jscript/lib/GeneratorLib.java index 6f055a7..9df2175 100644 --- a/src/me/topchetoeu/jscript/lib/GeneratorLib.java +++ b/src/me/topchetoeu/jscript/lib/GeneratorLib.java @@ -1,7 +1,6 @@ package me.topchetoeu.jscript.lib; import me.topchetoeu.jscript.engine.Context; -import me.topchetoeu.jscript.engine.StackData; import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.frame.Runners; import me.topchetoeu.jscript.engine.values.ObjectValue; @@ -25,7 +24,7 @@ import me.topchetoeu.jscript.interop.Native; } Object res = null; - StackData.pushFrame(ctx, frame); + ctx.pushFrame(frame); yielding = false; while (!yielding) { @@ -43,7 +42,7 @@ import me.topchetoeu.jscript.interop.Native; } } - StackData.popFrame(ctx, frame); + ctx.popFrame(frame); if (done) frame = null; else res = frame.pop(); -- 2.45.2 From df9932874d9a93997984a0e6b4144ddae22f5880 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Mon, 6 Nov 2023 14:03:15 +0200 Subject: [PATCH 02/36] feat: remove unnececeary @NativeInit directives --- src/me/topchetoeu/jscript/engine/Context.java | 2 -- src/me/topchetoeu/jscript/engine/Engine.java | 28 +++++++++---------- .../interop/NativeWrapperProvider.java | 5 ++++ src/me/topchetoeu/jscript/lib/ArrayLib.java | 7 ----- src/me/topchetoeu/jscript/lib/BooleanLib.java | 6 ---- src/me/topchetoeu/jscript/lib/ErrorLib.java | 1 - .../topchetoeu/jscript/lib/FunctionLib.java | 8 ------ src/me/topchetoeu/jscript/lib/NumberLib.java | 7 ----- src/me/topchetoeu/jscript/lib/ObjectLib.java | 7 ----- src/me/topchetoeu/jscript/lib/PromiseLib.java | 7 ----- .../topchetoeu/jscript/lib/RangeErrorLib.java | 1 - src/me/topchetoeu/jscript/lib/StringLib.java | 7 ----- src/me/topchetoeu/jscript/lib/SymbolLib.java | 7 ----- .../jscript/lib/SyntaxErrorLib.java | 2 -- .../topchetoeu/jscript/lib/TypeErrorLib.java | 2 -- 15 files changed, 19 insertions(+), 78 deletions(-) diff --git a/src/me/topchetoeu/jscript/engine/Context.java b/src/me/topchetoeu/jscript/engine/Context.java index fa46bbe..1f00eb2 100644 --- a/src/me/topchetoeu/jscript/engine/Context.java +++ b/src/me/topchetoeu/jscript/engine/Context.java @@ -18,7 +18,6 @@ import me.topchetoeu.jscript.parsing.Parsing; public class Context { private final Stack env = new Stack<>(); private final ArrayList frames = new ArrayList<>(); - public final Data data; public final Engine engine; public Environment environment() { @@ -101,7 +100,6 @@ public class Context { } public Context(Engine engine) { - this.data = new Data(); this.engine = engine; } public Context(Engine engine, Environment env) { diff --git a/src/me/topchetoeu/jscript/engine/Engine.java b/src/me/topchetoeu/jscript/engine/Engine.java index a48366f..a5c7dc6 100644 --- a/src/me/topchetoeu/jscript/engine/Engine.java +++ b/src/me/topchetoeu/jscript/engine/Engine.java @@ -81,20 +81,6 @@ public class Engine implements DebugController { return true; } - @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) { - if (!debugging) return; - if (debugger != null) debugger.onSource(filename, source, breakpoints); - sources.put(filename, source); - bpts.put(filename, breakpoints); - } - private void runTask(Task task) { try { task.notifier.next(task.func.call(task.ctx, task.thisArg, task.args)); @@ -150,6 +136,20 @@ public class Engine implements DebugController { return pushMsg(micro, ctx, new UncompiledFunction(filename, raw), thisArg, args); } + @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) { + if (!debugging) return; + if (debugger != null) debugger.onSource(filename, source, breakpoints); + sources.put(filename, source); + bpts.put(filename, breakpoints); + } + public Engine(boolean debugging) { this.debugging = debugging; } diff --git a/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java b/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java index a120171..11f7a61 100644 --- a/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java +++ b/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java @@ -117,6 +117,11 @@ public class NativeWrapperProvider implements WrappersProvider { public static ObjectValue makeProto(Environment ctx, Class clazz) { var res = new ObjectValue(); + var name = clazz.getName(); + var classNat = clazz.getAnnotation(Native.class); + if (classNat != null && !classNat.value().trim().equals("")) name = classNat.value().trim(); + res.defineProperty(null, ctx.symbol("Symbol.typeName"), name); + for (var overload : clazz.getDeclaredMethods()) { var init = overload.getAnnotation(NativeInit.class); if (init == null || init.value() != InitType.PROTOTYPE) continue; diff --git a/src/me/topchetoeu/jscript/lib/ArrayLib.java b/src/me/topchetoeu/jscript/lib/ArrayLib.java index 413e0c7..25756bc 100644 --- a/src/me/topchetoeu/jscript/lib/ArrayLib.java +++ b/src/me/topchetoeu/jscript/lib/ArrayLib.java @@ -4,16 +4,13 @@ import java.util.Iterator; import java.util.Stack; 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.Values; -import me.topchetoeu.jscript.interop.InitType; import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.NativeConstructor; import me.topchetoeu.jscript.interop.NativeGetter; -import me.topchetoeu.jscript.interop.NativeInit; import me.topchetoeu.jscript.interop.NativeSetter; @Native("Array") public class ArrayLib { @@ -369,8 +366,4 @@ import me.topchetoeu.jscript.interop.NativeSetter; return res; } - - @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { - target.defineProperty(null, env.symbol("Symbol.typeName"), "Array"); - } } diff --git a/src/me/topchetoeu/jscript/lib/BooleanLib.java b/src/me/topchetoeu/jscript/lib/BooleanLib.java index e9c2a3f..2ac5e0d 100644 --- a/src/me/topchetoeu/jscript/lib/BooleanLib.java +++ b/src/me/topchetoeu/jscript/lib/BooleanLib.java @@ -1,13 +1,10 @@ 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.Values; -import me.topchetoeu.jscript.interop.InitType; import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.NativeConstructor; -import me.topchetoeu.jscript.interop.NativeInit; @Native("Boolean") public class BooleanLib { public static final BooleanLib TRUE = new BooleanLib(true); @@ -30,7 +27,4 @@ import me.topchetoeu.jscript.interop.NativeInit; public BooleanLib(boolean val) { this.value = val; } - @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { - target.defineProperty(null, env.symbol("Symbol.typeName"), "Boolean"); - } } diff --git a/src/me/topchetoeu/jscript/lib/ErrorLib.java b/src/me/topchetoeu/jscript/lib/ErrorLib.java index 6952e97..aeefa63 100644 --- a/src/me/topchetoeu/jscript/lib/ErrorLib.java +++ b/src/me/topchetoeu/jscript/lib/ErrorLib.java @@ -59,7 +59,6 @@ import me.topchetoeu.jscript.interop.NativeInit; } @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { - target.defineProperty(null, env.symbol("Symbol.typeName"), "Error"); target.defineProperty(null, "name", "Error"); } } diff --git a/src/me/topchetoeu/jscript/lib/FunctionLib.java b/src/me/topchetoeu/jscript/lib/FunctionLib.java index 51581a0..ffa2ff7 100644 --- a/src/me/topchetoeu/jscript/lib/FunctionLib.java +++ b/src/me/topchetoeu/jscript/lib/FunctionLib.java @@ -2,16 +2,12 @@ package me.topchetoeu.jscript.lib; import me.topchetoeu.jscript.Location; 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.CodeFunction; import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.NativeFunction; -import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.exceptions.EngineException; -import me.topchetoeu.jscript.interop.InitType; import me.topchetoeu.jscript.interop.Native; -import me.topchetoeu.jscript.interop.NativeInit; @Native("Function") public class FunctionLib { @Native(thisArg = true) public static Object location(Context ctx, FunctionValue func) { @@ -55,8 +51,4 @@ import me.topchetoeu.jscript.interop.NativeInit; @Native public static FunctionValue generator(FunctionValue func) { return new GeneratorFunctionLib(func); } - - @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { - target.defineProperty(null, env.symbol("Symbol.typeName"), "Function"); - } } diff --git a/src/me/topchetoeu/jscript/lib/NumberLib.java b/src/me/topchetoeu/jscript/lib/NumberLib.java index 29e280a..53998e5 100644 --- a/src/me/topchetoeu/jscript/lib/NumberLib.java +++ b/src/me/topchetoeu/jscript/lib/NumberLib.java @@ -1,13 +1,10 @@ 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.Values; -import me.topchetoeu.jscript.interop.InitType; import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.NativeConstructor; -import me.topchetoeu.jscript.interop.NativeInit; @Native("Number") public class NumberLib { @Native public static final double EPSILON = java.lang.Math.ulp(1.0); @@ -52,8 +49,4 @@ import me.topchetoeu.jscript.interop.NativeInit; public NumberLib(double val) { this.value = val; } - - @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { - target.defineProperty(null, env.symbol("Symbol.typeName"), "Number"); - } } diff --git a/src/me/topchetoeu/jscript/lib/ObjectLib.java b/src/me/topchetoeu/jscript/lib/ObjectLib.java index 165bee5..30bd47b 100644 --- a/src/me/topchetoeu/jscript/lib/ObjectLib.java +++ b/src/me/topchetoeu/jscript/lib/ObjectLib.java @@ -1,17 +1,14 @@ 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.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.InitType; import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.NativeConstructor; -import me.topchetoeu.jscript.interop.NativeInit; @Native("Object") public class ObjectLib { @Native public static ObjectValue assign(Context ctx, ObjectValue dst, Object... src) { @@ -212,8 +209,4 @@ import me.topchetoeu.jscript.interop.NativeInit; // else if (arg instanceof Symbol) return SymbolPolyfill.constructor(ctx, thisArg, arg); else return arg; } - - @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { - target.defineProperty(null, env.symbol("Symbol.typeName"), "Object"); - } } diff --git a/src/me/topchetoeu/jscript/lib/PromiseLib.java b/src/me/topchetoeu/jscript/lib/PromiseLib.java index 769fa80..f01cb01 100644 --- a/src/me/topchetoeu/jscript/lib/PromiseLib.java +++ b/src/me/topchetoeu/jscript/lib/PromiseLib.java @@ -5,7 +5,6 @@ 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.values.ArrayValue; import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.NativeFunction; @@ -14,9 +13,7 @@ import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.InterruptException; -import me.topchetoeu.jscript.interop.InitType; import me.topchetoeu.jscript.interop.Native; -import me.topchetoeu.jscript.interop.NativeInit; @Native("Promise") public class PromiseLib { private static class Handle { @@ -352,8 +349,4 @@ import me.topchetoeu.jscript.interop.NativeInit; public PromiseLib() { this(STATE_PENDING, null); } - - @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { - target.defineProperty(null, env.symbol("Symbol.typeName"), "Promise"); - } } diff --git a/src/me/topchetoeu/jscript/lib/RangeErrorLib.java b/src/me/topchetoeu/jscript/lib/RangeErrorLib.java index 9e24482..540fec3 100644 --- a/src/me/topchetoeu/jscript/lib/RangeErrorLib.java +++ b/src/me/topchetoeu/jscript/lib/RangeErrorLib.java @@ -15,7 +15,6 @@ import me.topchetoeu.jscript.interop.NativeInit; return target; } @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { - target.defineProperty(null, env.symbol("Symbol.typeName"), "RangeError"); target.defineProperty(null, "name", "RangeError"); } } \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/lib/StringLib.java b/src/me/topchetoeu/jscript/lib/StringLib.java index 5e5df1f..eaee54b 100644 --- a/src/me/topchetoeu/jscript/lib/StringLib.java +++ b/src/me/topchetoeu/jscript/lib/StringLib.java @@ -3,17 +3,14 @@ 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.Values; import me.topchetoeu.jscript.exceptions.EngineException; -import me.topchetoeu.jscript.interop.InitType; import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.NativeConstructor; import me.topchetoeu.jscript.interop.NativeGetter; -import me.topchetoeu.jscript.interop.NativeInit; // TODO: implement index wrapping properly @Native("String") public class StringLib { @@ -263,8 +260,4 @@ import me.topchetoeu.jscript.interop.NativeInit; public StringLib(String val) { this.value = val; } - - @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { - target.defineProperty(null, env.symbol("Symbol.typeName"), "String"); - } } diff --git a/src/me/topchetoeu/jscript/lib/SymbolLib.java b/src/me/topchetoeu/jscript/lib/SymbolLib.java index 03fed13..f8bb132 100644 --- a/src/me/topchetoeu/jscript/lib/SymbolLib.java +++ b/src/me/topchetoeu/jscript/lib/SymbolLib.java @@ -4,16 +4,13 @@ import java.util.HashMap; import java.util.Map; 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.Symbol; import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.exceptions.EngineException; -import me.topchetoeu.jscript.interop.InitType; import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.NativeConstructor; import me.topchetoeu.jscript.interop.NativeGetter; -import me.topchetoeu.jscript.interop.NativeInit; @Native("Symbol") public class SymbolLib { private static final Map symbols = new HashMap<>(); @@ -63,8 +60,4 @@ import me.topchetoeu.jscript.interop.NativeInit; public SymbolLib(Symbol val) { this.value = val; } - - @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { - target.defineProperty(null, env.symbol("Symbol.typeName"), "Symbol"); - } } diff --git a/src/me/topchetoeu/jscript/lib/SyntaxErrorLib.java b/src/me/topchetoeu/jscript/lib/SyntaxErrorLib.java index 7c2a1ef..2d8df7f 100644 --- a/src/me/topchetoeu/jscript/lib/SyntaxErrorLib.java +++ b/src/me/topchetoeu/jscript/lib/SyntaxErrorLib.java @@ -11,11 +11,9 @@ import me.topchetoeu.jscript.interop.NativeInit; @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); - target.defineProperty(ctx, "name", "SyntaxError"); return target; } @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { - target.defineProperty(null, env.symbol("Symbol.typeName"), "SyntaxError"); 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 27298cd..906b096 100644 --- a/src/me/topchetoeu/jscript/lib/TypeErrorLib.java +++ b/src/me/topchetoeu/jscript/lib/TypeErrorLib.java @@ -11,11 +11,9 @@ import me.topchetoeu.jscript.interop.NativeInit; @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.defineProperty(ctx, "name", "TypeError"); return target; } @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { - target.defineProperty(null, env.symbol("Symbol.typeName"), "TypeError"); target.defineProperty(null, "name", "TypeError"); } } \ No newline at end of file -- 2.45.2 From 1acd78e119b606f088157edc092d84e82389da3a Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Mon, 13 Nov 2023 18:56:59 +0200 Subject: [PATCH 03/36] refactor: clean up Main class --- src/me/topchetoeu/jscript/Main.java | 146 +++++++++++++++++----------- 1 file changed, 88 insertions(+), 58 deletions(-) diff --git a/src/me/topchetoeu/jscript/Main.java b/src/me/topchetoeu/jscript/Main.java index e0535c8..91baec7 100644 --- a/src/me/topchetoeu/jscript/Main.java +++ b/src/me/topchetoeu/jscript/Main.java @@ -18,13 +18,8 @@ import me.topchetoeu.jscript.exceptions.InterruptException; import me.topchetoeu.jscript.exceptions.SyntaxException; import me.topchetoeu.jscript.lib.Internals; -public class Main { - static Thread engineTask, debugTask; - static Engine engine; - static Environment env; - static int j = 0; - - private static Observer valuePrinter = new Observer() { +public class Main { + public static class Printer implements Observer { public void next(Object data) { Values.printValue(null, data); System.out.println(); @@ -34,27 +29,80 @@ public class Main { Values.printError(err, null); } - @Override public void finish() { engineTask.interrupt(); } - }; + } - public static void main(String args[]) { - System.out.println(String.format("Running %s v%s by %s", Metadata.NAME, Metadata.VERSION, Metadata.AUTHOR)); - engine = new Engine(true); + static Thread engineTask, debugTask; + static Engine engine = new Engine(true); + static DebugServer debugServer = new DebugServer(); + static Environment environment = new Environment(null, null, null); - var exited = new boolean[1]; - var server = new DebugServer(); - server.targets.put("target", (ws, req) -> new SimpleDebugger(ws, engine)); - - env = Internals.apply(new Environment(null, null, null)); + static int j = 0; + static boolean exited = false; + static String[] args; - env.global.define("exit", _ctx -> { - exited[0] = true; + private static void reader() { + try { + for (var arg : args) { + try { + if (arg.equals("--ts")) initTypescript(); + else { + var file = Path.of(arg); + var raw = Files.readString(file); + var res = engine.pushMsg( + false, new Context(engine, environment), + Filename.fromFile(file.toFile()), + raw, null + ).await(); + Values.printValue(null, res); + System.out.println(); + } + } + catch (EngineException e) { Values.printError(e, ""); } + } + for (var i = 0; ; i++) { + try { + var raw = Reading.read(); + + if (raw == null) break; + var res = engine.pushMsg( + false, new Context(engine, environment), + new Filename("jscript", "repl/" + i + ".js"), + raw, null + ).await(); + Values.printValue(null, res); + System.out.println(); + } + catch (EngineException e) { Values.printError(e, ""); } + catch (SyntaxException e) { Values.printError(e, ""); } + } + } + catch (IOException e) { + System.out.println(e.toString()); + exited = true; + } + catch (RuntimeException ex) { + if (!exited) { + System.out.println("Internal error ocurred:"); + ex.printStackTrace(); + } + } + if (exited) { + debugTask.interrupt(); + engineTask.interrupt(); + } + } + + private static void initEnv() { + environment = Internals.apply(environment); + + environment.global.define("exit", _ctx -> { + exited = true; throw new InterruptException(); }); - env.global.define("go", _ctx -> { + environment.global.define("go", _ctx -> { try { var f = Path.of("do.js"); var func = _ctx.compile(new Filename("do", "do/" + j++ + ".js"), new String(Files.readAllBytes(f))); @@ -64,10 +112,13 @@ public class Main { throw new EngineException("Couldn't open do.js"); } }); - + } + private static void initEngine() { + debugServer.targets.put("target", (ws, req) -> new SimpleDebugger(ws, engine)); engineTask = engine.start(); - debugTask = server.start(new InetSocketAddress("127.0.0.1", 9229), true); - + debugTask = debugServer.start(new InetSocketAddress("127.0.0.1", 9229), true); + } + private static void initTypescript() { try { var tsEnv = Internals.apply(new Environment(null, null, null)); var bsEnv = Internals.apply(new Environment(null, null, null)); @@ -84,48 +135,27 @@ public class Main { engine.pushMsg( false, ctx, new Filename("jscript", "internals/bootstrap.js"), Reading.resourceToString("js/bootstrap.js"), null, - tsEnv.global.get(ctx, "ts"), env, new ArrayValue(null, Reading.resourceToString("js/lib.d.ts")) + tsEnv.global.get(ctx, "ts"), environment, new ArrayValue(null, Reading.resourceToString("js/lib.d.ts")) ).await(); } catch (EngineException e) { Values.printError(e, "(while initializing TS)"); } - - var reader = new Thread(() -> { - try { - for (var arg : args) { - try { - var file = Path.of(arg); - var raw = Files.readString(file); - valuePrinter.next(engine.pushMsg(false, new Context(engine).pushEnv(env), Filename.fromFile(file.toFile()), raw, null).await()); - } - catch (EngineException e) { Values.printError(e, ""); } - } - for (var i = 0; ; i++) { - try { - var raw = Reading.read(); - - if (raw == null) break; - valuePrinter.next(engine.pushMsg(false, new Context(engine).pushEnv(env), new Filename("jscript", "repl/" + i + ".js"), raw, null).await()); - } - catch (EngineException e) { Values.printError(e, ""); } - } - } - catch (IOException e) { exited[0] = true; } - catch (SyntaxException ex) { - if (exited[0]) return; - System.out.println("Syntax error:" + ex.msg); - } - catch (RuntimeException ex) { - if (!exited[0]) { - System.out.println("Internal error ocurred:"); - ex.printStackTrace(); - } - } - if (exited[0]) debugTask.interrupt(); - }); + } + private static void initReader() { + var reader = new Thread(Main::reader); reader.setDaemon(true); reader.setName("STD Reader"); reader.start(); } + + public static void main(String args[]) { + System.out.println(String.format("Running %s v%s by %s", Metadata.NAME, Metadata.VERSION, Metadata.AUTHOR)); + + Main.args = args; + + initEnv(); + initEngine(); + initReader(); + } } -- 2.45.2 From 1eeac3ae973b62599bee0c37691810a2046227c2 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Mon, 13 Nov 2023 19:08:02 +0200 Subject: [PATCH 04/36] fix: replace templates in Metadata class with placeholder data --- src/me/topchetoeu/jscript/Main.java | 20 +++++++++---------- src/me/topchetoeu/jscript/Metadata.java | 19 +++++++++++++++--- .../jscript/engine/debug/DebugServer.java | 8 ++++---- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/me/topchetoeu/jscript/Main.java b/src/me/topchetoeu/jscript/Main.java index 91baec7..abe8ba8 100644 --- a/src/me/topchetoeu/jscript/Main.java +++ b/src/me/topchetoeu/jscript/Main.java @@ -142,20 +142,18 @@ public class Main { Values.printError(e, "(while initializing TS)"); } } - private static void initReader() { + + public static void main(String args[]) { + System.out.println(String.format("Running %s v%s by %s", Metadata.name(), Metadata.version(), Metadata.author())); + + Main.args = args; var reader = new Thread(Main::reader); + + initEnv(); + initEngine(); + reader.setDaemon(true); reader.setName("STD Reader"); reader.start(); } - - public static void main(String args[]) { - System.out.println(String.format("Running %s v%s by %s", Metadata.NAME, Metadata.VERSION, Metadata.AUTHOR)); - - Main.args = args; - - initEnv(); - initEngine(); - initReader(); - } } diff --git a/src/me/topchetoeu/jscript/Metadata.java b/src/me/topchetoeu/jscript/Metadata.java index 5a0bf0b..05eefaf 100644 --- a/src/me/topchetoeu/jscript/Metadata.java +++ b/src/me/topchetoeu/jscript/Metadata.java @@ -1,7 +1,20 @@ package me.topchetoeu.jscript; public class Metadata { - public static final String VERSION = "${VERSION}"; - public static final String AUTHOR = "${AUTHOR}"; - public static final String NAME = "${NAME}"; + private static final String VERSION = "${VERSION}"; + private static final String AUTHOR = "${AUTHOR}"; + private static final String NAME = "${NAME}"; + + public static String version() { + if (VERSION.equals("$" + "{VERSION}")) return "1337-devel"; + else return VERSION; + } + public static String author() { + if (AUTHOR.equals("$" + "{AUTHOR}")) return "anonymous"; + else return AUTHOR; + } + public static String name() { + if (NAME.equals("$" + "{NAME}")) return "some-product"; + else return NAME; + } } diff --git a/src/me/topchetoeu/jscript/engine/debug/DebugServer.java b/src/me/topchetoeu/jscript/engine/debug/DebugServer.java index 5d7248a..3154ab6 100644 --- a/src/me/topchetoeu/jscript/engine/debug/DebugServer.java +++ b/src/me/topchetoeu/jscript/engine/debug/DebugServer.java @@ -19,7 +19,7 @@ import me.topchetoeu.jscript.json.JSONList; import me.topchetoeu.jscript.json.JSONMap; public class DebugServer { - public static String browserDisplayName = Metadata.NAME + "/" + Metadata.VERSION; + public static String browserDisplayName = Metadata.name() + "/" + Metadata.version(); public final HashMap targets = new HashMap<>(); @@ -236,9 +236,9 @@ public class DebugServer { this.protocol = getClass().getClassLoader().getResourceAsStream("assets/protocol.json").readAllBytes(); var index = new String(getClass().getClassLoader().getResourceAsStream("assets/index.html").readAllBytes()); this.index = index - .replace("${NAME}", Metadata.NAME) - .replace("${VERSION}", Metadata.VERSION) - .replace("${AUTHOR}", Metadata.AUTHOR) + .replace("${NAME}", Metadata.name()) + .replace("${VERSION}", Metadata.version()) + .replace("${AUTHOR}", Metadata.author()) .getBytes(); } catch (IOException e) { throw new UncheckedIOException(e); } -- 2.45.2 From 455f5a613e8e564dd843f277303021b1f3aa7136 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Mon, 13 Nov 2023 19:09:33 +0200 Subject: [PATCH 05/36] feat: implement simple permission matching --- .../jscript/permissions/Permission.java | 170 ++++++++++++++++++ .../permissions/PermissionsManager.java | 8 + 2 files changed, 178 insertions(+) create mode 100644 src/me/topchetoeu/jscript/permissions/Permission.java create mode 100644 src/me/topchetoeu/jscript/permissions/PermissionsManager.java diff --git a/src/me/topchetoeu/jscript/permissions/Permission.java b/src/me/topchetoeu/jscript/permissions/Permission.java new file mode 100644 index 0000000..0f81dfe --- /dev/null +++ b/src/me/topchetoeu/jscript/permissions/Permission.java @@ -0,0 +1,170 @@ +package me.topchetoeu.jscript.permissions; + +import java.util.ArrayList; + +public class Permission { + public final String[][] segments; + + private boolean matchSeg(String a, String b) { + if (a.contains("**") || b.contains("**")) throw new IllegalArgumentException("A '**' segment may not contain other characters."); + + var segA = a.split("\\*", -1); + var segB = b.split("\\*", -1); + + if (segA.length == 1 || segB.length == 1) { + if (segA.length == 1 && segB.length == 1) return a.equals(b); + else if (segA.length == 1) return matchSeg(b, a); + else { + if (!b.startsWith(segA[0]) || !b.endsWith(segA[segA.length - 1])) return false; + + int end = b.length() - segA[segA.length - 1].length(); + + for (int i = 1, findI = 1; i < segA.length - 1; i++) { + findI = b.indexOf(segA[i], findI); + if (findI < 0 || findI + segA[i].length() > end) return false; + } + + return true; + } + } + + String firstA = segA[0], firstB = segB[0]; + String lastA = segA[segA.length - 1], lastB = segB[segB.length - 1]; + + if (!firstA.startsWith(firstB) && !firstB.startsWith(firstA)) return false; + if (!lastA.endsWith(lastB) && !lastB.endsWith(lastA)) return false; + + return true; + } + private boolean matchArrs(String[] a, String[] b, int start, int end) { + if (a.length != end - start) return false; + if (a.length == 0) return true; + if (start >= b.length || end > b.length) return false; + if (start < 0 || end <= 0) return false; + + for (var i = start; i < end; i++) { + if (!matchSeg(a[i - start], b[i])) return false; + } + + return true; + } + private boolean matchFull(String[] a, String[] b) { + return matchArrs(a, b, 0, b.length); + } + private boolean matchStart(String[] a, String[] b) { + return matchArrs(a, b, 0, a.length); + } + private boolean matchEnd(String[] a, String[] b) { + return matchArrs(a, b, b.length - a.length, b.length); + } + + private int find(String[] query, String[] target, int start, int end) { + var findI = 0; + + if (start < 0) start = 0; + if (query.length == 0) return start; + if (start != 0 && start >= target.length) return -1; + + for (var i = start; i < end; i++) { + if (findI == query.length) return i - findI; + else if (matchSeg(query[findI], target[i])) findI++; + else { + i -= findI; + findI = 0; + } + } + + return -1; + } + + public boolean match(Permission other) { + var an = this.segments.length; + var bn = other.segments.length; + + // We must have at least one segment, even if empty + if (an == 0 || bn == 0) throw new IllegalArgumentException("Can't have a permission with 0 segments."); + + if (an == 1 || bn == 1) { + // If both perms are one segment, we just match the segments themselves + if (an == 1 && bn == 1) return matchFull(this.segments[0], other.segments[0]); + else if (an == 1) return other.match(this); + else { + // If just the other perm is one segment, we neet to find all + // the segments of this perm sequentially in the other segment. + + var seg = other.segments[0]; + // Here we check for the prefix and suffix + if (!matchStart(this.segments[0], seg)) return false; + if (!matchEnd(this.segments[this.segments.length - 1], seg)) return false; + + int end = seg.length - this.segments[this.segments.length - 1].length; + + // Here we go and look for the segments one by one, until one isn't found + for (int i = 1, findI = 1; i < this.segments.length - 1; i++) { + findI = find(this.segments[i], seg, findI, end); + if (findI < 0) return false; + } + + return true; + } + } + + // If both perms have more than one segment (a.k.a both have **), + // we can ignore everything in the middle, as it will always match. + // Instead, we check if the prefixes and suffixes match + + var firstA = this.segments[0]; + var firstB = other.segments[0]; + + var lastA = this.segments[this.segments.length - 1]; + var lastB = other.segments[other.segments.length - 1]; + + if (!matchStart(firstA, firstB) && !matchStart(firstB, firstA)) return false; + if (!matchEnd(lastA, lastB) && !matchEnd(lastB, lastA)) return false; + + return true; + } + + @Override + public String toString() { + var sb = new StringBuilder(); + var firstSeg = true; + var firstEl = true; + + for (var seg : segments) { + if (!firstSeg) { + if (!firstEl) sb.append("."); + sb.append("**"); + } + firstSeg = false; + for (var el : seg) { + if (!firstEl) sb.append("."); + sb.append(el); + firstEl = false; + } + } + + return sb.toString(); + } + + public Permission(String raw) { + var segs = raw.split("\\."); + var curr = new ArrayList(); + var res = new ArrayList(); + + for (var seg : segs) { + if (seg.equals("**")) { + res.add(curr.toArray(String[]::new)); + curr.clear(); + } + else curr.add(seg); + } + res.add(curr.toArray(String[]::new)); + + segments = res.toArray(String[][]::new); + } + + public static boolean match(String a, String b) { + return new Permission(a).match(new Permission(b)); + } +} diff --git a/src/me/topchetoeu/jscript/permissions/PermissionsManager.java b/src/me/topchetoeu/jscript/permissions/PermissionsManager.java new file mode 100644 index 0000000..0653795 --- /dev/null +++ b/src/me/topchetoeu/jscript/permissions/PermissionsManager.java @@ -0,0 +1,8 @@ +package me.topchetoeu.jscript.permissions; + +public interface PermissionsManager { + public static final PermissionsManager ALL_PERMS = perm -> true; + public static final PermissionsManager NO_PERMS = perm -> false; + + boolean hasPermissions(String perm); +} -- 2.45.2 From e7dbe91374429b0ebc8c7d0b0cf3d1ce4e75c3e4 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Mon, 13 Nov 2023 20:06:07 +0200 Subject: [PATCH 06/36] refactor: clean up protocol.json --- src/assets/protocol.json | 1359 +++-------------- .../jscript/engine/debug/SimpleDebugger.java | 7 +- 2 files changed, 182 insertions(+), 1184 deletions(-) diff --git a/src/assets/protocol.json b/src/assets/protocol.json index 246e736..4e5dcfe 100644 --- a/src/assets/protocol.json +++ b/src/assets/protocol.json @@ -56,7 +56,7 @@ "properties": [ { "name": "scriptId", "description": "Script identifier as reported in the `Debugger.scriptParsed`.", - "$ref": "Runtime.ScriptId" + "$ref": "Runtime.ID" }, { "name": "lineNumber", "description": "Line number in the script (0-based).", @@ -108,57 +108,21 @@ "description": "Disables debugging. This will have some performance benefit." }, - { "name": "continueToLocation", - "description": "Sets a one-off breakpoint to the specified location, and continues exectuion.", + { "name": "pause", + "description": "Stops on the next JavaScript statement." + }, + { "name": "resume", + "description": "Resumes JavaScript execution.", "parameters": [ - { "name": "location", - "description": "Location to continue to.", - "$ref": "Location" - } - ] - }, - { "name": "evaluateOnCallFrame", - "description": "Evaluates expression on a given call frame, without triggering any breakpoints. The expression should be pure.", - "parameters": [ - { "name": "callFrameId", - "description": "Call frame to evaluate on.", - "$ref": "CallFrameId" - }, - { "name": "expression", - "description": "Expression to evaluate.", - "type": "string" - }, - { "name": "silent", - "description": "Ignores any exceptions that may occur during evaluation. Overrides `setPauseOnException` state.", - "optional": true, - "type": "boolean" - }, - { "name": "returnByValue", - "description": "Whether the result is expected to be a JSON object that should be sent by value.", - "deprecated": true, - "optional": true, - "type": "boolean" - }, - { "name": "timeout", - "description": "Terminate execution after timing out (number of milliseconds).", - "optional": true, - "type": "number" - } - ], - "returns": [ - { - "name": "result", - "description": "Object wrapper for the evaluation result.", - "$ref": "Runtime.RemoteObject" - }, - { - "name": "exceptionDetails", - "description": "Exception details.", - "optional": true, - "$ref": "Runtime.ExceptionDetails" + { + "name": "terminateOnResume", + "description": "Set to true to terminate execution upon resuming execution. In contrast\nto Runtime.terminateExecution, this will allows to execute further\nJavaScript (i.e. via evaluation) until execution of the paused code\nis actually resumed, at which point termination is triggered.\nIf execution is currently not paused, this parameter has no effect.", + "optional": true, + "type": "boolean" } ] }, + { "name": "getPossibleBreakpoints", "description": "Returns possible locations for breakpoint. scriptId in start and end range locations should be\nthe same.", "parameters": [ @@ -197,7 +161,7 @@ { "name": "scriptId", "description": "Id of the script to get source for.", - "$ref": "Runtime.ScriptId" + "$ref": "Runtime.ID" } ], "returns": [ @@ -214,24 +178,6 @@ } ] }, - { "name": "getWasmBytecode", - "description": "This command is deprecated. Use getScriptSource instead.", - "deprecated": true, - "parameters": [ - { - "name": "scriptId", - "description": "Id of the Wasm script to get source for.", - "$ref": "Runtime.ScriptId" - } - ], - "returns": [ - { - "name": "bytecode", - "description": "Script source.", - "type": "binary" - } - ] - }, { "name": "getStackTrace", "description": "Returns stack trace with given `stackTraceId`.", "experimental": true, @@ -248,202 +194,7 @@ } ] }, - { "name": "pause", - "description": "Stops on the next JavaScript statement." - }, - { "name": "pauseOnAsyncCall", - "experimental": true, - "deprecated": true, - "parameters": [ - { - "name": "parentStackTraceId", - "description": "Debugger will pause when async call with given stack trace is started.", - "$ref": "Runtime.StackTraceId" - } - ] - }, - { "name": "removeBreakpoint", - "description": "Removes JavaScript breakpoint.", - "parameters": [ - { - "name": "breakpointId", - "$ref": "BreakpointId" - } - ] - }, - { "name": "restartFrame", - "description": "Restarts particular call frame from the beginning.", - "deprecated": true, - "parameters": [ - { - "name": "callFrameId", - "description": "Call frame identifier to evaluate on.", - "$ref": "CallFrameId" - } - ], - "returns": [ - { - "name": "callFrames", - "description": "New stack trace.", - "type": "array", - "items": { - "$ref": "CallFrame" - } - }, - { - "name": "asyncStackTrace", - "description": "Async stack trace, if any.", - "optional": true, - "$ref": "Runtime.StackTrace" - }, - { - "name": "asyncStackTraceId", - "description": "Async stack trace, if any.", - "experimental": true, - "optional": true, - "$ref": "Runtime.StackTraceId" - } - ] - }, - { "name": "resume", - "description": "Resumes JavaScript execution.", - "parameters": [ - { - "name": "terminateOnResume", - "description": "Set to true to terminate execution upon resuming execution. In contrast\nto Runtime.terminateExecution, this will allows to execute further\nJavaScript (i.e. via evaluation) until execution of the paused code\nis actually resumed, at which point termination is triggered.\nIf execution is currently not paused, this parameter has no effect.", - "optional": true, - "type": "boolean" - } - ] - }, - { "name": "searchInContent", - "description": "Searches for given string in script content.", - "parameters": [ - { - "name": "scriptId", - "description": "Id of the script to search in.", - "$ref": "Runtime.ScriptId" - }, - { - "name": "query", - "description": "String to search for.", - "type": "string" - }, - { - "name": "caseSensitive", - "description": "If true, search is case sensitive.", - "optional": true, - "type": "boolean" - }, - { - "name": "isRegex", - "description": "If true, treats string parameter as regex.", - "optional": true, - "type": "boolean" - } - ], - "returns": [ - { - "name": "result", - "description": "List of search matches.", - "type": "array", - "items": { - "$ref": "SearchMatch" - } - } - ] - }, - { "name": "setAsyncCallStackDepth", - "description": "Enables or disables async call stacks tracking.", - "parameters": [ - { - "name": "maxDepth", - "description": "Maximum depth of async call stacks. Setting to `0` will effectively disable collecting async\ncall stacks (default).", - "type": "integer" - } - ] - }, - { "name": "setBlackboxPatterns", - "description": "Replace previous blackbox patterns with passed ones. Forces backend to skip stepping/pausing in\nscripts with url matching one of the patterns. VM will try to leave blackboxed script by\nperforming 'step in' several times, finally resorting to 'step out' if unsuccessful.", - "experimental": true, - "parameters": [ - { - "name": "patterns", - "description": "Array of regexps that will be used to check script url for blackbox state.", - "type": "array", - "items": { - "type": "string" - } - } - ] - }, - { "name": "setBlackboxedRanges", - "description": "Makes backend skip steps in the script in blackboxed ranges. VM will try leave blacklisted\nscripts by performing 'step in' several times, finally resorting to 'step out' if unsuccessful.\nPositions array contains positions where blackbox state is changed. First interval isn't\nblackboxed. Array should be sorted.", - "experimental": true, - "parameters": [ - { - "name": "scriptId", - "description": "Id of the script.", - "$ref": "Runtime.ScriptId" - }, - { - "name": "positions", - "type": "array", - "items": { - "$ref": "ScriptPosition" - } - } - ] - }, - { "name": "setBreakpoint", - "description": "Sets JavaScript breakpoint at a given location.", - "parameters": [ - { - "name": "location", - "description": "Location to set breakpoint in.", - "$ref": "Location" - }, - { - "name": "condition", - "description": "Expression to use as a breakpoint condition. When specified, debugger will only stop on the\nbreakpoint if this expression evaluates to true.", - "optional": true, - "type": "string" - } - ], - "returns": [ - { - "name": "breakpointId", - "description": "Id of the created breakpoint for further reference.", - "$ref": "BreakpointId" - }, - { - "name": "actualLocation", - "description": "Location this breakpoint resolved into.", - "$ref": "Location" - } - ] - }, - { "name": "setInstrumentationBreakpoint", - "description": "Sets instrumentation breakpoint.", - "parameters": [ - { - "name": "instrumentation", - "description": "Instrumentation name.", - "type": "string", - "enum": [ - "beforeScriptExecution", - "beforeScriptWithSourceMapExecution" - ] - } - ], - "returns": [ - { - "name": "breakpointId", - "description": "Id of the created breakpoint for further reference.", - "$ref": "BreakpointId" - } - ] - }, + { "name": "setBreakpointByUrl", "description": "Sets JavaScript breakpoint at given location specified either by URL or URL regex. Once this\ncommand is issued, all existing parsed scripts will have breakpoints resolved and returned in\n`locations` property. Further matching script parsing will result in subsequent\n`breakpointResolved` events issued. This logical breakpoint will survive page reloads.", "parameters": [ @@ -499,18 +250,17 @@ } ] }, - { "name": "setBreakpointOnFunctionCall", - "description": "Sets JavaScript breakpoint before each call to the given function.\nIf another function was created from the same source as a given one,\ncalling it will also trigger the breakpoint.", - "experimental": true, + { "name": "setBreakpoint", + "description": "Sets JavaScript breakpoint at a given location.", "parameters": [ { - "name": "objectId", - "description": "Function object id.", - "$ref": "Runtime.RemoteObjectId" + "name": "location", + "description": "Location to set breakpoint in.", + "$ref": "Location" }, { "name": "condition", - "description": "Expression to use as a breakpoint condition. When specified, debugger will\nstop on the breakpoint if this expression evaluates to true.", + "description": "Expression to use as a breakpoint condition. When specified, debugger will only stop on the\nbreakpoint if this expression evaluates to true.", "optional": true, "type": "string" } @@ -520,9 +270,36 @@ "name": "breakpointId", "description": "Id of the created breakpoint for further reference.", "$ref": "BreakpointId" + }, + { + "name": "actualLocation", + "description": "Location this breakpoint resolved into.", + "$ref": "Location" } ] }, + { "name": "setInstrumentationBreakpoint", + "description": "Sets instrumentation breakpoint.", + "parameters": [ + { + "name": "instrumentation", + "description": "Instrumentation name.", + "type": "string", + "enum": [ + "beforeScriptExecution", + "beforeScriptWithSourceMapExecution" + ] + } + ], + "returns": [ + { + "name": "breakpointId", + "description": "Id of the created breakpoint for further reference.", + "$ref": "BreakpointId" + } + ], + "todo": "// TODO: implement!!" + }, { "name": "setBreakpointsActive", "description": "Activates / deactivates all breakpoints on the page.", "parameters": [ @@ -543,114 +320,74 @@ "enum": [ "none", "uncaught", - "all" + "all" ] } ] }, - { "name": "setReturnValue", - "description": "Changes return value in top frame. Available only at return break position.", - "experimental": true, + + { "name": "continueToLocation", + "description": "Sets a one-off breakpoint to the specified location, and continues exectuion.", "parameters": [ - { - "name": "newValue", - "description": "New return value.", - "$ref": "Runtime.CallArgument" + { "name": "location", + "description": "Location to continue to.", + "$ref": "Location" } ] }, - { "name": "setScriptSource", - "description": "Edits JavaScript source live.", + { "name": "removeBreakpoint", + "description": "Removes JavaScript breakpoint.", "parameters": [ { - "name": "scriptId", - "description": "Id of the script to edit.", - "$ref": "Runtime.ScriptId" + "name": "breakpointId", + "$ref": "BreakpointId" + } + ] + }, + + { "name": "evaluateOnCallFrame", + "description": "Evaluates expression on a given call frame, without triggering any breakpoints. The expression should be pure.", + "parameters": [ + { "name": "callFrameId", + "description": "Call frame to evaluate on.", + "$ref": "CallFrameId" }, - { - "name": "scriptSource", - "description": "New content of the script.", + { "name": "expression", + "description": "Expression to evaluate.", "type": "string" }, - { - "name": "dryRun", - "description": "If true the change will not actually be applied. Dry run may be used to get result\ndescription without actually modifying the code.", + { "name": "silent", + "description": "Ignores any exceptions that may occur during evaluation. Overrides `setPauseOnException` state.", "optional": true, "type": "boolean" + }, + { "name": "returnByValue", + "description": "Whether the result is expected to be a JSON object that should be sent by value.", + "deprecated": true, + "optional": true, + "type": "boolean" + }, + { "name": "timeout", + "description": "Terminate execution after timing out (number of milliseconds).", + "optional": true, + "type": "number" } ], "returns": [ { - "name": "callFrames", - "description": "New stack trace in case editing has happened while VM was stopped.", - "optional": true, - "type": "array", - "items": { - "$ref": "CallFrame" - } - }, - { - "name": "stackChanged", - "description": "Whether current call stack was modified after applying the changes.", - "optional": true, - "type": "boolean" - }, - { - "name": "asyncStackTrace", - "description": "Async stack trace, if any.", - "optional": true, - "$ref": "Runtime.StackTrace" - }, - { - "name": "asyncStackTraceId", - "description": "Async stack trace, if any.", - "experimental": true, - "optional": true, - "$ref": "Runtime.StackTraceId" + "name": "result", + "description": "Object wrapper for the evaluation result.", + "$ref": "Runtime.RemoteObject" }, { "name": "exceptionDetails", - "description": "Exception details if any.", + "description": "Exception details.", "optional": true, "$ref": "Runtime.ExceptionDetails" } ] }, - { "name": "setSkipAllPauses", - "description": "Makes page not interrupt on any pauses (breakpoint, exception, dom exception etc).", - "parameters": [ - { - "name": "skip", - "description": "New value for skip pauses state.", - "type": "boolean" - } - ] - }, - { "name": "setVariableValue", - "description": "Changes value of variable in a callframe. Object-based scopes are not supported and must be\nmutated manually.", - "parameters": [ - { - "name": "scopeNumber", - "description": "0-based number of scope as was listed in scope chain. Only 'local', 'closure' and 'catch'\nscope types are allowed. Other scopes could be manipulated manually.", - "type": "integer" - }, - { - "name": "variableName", - "description": "Variable name.", - "type": "string" - }, - { - "name": "newValue", - "description": "New variable value.", - "$ref": "Runtime.CallArgument" - }, - { - "name": "callFrameId", - "description": "Id of callframe that holds variable.", - "$ref": "CallFrameId" - } - ] - }, + { "name": "stepInto", "description": "Steps into the function call." }, @@ -754,7 +491,7 @@ { "name": "scriptId", "description": "Identifier of the script parsed.", - "$ref": "Runtime.ScriptId" + "$ref": "Runtime.ID" }, { "name": "url", @@ -852,7 +589,7 @@ { "name": "scriptId", "description": "Identifier of the script parsed.", - "$ref": "Runtime.ScriptId" + "$ref": "Runtime.ID" }, { "name": "url", @@ -963,32 +700,29 @@ { "domain": "Runtime", "types": [ - { "id": "ScriptId", - "description": "Unique script identifier.", + { "id": "ID", + "description": "The string representation of an integer ID. All identifiable constructs share the same ID repository.", "type": "string" }, - { "id": "RemoteObjectId", - "description": "Unique object identifier.", - "type": "string" - }, - { "id": "Timestamp", "description": "Number of milliseconds since epoch.", "type": "number" }, - { "id": "TimeDelta", - "description": "Number of milliseconds.", - "type": "number" - }, + { "id": "UnserializableValue", - "description": "Primitive value which cannot be JSON-stringified. Includes values `-0`, `NaN`, `Infinity`,\n`-Infinity`, and bigint literals.", + "description": "The string representation of a value that can't be JSON serialized.", "type": "string" }, { "id": "RemoteObject", "description": "Mirror object referencing original JavaScript object.", "type": "object", "properties": [ + { "name": "objectId", + "description": "Unique object identifier (for non-primitive values).", + "optional": true, + "$ref": "ID" + }, { "name": "type", "description": "Object type.", "type": "string", @@ -1008,13 +742,7 @@ "type": "string", "enum": [ "array", - "null", - "regexp", - "date", - "map", - "set", - "generator", - "promise" + "null" ] }, { "name": "className", @@ -1023,12 +751,12 @@ "type": "string" }, { "name": "value", - "description": "Remote object value in case of primitive values or JSON values (if it was requested).", + "description": "Remote object value in case of primitive values.", "optional": true, "type": "any" }, { "name": "unserializableValue", - "description": "Primitive value which can not be JSON-stringified does not have `value`, but gets this\nproperty.", + "description": "In case of a non JSON-serializable value, its string representation is provided here.", "optional": true, "$ref": "UnserializableValue" }, @@ -1036,85 +764,6 @@ "description": "String representation of the object.", "optional": true, "type": "string" - }, - { "name": "objectId", - "description": "Unique object identifier (for non-primitive values).", - "optional": true, - "$ref": "RemoteObjectId" - } - ] - }, - { "id": "MemberDescriptor", - "description": "Object member descriptor, mirroring the object descriptor in JavaScript (https://developer.mozilla.org/en-US/docs/Glossary/Property/JavaScript).", - "type": "object", - "properties": [ - { "name": "name", - "description": "String representation of the member name.", - "type": "string" - }, - { "name": "value", - "description": "The value of the member, if it's a field.", - "optional": true, - "$ref": "RemoteObject" - }, - { "name": "writable", - "description": "Whether or not the member is writable. Present only if the member is a field.", - "optional": true, - "type": "boolean" - }, - { "name": "configurable", - "description": "Whether or not the member is configurable.", - "type": "boolean" - }, - { "name": "enumerable", - "description": "Whether or not the member is enumerable..", - "type": "boolean" - }, - { "name": "get", - "description": "The getter of the member. Present only if the member is a property and the property has a getter.", - "optional": true, - "$ref": "RemoteObject" - }, - { "name": "set", - "description": "The setter of the member. Present only if the member is a property and the property has a setter.", - "optional": true, - "$ref": "RemoteObject" - }, - { "name": "wasThrown", - "description": "True if the member's value was thrown during the evaluation of the getter.", - "optional": true, - "type": "boolean" - }, - { "name": "isOwn", - "description": "True if the property is owned for the object.", - "optional": true, - "type": "boolean" - }, - { "name": "symbol", - "description": "If the member name is a symbol, this will be the reference of the symbol.", - "optional": true, - "$ref": "RemoteObject" - } - ] - }, - { "id": "CallArgument", - "description": "Represents function call argument. Only one of the three optional fields should be specified, or none for undefined.", - "type": "object", - "properties": [ - { "name": "value", - "description": "Primitive value or serializable javascript object.", - "optional": true, - "type": "any" - }, - { "name": "unserializableValue", - "description": "Primitive value which can not be JSON-stringified.", - "optional": true, - "$ref": "UnserializableValue" - }, - { "name": "objectId", - "description": "Remote object handle.", - "optional": true, - "$ref": "RemoteObjectId" } ] }, @@ -1142,7 +791,7 @@ }, { "name": "scriptId", "description": "Script ID of the exception location.", - "$ref": "ScriptId" + "$ref": "ID" }, { "name": "lineNumber", "description": "Line number of the exception location (0-based).", @@ -1164,7 +813,7 @@ }, { "name": "scriptId", "description": "JavaScript script id.", - "$ref": "ScriptId" + "$ref": "ID" }, { "name": "lineNumber", "description": "JavaScript script line number (0-based).", @@ -1191,637 +840,106 @@ "items": { "$ref": "CallFrame" } } ] + }, + + { "id": "MemberDescriptor", + "description": "An object, representing a property of another object.", + "type": "object", + "properties": [ + { "name": "name", + "description": "The name of the property. Although the property's key might not be a string,\nthe key is always converted to a string.", + "type": "string" + }, + + { "name": "writable", + "description": "The writable flag of the property. Present only if the member is a field.", + "type": "boolean", + "optional": true + }, + { "name": "enumerable", + "description": "The enumerable flag of the property.", + "type": "boolean", + "optional": true + }, + { "name": "configurable", + "description": "The configurable flag of the property.", + "type": "boolean", + "optional": true + }, + { "name": "isOwn", + "description": "Always true.", + "type": "boolean" + }, + + { "name": "get", + "description": "The remote function that is the getter of the property.\nNot present if the member is not a property or it doesn't have a getter.", + "$ref": "RemoteObject", + "optional": true + }, + { "name": "set", + "description": "The remote function that is the setter of the property.\nNot present if the member is not a property or it doesn't have a setter.", + "$ref": "RemoteObject", + "optional": true + }, + { "name": "value", + "description": "The remote value of the field. Present only if the member is a field.", + "$ref": "RemoteObject", + "optional": true + } + ] } ], "commands": [ - { "name": "awaitPromise", - "description": "Add handler to promise with given promise object id.", + { "name": "enable", + "description": "Enables reporting of execution contexts creation by means of `executionContextCreated` event.\nWhen the reporting gets enabled the event will be sent immediately for each existing execution\ncontext." + }, + { "name": "getProperties", + "description": "Returns a list of all owned members of the specified object.", "parameters": [ - { "name": "promiseObjectId", - "description": "Identifier of the promise.", - "$ref": "RemoteObjectId" - }, - { "name": "returnByValue", - "description": "Whether the result is expected to be a JSON object that should be sent by value.", - "optional": true, - "type": "boolean" + { "name": "objectId", + "description": "Identifier of the object to return properties for.", + "$ref": "ID" } ], "returns": [ { "name": "result", - "description": "Promise result. Will contain rejected value if promise was rejected.", - "$ref": "RemoteObject" - }, - { "name": "exceptionDetails", - "description": "Exception details if stack strace is available.", - "optional": true, - "$ref": "ExceptionDetails" - } - ] - }, - { "name": "callFunctionOn", - "description": "Calls function with given declaration on the given object.", - "parameters": [ - { "name": "functionDeclaration", - "description": "Declaration of the function to call.", - "type": "string" - }, - { "name": "objectId", - "description": "Identifier of the object to call function on. Either objectId or executionContextId should be specified.", - "optional": true, - "$ref": "RemoteObjectId" - }, - { "name": "arguments", - "description": "Call arguments. All call arguments must belong to the same JavaScript world as the target\nobject.", - "optional": true, - "type": "array", - "items": { - "$ref": "CallArgument" - } - }, - { "name": "silent", - "description": "In silent mode exceptions thrown during evaluation are not reported and do not pause\nexecution. Overrides `setPauseOnException` state.", - "optional": true, - "type": "boolean" - }, - { "name": "returnByValue", - "description": "Whether the result is expected to be a JSON object which should be sent by value.", - "optional": true, - "type": "boolean" - }, - { "name": "awaitPromise", - "description": "Whether execution should `await` for resulting value and return once awaited promise is\nresolved.", - "optional": true, - "type": "boolean" - }, - { - "name": "objectGroup", - "description": "Symbolic group name that can be used to release multiple objects. If objectGroup is not\nspecified and objectId is, objectGroup will be inherited from object.", - "optional": true, - "type": "string" - }, - { - "name": "throwOnSideEffect", - "description": "Whether to throw an exception if side effect cannot be ruled out during evaluation.", - "experimental": true, - "optional": true, - "type": "boolean" - }, - { - "name": "generateWebDriverValue", - "description": "Whether the result should be serialized according to https://w3c.github.io/webdriver-bidi.", - "experimental": true, - "optional": true, - "type": "boolean" - } - ], - "returns": [ - { - "name": "result", - "description": "Call result.", - "$ref": "RemoteObject" - }, - { - "name": "exceptionDetails", - "description": "Exception details.", - "optional": true, - "$ref": "ExceptionDetails" - } - ] - }, - { - "name": "compileScript", - "description": "Compiles expression.", - "parameters": [ - { - "name": "expression", - "description": "Expression to compile.", - "type": "string" - }, - { - "name": "sourceURL", - "description": "Source url to be set for the script.", - "type": "string" - }, - { - "name": "persistScript", - "description": "Specifies whether the compiled script should be persisted.", - "type": "boolean" - }, - { - "name": "executionContextId", - "description": "Specifies in which execution context to perform script run. If the parameter is omitted the\nevaluation will be performed in the context of the inspected page.", - "optional": true, - "$ref": "ExecutionContextId" - } - ], - "returns": [ - { - "name": "scriptId", - "description": "Id of the script.", - "optional": true, - "$ref": "ScriptId" - }, - { - "name": "exceptionDetails", - "description": "Exception details.", - "optional": true, - "$ref": "ExceptionDetails" - } - ] - }, - { - "name": "disable", - "description": "Disables reporting of execution contexts creation." - }, - { - "name": "discardConsoleEntries", - "description": "Discards collected exceptions and console API calls." - }, - { - "name": "enable", - "description": "Enables reporting of execution contexts creation by means of `executionContextCreated` event.\nWhen the reporting gets enabled the event will be sent immediately for each existing execution\ncontext." - }, - { - "name": "evaluate", - "description": "Evaluates expression on global object.", - "parameters": [ - { - "name": "expression", - "description": "Expression to evaluate.", - "type": "string" - }, - { - "name": "objectGroup", - "description": "Symbolic group name that can be used to release multiple objects.", - "optional": true, - "type": "string" - }, - { - "name": "includeCommandLineAPI", - "description": "Determines whether Command Line API should be available during the evaluation.", - "optional": true, - "type": "boolean" - }, - { - "name": "silent", - "description": "In silent mode exceptions thrown during evaluation are not reported and do not pause\nexecution. Overrides `setPauseOnException` state.", - "optional": true, - "type": "boolean" - }, - { - "name": "contextId", - "description": "Specifies in which execution context to perform evaluation. If the parameter is omitted the\nevaluation will be performed in the context of the inspected page.\nThis is mutually exclusive with `uniqueContextId`, which offers an\nalternative way to identify the execution context that is more reliable\nin a multi-process environment.", - "optional": true, - "$ref": "ExecutionContextId" - }, - { - "name": "returnByValue", - "description": "Whether the result is expected to be a JSON object that should be sent by value.", - "optional": true, - "type": "boolean" - }, - { - "name": "generatePreview", - "description": "Whether preview should be generated for the result.", - "experimental": true, - "optional": true, - "type": "boolean" - }, - { - "name": "userGesture", - "description": "Whether execution should be treated as initiated by user in the UI.", - "optional": true, - "type": "boolean" - }, - { - "name": "awaitPromise", - "description": "Whether execution should `await` for resulting value and return once awaited promise is\nresolved.", - "optional": true, - "type": "boolean" - }, - { - "name": "throwOnSideEffect", - "description": "Whether to throw an exception if side effect cannot be ruled out during evaluation.\nThis implies `disableBreaks` below.", - "experimental": true, - "optional": true, - "type": "boolean" - }, - { - "name": "timeout", - "description": "Terminate execution after timing out (number of milliseconds).", - "experimental": true, - "optional": true, - "$ref": "TimeDelta" - }, - { - "name": "disableBreaks", - "description": "Disable breakpoints during execution.", - "experimental": true, - "optional": true, - "type": "boolean" - }, - { - "name": "replMode", - "description": "Setting this flag to true enables `let` re-declaration and top-level `await`.\nNote that `let` variables can only be re-declared if they originate from\n`replMode` themselves.", - "experimental": true, - "optional": true, - "type": "boolean" - }, - { - "name": "allowUnsafeEvalBlockedByCSP", - "description": "The Content Security Policy (CSP) for the target might block 'unsafe-eval'\nwhich includes eval(), Function(), setTimeout() and setInterval()\nwhen called with non-callable arguments. This flag bypasses CSP for this\nevaluation and allows unsafe-eval. Defaults to true.", - "experimental": true, - "optional": true, - "type": "boolean" - }, - { - "name": "uniqueContextId", - "description": "An alternative way to specify the execution context to evaluate in.\nCompared to contextId that may be reused across processes, this is guaranteed to be\nsystem-unique, so it can be used to prevent accidental evaluation of the expression\nin context different than intended (e.g. as a result of navigation across process\nboundaries).\nThis is mutually exclusive with `contextId`.", - "experimental": true, - "optional": true, - "type": "string" - }, - { - "name": "generateWebDriverValue", - "description": "Whether the result should be serialized according to https://w3c.github.io/webdriver-bidi.", - "experimental": true, - "optional": true, - "type": "boolean" - } - ], - "returns": [ - { - "name": "result", - "description": "Evaluation result.", - "$ref": "RemoteObject" - }, - { - "name": "exceptionDetails", - "description": "Exception details.", - "optional": true, - "$ref": "ExceptionDetails" - } - ] - }, - { - "name": "getIsolateId", - "description": "Returns the isolate id.", - "experimental": true, - "returns": [ - { - "name": "id", - "description": "The isolate id.", - "type": "string" - } - ] - }, - { - "name": "getHeapUsage", - "description": "Returns the JavaScript heap usage.\nIt is the total usage of the corresponding isolate not scoped to a particular Runtime.", - "experimental": true, - "returns": [ - { - "name": "usedSize", - "description": "Used heap size in bytes.", - "type": "number" - }, - { - "name": "totalSize", - "description": "Allocated heap size in bytes.", - "type": "number" - } - ] - }, - { - "name": "getProperties", - "description": "Returns properties of a given object. Object group of the result is inherited from the target\nobject.", - "parameters": [ - { - "name": "objectId", - "description": "Identifier of the object to return properties for.", - "$ref": "RemoteObjectId" - }, - { - "name": "ownProperties", - "description": "If true, returns properties belonging only to the element itself, not to its prototype\nchain.", - "optional": true, - "type": "boolean" - }, - { - "name": "accessorPropertiesOnly", - "description": "If true, returns accessor properties (with getter/setter) only; internal properties are not\nreturned either.", - "experimental": true, - "optional": true, - "type": "boolean" - }, - { - "name": "generatePreview", - "description": "Whether preview should be generated for the results.", - "experimental": true, - "optional": true, - "type": "boolean" - }, - { - "name": "nonIndexedPropertiesOnly", - "description": "If true, returns non-indexed properties only.", - "experimental": true, - "optional": true, - "type": "boolean" - } - ], - "returns": [ - { - "name": "result", "description": "Object properties.", "type": "array", "items": { - "$ref": "PropertyDescriptor" - } - }, - { - "name": "internalProperties", - "description": "Internal object properties (only of the element itself).", - "optional": true, - "type": "array", - "items": { - "$ref": "InternalPropertyDescriptor" - } - }, - { - "name": "privateProperties", - "description": "Object private properties.", - "experimental": true, - "optional": true, - "type": "array", - "items": { - "$ref": "PrivatePropertyDescriptor" - } - }, - { - "name": "exceptionDetails", - "description": "Exception details.", - "optional": true, - "$ref": "ExceptionDetails" - } - ] - }, - { - "name": "globalLexicalScopeNames", - "description": "Returns all let, const and class variables from global scope.", - "parameters": [ - { - "name": "executionContextId", - "description": "Specifies in which execution context to lookup global scope variables.", - "optional": true, - "$ref": "ExecutionContextId" - } - ], - "returns": [ - { - "name": "names", - "type": "array", - "items": { - "type": "string" + "$ref": "MemberDescriptor" } } ] }, - { - "name": "queryObjects", - "parameters": [ - { - "name": "prototypeObjectId", - "description": "Identifier of the prototype to return objects for.", - "$ref": "RemoteObjectId" - }, - { - "name": "objectGroup", - "description": "Symbolic group name that can be used to release the results.", - "optional": true, - "type": "string" - } - ], - "returns": [ - { - "name": "objects", - "description": "Array with objects.", - "$ref": "RemoteObject" - } - ] - }, - { - "name": "releaseObject", + { "name": "releaseObject", "description": "Releases remote object with given id.", "parameters": [ - { - "name": "objectId", + { "name": "objectId", "description": "Identifier of the object to release.", - "$ref": "RemoteObjectId" + "$ref": "ID" } ] }, - { - "name": "releaseObjectGroup", + { "name": "releaseObjectGroup", "description": "Releases all remote objects that belong to a given group.", "parameters": [ - { - "name": "objectGroup", + { "name": "objectGroup", "description": "Symbolic object group name.", "type": "string" } ] }, - { - "name": "runIfWaitingForDebugger", - "description": "Tells inspected instance to run if it was waiting for debugger to attach." - }, - { - "name": "runScript", - "description": "Runs script with given id in a given context.", - "parameters": [ - { - "name": "scriptId", - "description": "Id of the script to run.", - "$ref": "ScriptId" - }, - { - "name": "executionContextId", - "description": "Specifies in which execution context to perform script run. If the parameter is omitted the\nevaluation will be performed in the context of the inspected page.", - "optional": true, - "$ref": "ExecutionContextId" - }, - { - "name": "objectGroup", - "description": "Symbolic group name that can be used to release multiple objects.", - "optional": true, - "type": "string" - }, - { - "name": "silent", - "description": "In silent mode exceptions thrown during evaluation are not reported and do not pause\nexecution. Overrides `setPauseOnException` state.", - "optional": true, - "type": "boolean" - }, - { - "name": "includeCommandLineAPI", - "description": "Determines whether Command Line API should be available during the evaluation.", - "optional": true, - "type": "boolean" - }, - { - "name": "returnByValue", - "description": "Whether the result is expected to be a JSON object which should be sent by value.", - "optional": true, - "type": "boolean" - }, - { - "name": "generatePreview", - "description": "Whether preview should be generated for the result.", - "optional": true, - "type": "boolean" - }, - { - "name": "awaitPromise", - "description": "Whether execution should `await` for resulting value and return once awaited promise is\nresolved.", - "optional": true, - "type": "boolean" - } - ], - "returns": [ - { - "name": "result", - "description": "Run result.", - "$ref": "RemoteObject" - }, - { - "name": "exceptionDetails", - "description": "Exception details.", - "optional": true, - "$ref": "ExceptionDetails" - } - ] - }, - { - "name": "setAsyncCallStackDepth", - "description": "Enables or disables async call stacks tracking.", - "redirect": "Debugger", - "parameters": [ - { - "name": "maxDepth", - "description": "Maximum depth of async call stacks. Setting to `0` will effectively disable collecting async\ncall stacks (default).", - "type": "integer" - } - ] - }, - { - "name": "setCustomObjectFormatterEnabled", - "experimental": true, - "parameters": [ - { - "name": "enabled", - "type": "boolean" - } - ] - }, - { - "name": "setMaxCallStackSizeToCapture", - "experimental": true, - "parameters": [ - { - "name": "size", - "type": "integer" - } - ] - }, - { - "name": "terminateExecution", - "description": "Terminate current or next JavaScript execution.\nWill cancel the termination when the outer-most script execution ends.", - "experimental": true - }, - { - "name": "addBinding", - "description": "If executionContextId is empty, adds binding with the given name on the\nglobal objects of all inspected contexts, including those created later,\nbindings survive reloads.\nBinding function takes exactly one argument, this argument should be string,\nin case of any other input, function throws an exception.\nEach binding function call produces Runtime.bindingCalled notification.", - "experimental": true, - "parameters": [ - { - "name": "name", - "type": "string" - }, - { - "name": "executionContextId", - "description": "If specified, the binding would only be exposed to the specified\nexecution context. If omitted and `executionContextName` is not set,\nthe binding is exposed to all execution contexts of the target.\nThis parameter is mutually exclusive with `executionContextName`.\nDeprecated in favor of `executionContextName` due to an unclear use case\nand bugs in implementation (crbug.com/1169639). `executionContextId` will be\nremoved in the future.", - "deprecated": true, - "optional": true, - "$ref": "ExecutionContextId" - }, - { - "name": "executionContextName", - "description": "If specified, the binding is exposed to the executionContext with\nmatching name, even for contexts created after the binding is added.\nSee also `ExecutionContext.name` and `worldName` parameter to\n`Page.addScriptToEvaluateOnNewDocument`.\nThis parameter is mutually exclusive with `executionContextId`.", - "experimental": true, - "optional": true, - "type": "string" - } - ] - }, - { - "name": "removeBinding", - "description": "This method does not remove binding function from global object but\nunsubscribes current runtime agent from Runtime.bindingCalled notifications.", - "experimental": true, - "parameters": [ - { - "name": "name", - "type": "string" - } - ] - }, - { - "name": "getExceptionDetails", - "description": "This method tries to lookup and populate exception details for a\nJavaScript Error object.\nNote that the stackTrace portion of the resulting exceptionDetails will\nonly be populated if the Runtime domain was enabled at the time when the\nError was thrown.", - "experimental": true, - "parameters": [ - { - "name": "errorObjectId", - "description": "The error object for which to resolve the exception details.", - "$ref": "RemoteObjectId" - } - ], - "returns": [ - { - "name": "exceptionDetails", - "optional": true, - "$ref": "ExceptionDetails" - } - ] + { "name": "runIfWaitingForDebugger", + "description": "Tells inspected instance to run if it was waiting for debugger to attach.", + "todo": "// TODO: implement!!" } ], "events": [ - { - "name": "bindingCalled", - "description": "Notification is issued every time when binding is called.", - "experimental": true, - "parameters": [ - { - "name": "name", - "type": "string" - }, - { - "name": "payload", - "type": "string" - }, - { - "name": "executionContextId", - "description": "Identifier of the context where the call was made.", - "$ref": "ExecutionContextId" - } - ] - }, - { - "name": "consoleAPICalled", + { "name": "consoleAPICalled", + "todo":"// TODO: implement!!", "description": "Issued when console API was called.", "parameters": [ - { - "name": "type", + { "name": "type", "description": "Type of the call.", "type": "string", "enum": [ @@ -1845,57 +963,28 @@ "timeEnd" ] }, - { - "name": "args", + { "name": "args", "description": "Call arguments.", "type": "array", - "items": { - "$ref": "RemoteObject" - } + "items": { "$ref": "RemoteObject" } }, - { - "name": "executionContextId", + { "name": "executionContextId", "description": "Identifier of the context where the call was made.", "$ref": "ExecutionContextId" }, - { - "name": "timestamp", + { "name": "timestamp", "description": "Call timestamp.", "$ref": "Timestamp" }, - { - "name": "stackTrace", + { "name": "stackTrace", "description": "Stack trace captured when the call was made. The async stack chain is automatically reported for\nthe following call types: `assert`, `error`, `trace`, `warning`. For other types the async call\nchain can be retrieved using `Debugger.getStackTrace` and `stackTrace.parentId` field.", "optional": true, "$ref": "StackTrace" - }, - { - "name": "context", - "description": "Console context descriptor for calls on non-default console context (not console.*):\n'anonymous#unique-logger-id' for call on unnamed context, 'name#unique-logger-id' for call\non named context.", - "experimental": true, - "optional": true, - "type": "string" } ] }, - { - "name": "exceptionRevoked", - "description": "Issued when unhandled exception was revoked.", - "parameters": [ - { - "name": "reason", - "description": "Reason describing why exception was revoked.", - "type": "string" - }, - { - "name": "exceptionId", - "description": "The id of revoked exception, as reported in `exceptionThrown`.", - "type": "integer" - } - ] - }, - { - "name": "exceptionThrown", + { "name": "exceptionThrown", + "todo":"// TODO: implement!!", "description": "Issued when exception was thrown and unhandled.", "parameters": [ { @@ -1908,98 +997,6 @@ "$ref": "ExceptionDetails" } ] - }, - { - "name": "executionContextCreated", - "description": "Issued when new execution context is created.", - "parameters": [ - { - "name": "context", - "description": "A newly created execution context.", - "$ref": "ExecutionContextDescription" - } - ] - }, - { - "name": "executionContextDestroyed", - "description": "Issued when execution context is destroyed.", - "parameters": [ - { - "name": "executionContextId", - "description": "Id of the destroyed context", - "$ref": "ExecutionContextId" - } - ] - }, - { - "name": "executionContextsCleared", - "description": "Issued when all executionContexts were cleared in browser" - }, - { - "name": "inspectRequested", - "description": "Issued when object should be inspected (for example, as a result of inspect() command line API\ncall).", - "parameters": [ - { - "name": "object", - "$ref": "RemoteObject" - }, - { - "name": "hints", - "type": "object" - }, - { - "name": "executionContextId", - "description": "Identifier of the context where the call was made.", - "experimental": true, - "optional": true, - "$ref": "ExecutionContextId" - } - ] - } - ] - }, - { - "domain": "NodeWorker", - "description": "Implemented partly just because of pesky vscode.", - "deprecated": true, - "commands": [ - { - "name": "enable", - "description": "Used to get the attachedToWorker event", - "parameters": [ ] - } - ], - "events": [ - { - "name": "attachedToWorker", - "description": "Issued when attached to a worker.", - "parameters": [ - { "name": "sessionId", - "description": "Identifier assigned to the session used to send/receive messages.", - "$ref": "string" - }, - { - "name": "workerInfo", - "type": "object", - "properties": [ - { "name": "workerId", - "$ref": "string" - }, - { "name": "type", - "type": "string" - }, - { "name": "title", - "type": "string" - }, - { "name": "url", - "type": "string" - } - ] - }, - { "name": "waitingForDebugger", - "type": "boolean" - } - ] } ] } diff --git a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java index bee5428..7ed5946 100644 --- a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java +++ b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java @@ -31,6 +31,7 @@ import me.topchetoeu.jscript.json.JSONElement; import me.topchetoeu.jscript.json.JSONList; import me.topchetoeu.jscript.json.JSONMap; +// very simple indeed public class SimpleDebugger implements Debugger { public static final String CHROME_GET_PROP_FUNC = "function s(e){let t=this;const n=JSON.parse(e);for(let e=0,i=n.length;e>\",i=e.call(this,r);if(i!==r)return String(i)}catch(r){return`<>${JSON.stringify([String(r),\"object\"])}`}if(typeof this==\"object\"&&this){let r;for(let i of[Symbol.for(\"debug.description\"),Symbol.for(\"nodejs.util.inspect.custom\")])try{r=this[i]();break}catch{}if(!r&&!String(this.toString).includes(\"[native code]\")&&(r=String(this)),r&&!r.startsWith(\"[object \"))return r.length>=t?r.slice(0,t)+\"\\u2026\":r}\n ;\n\n}"; @@ -136,7 +137,7 @@ public class SimpleDebugger implements Debugger { } } - private class RunResult { + private static class RunResult { public final Context ctx; public final Object result; public final EngineException error; @@ -598,10 +599,10 @@ public class SimpleDebugger implements Debugger { if (obj != emptyObject) { for (var key : obj.keys(true)) { var propDesc = new JSONMap(); - + if (obj.properties.containsKey(key)) { var prop = obj.properties.get(key); - + propDesc.set("name", Values.toString(ctx, key)); if (prop.getter != null) propDesc.set("get", serializeObj(ctx, prop.getter)); if (prop.setter != null) propDesc.set("set", serializeObj(ctx, prop.setter)); -- 2.45.2 From 30f5d619c39e725b7ee56220ea8cc000c1f90251 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Tue, 14 Nov 2023 09:22:56 +0200 Subject: [PATCH 07/36] fix: errors now have the right prototype and name --- src/me/topchetoeu/jscript/lib/ErrorLib.java | 3 ++- src/me/topchetoeu/jscript/lib/RangeErrorLib.java | 2 ++ src/me/topchetoeu/jscript/lib/SyntaxErrorLib.java | 2 ++ src/me/topchetoeu/jscript/lib/TypeErrorLib.java | 2 ++ 4 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/me/topchetoeu/jscript/lib/ErrorLib.java b/src/me/topchetoeu/jscript/lib/ErrorLib.java index aeefa63..c681ed4 100644 --- a/src/me/topchetoeu/jscript/lib/ErrorLib.java +++ b/src/me/topchetoeu/jscript/lib/ErrorLib.java @@ -5,6 +5,7 @@ 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.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; @@ -50,8 +51,8 @@ import me.topchetoeu.jscript.interop.NativeInit; var target = new ObjectValue(); if (thisArg instanceof ObjectValue) target = (ObjectValue)thisArg; + target.setPrototype(PlaceholderProto.ERROR); target.defineProperty(ctx, "stack", ArrayValue.of(ctx, ctx.stackTrace())); - target.defineProperty(ctx, "name", "Error"); if (message == null) target.defineProperty(ctx, "message", ""); else target.defineProperty(ctx, "message", Values.toString(ctx, message)); diff --git a/src/me/topchetoeu/jscript/lib/RangeErrorLib.java b/src/me/topchetoeu/jscript/lib/RangeErrorLib.java index 540fec3..3c48d42 100644 --- a/src/me/topchetoeu/jscript/lib/RangeErrorLib.java +++ b/src/me/topchetoeu/jscript/lib/RangeErrorLib.java @@ -3,6 +3,7 @@ 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; @@ -11,6 +12,7 @@ import me.topchetoeu.jscript.interop.NativeInit; @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"); return target; } diff --git a/src/me/topchetoeu/jscript/lib/SyntaxErrorLib.java b/src/me/topchetoeu/jscript/lib/SyntaxErrorLib.java index 2d8df7f..d47feb3 100644 --- a/src/me/topchetoeu/jscript/lib/SyntaxErrorLib.java +++ b/src/me/topchetoeu/jscript/lib/SyntaxErrorLib.java @@ -3,6 +3,7 @@ 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; @@ -11,6 +12,7 @@ import me.topchetoeu.jscript.interop.NativeInit; @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); + target.setPrototype(PlaceholderProto.SYNTAX_ERROR); return target; } @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { diff --git a/src/me/topchetoeu/jscript/lib/TypeErrorLib.java b/src/me/topchetoeu/jscript/lib/TypeErrorLib.java index 906b096..7e4179c 100644 --- a/src/me/topchetoeu/jscript/lib/TypeErrorLib.java +++ b/src/me/topchetoeu/jscript/lib/TypeErrorLib.java @@ -3,6 +3,7 @@ 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; @@ -11,6 +12,7 @@ import me.topchetoeu.jscript.interop.NativeInit; @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); return target; } @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { -- 2.45.2 From 0a4149ba81ad047f1a9e0e2f6df66424bff91639 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Tue, 14 Nov 2023 09:23:15 +0200 Subject: [PATCH 08/36] fix: remove double space in "Uncaught ..." --- src/me/topchetoeu/jscript/Main.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/me/topchetoeu/jscript/Main.java b/src/me/topchetoeu/jscript/Main.java index abe8ba8..6082226 100644 --- a/src/me/topchetoeu/jscript/Main.java +++ b/src/me/topchetoeu/jscript/Main.java @@ -60,7 +60,7 @@ public class Main { System.out.println(); } } - catch (EngineException e) { Values.printError(e, ""); } + catch (EngineException e) { Values.printError(e, null); } } for (var i = 0; ; i++) { try { @@ -75,8 +75,8 @@ public class Main { Values.printValue(null, res); System.out.println(); } - catch (EngineException e) { Values.printError(e, ""); } - catch (SyntaxException e) { Values.printError(e, ""); } + catch (EngineException e) { Values.printError(e, null); } + catch (SyntaxException e) { Values.printError(e, null); } } } catch (IOException e) { -- 2.45.2 From ed08041335430667fa4d24797ff5778715d5bca6 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Tue, 14 Nov 2023 09:24:00 +0200 Subject: [PATCH 09/36] fix: internal error when trying to use key of "undefined" --- .../jscript/engine/values/FunctionValue.java | 12 ++++++------ .../jscript/engine/values/ObjectValue.java | 4 ++-- src/me/topchetoeu/jscript/engine/values/Values.java | 10 +++++----- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/me/topchetoeu/jscript/engine/values/FunctionValue.java b/src/me/topchetoeu/jscript/engine/values/FunctionValue.java index 865a381..1a19925 100644 --- a/src/me/topchetoeu/jscript/engine/values/FunctionValue.java +++ b/src/me/topchetoeu/jscript/engine/values/FunctionValue.java @@ -21,21 +21,21 @@ public abstract class FunctionValue extends ObjectValue { @Override protected Object getField(Context ctx, Object key) { - if (key.equals("name")) return name; - if (key.equals("length")) return length; + if ("name".equals(key)) return name; + if ("length".equals(key)) return length; return super.getField(ctx, key); } @Override protected boolean setField(Context ctx, Object key, Object val) { - if (key.equals("name")) name = Values.toString(ctx, val); - else if (key.equals("length")) length = (int)Values.toNumber(ctx, val); + if ("name".equals(key)) name = Values.toString(ctx, val); + else if ("length".equals(key)) length = (int)Values.toNumber(ctx, val); else return super.setField(ctx, key, val); return true; } @Override protected boolean hasField(Context ctx, Object key) { - if (key.equals("name")) return true; - if (key.equals("length")) return true; + if ("name".equals(key)) return true; + if ("length".equals(key)) return true; return super.hasField(ctx, key); } diff --git a/src/me/topchetoeu/jscript/engine/values/ObjectValue.java b/src/me/topchetoeu/jscript/engine/values/ObjectValue.java index f6b4552..9bbc3ef 100644 --- a/src/me/topchetoeu/jscript/engine/values/ObjectValue.java +++ b/src/me/topchetoeu/jscript/engine/values/ObjectValue.java @@ -262,7 +262,7 @@ public class ObjectValue { values.put(key, val); return true; } - else if (key.equals("__proto__")) return setPrototype(ctx, val); + else if ("__proto__".equals(key)) return setPrototype(ctx, val); else if (nonWritableSet.contains(key)) return false; else return setField(ctx, key, val); } @@ -273,7 +273,7 @@ public class ObjectValue { public final boolean hasMember(Context ctx, Object key, boolean own) { key = Values.normalize(ctx, key); - if (key != null && key.equals("__proto__")) return true; + if (key != null && "__proto__".equals(key)) return true; if (hasField(ctx, key)) return true; if (properties.containsKey(key)) return true; if (own) return false; diff --git a/src/me/topchetoeu/jscript/engine/values/Values.java b/src/me/topchetoeu/jscript/engine/values/Values.java index 7ece5de..314d2f4 100644 --- a/src/me/topchetoeu/jscript/engine/values/Values.java +++ b/src/me/topchetoeu/jscript/engine/values/Values.java @@ -105,7 +105,7 @@ public class Values { } public static boolean toBoolean(Object obj) { if (obj == NULL || obj == null) return false; - if (obj instanceof Number && number(obj) == 0) return false; + if (obj instanceof Number && (number(obj) == 0 || Double.isNaN(number(obj)))) return false; if (obj instanceof String && ((String)obj).equals("")) return false; if (obj instanceof Boolean) return (Boolean)obj; return true; @@ -272,15 +272,15 @@ public class Values { var proto = getPrototype(ctx, obj); - if (proto == null) return key.equals("__proto__") ? NULL : null; - else if (key != null && key.equals("__proto__")) return proto; + if (proto == null) return "__proto__".equals(key) ? NULL : null; + else if (key != null && "__proto__".equals(key)) return proto; else return proto.getMember(ctx, key, obj); } public static boolean setMember(Context ctx, Object obj, Object key, Object val) { obj = normalize(ctx, obj); key = normalize(ctx, key); val = normalize(ctx, val); 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.equals("__proto__")) return setPrototype(ctx, obj, val); + if (key != null && "__proto__".equals(key)) return setPrototype(ctx, obj, val); if (isObject(obj)) return object(obj).setMember(ctx, key, val, false); var proto = getPrototype(ctx, obj); @@ -290,7 +290,7 @@ public class Values { if (obj == null || obj == NULL) return false; obj = normalize(ctx, obj); key = normalize(ctx, key); - if (key.equals("__proto__")) return true; + if ("__proto__".equals(key)) return true; if (isObject(obj)) return object(obj).hasMember(ctx, key, own); if (obj instanceof String && key instanceof Number) { -- 2.45.2 From 488deea164f5494e7fa1cff3e46848ca3e86bbac Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Tue, 14 Nov 2023 09:24:39 +0200 Subject: [PATCH 10/36] fix: improve performance of typescript by caching separate declarations --- src/me/topchetoeu/jscript/engine/Context.java | 5 +- .../jscript/engine/Environment.java | 4 ++ src/me/topchetoeu/jscript/js/bootstrap.js | 55 +++++++++++++------ 3 files changed, 46 insertions(+), 18 deletions(-) diff --git a/src/me/topchetoeu/jscript/engine/Context.java b/src/me/topchetoeu/jscript/engine/Context.java index 1f00eb2..c8246a5 100644 --- a/src/me/topchetoeu/jscript/engine/Context.java +++ b/src/me/topchetoeu/jscript/engine/Context.java @@ -33,7 +33,8 @@ public class Context { } public FunctionValue compile(Filename filename, String raw) { - var transpiled = environment().compile.call(this, null, raw, filename.toString()); + var env = environment(); + var transpiled = env.compile.call(this, null, raw, filename.toString(), env); String source = null; FunctionValue runner = null; @@ -45,7 +46,7 @@ public class Context { else source = Values.toString(this, transpiled); var breakpoints = new TreeSet(); - FunctionValue res = Parsing.compile(Engine.functions, breakpoints, environment(), filename, source); + FunctionValue res = Parsing.compile(Engine.functions, breakpoints, env, filename, source); engine.onSource(filename, source, breakpoints); if (runner != null) res = (FunctionValue)runner.call(this, null, res); diff --git a/src/me/topchetoeu/jscript/engine/Environment.java b/src/me/topchetoeu/jscript/engine/Environment.java index 397ec78..30a77a7 100644 --- a/src/me/topchetoeu/jscript/engine/Environment.java +++ b/src/me/topchetoeu/jscript/engine/Environment.java @@ -22,6 +22,10 @@ public class Environment { public GlobalScope global; public WrappersProvider wrappers; + private static int nextId = 0; + + @Native public int id = ++nextId; + @Native public FunctionValue compile; @Native public FunctionValue regexConstructor = new NativeFunction("RegExp", (ctx, thisArg, args) -> { throw EngineException.ofError("Regular expressions not supported.").setCtx(ctx.environment(), ctx.engine); diff --git a/src/me/topchetoeu/jscript/js/bootstrap.js b/src/me/topchetoeu/jscript/js/bootstrap.js index 74477af..6736a28 100644 --- a/src/me/topchetoeu/jscript/js/bootstrap.js +++ b/src/me/topchetoeu/jscript/js/bootstrap.js @@ -1,7 +1,13 @@ (function (_arguments) { var ts = _arguments[0]; - var src = '', lib = _arguments[2].concat([ 'declare const exit: never; declare const go: any;' ]).join(''), decls = '', version = 0; + var src = '', version = 0; + var lib = _arguments[2].concat([ + 'declare const exit: never; declare const go: any;', + 'declare function getTsDeclarations(): string[];' + ]).join(''); var libSnapshot = ts.ScriptSnapshot.fromString(lib); + var environments = {}; + var declSnapshots = []; var settings = { outDir: "/out", @@ -21,18 +27,27 @@ var service = ts.createLanguageService({ getCurrentDirectory: function() { return "/"; }, getDefaultLibFileName: function() { return "/lib.d.ts"; }, - getScriptFileNames: function() { return [ "/src.ts", "/lib.d.ts", "/glob.d.ts" ]; }, + getScriptFileNames: function() { + var res = [ "/src.ts", "/lib.d.ts" ]; + for (var i = 0; i < declSnapshots.length; i++) res.push("/glob." + (i + 1) + ".d.ts"); + return res; + }, getCompilationSettings: function () { return settings; }, fileExists: function(filename) { return filename === "/lib.d.ts" || filename === "/src.ts" || filename === "/glob.d.ts"; }, getScriptSnapshot: function(filename) { if (filename === "/lib.d.ts") return libSnapshot; if (filename === "/src.ts") return ts.ScriptSnapshot.fromString(src); - if (filename === "/glob.d.ts") return ts.ScriptSnapshot.fromString(decls); + + var index = /\/glob\.(\d+)\.d\.ts/g.exec(filename); + if (index && index[1] && (index = Number(index[1])) && index > 0 && index <= declSnapshots.length) { + return declSnapshots[index - 1]; + } + throw new Error("File '" + filename + "' doesn't exist."); }, getScriptVersion: function (filename) { - if (filename === "/lib.d.ts") return 0; + if (filename === "/lib.d.ts" || filename.startsWith("/glob.")) return 0; else return version; }, }, reg); @@ -40,10 +55,12 @@ service.getEmitOutput("/lib.d.ts"); log("Loaded libraries!"); - function compile(code, filename) { + function compile(code, filename, env) { src = code; version++; + if (!environments[env.id]) environments[env.id] = [] + declSnapshots = environments[env.id]; var emit = service.getEmitOutput("/src.ts"); var diagnostics = [] @@ -58,31 +75,37 @@ if (file === "src.ts") file = filename; return file + ":" + (pos.line + 1) + ":" + (pos.character + 1) + ": " + message; } - else return "Error: " + message; + else return message; }); if (diagnostics.length > 0) { throw new SyntaxError(diagnostics.join("\n")); } - return { - result: emit.outputFiles[0].text, - declaration: emit.outputFiles[1].text - }; - } - - _arguments[1].compile = function (filename, code) { - var res = compile(filename, code); + var result = emit.outputFiles[0].text; + var declaration = emit.outputFiles[1].text; + return { - source: res.result, + source: result, runner: function(func) { return function() { var val = func.apply(this, arguments); - decls += res.declaration; + if (declaration !== '') { + declSnapshots.push(ts.ScriptSnapshot.fromString(declaration)); + } return val; } } + }; + } + + function apply(env) { + env.compile = compile; + env.global.getTsDeclarations = function() { + return environments[env.id]; } } + + apply(_arguments[1]); })(arguments); -- 2.45.2 From 7ecb8bfabbb4ac4e55216269b57b5502ae3f0231 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 25 Nov 2023 14:16:02 +0200 Subject: [PATCH 11/36] feat: implement permissions --- .../jscript/permissions/Permission.java | 242 +++++++----------- .../permissions/PermissionsManager.java | 39 ++- 2 files changed, 133 insertions(+), 148 deletions(-) diff --git a/src/me/topchetoeu/jscript/permissions/Permission.java b/src/me/topchetoeu/jscript/permissions/Permission.java index 0f81dfe..7c8b591 100644 --- a/src/me/topchetoeu/jscript/permissions/Permission.java +++ b/src/me/topchetoeu/jscript/permissions/Permission.java @@ -1,170 +1,124 @@ package me.topchetoeu.jscript.permissions; -import java.util.ArrayList; +import java.util.LinkedList; public class Permission { - public final String[][] segments; + private static class State { + public final int predI, trgI, wildcardI; + public final boolean wildcard; - private boolean matchSeg(String a, String b) { - if (a.contains("**") || b.contains("**")) throw new IllegalArgumentException("A '**' segment may not contain other characters."); - - var segA = a.split("\\*", -1); - var segB = b.split("\\*", -1); - - if (segA.length == 1 || segB.length == 1) { - if (segA.length == 1 && segB.length == 1) return a.equals(b); - else if (segA.length == 1) return matchSeg(b, a); - else { - if (!b.startsWith(segA[0]) || !b.endsWith(segA[segA.length - 1])) return false; - - int end = b.length() - segA[segA.length - 1].length(); - - for (int i = 1, findI = 1; i < segA.length - 1; i++) { - findI = b.indexOf(segA[i], findI); - if (findI < 0 || findI + segA[i].length() > end) return false; - } - - return true; - } + @Override + public String toString() { + return "State [pr=%s;trg=%s;wildN=%s;wild=%s]".formatted(predI, trgI, wildcardI, wildcard); } - String firstA = segA[0], firstB = segB[0]; - String lastA = segA[segA.length - 1], lastB = segB[segB.length - 1]; - - if (!firstA.startsWith(firstB) && !firstB.startsWith(firstA)) return false; - if (!lastA.endsWith(lastB) && !lastB.endsWith(lastA)) return false; - - return true; - } - private boolean matchArrs(String[] a, String[] b, int start, int end) { - if (a.length != end - start) return false; - if (a.length == 0) return true; - if (start >= b.length || end > b.length) return false; - if (start < 0 || end <= 0) return false; - - for (var i = start; i < end; i++) { - if (!matchSeg(a[i - start], b[i])) return false; + public State(int predicateI, int targetI, int wildcardI, boolean wildcard) { + this.predI = predicateI; + this.trgI = targetI; + this.wildcardI = wildcardI; + this.wildcard = wildcard; } - - return true; - } - private boolean matchFull(String[] a, String[] b) { - return matchArrs(a, b, 0, b.length); - } - private boolean matchStart(String[] a, String[] b) { - return matchArrs(a, b, 0, a.length); - } - private boolean matchEnd(String[] a, String[] b) { - return matchArrs(a, b, b.length - a.length, b.length); } - private int find(String[] query, String[] target, int start, int end) { - var findI = 0; + public final String namespace; + public final String value; - if (start < 0) start = 0; - if (query.length == 0) return start; - if (start != 0 && start >= target.length) return -1; - - for (var i = start; i < end; i++) { - if (findI == query.length) return i - findI; - else if (matchSeg(query[findI], target[i])) findI++; - else { - i -= findI; - findI = 0; - } - } - - return -1; + public boolean match(Permission perm) { + if (!Permission.match(namespace, perm.namespace, '.')) return false; + if (value == null || perm.value == null) return true; + return Permission.match(value, perm.value); + } + public boolean match(Permission perm, char delim) { + if (!Permission.match(namespace, perm.namespace, '.')) return false; + if (value == null || perm.value == null) return true; + return Permission.match(value, perm.value, delim); } - public boolean match(Permission other) { - var an = this.segments.length; - var bn = other.segments.length; - - // We must have at least one segment, even if empty - if (an == 0 || bn == 0) throw new IllegalArgumentException("Can't have a permission with 0 segments."); - - if (an == 1 || bn == 1) { - // If both perms are one segment, we just match the segments themselves - if (an == 1 && bn == 1) return matchFull(this.segments[0], other.segments[0]); - else if (an == 1) return other.match(this); - else { - // If just the other perm is one segment, we neet to find all - // the segments of this perm sequentially in the other segment. - - var seg = other.segments[0]; - // Here we check for the prefix and suffix - if (!matchStart(this.segments[0], seg)) return false; - if (!matchEnd(this.segments[this.segments.length - 1], seg)) return false; - - int end = seg.length - this.segments[this.segments.length - 1].length; - - // Here we go and look for the segments one by one, until one isn't found - for (int i = 1, findI = 1; i < this.segments.length - 1; i++) { - findI = find(this.segments[i], seg, findI, end); - if (findI < 0) return false; - } - - return true; - } - } - - // If both perms have more than one segment (a.k.a both have **), - // we can ignore everything in the middle, as it will always match. - // Instead, we check if the prefixes and suffixes match - - var firstA = this.segments[0]; - var firstB = other.segments[0]; - - var lastA = this.segments[this.segments.length - 1]; - var lastB = other.segments[other.segments.length - 1]; - - if (!matchStart(firstA, firstB) && !matchStart(firstB, firstA)) return false; - if (!matchEnd(lastA, lastB) && !matchEnd(lastB, lastA)) return false; - - return true; + public boolean match(String perm) { + return match(new Permission(perm)); + } + public boolean match(String perm, char delim) { + return match(new Permission(perm), delim); } @Override public String toString() { - var sb = new StringBuilder(); - var firstSeg = true; - var firstEl = true; - - for (var seg : segments) { - if (!firstSeg) { - if (!firstEl) sb.append("."); - sb.append("**"); - } - firstSeg = false; - for (var el : seg) { - if (!firstEl) sb.append("."); - sb.append(el); - firstEl = false; - } - } - - return sb.toString(); + if (value != null) return namespace + ":" + value; + else return namespace; } public Permission(String raw) { - var segs = raw.split("\\."); - var curr = new ArrayList(); - var res = new ArrayList(); + var i = raw.indexOf(':'); - for (var seg : segs) { - if (seg.equals("**")) { - res.add(curr.toArray(String[]::new)); - curr.clear(); - } - else curr.add(seg); + if (i > 0) { + value = raw.substring(i + 1); + namespace = raw.substring(0, i); + } + else { + value = null; + namespace = raw; } - res.add(curr.toArray(String[]::new)); - - segments = res.toArray(String[][]::new); } - public static boolean match(String a, String b) { - return new Permission(a).match(new Permission(b)); + public static boolean match(String predicate, String target, char delim) { + if (predicate.equals("")) return target.equals(""); + + var queue = new LinkedList(); + queue.push(new State(0, 0, 0, false)); + + while (!queue.isEmpty()) { + var state = queue.poll(); + var predEnd = state.predI >= predicate.length(); + + if (state.trgI >= target.length()) return predEnd; + var predC = predEnd ? 0 : predicate.charAt(state.predI); + var trgC = target.charAt(state.trgI); + + if (state.wildcard) { + if (state.wildcardI == 2 || trgC != delim) { + queue.add(new State(state.predI, state.trgI + 1, state.wildcardI, true)); + } + queue.add(new State(state.predI, state.trgI, 0, false)); + } + else if (predC == '*') { + queue.add(new State(state.predI + 1, state.trgI, state.wildcardI + 1, false)); + } + else if (state.wildcardI > 0) { + if (state.wildcardI > 2) throw new IllegalArgumentException("Too many sequential stars."); + queue.add(new State(state.predI, state.trgI, state.wildcardI, true)); + } + else if (!predEnd && (predC == '?' || predC == trgC)) { + queue.add(new State(state.predI + 1, state.trgI + 1, 0, false)); + } + } + + return false; + } + public static boolean match(String predicate, String target) { + if (predicate.equals("")) return target.equals(""); + + var queue = new LinkedList(); + queue.push(new State(0, 0, 0, false)); + + while (!queue.isEmpty()) { + var state = queue.poll(); + + if (state.predI >= predicate.length() || state.trgI >= target.length()) { + return state.predI >= predicate.length() && state.trgI >= target.length(); + } + + var predC = predicate.charAt(state.predI); + var trgC = target.charAt(state.trgI); + + if (predC == '*') { + queue.add(new State(state.predI, state.trgI + 1, state.wildcardI, true)); + queue.add(new State(state.predI + 1, state.trgI, 0, false)); + } + else if (predC == '?' || predC == trgC) { + queue.add(new State(state.predI + 1, state.trgI + 1, 0, false)); + } + } + + return false; } } diff --git a/src/me/topchetoeu/jscript/permissions/PermissionsManager.java b/src/me/topchetoeu/jscript/permissions/PermissionsManager.java index 0653795..1505fb8 100644 --- a/src/me/topchetoeu/jscript/permissions/PermissionsManager.java +++ b/src/me/topchetoeu/jscript/permissions/PermissionsManager.java @@ -1,8 +1,39 @@ package me.topchetoeu.jscript.permissions; -public interface PermissionsManager { - public static final PermissionsManager ALL_PERMS = perm -> true; - public static final PermissionsManager NO_PERMS = perm -> false; +import java.util.ArrayList; - boolean hasPermissions(String perm); +public class PermissionsManager { + public static final PermissionsManager ALL_PERMS = new PermissionsManager().add(new Permission("**")); + + public final ArrayList allowed = new ArrayList<>(); + public final ArrayList denied = new ArrayList<>(); + + public PermissionsManager add(Permission perm) { + allowed.add(perm); + return this; + } + public PermissionsManager add(String perm) { + allowed.add(new Permission(perm)); + return this; + } + + public boolean has(Permission perm, char delim) { + for (var el : denied) if (el.match(perm, delim)) return false; + for (var el : allowed) if (el.match(perm, delim)) return true; + + return false; + } + public boolean has(Permission perm) { + for (var el : denied) if (el.match(perm)) return false; + for (var el : allowed) if (el.match(perm)) return true; + + return false; + } + + public boolean has(String perm, char delim) { + return has(new Permission(perm), delim); + } + public boolean has(String perm) { + return has(new Permission(perm)); + } } -- 2.45.2 From 3e2506821981a217a43001c6683026d6dd10c19d Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 25 Nov 2023 14:17:08 +0200 Subject: [PATCH 12/36] feat: implement bulk of fs --- .../jscript/filesystem/EntryType.java | 12 ++- .../topchetoeu/jscript/filesystem/File.java | 44 +++++---- .../jscript/filesystem/FileStat.java | 11 +++ .../jscript/filesystem/Filesystem.java | 11 +-- .../filesystem/FilesystemException.java | 55 +++++++++++ .../jscript/filesystem/InaccessibleFile.java | 35 ------- .../topchetoeu/jscript/filesystem/Mode.java | 31 +++++++ .../jscript/filesystem/Permissions.java | 17 ---- .../jscript/filesystem/PhysicalFile.java | 68 ++++++++------ .../filesystem/PhysicalFilesystem.java | 91 ++++++++++--------- 10 files changed, 224 insertions(+), 151 deletions(-) create mode 100644 src/me/topchetoeu/jscript/filesystem/FileStat.java create mode 100644 src/me/topchetoeu/jscript/filesystem/FilesystemException.java delete mode 100644 src/me/topchetoeu/jscript/filesystem/InaccessibleFile.java create mode 100644 src/me/topchetoeu/jscript/filesystem/Mode.java delete mode 100644 src/me/topchetoeu/jscript/filesystem/Permissions.java diff --git a/src/me/topchetoeu/jscript/filesystem/EntryType.java b/src/me/topchetoeu/jscript/filesystem/EntryType.java index 26e255a..c22a8df 100644 --- a/src/me/topchetoeu/jscript/filesystem/EntryType.java +++ b/src/me/topchetoeu/jscript/filesystem/EntryType.java @@ -1,7 +1,13 @@ package me.topchetoeu.jscript.filesystem; public enum EntryType { - NONE, - FILE, - FOLDER, + NONE("none"), + FILE("file"), + FOLDER("folder"); + + public final String name; + + private EntryType(String name) { + this.name = name; + } } \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/filesystem/File.java b/src/me/topchetoeu/jscript/filesystem/File.java index b7a14a8..487b5dc 100644 --- a/src/me/topchetoeu/jscript/filesystem/File.java +++ b/src/me/topchetoeu/jscript/filesystem/File.java @@ -1,27 +1,39 @@ package me.topchetoeu.jscript.filesystem; -import java.io.IOException; - public interface File { - int read() throws IOException, InterruptedException; - boolean write(byte val) throws IOException, InterruptedException; - long tell() throws IOException, InterruptedException; - void seek(long offset, int pos) throws IOException, InterruptedException; - void close() throws IOException, InterruptedException; - Permissions perms(); + int read(byte[] buff); + void write(byte[] buff); + long getPtr(); + void setPtr(long offset, int pos); + void close(); + Mode mode(); - default String readToString() throws IOException, InterruptedException { - seek(0, 2); - long len = tell(); + default String readToString() { + setPtr(0, 2); + long len = getPtr(); if (len < 0) return null; - seek(0, 0); - byte[] res = new byte[(int)len]; + setPtr(0, 0); - for (var i = 0; i < len; i++) { - res[i] = (byte)read(); - } + byte[] res = new byte[(int)len]; + read(res); return new String(res); } + default String readLine() { + var res = new Buffer(new byte[0]); + var buff = new byte[1]; + + while (true) { + if (read(buff) == 0) { + if (res.length() == 0) return null; + else break; + } + + if (buff[0] == '\n') break; + + res.write(res.length(), buff); + } + return new String(res.data()); + } } \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/filesystem/FileStat.java b/src/me/topchetoeu/jscript/filesystem/FileStat.java new file mode 100644 index 0000000..8140d4a --- /dev/null +++ b/src/me/topchetoeu/jscript/filesystem/FileStat.java @@ -0,0 +1,11 @@ +package me.topchetoeu.jscript.filesystem; + +public class FileStat { + public final Mode mode; + public final EntryType type; + + public FileStat(Mode mode, EntryType type) { + this.mode = mode; + this.type = type; + } +} \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/filesystem/Filesystem.java b/src/me/topchetoeu/jscript/filesystem/Filesystem.java index 02ae9d5..0cfea46 100644 --- a/src/me/topchetoeu/jscript/filesystem/Filesystem.java +++ b/src/me/topchetoeu/jscript/filesystem/Filesystem.java @@ -1,10 +1,7 @@ package me.topchetoeu.jscript.filesystem; -import java.io.IOException; - public interface Filesystem { - File open(String path) throws IOException, InterruptedException; - boolean mkdir(String path) throws IOException, InterruptedException; - EntryType type(String path) throws IOException, InterruptedException; - boolean rm(String path) throws IOException, InterruptedException; -} + File open(String path, Mode mode) throws FilesystemException; + void create(String path, EntryType type) throws FilesystemException; + FileStat stat(String path) throws FilesystemException; +} \ No newline at end of file diff --git a/src/me/topchetoeu/jscript/filesystem/FilesystemException.java b/src/me/topchetoeu/jscript/filesystem/FilesystemException.java new file mode 100644 index 0000000..63745e3 --- /dev/null +++ b/src/me/topchetoeu/jscript/filesystem/FilesystemException.java @@ -0,0 +1,55 @@ +package me.topchetoeu.jscript.filesystem; + +import me.topchetoeu.jscript.engine.values.Values; +import me.topchetoeu.jscript.exceptions.EngineException; + +public class FilesystemException extends RuntimeException { + public static enum FSCode { + DOESNT_EXIST(0x1), + NOT_FILE(0x2), + NOT_FOLDER(0x3), + NO_PERMISSIONS_R(0x4), + NO_PERMISSIONS_RW(0x5), + FOLDER_NOT_EMPTY(0x6), + ALREADY_EXISTS(0x7), + FOLDER_EXISTS(0x8); + + public final int code; + + private FSCode(int code) { this.code = code; } + } + + public static final String[] MESSAGES = { + "How did we get here?", + "The file or folder '%s' doesn't exist or is inaccessible.", + "'%s' is not a file", + "'%s' is not a folder", + "No permissions to read '%s'", + "No permissions to write '%s'", + "Can't delete '%s', since it is a full folder.", + "'%s' already exists." + }; + + public final String message, filename; + public final FSCode code; + + public FilesystemException(String message, String filename, FSCode code) { + super(code + ": " + message.formatted(filename)); + this.message = message; + this.code = code; + this.filename = filename; + } + public FilesystemException(String filename, FSCode code) { + super(code + ": " + MESSAGES[code.code].formatted(filename)); + this.message = MESSAGES[code.code]; + this.code = code; + this.filename = filename; + } + + public EngineException toEngineException() { + var res = EngineException.ofError("IOError", getMessage()); + Values.setMember(null, res.value, "code", code); + Values.setMember(null, res.value, "filename", filename.toString()); + return res; + } +} diff --git a/src/me/topchetoeu/jscript/filesystem/InaccessibleFile.java b/src/me/topchetoeu/jscript/filesystem/InaccessibleFile.java deleted file mode 100644 index 2cc59b4..0000000 --- a/src/me/topchetoeu/jscript/filesystem/InaccessibleFile.java +++ /dev/null @@ -1,35 +0,0 @@ -package me.topchetoeu.jscript.filesystem; - -import java.io.IOException; - -public class InaccessibleFile implements File { - public static final InaccessibleFile INSTANCE = new InaccessibleFile(); - - @Override - public int read() throws IOException, InterruptedException { - return -1; - } - - @Override - public boolean write(byte val) throws IOException, InterruptedException { - return false; - } - - @Override - public long tell() throws IOException, InterruptedException { - return 0; - } - - @Override - public void seek(long offset, int pos) throws IOException, InterruptedException { } - - @Override - public void close() throws IOException, InterruptedException { } - - @Override - public Permissions perms() { - return Permissions.NONE; - } - - private InaccessibleFile() { } -} diff --git a/src/me/topchetoeu/jscript/filesystem/Mode.java b/src/me/topchetoeu/jscript/filesystem/Mode.java new file mode 100644 index 0000000..617e77b --- /dev/null +++ b/src/me/topchetoeu/jscript/filesystem/Mode.java @@ -0,0 +1,31 @@ +package me.topchetoeu.jscript.filesystem; + +public enum Mode { + NONE("", false, false), + READ("r", true, false), + READ_WRITE("rw", true, true); + + public final String name; + public final boolean readable; + public final boolean writable; + + public Mode intersect(Mode other) { + if (this == NONE || other == NONE) return NONE; + if (this == READ_WRITE && other == READ_WRITE) return READ_WRITE; + return READ; + } + + private Mode(String mode, boolean r, boolean w) { + this.name = mode; + this.readable = r; + this.writable = w; + } + + public static Mode parse(String mode) { + switch (mode) { + case "r": return READ; + case "rw": return READ_WRITE; + default: return NONE; + } + } +} diff --git a/src/me/topchetoeu/jscript/filesystem/Permissions.java b/src/me/topchetoeu/jscript/filesystem/Permissions.java deleted file mode 100644 index 5a5ff34..0000000 --- a/src/me/topchetoeu/jscript/filesystem/Permissions.java +++ /dev/null @@ -1,17 +0,0 @@ -package me.topchetoeu.jscript.filesystem; - -public enum Permissions { - NONE("", false, false), - READ("r", true, false), - READ_WRITE("rw", true, true); - - public final String readMode; - public final boolean readable; - public final boolean writable; - - private Permissions(String mode, boolean r, boolean w) { - this.readMode = mode; - this.readable = r; - this.writable = w; - } -} diff --git a/src/me/topchetoeu/jscript/filesystem/PhysicalFile.java b/src/me/topchetoeu/jscript/filesystem/PhysicalFile.java index cb5d067..5fba05e 100644 --- a/src/me/topchetoeu/jscript/filesystem/PhysicalFile.java +++ b/src/me/topchetoeu/jscript/filesystem/PhysicalFile.java @@ -1,50 +1,62 @@ package me.topchetoeu.jscript.filesystem; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; +import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode; + public class PhysicalFile implements File { + private String filename; private RandomAccessFile file; - private Permissions perms; + private Mode perms; @Override - public int read() throws IOException, InterruptedException { - if (file == null || !perms.readable) return -1; - else return file.read(); + public int read(byte[] buff) { + if (file == null || !perms.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); + else try { return file.read(buff); } + catch (IOException e) { throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); } + } + @Override + public void write(byte[] buff) { + if (file == null || !perms.writable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW); + else try { file.write(buff); } + catch (IOException e) { throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW); } } @Override - public boolean write(byte val) throws IOException, InterruptedException { - if (file == null || !perms.writable) return false; - file.write(val); - return true; + public long getPtr() { + if (file == null || !perms.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); + else try { return file.getFilePointer(); } + catch (IOException e) { throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); } + } + @Override + public void setPtr(long offset, int pos) { + if (file == null || !perms.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); + + try { + if (pos == 1) pos += file.getFilePointer(); + else if (pos == 2) pos += file.length(); + file.seek(pos); + } + catch (IOException e) { throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); } } @Override - public long tell() throws IOException, InterruptedException { - if (file == null) return 0; - return file.getFilePointer(); - } - @Override - public void seek(long offset, int pos) throws IOException, InterruptedException { + public void close() { if (file == null) return; - if (pos == 0) file.seek(pos); - else if (pos == 1) file.seek(file.getFilePointer() + pos); - else if (pos == 2) file.seek(file.length() + pos); + try { file.close(); } + catch (IOException e) {} // SHUT + file = null; + perms = Mode.NONE; } - @Override - public void close() throws IOException, InterruptedException { - if (file == null) return; - file.close(); - } + public Mode mode() { return perms; } - @Override - public Permissions perms() { return perms; } - - public PhysicalFile(String path, Permissions mode) throws IOException { - if (mode == Permissions.NONE) file = null; - else file = new RandomAccessFile(path, mode.readMode); + public PhysicalFile(String path, Mode mode) throws FileNotFoundException { + if (mode == Mode.NONE) file = null; + else try { file = new RandomAccessFile(path, mode.name); } + catch (FileNotFoundException e) { throw new FilesystemException(filename, FSCode.DOESNT_EXIST); } perms = mode; } diff --git a/src/me/topchetoeu/jscript/filesystem/PhysicalFilesystem.java b/src/me/topchetoeu/jscript/filesystem/PhysicalFilesystem.java index 0241676..a694520 100644 --- a/src/me/topchetoeu/jscript/filesystem/PhysicalFilesystem.java +++ b/src/me/topchetoeu/jscript/filesystem/PhysicalFilesystem.java @@ -1,74 +1,75 @@ package me.topchetoeu.jscript.filesystem; +import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.Path; +import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode; + public class PhysicalFilesystem implements Filesystem { public final Path root; - private Permissions getPerms(Path path) { - var file = path.toFile(); - if (!path.startsWith(root)) return Permissions.NONE; - if (file.canRead() && file.canWrite()) return Permissions.READ_WRITE; - if (file.canRead()) return Permissions.READ; - - return Permissions.NONE; - } private Path getPath(String name) { - return root.resolve(name); + return root.resolve(name.replace("\\", "/")).normalize(); + } + + private void checkMode(Path path, Mode mode) { + if (!path.startsWith(root)) throw new FilesystemException(path.toString(), FSCode.NO_PERMISSIONS_R); + if (mode.readable && !path.toFile().canRead()) throw new FilesystemException(path.toString(), FSCode.NO_PERMISSIONS_R); + if (mode.writable && !path.toFile().canWrite()) throw new FilesystemException(path.toString(), FSCode.NO_PERMISSIONS_RW); } @Override - public File open(String path) throws IOException, InterruptedException { - var _path = root.resolve(path); - - var perms = getPerms(_path); - if (perms == Permissions.NONE) return InaccessibleFile.INSTANCE; - + public File open(String path, Mode perms) { + var _path = getPath(path); var f = _path.toFile(); - if (f.isDirectory()) { - var res = new StringBuilder(); + checkMode(_path, perms); - for (var child : f.listFiles()) res.append(child.toString()).append('\n'); - return new MemoryFile(res.toString().getBytes(), Permissions.READ); + if (f.isDirectory()) return MemoryFile.fromFileList(path, f.listFiles()); + else try { return new PhysicalFile(path, perms); } + catch (FileNotFoundException e) { throw new FilesystemException(_path.toString(), FSCode.DOESNT_EXIST); } + } + + @Override + public void create(String path, EntryType type) { + var _path = getPath(path); + var f = _path.toFile(); + + checkMode(_path, Mode.READ_WRITE); + switch (type) { + case FILE: + try { + if (!f.createNewFile()) throw new FilesystemException(_path.toString(), FSCode.ALREADY_EXISTS); + else break; + } + catch (IOException e) { throw new FilesystemException(_path.toString(), FSCode.NO_PERMISSIONS_RW); } + case FOLDER: + if (!f.mkdir()) throw new FilesystemException(_path.toString(), FSCode.ALREADY_EXISTS); + else break; + case NONE: + default: + if (!f.delete()) throw new FilesystemException(_path.toString(), FSCode.DOESNT_EXIST); + else break; } - else return new PhysicalFile(path, perms); } @Override - public boolean mkdir(String path) throws IOException, InterruptedException { + public FileStat stat(String path) { var _path = getPath(path); - var perms = getPerms(_path); var f = _path.toFile(); - if (!perms.writable) return false; - else return f.mkdir(); - } + if (f.exists()) throw new FilesystemException(_path.toString(), FSCode.DOESNT_EXIST); + checkMode(_path, Mode.READ); - @Override - public EntryType type(String path) throws IOException, InterruptedException { - var _path = getPath(path); - var perms = getPerms(_path); - var f = _path.toFile(); - - if (perms == Permissions.NONE) return EntryType.NONE; - else if (f.isFile()) return EntryType.FILE; - else return EntryType.FOLDER; - } - - @Override - public boolean rm(String path) throws IOException, InterruptedException { - var _path = getPath(path); - var perms = getPerms(_path); - var f = _path.toFile(); - - if (!perms.writable) return false; - else return f.delete(); + return new FileStat( + f.canWrite() ? Mode.READ_WRITE : Mode.READ, + f.isFile() ? EntryType.FILE : EntryType.FOLDER + ); } public PhysicalFilesystem(Path root) { - this.root = root; + this.root = root.toAbsolutePath().normalize(); } } -- 2.45.2 From 55e3d46bc2181be957fc50b0f641d8de66098a0c Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 25 Nov 2023 14:18:04 +0200 Subject: [PATCH 13/36] feat: implement memory and root fs --- .../topchetoeu/jscript/filesystem/Buffer.java | 41 +++++++++ .../jscript/filesystem/MemoryFile.java | 61 ++++++++----- .../jscript/filesystem/MemoryFilesystem.java | 89 +++++++++++++++++++ .../jscript/filesystem/RootFilesystem.java | 60 +++++++++++++ 4 files changed, 227 insertions(+), 24 deletions(-) create mode 100644 src/me/topchetoeu/jscript/filesystem/Buffer.java create mode 100644 src/me/topchetoeu/jscript/filesystem/MemoryFilesystem.java create mode 100644 src/me/topchetoeu/jscript/filesystem/RootFilesystem.java diff --git a/src/me/topchetoeu/jscript/filesystem/Buffer.java b/src/me/topchetoeu/jscript/filesystem/Buffer.java new file mode 100644 index 0000000..3a77157 --- /dev/null +++ b/src/me/topchetoeu/jscript/filesystem/Buffer.java @@ -0,0 +1,41 @@ +package me.topchetoeu.jscript.filesystem; + +public class Buffer { + private byte[] data; + private int length; + + public void write(int i, byte[] val) { + if (i + val.length > data.length) { + var newCap = i + val.length + 1; + if (newCap < data.length * 2) newCap = data.length * 2; + + var tmp = new byte[newCap]; + System.arraycopy(this.data, 0, tmp, 0, length); + this.data = tmp; + } + + System.arraycopy(val, 0, data, i, val.length); + if (i + val.length > length) length = i + val.length; + } + public int read(int i, byte[] buff) { + int n = buff.length; + if (i + n > length) n = length - i; + System.arraycopy(data, i, buff, 0, n); + return n; + } + + public byte[] data() { + var res = new byte[length]; + System.arraycopy(this.data, 0, res, 0, length); + return res; + } + public int length() { + return length; + } + + public Buffer(byte[] data) { + this.data = new byte[data.length]; + this.length = data.length; + System.arraycopy(data, 0, this.data, 0, data.length); + } +} diff --git a/src/me/topchetoeu/jscript/filesystem/MemoryFile.java b/src/me/topchetoeu/jscript/filesystem/MemoryFile.java index c414a7e..0ca8425 100644 --- a/src/me/topchetoeu/jscript/filesystem/MemoryFile.java +++ b/src/me/topchetoeu/jscript/filesystem/MemoryFile.java @@ -1,53 +1,66 @@ package me.topchetoeu.jscript.filesystem; -import java.io.IOException; +import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode; public class MemoryFile implements File { private int ptr; - private Permissions mode; - public final byte[] data; + private Mode mode; + private Buffer data; + private String filename; + + public Buffer data() { return data; } @Override - public int read() throws IOException, InterruptedException { - if (data == null || !mode.readable || ptr >= data.length) return -1; - return data[ptr++]; + public int read(byte[] buff) { + if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); + var res = data.read(ptr, buff); + ptr += res; + return res; + } + @Override + public void write(byte[] buff) { + if (data == null || !mode.writable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW); + + data.write(ptr, buff); + ptr += buff.length; } @Override - public boolean write(byte val) throws IOException, InterruptedException { - if (data == null || !mode.writable || ptr >= data.length) return false; - data[ptr++] = val; - return true; - } - - @Override - public long tell() throws IOException, InterruptedException { + public long getPtr() { + if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); return ptr; } - @Override - public void seek(long offset, int pos) throws IOException, InterruptedException { - if (data == null) return; + public void setPtr(long offset, int pos) { + if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); if (pos == 0) ptr = (int)offset; else if (pos == 1) ptr += (int)offset; - else if (pos == 2) ptr = data.length - (int)offset; + else if (pos == 2) ptr = data.length() - (int)offset; } @Override - public void close() throws IOException, InterruptedException { - mode = null; + public void close() { + mode = Mode.NONE; ptr = 0; } - @Override - public Permissions perms() { - if (data == null) return Permissions.NONE; + public Mode mode() { + if (data == null) return Mode.NONE; return mode; } - public MemoryFile(byte[] buff, Permissions mode) { + public MemoryFile(String filename, Buffer buff, Mode mode) { + this.filename = filename; this.data = buff; this.mode = mode; } + + public static MemoryFile fromFileList(String filename, java.io.File[] list) { + var res = new StringBuilder(); + + for (var el : list) res.append(el.getName()).append('\n'); + + return new MemoryFile(filename, new Buffer(res.toString().getBytes()), Mode.READ); + } } diff --git a/src/me/topchetoeu/jscript/filesystem/MemoryFilesystem.java b/src/me/topchetoeu/jscript/filesystem/MemoryFilesystem.java new file mode 100644 index 0000000..7db85db --- /dev/null +++ b/src/me/topchetoeu/jscript/filesystem/MemoryFilesystem.java @@ -0,0 +1,89 @@ +package me.topchetoeu.jscript.filesystem; + +import java.nio.file.Path; +import java.util.HashMap; +import java.util.HashSet; + +import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode; + +public class MemoryFilesystem implements Filesystem { + public final Mode mode; + private HashMap files = new HashMap<>(); + private HashSet folders = new HashSet<>(); + + private Path getPath(String name) { + return Path.of("/" + name.replace("\\", "/")).normalize(); + } + + @Override + public void create(String path, EntryType type) { + var _path = getPath(path); + + switch (type) { + case FILE: + if (!folders.contains(_path.getParent())) throw new FilesystemException(path, FSCode.DOESNT_EXIST); + if (folders.contains(_path) || files.containsKey(_path)) throw new FilesystemException(path, FSCode.ALREADY_EXISTS); + if (folders.contains(_path)) throw new FilesystemException(path, FSCode.ALREADY_EXISTS); + files.put(_path, new Buffer(new byte[0])); + break; + case FOLDER: + if (!folders.contains(_path.getParent())) throw new FilesystemException(path, FSCode.DOESNT_EXIST); + if (folders.contains(_path) || files.containsKey(_path)) throw new FilesystemException(path, FSCode.ALREADY_EXISTS); + folders.add(_path); + break; + default: + case NONE: + if (!folders.remove(_path) && files.remove(_path) == null) throw new FilesystemException(path, FSCode.DOESNT_EXIST); + } + } + + @Override + public File open(String path, Mode perms) { + var _path = getPath(path); + var pcount = _path.getNameCount(); + + if (files.containsKey(_path)) return new MemoryFile(path, files.get(_path), perms); + else if (folders.contains(_path)) { + var res = new StringBuilder(); + for (var folder : folders) { + if (pcount + 1 != folder.getNameCount()) continue; + if (!folder.startsWith(_path)) continue; + res.append(folder.toFile().getName()).append('\n'); + } + for (var file : files.keySet()) { + if (pcount + 1 != file.getNameCount()) continue; + if (!file.startsWith(_path)) continue; + res.append(file.toFile().getName()).append('\n'); + } + return new MemoryFile(path, new Buffer(res.toString().getBytes()), perms.intersect(Mode.READ)); + } + else throw new FilesystemException(path, FSCode.DOESNT_EXIST); + } + + @Override + public FileStat stat(String path) { + var _path = getPath(path); + + if (files.containsKey(_path)) return new FileStat(mode, EntryType.FILE); + else if (folders.contains(_path)) return new FileStat(mode, EntryType.FOLDER); + else throw new FilesystemException(path, FSCode.DOESNT_EXIST); + } + + public MemoryFilesystem put(String path, byte[] data) { + var _path = getPath(path); + var _curr = "/"; + + for (var seg : _path) { + create(_curr, EntryType.FOLDER); + _curr += seg + "/"; + } + + files.put(_path, new Buffer(data)); + return this; + } + + public MemoryFilesystem(Mode mode) { + this.mode = mode; + folders.add(Path.of("/")); + } +} diff --git a/src/me/topchetoeu/jscript/filesystem/RootFilesystem.java b/src/me/topchetoeu/jscript/filesystem/RootFilesystem.java new file mode 100644 index 0000000..ba9296a --- /dev/null +++ b/src/me/topchetoeu/jscript/filesystem/RootFilesystem.java @@ -0,0 +1,60 @@ +package me.topchetoeu.jscript.filesystem; + +import java.util.HashMap; +import java.util.Map; + +import me.topchetoeu.jscript.Filename; +import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode; +import me.topchetoeu.jscript.permissions.PermissionsManager; + +public class RootFilesystem implements Filesystem { + public final Map protocols = new HashMap<>(); + public final PermissionsManager perms; + + private boolean canRead(PermissionsManager perms, String _path) { + return perms.has("jscript.file.read:" + _path, '/'); + } + private boolean canWrite(PermissionsManager perms, String _path) { + return perms.has("jscript.file.write:" + _path, '/'); + } + + private void modeAllowed(String _path, Mode mode) throws FilesystemException { + if (mode.readable && perms != null && !canRead(perms, _path)) throw new FilesystemException(_path, FSCode.NO_PERMISSIONS_R); + if (mode.writable && perms != null && !canWrite(perms, _path)) throw new FilesystemException(_path, FSCode.NO_PERMISSIONS_RW); + } + + @Override + public File open(String path, Mode perms) throws FilesystemException { + var filename = Filename.parse(path); + var protocol = protocols.get(filename.protocol); + if (protocol == null) throw new FilesystemException(filename.toString(), FSCode.DOESNT_EXIST); + modeAllowed(filename.toString(), perms); + + try { return protocol.open(filename.path, perms); } + catch (FilesystemException e) { throw new FilesystemException(filename.toString(), e.code); } + } + @Override + public void create(String path, EntryType type) throws FilesystemException { + var filename = Filename.parse(path); + var protocol = protocols.get(filename.protocol); + if (protocol == null) throw new FilesystemException(filename.toString(), FSCode.DOESNT_EXIST); + modeAllowed(filename.toString(), Mode.READ_WRITE); + + try { protocol.create(filename.path, type); } + catch (FilesystemException e) { throw new FilesystemException(filename.toString(), e.code); } + } + @Override + public FileStat stat(String path) throws FilesystemException { + var filename = Filename.parse(path); + var protocol = protocols.get(filename.protocol); + if (protocol == null) throw new FilesystemException(filename.toString(), FSCode.DOESNT_EXIST); + modeAllowed(filename.toString(), Mode.READ); + + try { return protocol.stat(path); } + catch (FilesystemException e) { throw new FilesystemException(filename.toString(), e.code); } + } + + public RootFilesystem(PermissionsManager perms) { + this.perms = perms; + } +} -- 2.45.2 From 987f8b8f0045c7d924672d7514cb61cd08e6e827 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 25 Nov 2023 14:18:46 +0200 Subject: [PATCH 14/36] fix: handle native errors properly-ish --- .../interop/NativeWrapperProvider.java | 37 ++++++++++++++----- .../jscript/interop/OverloadFunction.java | 10 ++++- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java b/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java index 11f7a61..6edc93f 100644 --- a/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java +++ b/src/me/topchetoeu/jscript/interop/NativeWrapperProvider.java @@ -108,6 +108,12 @@ public class NativeWrapperProvider implements WrappersProvider { } } + public static String getName(Class clazz) { + var classNat = clazz.getAnnotation(Native.class); + if (classNat != null && !classNat.value().trim().equals("")) return classNat.value().trim(); + else return clazz.getSimpleName(); + } + /** * Generates a prototype for the given class. * The returned object will have appropriate wrappers for all instance members. @@ -117,10 +123,7 @@ public class NativeWrapperProvider implements WrappersProvider { public static ObjectValue makeProto(Environment ctx, Class clazz) { var res = new ObjectValue(); - var name = clazz.getName(); - var classNat = clazz.getAnnotation(Native.class); - if (classNat != null && !classNat.value().trim().equals("")) name = classNat.value().trim(); - res.defineProperty(null, ctx.symbol("Symbol.typeName"), name); + res.defineProperty(null, ctx.symbol("Symbol.typeName"), getName(clazz)); for (var overload : clazz.getDeclaredMethods()) { var init = overload.getAnnotation(NativeInit.class); @@ -142,11 +145,7 @@ public class NativeWrapperProvider implements WrappersProvider { * @param clazz The class for which a constructor should be generated */ public static FunctionValue makeConstructor(Environment ctx, Class clazz) { - var name = clazz.getName(); - var classNat = clazz.getAnnotation(Native.class); - if (classNat != null && !classNat.value().trim().equals("")) name = classNat.value().trim(); - - FunctionValue func = new OverloadFunction(name); + FunctionValue func = new OverloadFunction(getName(clazz)); for (var overload : clazz.getDeclaredConstructors()) { var nat = overload.getAnnotation(Native.class); @@ -261,7 +260,27 @@ public class NativeWrapperProvider implements WrappersProvider { constructors.put(clazz, value); } + 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(); + else return null; + })); + proto.defineProperty(null, "name", new NativeFunction("name", (ctx, thisArg, args) -> getName(thisArg.getClass()))); + + var constr = makeConstructor(null, Throwable.class); + proto.defineProperty(null, "constructor", constr, true, false, false); + constr.defineProperty(null, "prototype", proto, true, false, false); + + proto.setPrototype(null, getProto(Object.class)); + constr.setPrototype(null, getConstr(Object.class)); + + setProto(Throwable.class, proto); + setConstr(Throwable.class, constr); + } + public NativeWrapperProvider(Environment env) { this.env = env; + initError(); } } diff --git a/src/me/topchetoeu/jscript/interop/OverloadFunction.java b/src/me/topchetoeu/jscript/interop/OverloadFunction.java index ee0470a..9edd2a5 100644 --- a/src/me/topchetoeu/jscript/interop/OverloadFunction.java +++ b/src/me/topchetoeu/jscript/interop/OverloadFunction.java @@ -8,6 +8,7 @@ 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; @@ -92,7 +93,14 @@ public class OverloadFunction extends FunctionValue { throw new InterruptException(); } else { - throw EngineException.ofError(e.getTargetException().getMessage()).add(name, loc); + 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(name, loc); } } catch (ReflectiveOperationException e) { -- 2.45.2 From 4b0dcffd13b1acbbe7ea57aa58972b84f0eb3afb Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 25 Nov 2023 18:37:40 +0200 Subject: [PATCH 15/36] feat: add fs declarations in lib.d.ts --- src/me/topchetoeu/jscript/js/lib.d.ts | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/me/topchetoeu/jscript/js/lib.d.ts b/src/me/topchetoeu/jscript/js/lib.d.ts index df95570..490ea1a 100644 --- a/src/me/topchetoeu/jscript/js/lib.d.ts +++ b/src/me/topchetoeu/jscript/js/lib.d.ts @@ -482,6 +482,27 @@ interface PromiseConstructor { allSettled(...promises: T): Promise<[...{ [P in keyof T]: PromiseResult>}]>; } +interface FileStat { + type: 'file' | 'folder'; + mode: 'r' | 'rw'; +} +interface File { + readonly pointer: Promise; + readonly length: Promise; + read(buff: number[], n: number): Promise; + write(buff: number[]): Promise; + setPointer(val: number): Promise; +} +interface Filesystem { + open(path: string, mode: 'r' | 'rw'): Promise; + ls(path: string): AsyncIterableIterator; + mkdir(path: string): Promise; + mkfile(path: string): Promise; + rm(path: string, recursive?: boolean): Promise; + stat(path: string): Promise; + exists(path: string): Promise; +} + declare var String: StringConstructor; //@ts-ignore declare const arguments: IArguments; @@ -508,6 +529,7 @@ declare var Object: ObjectConstructor; declare var Symbol: SymbolConstructor; declare var Promise: PromiseConstructor; declare var Math: MathObject; +declare var fs: Filesystem; declare var Error: ErrorConstructor; declare var RangeError: RangeErrorConstructor; -- 2.45.2 From 829bea755d3daf47a2d235a3167dc6c8f0784527 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 25 Nov 2023 18:38:16 +0200 Subject: [PATCH 16/36] fix: CodeFrame didnt't handle out of bounds jumps well --- .../jscript/engine/frame/CodeFrame.java | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java index 57a4e05..7ea3a4d 100644 --- a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java +++ b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java @@ -4,6 +4,7 @@ import java.util.List; 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.scope.LocalScope; import me.topchetoeu.jscript.engine.scope.ValueVariable; @@ -151,15 +152,17 @@ public class CodeFrame { public Object next(Context ctx, Object value, Object returnValue, EngineException error) { if (value != Runners.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) { try { if (Thread.currentThread().isInterrupted()) throw new InterruptException(); - var instr = function.body[codePtr]; - ctx.engine.onInstruction(ctx, this, instr, Runners.NO_RETURN, null, false); - - if (codePtr < 0 || codePtr >= function.body.length) returnValue = null; + if (instr == null) returnValue = null; else { + ctx.engine.onInstruction(ctx, this, instr, Runners.NO_RETURN, null, false); + if (instr.location != null) prevLoc = instr.location; try { @@ -278,11 +281,11 @@ public class CodeFrame { } if (error != null) { - ctx.engine.onInstruction(ctx, this, function.body[codePtr], null, error, false); + ctx.engine.onInstruction(ctx, this, instr, null, error, false); throw error; } if (returnValue != Runners.NO_RETURN) { - ctx.engine.onInstruction(ctx, this, function.body[codePtr], returnValue, null, false); + ctx.engine.onInstruction(ctx, this, instr, returnValue, null, false); return returnValue; } -- 2.45.2 From f5a0b6eaf75b7ce59d13c806f57caaa6a03af91d Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 25 Nov 2023 18:38:43 +0200 Subject: [PATCH 17/36] fix: some improvements of compiled bytecode --- .../compilation/control/SwitchStatement.java | 27 ++++++++++--------- .../compilation/values/CommaStatement.java | 2 +- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/src/me/topchetoeu/jscript/compilation/control/SwitchStatement.java b/src/me/topchetoeu/jscript/compilation/control/SwitchStatement.java index 0d07093..9efc9e7 100644 --- a/src/me/topchetoeu/jscript/compilation/control/SwitchStatement.java +++ b/src/me/topchetoeu/jscript/compilation/control/SwitchStatement.java @@ -33,8 +33,8 @@ public class SwitchStatement extends Statement { @Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { - var caseMap = new HashMap(); - var stmIndexMap = new HashMap(); + var caseToStatement = new HashMap(); + var statementToIndex = new HashMap(); value.compile(target, scope, true); @@ -42,7 +42,7 @@ public class SwitchStatement extends Statement { target.add(Instruction.dup().locate(loc())); ccase.value.compile(target, scope, true); target.add(Instruction.operation(Operation.EQUALS).locate(loc())); - caseMap.put(target.size(), ccase.statementI); + caseToStatement.put(target.size(), ccase.statementI); target.add(Instruction.nop().locate(ccase.value.loc())); } @@ -51,28 +51,31 @@ public class SwitchStatement extends Statement { target.add(Instruction.nop()); for (var stm : body) { - stmIndexMap.put(stmIndexMap.size(), target.size()); + statementToIndex.put(statementToIndex.size(), target.size()); stm.compileWithDebug(target, scope, false); } - if (defaultI < 0 || defaultI >= body.length) target.set(start, Instruction.jmp(target.size() - start).locate(loc())); - else target.set(start, Instruction.jmp(stmIndexMap.get(defaultI) - start)).locate(loc()); + int end = target.size(); + target.add(Instruction.discard().locate(loc())); + if (pollute) target.add(Instruction.loadValue(null)); - for (int i = start; i < target.size(); i++) { + if (defaultI < 0 || defaultI >= body.length) target.set(start, Instruction.jmp(end - start).locate(loc())); + else target.set(start, Instruction.jmp(statementToIndex.get(defaultI) - start)).locate(loc()); + + for (int i = start; i < end; i++) { var instr = target.get(i); if (instr.type == Type.NOP && instr.is(0, "break") && instr.get(1) == null) { - target.set(i, Instruction.jmp(target.size() - i).locate(instr.location)); + target.set(i, Instruction.jmp(end - i).locate(instr.location)); } } - for (var el : caseMap.entrySet()) { + for (var el : caseToStatement.entrySet()) { var loc = target.get(el.getKey()).location; - var i = stmIndexMap.get(el.getValue()); - if (i == null) i = target.size(); + var i = statementToIndex.get(el.getValue()); + if (i == null) i = end; target.set(el.getKey(), Instruction.jmpIf(i - el.getKey()).locate(loc)); target.setDebug(el.getKey()); } - target.add(Instruction.discard().locate(loc())); } public SwitchStatement(Location loc, Statement value, int defaultI, SwitchCase[] cases, Statement[] body) { diff --git a/src/me/topchetoeu/jscript/compilation/values/CommaStatement.java b/src/me/topchetoeu/jscript/compilation/values/CommaStatement.java index f92f41c..6bbf9c6 100644 --- a/src/me/topchetoeu/jscript/compilation/values/CommaStatement.java +++ b/src/me/topchetoeu/jscript/compilation/values/CommaStatement.java @@ -13,7 +13,7 @@ public class CommaStatement extends Statement { @Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { for (var i = 0; i < values.length; i++) { - values[i].compile(target, scope, i == values.length - 1 && pollute); + values[i].compileWithDebug(target, scope, i == values.length - 1 && pollute); } } -- 2.45.2 From f2b33d0233128dd7ed1647f031a35ad03c901d62 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 25 Nov 2023 18:40:49 +0200 Subject: [PATCH 18/36] feat: add support for async iterators fix: comparasions had small behavioural issue --- .../jscript/engine/values/Values.java | 82 +++++++++++++++---- 1 file changed, 66 insertions(+), 16 deletions(-) diff --git a/src/me/topchetoeu/jscript/engine/values/Values.java b/src/me/topchetoeu/jscript/engine/values/Values.java index 314d2f4..b4cc5a4 100644 --- a/src/me/topchetoeu/jscript/engine/values/Values.java +++ b/src/me/topchetoeu/jscript/engine/values/Values.java @@ -17,8 +17,27 @@ import me.topchetoeu.jscript.exceptions.ConvertException; import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.SyntaxException; import me.topchetoeu.jscript.exceptions.UncheckedException; +import me.topchetoeu.jscript.lib.PromiseLib; public class Values { + public static enum CompareResult { + NOT_EQUAL, + EQUAL, + LESS, + GREATER; + + public boolean less() { return this == LESS; } + public boolean greater() { return this == GREATER; } + public boolean lessOrEqual() { return this == LESS || this == EQUAL; } + public boolean greaterOrEqual() { return this == GREATER || this == EQUAL; } + + public static CompareResult from(int cmp) { + if (cmp < 0) return LESS; + if (cmp > 0) return GREATER; + return EQUAL; + } + } + public static final Object NULL = new Object(); public static boolean isObject(Object val) { return val instanceof ObjectValue; } @@ -137,7 +156,7 @@ public class Values { } if (val instanceof Boolean) return (Boolean)val ? "true" : "false"; if (val instanceof String) return (String)val; - if (val instanceof Symbol) return ((Symbol)val).toString(); + if (val instanceof Symbol) return val.toString(); return "Unknown value"; } @@ -191,12 +210,18 @@ public class Values { return _a >>> _b; } - public static int compare(Context ctx, Object a, Object b) { + public static CompareResult compare(Context ctx, Object a, Object b) { a = toPrimitive(ctx, a, ConvertHint.VALUEOF); b = toPrimitive(ctx, b, ConvertHint.VALUEOF); - if (a instanceof String && b instanceof String) return ((String)a).compareTo((String)b); - else return Double.compare(toNumber(ctx, a), toNumber(ctx, b)); + if (a instanceof String && b instanceof String) CompareResult.from(((String)a).compareTo((String)b)); + + var _a = toNumber(ctx, a); + var _b = toNumber(ctx, b); + + if (Double.isNaN(_a) || Double.isNaN(_b)) return CompareResult.NOT_EQUAL; + + return CompareResult.from(Double.compare(_a, _b)); } public static boolean not(Object obj) { @@ -232,10 +257,10 @@ public class Values { case LOOSE_EQUALS: return looseEqual(ctx, args[0], args[1]); case LOOSE_NOT_EQUALS: return !looseEqual(ctx, args[0], args[1]); - case GREATER: return compare(ctx, args[0], args[1]) > 0; - case GREATER_EQUALS: return compare(ctx, args[0], args[1]) >= 0; - case LESS: return compare(ctx, args[0], args[1]) < 0; - case LESS_EQUALS: return compare(ctx, args[0], args[1]) <= 0; + case GREATER: return compare(ctx, args[0], args[1]).greater(); + case GREATER_EQUALS: return compare(ctx, args[0], args[1]).greaterOrEqual(); + case LESS: return compare(ctx, args[0], args[1]).less(); + case LESS_EQUALS: return compare(ctx, args[0], args[1]).lessOrEqual(); case INVERSE: return bitwiseNot(ctx, args[0]); case NOT: return not(args[0]); @@ -276,6 +301,11 @@ public class Values { else if (key != null && "__proto__".equals(key)) return proto; else return proto.getMember(ctx, key, obj); } + public static Object getMemberPath(Context ctx, Object obj, Object ...path) { + var res = obj; + for (var key : path) res = getMember(ctx, res, key); + return res; + } public static boolean setMember(Context ctx, Object obj, Object key, Object val) { obj = normalize(ctx, obj); key = normalize(ctx, key); val = normalize(ctx, val); if (obj == null) throw EngineException.ofType("Tried to access member of undefined."); @@ -518,7 +548,7 @@ public class Values { throw new ConvertException(type(obj), clazz.getSimpleName()); } - public static Iterable toJavaIterable(Context ctx, Object obj) { + public static Iterable fromJSIterator(Context ctx, Object obj) { return () -> { try { var symbol = ctx.environment().symbol("Symbol.iterator"); @@ -571,7 +601,7 @@ public class Values { }; } - public static ObjectValue fromJavaIterator(Context ctx, Iterator it) { + public static ObjectValue toJSIterator(Context ctx, Iterator it) { var res = new ObjectValue(); try { @@ -592,8 +622,31 @@ public class Values { return res; } - public static ObjectValue fromJavaIterable(Context ctx, Iterable it) { - return fromJavaIterator(ctx, it.iterator()); + public static ObjectValue toJSIterator(Context ctx, Iterable it) { + return toJSIterator(ctx, it.iterator()); + } + + public static ObjectValue toJSAsyncIterator(Context ctx, Iterator it) { + var res = new ObjectValue(); + + try { + var key = getMemberPath(ctx, ctx.environment().proto("symbol"), "constructor", "asyncIterator"); + res.defineProperty(ctx, key, new NativeFunction("", (_ctx, thisArg, args) -> thisArg)); + } + catch (IllegalArgumentException | NullPointerException e) { } + + res.defineProperty(ctx, "next", new NativeFunction("", (_ctx, _th, _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()); + return obj; + } + }); + })); + + return res; } private static boolean isEmptyFunc(ObjectValue val) { @@ -620,10 +673,7 @@ public class Values { var printed = true; if (val instanceof FunctionValue) { - System.out.print("function "); - var name = Values.getMember(ctx, val, "name"); - if (name != null) System.out.print(Values.toString(ctx, name)); - System.out.print("(...)"); + System.out.print(val.toString()); var loc = val instanceof CodeFunction ? ((CodeFunction)val).loc() : null; if (loc != null) System.out.print(" @ " + loc); -- 2.45.2 From 1666682dc2527d19c0d461df4a7210fc6fc29fce Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 25 Nov 2023 18:41:18 +0200 Subject: [PATCH 19/36] refactor: get rid of data parent hierarchy --- src/me/topchetoeu/jscript/engine/Data.java | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/src/me/topchetoeu/jscript/engine/Data.java b/src/me/topchetoeu/jscript/engine/Data.java index 6c2d58f..54b78a2 100644 --- a/src/me/topchetoeu/jscript/engine/Data.java +++ b/src/me/topchetoeu/jscript/engine/Data.java @@ -5,7 +5,6 @@ import java.util.Map; @SuppressWarnings("unchecked") public class Data { - public final Data parent; private HashMap, Object> data = new HashMap<>(); public Data copy() { @@ -33,19 +32,12 @@ public class Data { return this; } public T get(DataKey key, T val) { - for (var it = this; it != null; it = it.parent) { - if (it.data.containsKey(key)) { - return (T)it.data.get((DataKey)key); - } - } - + if (data.containsKey(key)) return (T)data.get((DataKey)key); set(key, val); return val; } public T get(DataKey key) { - for (var it = this; it != null; it = it.parent) { - if (it.data.containsKey(key)) return (T)it.data.get((DataKey)key); - } + if (data.containsKey(key)) return (T)data.get((DataKey)key); return null; } public boolean has(DataKey key) { return data.containsKey(key); } @@ -61,11 +53,4 @@ public class Data { public int increase(DataKey key) { return increase(key, 1, 0); } - - public Data() { - this.parent = null; - } - public Data(Data parent) { - this.parent = parent; - } } -- 2.45.2 From 4111dbf5c4d2035a83d5271a6196f0be1811cd59 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 25 Nov 2023 18:41:47 +0200 Subject: [PATCH 20/36] fix: functions now print their name when stringified --- src/me/topchetoeu/jscript/engine/values/FunctionValue.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/me/topchetoeu/jscript/engine/values/FunctionValue.java b/src/me/topchetoeu/jscript/engine/values/FunctionValue.java index 1a19925..01eb281 100644 --- a/src/me/topchetoeu/jscript/engine/values/FunctionValue.java +++ b/src/me/topchetoeu/jscript/engine/values/FunctionValue.java @@ -11,7 +11,7 @@ public abstract class FunctionValue extends ObjectValue { @Override public String toString() { - return "function(...) { ...}"; + return "function %s(...)".formatted(name); } public abstract Object call(Context ctx, Object thisArg, Object ...args); -- 2.45.2 From 4a1473c5be721b3165c143dbf968b73b33742c07 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 25 Nov 2023 18:42:54 +0200 Subject: [PATCH 21/36] fix: sorting with no comparator now works --- src/me/topchetoeu/jscript/lib/ArrayLib.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/me/topchetoeu/jscript/lib/ArrayLib.java b/src/me/topchetoeu/jscript/lib/ArrayLib.java index 25756bc..b9b1065 100644 --- a/src/me/topchetoeu/jscript/lib/ArrayLib.java +++ b/src/me/topchetoeu/jscript/lib/ArrayLib.java @@ -6,6 +6,7 @@ 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; @@ -22,10 +23,10 @@ import me.topchetoeu.jscript.interop.NativeSetter; } @Native(thisArg = true) public static ObjectValue values(Context ctx, ArrayValue thisArg) { - return Values.fromJavaIterable(ctx, thisArg); + return Values.toJSIterator(ctx, thisArg); } @Native(thisArg = true) public static ObjectValue keys(Context ctx, ArrayValue thisArg) { - return Values.fromJavaIterable(ctx, () -> new Iterator() { + return Values.toJSIterator(ctx, () -> new Iterator() { private int i = 0; @Override @@ -40,7 +41,7 @@ import me.topchetoeu.jscript.interop.NativeSetter; }); } @Native(thisArg = true) public static ObjectValue entries(Context ctx, ArrayValue thisArg) { - return Values.fromJavaIterable(ctx, () -> new Iterator() { + return Values.toJSIterator(ctx, () -> new Iterator() { private int i = 0; @Override @@ -91,8 +92,11 @@ import me.topchetoeu.jscript.interop.NativeSetter; } @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.call(ctx, null, 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; -- 2.45.2 From f0ad936e5ba79befb9a5ac8f50edd62f65c5ed48 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 25 Nov 2023 18:43:43 +0200 Subject: [PATCH 22/36] refactor: use new iterable names --- src/me/topchetoeu/jscript/lib/MapLib.java | 8 ++++---- src/me/topchetoeu/jscript/lib/RegExpLib.java | 2 +- src/me/topchetoeu/jscript/lib/SetLib.java | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/me/topchetoeu/jscript/lib/MapLib.java b/src/me/topchetoeu/jscript/lib/MapLib.java index 53acc2a..f7762c5 100644 --- a/src/me/topchetoeu/jscript/lib/MapLib.java +++ b/src/me/topchetoeu/jscript/lib/MapLib.java @@ -35,15 +35,15 @@ import me.topchetoeu.jscript.interop.NativeGetter; var res = map.entrySet().stream().map(v -> { return new ArrayValue(ctx, v.getKey(), v.getValue()); }).collect(Collectors.toList()); - return Values.fromJavaIterator(ctx, res.iterator()); + return Values.toJSIterator(ctx, res.iterator()); } @Native public ObjectValue keys(Context ctx) { var res = new ArrayList<>(map.keySet()); - return Values.fromJavaIterator(ctx, res.iterator()); + return Values.toJSIterator(ctx, res.iterator()); } @Native public ObjectValue values(Context ctx) { var res = new ArrayList<>(map.values()); - return Values.fromJavaIterator(ctx, res.iterator()); + return Values.toJSIterator(ctx, res.iterator()); } @Native public Object get(Object key) { @@ -68,7 +68,7 @@ import me.topchetoeu.jscript.interop.NativeGetter; } @Native public MapLib(Context ctx, Object iterable) { - for (var el : Values.toJavaIterable(ctx, iterable)) { + for (var el : Values.fromJSIterator(ctx, iterable)) { try { set(Values.getMember(ctx, el, 0), Values.getMember(ctx, el, 1)); } diff --git a/src/me/topchetoeu/jscript/lib/RegExpLib.java b/src/me/topchetoeu/jscript/lib/RegExpLib.java index f8efa93..cea6970 100644 --- a/src/me/topchetoeu/jscript/lib/RegExpLib.java +++ b/src/me/topchetoeu/jscript/lib/RegExpLib.java @@ -153,7 +153,7 @@ import me.topchetoeu.jscript.interop.NativeGetter; @Native("@@Symbol.matchAll") public Object matchAll(Context ctx, String target) { var pattern = new RegExpLib(this.source, this.flags() + "g"); - return Values.fromJavaIterator(ctx, new Iterator() { + return Values.toJSIterator(ctx, new Iterator() { private Object val = null; private boolean updated = false; diff --git a/src/me/topchetoeu/jscript/lib/SetLib.java b/src/me/topchetoeu/jscript/lib/SetLib.java index 0afc096..90ab717 100644 --- a/src/me/topchetoeu/jscript/lib/SetLib.java +++ b/src/me/topchetoeu/jscript/lib/SetLib.java @@ -22,15 +22,15 @@ import me.topchetoeu.jscript.interop.NativeGetter; @Native public ObjectValue entries(Context ctx) { var res = set.stream().map(v -> new ArrayValue(ctx, v, v)).collect(Collectors.toList()); - return Values.fromJavaIterator(ctx, res.iterator()); + return Values.toJSIterator(ctx, res.iterator()); } @Native public ObjectValue keys(Context ctx) { var res = new ArrayList<>(set); - return Values.fromJavaIterator(ctx, res.iterator()); + return Values.toJSIterator(ctx, res.iterator()); } @Native public ObjectValue values(Context ctx) { var res = new ArrayList<>(set); - return Values.fromJavaIterator(ctx, res.iterator()); + return Values.toJSIterator(ctx, res.iterator()); } @Native public Object add(Object key) { @@ -58,6 +58,6 @@ import me.topchetoeu.jscript.interop.NativeGetter; } @Native public SetLib(Context ctx, Object iterable) { - for (var el : Values.toJavaIterable(ctx, iterable)) add(el); + for (var el : Values.fromJSIterator(ctx, iterable)) add(el); } } -- 2.45.2 From 86d205e521e347115e23409fb9bea56b4f66338e Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 25 Nov 2023 18:44:11 +0200 Subject: [PATCH 23/36] fix: parsing files won't modify last instruction to RET, instead will just append it --- src/me/topchetoeu/jscript/parsing/Parsing.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/me/topchetoeu/jscript/parsing/Parsing.java b/src/me/topchetoeu/jscript/parsing/Parsing.java index d96aa34..fd598c8 100644 --- a/src/me/topchetoeu/jscript/parsing/Parsing.java +++ b/src/me/topchetoeu/jscript/parsing/Parsing.java @@ -1896,10 +1896,7 @@ public class Parsing { res.add(Instruction.throwSyntax(e)); } - if (res.size() != 0 && res.get(res.size() - 1).type == Type.DISCARD) { - res.set(res.size() - 1, Instruction.ret()); - } - else res.add(Instruction.ret()); + res.add(Instruction.ret()); return new CodeFunction(environment, "", subscope.localsCount(), 0, new ValueVariable[0], new FunctionBody(res.array(), subscope.captures(), subscope.locals())); } -- 2.45.2 From e1ce384815dcf7709a6ee8f84a052ed5acf026e5 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 25 Nov 2023 18:44:27 +0200 Subject: [PATCH 24/36] feat: add parse function to Filename --- src/me/topchetoeu/jscript/Filename.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/me/topchetoeu/jscript/Filename.java b/src/me/topchetoeu/jscript/Filename.java index 8e87ef6..3138887 100644 --- a/src/me/topchetoeu/jscript/Filename.java +++ b/src/me/topchetoeu/jscript/Filename.java @@ -51,4 +51,10 @@ public class Filename { this.protocol = protocol; this.path = path; } + + public static Filename parse(String val) { + var i = val.indexOf("://"); + if (i >= 0) return new Filename(val.substring(0, i).trim(), val.substring(i + 3).trim()); + else return new Filename("file", val.trim()); + } } -- 2.45.2 From 8b743f49d185b9da5200a1da07bcebed00b7e013 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 25 Nov 2023 18:44:55 +0200 Subject: [PATCH 25/36] feat: add toString, equals and hashCode overrides to wrappers and objects --- .../jscript/engine/values/NativeWrapper.java | 13 +++++++++++++ src/me/topchetoeu/jscript/lib/ObjectLib.java | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/me/topchetoeu/jscript/engine/values/NativeWrapper.java b/src/me/topchetoeu/jscript/engine/values/NativeWrapper.java index 49d785e..cda9881 100644 --- a/src/me/topchetoeu/jscript/engine/values/NativeWrapper.java +++ b/src/me/topchetoeu/jscript/engine/values/NativeWrapper.java @@ -12,6 +12,19 @@ public class NativeWrapper extends ObjectValue { else return super.getPrototype(ctx); } + @Override + public String toString() { + return wrapped.toString(); + } + @Override + public boolean equals(Object obj) { + return wrapped.equals(obj); + } + @Override + public int hashCode() { + return wrapped.hashCode(); + } + public NativeWrapper(Object wrapped) { this.wrapped = wrapped; prototype = NATIVE_PROTO; diff --git a/src/me/topchetoeu/jscript/lib/ObjectLib.java b/src/me/topchetoeu/jscript/lib/ObjectLib.java index 30bd47b..83457fa 100644 --- a/src/me/topchetoeu/jscript/lib/ObjectLib.java +++ b/src/me/topchetoeu/jscript/lib/ObjectLib.java @@ -140,7 +140,7 @@ import me.topchetoeu.jscript.interop.NativeConstructor; @Native public static ObjectValue fromEntries(Context ctx, Object iterable) { var res = new ObjectValue(); - for (var el : Values.toJavaIterable(ctx, iterable)) { + for (var el : Values.fromJSIterator(ctx, iterable)) { if (el instanceof ArrayValue) { res.defineProperty(ctx, ((ArrayValue)el).get(0), ((ArrayValue)el).get(1)); } -- 2.45.2 From 6af3c70fce2778be223caf55eef9c98009f09cc9 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 25 Nov 2023 18:47:01 +0200 Subject: [PATCH 26/36] feat: add filesystem libs --- src/me/topchetoeu/jscript/Main.java | 9 + .../jscript/engine/Environment.java | 2 + src/me/topchetoeu/jscript/lib/FileLib.java | 88 ++++++++++ .../topchetoeu/jscript/lib/FilesystemLib.java | 161 ++++++++++++++++++ .../topchetoeu/jscript/lib/FunctionLib.java | 2 +- 5 files changed, 261 insertions(+), 1 deletion(-) create mode 100644 src/me/topchetoeu/jscript/lib/FileLib.java create mode 100644 src/me/topchetoeu/jscript/lib/FilesystemLib.java diff --git a/src/me/topchetoeu/jscript/Main.java b/src/me/topchetoeu/jscript/Main.java index 6082226..818b328 100644 --- a/src/me/topchetoeu/jscript/Main.java +++ b/src/me/topchetoeu/jscript/Main.java @@ -16,6 +16,10 @@ 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.MemoryFilesystem; +import me.topchetoeu.jscript.filesystem.Mode; +import me.topchetoeu.jscript.filesystem.RootFilesystem; +import me.topchetoeu.jscript.lib.FilesystemLib; import me.topchetoeu.jscript.lib.Internals; public class Main { @@ -112,6 +116,11 @@ public class Main { throw new EngineException("Couldn't open do.js"); } }); + + var fs = new RootFilesystem(null); + fs.protocols.put("file", new MemoryFilesystem(Mode.READ_WRITE)); + + environment.global.define((Context)null, "fs", false, new FilesystemLib(fs)); } private static void initEngine() { debugServer.targets.put("target", (ws, req) -> new SimpleDebugger(ws, engine)); diff --git a/src/me/topchetoeu/jscript/engine/Environment.java b/src/me/topchetoeu/jscript/engine/Environment.java index 30a77a7..8bca1de 100644 --- a/src/me/topchetoeu/jscript/engine/Environment.java +++ b/src/me/topchetoeu/jscript/engine/Environment.java @@ -12,6 +12,7 @@ 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.permissions.PermissionsManager; public class Environment { private HashMap prototypes = new HashMap<>(); @@ -21,6 +22,7 @@ public class Environment { public GlobalScope global; public WrappersProvider wrappers; + public PermissionsManager permissions; private static int nextId = 0; diff --git a/src/me/topchetoeu/jscript/lib/FileLib.java b/src/me/topchetoeu/jscript/lib/FileLib.java new file mode 100644 index 0000000..11c042f --- /dev/null +++ b/src/me/topchetoeu/jscript/lib/FileLib.java @@ -0,0 +1,88 @@ +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; + +@Native("File") +public class FileLib { + public final File file; + + @Native public PromiseLib read(Context ctx, int n) { + return PromiseLib.await(ctx, () -> { + 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]); + return res; + } + catch (FilesystemException e) { throw e.toEngineException(); } + }); + } + @Native public PromiseLib write(Context ctx, ArrayValue val) { + return PromiseLib.await(ctx, () -> { + try { + var res = new byte[val.size()]; + + for (var i = 0; i < val.size(); i++) res[i] = (byte)Values.toNumber(ctx, val.get(i)); + file.write(res); + + return null; + } + catch (FilesystemException e) { throw e.toEngineException(); } + }); + } + @Native public PromiseLib close(Context ctx) { + return PromiseLib.await(ctx, () -> { + file.close(); + return null; + }); + } + @NativeGetter public PromiseLib pointer(Context ctx) { + return PromiseLib.await(ctx, () -> { + try { + return file.getPtr(); + } + catch (FilesystemException e) { throw e.toEngineException(); } + }); + } + @Native public PromiseLib setPointer(Context ctx, long ptr) { + return PromiseLib.await(ctx, () -> { + try { + file.setPtr(ptr, 0); + return null; + } + catch (FilesystemException e) { throw e.toEngineException(); } + }); + } + @NativeGetter("length") public PromiseLib getLength(Context ctx) { + return PromiseLib.await(ctx, () -> { + try { + long curr = file.getPtr(); + file.setPtr(0, 2); + long res = file.getPtr(); + file.setPtr(curr, 0); + return res; + } + catch (FilesystemException e) { throw e.toEngineException(); } + }); + } + @NativeGetter("mode") public PromiseLib getMode(Context ctx) { + return PromiseLib.await(ctx, () -> { + try { + return file.mode().name; + } + catch (FilesystemException e) { throw e.toEngineException(); } + }); + } + + public FileLib(File file) { + this.file = file; + } +} diff --git a/src/me/topchetoeu/jscript/lib/FilesystemLib.java b/src/me/topchetoeu/jscript/lib/FilesystemLib.java new file mode 100644 index 0000000..8a9aaab --- /dev/null +++ b/src/me/topchetoeu/jscript/lib/FilesystemLib.java @@ -0,0 +1,161 @@ +package me.topchetoeu.jscript.lib; + +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; +import me.topchetoeu.jscript.filesystem.EntryType; +import me.topchetoeu.jscript.filesystem.File; +import me.topchetoeu.jscript.filesystem.FileStat; +import me.topchetoeu.jscript.filesystem.FilesystemException; +import me.topchetoeu.jscript.filesystem.Mode; +import me.topchetoeu.jscript.filesystem.RootFilesystem; +import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode; +import me.topchetoeu.jscript.interop.Native; + +@Native("Filesystem") +public class FilesystemLib { + public final RootFilesystem fs; + + @Native public PromiseLib open(Context ctx, String _path, String mode) { + var filename = Filename.parse(_path); + var _mode = Mode.parse(mode); + + return PromiseLib.await(ctx, () -> { + try { + if (fs.stat(filename.path).type != EntryType.FILE) { + throw new FilesystemException(filename.toString(), FSCode.NOT_FILE); + } + + var file = fs.open(filename.path, _mode); + return new FileLib(file); + } + catch (FilesystemException e) { throw e.toEngineException(); } + }); + } + @Native public ObjectValue ls(Context ctx, String _path) throws IOException { + var filename = Filename.parse(_path); + + return Values.toJSAsyncIterator(ctx, new Iterator<>() { + private boolean failed, done; + private File file; + private String nextLine; + + private void update() { + if (done) return; + if (!failed) { + if (file == null) { + if (fs.stat(filename.path).type != EntryType.FOLDER) { + throw new FilesystemException(filename.toString(), FSCode.NOT_FOLDER); + } + + file = fs.open(filename.path, Mode.READ); + } + + if (nextLine == null) { + while (true) { + nextLine = file.readLine(); + if (nextLine == null) { + done = true; + return; + } + nextLine = nextLine.trim(); + if (!nextLine.equals("")) break; + } + } + } + } + + @Override + public boolean hasNext() { + try { + update(); + return !done && !failed; + } + catch (FilesystemException e) { throw e.toEngineException(); } + } + @Override + public String next() { + try { + update(); + var res = nextLine; + nextLine = null; + return res; + } + catch (FilesystemException e) { throw e.toEngineException(); } + } + }); + } + @Native public PromiseLib mkdir(Context ctx, String _path) throws IOException { + return PromiseLib.await(ctx, () -> { + try { + fs.create(Filename.parse(_path).toString(), EntryType.FOLDER); + return null; + } + catch (FilesystemException e) { throw e.toEngineException(); } + }); + + } + @Native public PromiseLib mkfile(Context ctx, String _path) throws IOException { + return PromiseLib.await(ctx, () -> { + try { + fs.create(Filename.parse(_path).toString(), EntryType.FILE); + return null; + } + catch (FilesystemException e) { throw e.toEngineException(); } + }); + } + @Native public PromiseLib rm(Context ctx, String _path, boolean recursive) throws IOException { + return PromiseLib.await(ctx, () -> { + try { + if (!recursive) fs.create(Filename.parse(_path).toString(), EntryType.NONE); + else { + var stack = new Stack(); + stack.push(_path); + + while (!stack.empty()) { + var path = Filename.parse(stack.pop()).toString(); + FileStat stat; + + try { stat = fs.stat(path); } + catch (FilesystemException e) { continue; } + + if (stat.type == EntryType.FOLDER) { + for (var el : fs.open(path, Mode.READ).readToString().split("\n")) stack.push(el); + } + else fs.create(path, EntryType.NONE); + } + } + return null; + } + catch (FilesystemException e) { throw e.toEngineException(); } + }); + } + @Native public PromiseLib stat(Context ctx, String _path) throws IOException { + return PromiseLib.await(ctx, () -> { + try { + var stat = fs.stat(_path); + var res = new ObjectValue(); + + res.defineProperty(ctx, "type", stat.type.name); + res.defineProperty(ctx, "mode", stat.mode.name); + return res; + } + catch (FilesystemException e) { throw e.toEngineException(); } + }); + } + @Native public PromiseLib exists(Context ctx, String _path) throws IOException { + return PromiseLib.await(ctx, () -> { + try { fs.stat(_path); return true; } + catch (FilesystemException e) { return false; } + }); + } + + public FilesystemLib(RootFilesystem fs) { + this.fs = fs; + } +} diff --git a/src/me/topchetoeu/jscript/lib/FunctionLib.java b/src/me/topchetoeu/jscript/lib/FunctionLib.java index ffa2ff7..77f5068 100644 --- a/src/me/topchetoeu/jscript/lib/FunctionLib.java +++ b/src/me/topchetoeu/jscript/lib/FunctionLib.java @@ -39,7 +39,7 @@ import me.topchetoeu.jscript.interop.Native; }); } @Native(thisArg = true) public static String toString(Context ctx, Object func) { - return "function (...) { ... }"; + return func.toString(); } @Native public static FunctionValue async(FunctionValue func) { -- 2.45.2 From e107dd350749712fbc201f026c68f1e79d472146 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 25 Nov 2023 18:47:36 +0200 Subject: [PATCH 27/36] fix: promise doesn't use microtasks in some cases --- src/me/topchetoeu/jscript/lib/PromiseLib.java | 58 +++++++++---------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/src/me/topchetoeu/jscript/lib/PromiseLib.java b/src/me/topchetoeu/jscript/lib/PromiseLib.java index f01cb01..3c93a40 100644 --- a/src/me/topchetoeu/jscript/lib/PromiseLib.java +++ b/src/me/topchetoeu/jscript/lib/PromiseLib.java @@ -12,10 +12,12 @@ 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.exceptions.InterruptException; import me.topchetoeu.jscript.interop.Native; @Native("Promise") public class PromiseLib { + public static interface PromiseRunner { + Object run(); + } private static class Handle { public final Context ctx; public final FunctionValue fulfilled; @@ -168,9 +170,7 @@ import me.topchetoeu.jscript.interop.Native; } var fulfillHandle = new NativeFunction(null, (_ctx, th, a) -> { - try { - res.fulfill(ctx, Values.convert(ctx, fulfill.call(ctx, null, a[0]), Object.class)); - } + 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; }); @@ -231,7 +231,7 @@ import me.topchetoeu.jscript.interop.Native; private boolean handled = false; private Object val; - public void fulfill(Context ctx, Object val) { + public synchronized void fulfill(Context ctx, Object val) { if (this.state != STATE_PENDING) return; if (val instanceof PromiseLib) ((PromiseLib)val).handle(ctx, @@ -239,12 +239,12 @@ import me.topchetoeu.jscript.interop.Native; new NativeFunction(null, (e, th, a) -> { this.reject(ctx, a[0]); return null; }) ); else { - Object next; - try { next = Values.getMember(ctx, val, "next"); } - catch (IllegalArgumentException e) { next = null; } + Object then; + try { then = Values.getMember(ctx, val, "then"); } + catch (IllegalArgumentException e) { then = null; } try { - if (next instanceof FunctionValue) ((FunctionValue)next).call(ctx, val, + 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; }) ); @@ -252,9 +252,13 @@ import me.topchetoeu.jscript.interop.Native; this.val = val; this.state = STATE_FULFILLED; - for (var handle : handles) handle.fulfilled.call(handle.ctx, null, val); - - handles = null; + ctx.engine.pushMsg(false, ctx, new NativeFunction((_ctx, _thisArg, _args) -> { + for (var handle : handles) { + handle.fulfilled.call(handle.ctx, null, val); + } + handles = null; + return null; + }), null); } } catch (EngineException err) { @@ -262,7 +266,7 @@ import me.topchetoeu.jscript.interop.Native; } } } - public void reject(Context ctx, Object val) { + public synchronized void reject(Context ctx, Object val) { if (this.state != STATE_PENDING) return; if (val instanceof PromiseLib) ((PromiseLib)val).handle(ctx, @@ -270,12 +274,12 @@ import me.topchetoeu.jscript.interop.Native; new NativeFunction(null, (e, th, a) -> { this.reject(ctx, a[0]); return null; }) ); else { - Object next; - try { next = Values.getMember(ctx, val, "next"); } - catch (IllegalArgumentException e) { next = null; } + Object then; + try { then = Values.getMember(ctx, val, "then"); } + catch (IllegalArgumentException e) { then = null; } try { - if (next instanceof FunctionValue) ((FunctionValue)next).call(ctx, val, + 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; }) ); @@ -283,19 +287,13 @@ import me.topchetoeu.jscript.interop.Native; this.val = val; this.state = STATE_REJECTED; - for (var handle : handles) handle.rejected.call(handle.ctx, null, val); - if (handles.size() == 0) { - ctx.engine.pushMsg(true, ctx, new NativeFunction((_ctx, _thisArg, _args) -> { - if (!handled) { - Values.printError(new EngineException(val).setCtx(ctx.environment(), ctx.engine), "(in promise)"); - throw new InterruptException(); - } - - return null; - }), null); - } - - handles = null; + ctx.engine.pushMsg(false, ctx, 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)"); + } + return null; + }), null); } } catch (EngineException err) { -- 2.45.2 From 443dc0ffa136a0108f1479846a9ad0a2e50fac1c Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 25 Nov 2023 18:47:51 +0200 Subject: [PATCH 28/36] feat: add await function to promise --- src/me/topchetoeu/jscript/lib/PromiseLib.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/me/topchetoeu/jscript/lib/PromiseLib.java b/src/me/topchetoeu/jscript/lib/PromiseLib.java index 3c93a40..3df19c0 100644 --- a/src/me/topchetoeu/jscript/lib/PromiseLib.java +++ b/src/me/topchetoeu/jscript/lib/PromiseLib.java @@ -347,4 +347,19 @@ import me.topchetoeu.jscript.interop.Native; public PromiseLib() { this(STATE_PENDING, null); } + + public static PromiseLib await(Context ctx, PromiseRunner runner) { + var res = new PromiseLib(); + + new Thread(() -> { + try { + res.fulfill(ctx, runner.run()); + } + catch (EngineException e) { + res.reject(ctx, e.value); + } + }, "Promisifier").start(); + + return res; + } } -- 2.45.2 From b6eaff65ca8985ad0c195c0e5e9278a97d1ae655 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 25 Nov 2023 18:48:46 +0200 Subject: [PATCH 29/36] style: remove unnececeary import --- src/me/topchetoeu/jscript/parsing/Parsing.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/me/topchetoeu/jscript/parsing/Parsing.java b/src/me/topchetoeu/jscript/parsing/Parsing.java index fd598c8..f57ab7e 100644 --- a/src/me/topchetoeu/jscript/parsing/Parsing.java +++ b/src/me/topchetoeu/jscript/parsing/Parsing.java @@ -11,7 +11,6 @@ import java.util.TreeSet; import me.topchetoeu.jscript.Filename; import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.compilation.*; -import me.topchetoeu.jscript.compilation.Instruction.Type; import me.topchetoeu.jscript.compilation.VariableDeclareStatement.Pair; import me.topchetoeu.jscript.compilation.control.*; import me.topchetoeu.jscript.compilation.control.SwitchStatement.SwitchCase; -- 2.45.2 From b127aadcf65609f9f422e43acc519f3416c39c55 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 25 Nov 2023 18:58:24 +0200 Subject: [PATCH 30/36] fix: promise was sending macro tasks instead of micro --- src/me/topchetoeu/jscript/lib/PromiseLib.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/me/topchetoeu/jscript/lib/PromiseLib.java b/src/me/topchetoeu/jscript/lib/PromiseLib.java index 3df19c0..9766ee6 100644 --- a/src/me/topchetoeu/jscript/lib/PromiseLib.java +++ b/src/me/topchetoeu/jscript/lib/PromiseLib.java @@ -252,7 +252,7 @@ import me.topchetoeu.jscript.interop.Native; this.val = val; this.state = STATE_FULFILLED; - ctx.engine.pushMsg(false, ctx, new NativeFunction((_ctx, _thisArg, _args) -> { + ctx.engine.pushMsg(true, ctx, new NativeFunction((_ctx, _thisArg, _args) -> { for (var handle : handles) { handle.fulfilled.call(handle.ctx, null, val); } @@ -287,11 +287,12 @@ import me.topchetoeu.jscript.interop.Native; this.val = val; this.state = STATE_REJECTED; - ctx.engine.pushMsg(false, ctx, new NativeFunction((_ctx, _thisArg, _args) -> { + ctx.engine.pushMsg(true, ctx, new NativeFunction((_ctx, _thisArg, _args) -> { for (var handle : handles) handle.rejected.call(handle.ctx, null, val); - if (!handled) { + if (handles.size() == 0) { Values.printError(new EngineException(val).setCtx(ctx.environment(), ctx.engine), "(in promise)"); } + handles = null; return null; }), null); } -- 2.45.2 From 4b1ec671e244292322be8dbc883eb12d70098c30 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 25 Nov 2023 18:58:35 +0200 Subject: [PATCH 31/36] fix: micro tasks not handled properly --- src/me/topchetoeu/jscript/engine/Engine.java | 33 ++++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/src/me/topchetoeu/jscript/engine/Engine.java b/src/me/topchetoeu/jscript/engine/Engine.java index a5c7dc6..5dde3da 100644 --- a/src/me/topchetoeu/jscript/engine/Engine.java +++ b/src/me/topchetoeu/jscript/engine/Engine.java @@ -2,7 +2,7 @@ package me.topchetoeu.jscript.engine; import java.util.HashMap; import java.util.TreeSet; -import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.PriorityBlockingQueue; import me.topchetoeu.jscript.Filename; import me.topchetoeu.jscript.Location; @@ -35,18 +35,25 @@ public class Engine implements DebugController { } } - private static class Task { + 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) { + 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); } } @@ -62,8 +69,7 @@ public class Engine implements DebugController { private DebugController debugger; private Thread thread; - private LinkedBlockingDeque macroTasks = new LinkedBlockingDeque<>(); - private LinkedBlockingDeque microTasks = new LinkedBlockingDeque<>(); + private PriorityBlockingQueue tasks = new PriorityBlockingQueue<>(); public boolean attachDebugger(DebugController debugger) { if (!debugging || this.debugger != null) return false; @@ -91,18 +97,12 @@ public class Engine implements DebugController { } } public void run(boolean untilEmpty) { - while (!untilEmpty || !macroTasks.isEmpty()) { + while (!untilEmpty || !tasks.isEmpty()) { try { - runTask(macroTasks.take()); - - while (!microTasks.isEmpty()) { - runTask(microTasks.take()); - } + runTask(tasks.take()); } catch (InterruptedException | InterruptException e) { - for (var msg : macroTasks) { - msg.notifier.error(new InterruptException(e)); - } + for (var msg : tasks) msg.notifier.error(new InterruptException(e)); break; } } @@ -127,9 +127,8 @@ public class Engine implements DebugController { } public Awaitable pushMsg(boolean micro, Context ctx, FunctionValue func, Object thisArg, Object ...args) { - var msg = new Task(ctx == null ? new Context(this) : ctx, func, thisArg, args); - if (micro) microTasks.addLast(msg); - else macroTasks.addLast(msg); + var msg = new Task(ctx == null ? new Context(this) : ctx, func, thisArg, args, micro); + tasks.add(msg); return msg.notifier; } public Awaitable pushMsg(boolean micro, Context ctx, Filename filename, String raw, Object thisArg, Object ...args) { -- 2.45.2 From 2cfdd8e335e62d5a3e8f26f2a480181a159ef3e1 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 25 Nov 2023 19:12:56 +0200 Subject: [PATCH 32/36] feat: add utf8 encoding and decoding --- .../topchetoeu/jscript/lib/EncodingLib.java | 20 +++++++++++++++++++ src/me/topchetoeu/jscript/lib/Internals.java | 1 + 2 files changed, 21 insertions(+) create mode 100644 src/me/topchetoeu/jscript/lib/EncodingLib.java diff --git a/src/me/topchetoeu/jscript/lib/EncodingLib.java b/src/me/topchetoeu/jscript/lib/EncodingLib.java new file mode 100644 index 0000000..3f7a495 --- /dev/null +++ b/src/me/topchetoeu/jscript/lib/EncodingLib.java @@ -0,0 +1,20 @@ +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.interop.Native; + +@Native("Encoding") +public class EncodingLib { + @Native public static ArrayValue encode(String value) { + var res = new ArrayValue(); + for (var el : value.getBytes()) res.set(null, res.size(), (int)el); + return res; + } + @Native public static String decode(Context ctx, ArrayValue raw) { + var res = new byte[raw.size()]; + for (var i = 0; i < raw.size(); i++) res[i] = (byte)Values.toNumber(ctx, raw.get(i)); + return new String(res); + } +} diff --git a/src/me/topchetoeu/jscript/lib/Internals.java b/src/me/topchetoeu/jscript/lib/Internals.java index 9247779..1409289 100644 --- a/src/me/topchetoeu/jscript/lib/Internals.java +++ b/src/me/topchetoeu/jscript/lib/Internals.java @@ -119,6 +119,7 @@ public class Internals { glob.define(null, "Math", false, wp.getNamespace(MathLib.class)); glob.define(null, "JSON", false, wp.getNamespace(JSONLib.class)); + glob.define(null, "Encoding", false, wp.getNamespace(EncodingLib.class)); glob.define(null, "Date", false, wp.getConstr(DateLib.class)); glob.define(null, "Object", false, wp.getConstr(ObjectLib.class)); -- 2.45.2 From 567eaa851441e547cae9a40de1f173502ddc6133 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 25 Nov 2023 19:13:20 +0200 Subject: [PATCH 33/36] fix: incorrect declarations --- src/me/topchetoeu/jscript/js/lib.d.ts | 11 +++- src/me/topchetoeu/jscript/lib/FileLib.java | 57 ++++++++++--------- src/me/topchetoeu/jscript/lib/PromiseLib.java | 2 +- 3 files changed, 40 insertions(+), 30 deletions(-) diff --git a/src/me/topchetoeu/jscript/js/lib.d.ts b/src/me/topchetoeu/jscript/js/lib.d.ts index 490ea1a..141e7cf 100644 --- a/src/me/topchetoeu/jscript/js/lib.d.ts +++ b/src/me/topchetoeu/jscript/js/lib.d.ts @@ -489,8 +489,11 @@ interface FileStat { interface File { readonly pointer: Promise; readonly length: Promise; - read(buff: number[], n: number): Promise; + readonly mode: Promise<'' | 'r' | 'rw'>; + + read(n: number): Promise; write(buff: number[]): Promise; + close(): Promise; setPointer(val: number): Promise; } interface Filesystem { @@ -503,6 +506,11 @@ interface Filesystem { exists(path: string): Promise; } +interface Encoding { + encode(val: string): number[]; + decode(val: number[]): string; +} + declare var String: StringConstructor; //@ts-ignore declare const arguments: IArguments; @@ -529,6 +537,7 @@ declare var Object: ObjectConstructor; declare var Symbol: SymbolConstructor; declare var Promise: PromiseConstructor; declare var Math: MathObject; +declare var Encoding: Encoding; declare var fs: Filesystem; declare var Error: ErrorConstructor; diff --git a/src/me/topchetoeu/jscript/lib/FileLib.java b/src/me/topchetoeu/jscript/lib/FileLib.java index 11c042f..8918f10 100644 --- a/src/me/topchetoeu/jscript/lib/FileLib.java +++ b/src/me/topchetoeu/jscript/lib/FileLib.java @@ -12,6 +12,35 @@ import me.topchetoeu.jscript.interop.NativeGetter; public class FileLib { public final File file; + @NativeGetter public PromiseLib pointer(Context ctx) { + return PromiseLib.await(ctx, () -> { + try { + return file.getPtr(); + } + catch (FilesystemException e) { throw e.toEngineException(); } + }); + } + @NativeGetter public PromiseLib length(Context ctx) { + return PromiseLib.await(ctx, () -> { + try { + long curr = file.getPtr(); + file.setPtr(0, 2); + long res = file.getPtr(); + file.setPtr(curr, 0); + return res; + } + catch (FilesystemException e) { throw e.toEngineException(); } + }); + } + @NativeGetter public PromiseLib getMode(Context ctx) { + return PromiseLib.await(ctx, () -> { + try { + return file.mode().name; + } + catch (FilesystemException e) { throw e.toEngineException(); } + }); + } + @Native public PromiseLib read(Context ctx, int n) { return PromiseLib.await(ctx, () -> { try { @@ -44,14 +73,6 @@ public class FileLib { return null; }); } - @NativeGetter public PromiseLib pointer(Context ctx) { - return PromiseLib.await(ctx, () -> { - try { - return file.getPtr(); - } - catch (FilesystemException e) { throw e.toEngineException(); } - }); - } @Native public PromiseLib setPointer(Context ctx, long ptr) { return PromiseLib.await(ctx, () -> { try { @@ -61,26 +82,6 @@ public class FileLib { catch (FilesystemException e) { throw e.toEngineException(); } }); } - @NativeGetter("length") public PromiseLib getLength(Context ctx) { - return PromiseLib.await(ctx, () -> { - try { - long curr = file.getPtr(); - file.setPtr(0, 2); - long res = file.getPtr(); - file.setPtr(curr, 0); - return res; - } - catch (FilesystemException e) { throw e.toEngineException(); } - }); - } - @NativeGetter("mode") public PromiseLib getMode(Context ctx) { - return PromiseLib.await(ctx, () -> { - try { - return file.mode().name; - } - catch (FilesystemException e) { throw e.toEngineException(); } - }); - } public FileLib(File file) { this.file = file; diff --git a/src/me/topchetoeu/jscript/lib/PromiseLib.java b/src/me/topchetoeu/jscript/lib/PromiseLib.java index 9766ee6..4489041 100644 --- a/src/me/topchetoeu/jscript/lib/PromiseLib.java +++ b/src/me/topchetoeu/jscript/lib/PromiseLib.java @@ -289,7 +289,7 @@ import me.topchetoeu.jscript.interop.Native; ctx.engine.pushMsg(true, ctx, new NativeFunction((_ctx, _thisArg, _args) -> { for (var handle : handles) handle.rejected.call(handle.ctx, null, val); - if (handles.size() == 0) { + if (!handled) { Values.printError(new EngineException(val).setCtx(ctx.environment(), ctx.engine), "(in promise)"); } handles = null; -- 2.45.2 From f52f47cdb4bdcaafe5a2ec5874923b5c3bb110f6 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 25 Nov 2023 19:36:18 +0200 Subject: [PATCH 34/36] feat: properly implement filesystems --- src/me/topchetoeu/jscript/Main.java | 9 ++-- .../jscript/engine/Environment.java | 16 ++++-- .../filesystem/PhysicalFilesystem.java | 2 +- .../jscript/filesystem/RootFilesystem.java | 27 +++++----- src/me/topchetoeu/jscript/js/lib.d.ts | 2 +- .../topchetoeu/jscript/lib/FilesystemLib.java | 54 ++++++++++--------- src/me/topchetoeu/jscript/lib/Internals.java | 1 + .../permissions/PermissionsManager.java | 21 +++----- .../permissions/PermissionsProvider.java | 13 +++++ 9 files changed, 81 insertions(+), 64 deletions(-) create mode 100644 src/me/topchetoeu/jscript/permissions/PermissionsProvider.java diff --git a/src/me/topchetoeu/jscript/Main.java b/src/me/topchetoeu/jscript/Main.java index 818b328..452660a 100644 --- a/src/me/topchetoeu/jscript/Main.java +++ b/src/me/topchetoeu/jscript/Main.java @@ -18,8 +18,7 @@ import me.topchetoeu.jscript.exceptions.InterruptException; import me.topchetoeu.jscript.exceptions.SyntaxException; import me.topchetoeu.jscript.filesystem.MemoryFilesystem; import me.topchetoeu.jscript.filesystem.Mode; -import me.topchetoeu.jscript.filesystem.RootFilesystem; -import me.topchetoeu.jscript.lib.FilesystemLib; +import me.topchetoeu.jscript.filesystem.PhysicalFilesystem; import me.topchetoeu.jscript.lib.Internals; public class Main { @@ -117,10 +116,8 @@ public class Main { } }); - var fs = new RootFilesystem(null); - fs.protocols.put("file", new MemoryFilesystem(Mode.READ_WRITE)); - - environment.global.define((Context)null, "fs", false, new FilesystemLib(fs)); + environment.filesystem.protocols.put("temp", new MemoryFilesystem(Mode.READ_WRITE)); + environment.filesystem.protocols.put("file", new PhysicalFilesystem(Path.of(".").toAbsolutePath())); } private static void initEngine() { debugServer.targets.put("target", (ws, req) -> new SimpleDebugger(ws, engine)); diff --git a/src/me/topchetoeu/jscript/engine/Environment.java b/src/me/topchetoeu/jscript/engine/Environment.java index 8bca1de..49d67c5 100644 --- a/src/me/topchetoeu/jscript/engine/Environment.java +++ b/src/me/topchetoeu/jscript/engine/Environment.java @@ -8,13 +8,15 @@ 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.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.permissions.PermissionsManager; +import me.topchetoeu.jscript.permissions.Permission; +import me.topchetoeu.jscript.permissions.PermissionsProvider; -public class Environment { +public class Environment implements PermissionsProvider { private HashMap prototypes = new HashMap<>(); public final Data data = new Data(); @@ -22,7 +24,8 @@ public class Environment { public GlobalScope global; public WrappersProvider wrappers; - public PermissionsManager permissions; + public PermissionsProvider permissions = null; + public final RootFilesystem filesystem = new RootFilesystem(this); private static int nextId = 0; @@ -74,6 +77,13 @@ public class Environment { 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).pushEnv(this); } diff --git a/src/me/topchetoeu/jscript/filesystem/PhysicalFilesystem.java b/src/me/topchetoeu/jscript/filesystem/PhysicalFilesystem.java index a694520..5a75929 100644 --- a/src/me/topchetoeu/jscript/filesystem/PhysicalFilesystem.java +++ b/src/me/topchetoeu/jscript/filesystem/PhysicalFilesystem.java @@ -60,7 +60,7 @@ public class PhysicalFilesystem implements Filesystem { var _path = getPath(path); var f = _path.toFile(); - if (f.exists()) throw new FilesystemException(_path.toString(), FSCode.DOESNT_EXIST); + if (!f.exists()) throw new FilesystemException(_path.toString(), FSCode.DOESNT_EXIST); checkMode(_path, Mode.READ); return new FileStat( diff --git a/src/me/topchetoeu/jscript/filesystem/RootFilesystem.java b/src/me/topchetoeu/jscript/filesystem/RootFilesystem.java index ba9296a..9afd813 100644 --- a/src/me/topchetoeu/jscript/filesystem/RootFilesystem.java +++ b/src/me/topchetoeu/jscript/filesystem/RootFilesystem.java @@ -5,26 +5,25 @@ import java.util.Map; import me.topchetoeu.jscript.Filename; import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode; -import me.topchetoeu.jscript.permissions.PermissionsManager; +import me.topchetoeu.jscript.permissions.PermissionsProvider; public class RootFilesystem implements Filesystem { public final Map protocols = new HashMap<>(); - public final PermissionsManager perms; + public final PermissionsProvider perms; - private boolean canRead(PermissionsManager perms, String _path) { - return perms.has("jscript.file.read:" + _path, '/'); + private boolean canRead(String _path) { + return perms.hasPermission("jscript.file.read:" + _path, '/'); } - private boolean canWrite(PermissionsManager perms, String _path) { - return perms.has("jscript.file.write:" + _path, '/'); + private boolean canWrite(String _path) { + return perms.hasPermission("jscript.file.write:" + _path, '/'); } private void modeAllowed(String _path, Mode mode) throws FilesystemException { - if (mode.readable && perms != null && !canRead(perms, _path)) throw new FilesystemException(_path, FSCode.NO_PERMISSIONS_R); - if (mode.writable && perms != null && !canWrite(perms, _path)) throw new FilesystemException(_path, FSCode.NO_PERMISSIONS_RW); + if (mode.readable && perms != null && !canRead(_path)) throw new FilesystemException(_path, FSCode.NO_PERMISSIONS_R); + if (mode.writable && perms != null && !canWrite(_path)) throw new FilesystemException(_path, FSCode.NO_PERMISSIONS_RW); } - @Override - public File open(String path, Mode perms) throws FilesystemException { + @Override public File open(String path, Mode perms) throws FilesystemException { var filename = Filename.parse(path); var protocol = protocols.get(filename.protocol); if (protocol == null) throw new FilesystemException(filename.toString(), FSCode.DOESNT_EXIST); @@ -33,8 +32,7 @@ public class RootFilesystem implements Filesystem { try { return protocol.open(filename.path, perms); } catch (FilesystemException e) { throw new FilesystemException(filename.toString(), e.code); } } - @Override - public void create(String path, EntryType type) throws FilesystemException { + @Override public void create(String path, EntryType type) throws FilesystemException { var filename = Filename.parse(path); var protocol = protocols.get(filename.protocol); if (protocol == null) throw new FilesystemException(filename.toString(), FSCode.DOESNT_EXIST); @@ -43,8 +41,7 @@ public class RootFilesystem implements Filesystem { try { protocol.create(filename.path, type); } catch (FilesystemException e) { throw new FilesystemException(filename.toString(), e.code); } } - @Override - public FileStat stat(String path) throws FilesystemException { + @Override public FileStat stat(String path) throws FilesystemException { var filename = Filename.parse(path); var protocol = protocols.get(filename.protocol); if (protocol == null) throw new FilesystemException(filename.toString(), FSCode.DOESNT_EXIST); @@ -54,7 +51,7 @@ public class RootFilesystem implements Filesystem { catch (FilesystemException e) { throw new FilesystemException(filename.toString(), e.code); } } - public RootFilesystem(PermissionsManager perms) { + public RootFilesystem(PermissionsProvider perms) { this.perms = perms; } } diff --git a/src/me/topchetoeu/jscript/js/lib.d.ts b/src/me/topchetoeu/jscript/js/lib.d.ts index 141e7cf..a128ca4 100644 --- a/src/me/topchetoeu/jscript/js/lib.d.ts +++ b/src/me/topchetoeu/jscript/js/lib.d.ts @@ -538,7 +538,7 @@ declare var Symbol: SymbolConstructor; declare var Promise: PromiseConstructor; declare var Math: MathObject; declare var Encoding: Encoding; -declare var fs: Filesystem; +declare var Filesystem: Filesystem; declare var Error: ErrorConstructor; declare var RangeError: RangeErrorConstructor; diff --git a/src/me/topchetoeu/jscript/lib/FilesystemLib.java b/src/me/topchetoeu/jscript/lib/FilesystemLib.java index 8a9aaab..98a14d3 100644 --- a/src/me/topchetoeu/jscript/lib/FilesystemLib.java +++ b/src/me/topchetoeu/jscript/lib/FilesystemLib.java @@ -8,36 +8,44 @@ 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; +import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.filesystem.EntryType; import me.topchetoeu.jscript.filesystem.File; import me.topchetoeu.jscript.filesystem.FileStat; +import me.topchetoeu.jscript.filesystem.Filesystem; import me.topchetoeu.jscript.filesystem.FilesystemException; import me.topchetoeu.jscript.filesystem.Mode; -import me.topchetoeu.jscript.filesystem.RootFilesystem; import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode; import me.topchetoeu.jscript.interop.Native; @Native("Filesystem") public class FilesystemLib { - public final RootFilesystem fs; + private static Filesystem fs(Context ctx) { + var env = ctx.environment(); + if (env != null) { + var fs = ctx.environment().filesystem; + if (fs != null) return fs; + } + throw EngineException.ofError("Current environment doesn't have a file system."); + } - @Native public PromiseLib open(Context ctx, String _path, String mode) { + @Native public static PromiseLib open(Context ctx, String _path, String mode) { var filename = Filename.parse(_path); var _mode = Mode.parse(mode); return PromiseLib.await(ctx, () -> { try { - if (fs.stat(filename.path).type != EntryType.FILE) { + if (fs(ctx).stat(filename.path).type != EntryType.FILE) { throw new FilesystemException(filename.toString(), FSCode.NOT_FILE); } - var file = fs.open(filename.path, _mode); + var file = fs(ctx).open(filename.path, _mode); return new FileLib(file); } catch (FilesystemException e) { throw e.toEngineException(); } }); } - @Native public ObjectValue ls(Context ctx, String _path) throws IOException { + @Native public static ObjectValue ls(Context ctx, String _path) throws IOException { var filename = Filename.parse(_path); return Values.toJSAsyncIterator(ctx, new Iterator<>() { @@ -49,11 +57,11 @@ public class FilesystemLib { if (done) return; if (!failed) { if (file == null) { - if (fs.stat(filename.path).type != EntryType.FOLDER) { + if (fs(ctx).stat(filename.path).type != EntryType.FOLDER) { throw new FilesystemException(filename.toString(), FSCode.NOT_FOLDER); } - file = fs.open(filename.path, Mode.READ); + file = fs(ctx).open(filename.path, Mode.READ); } if (nextLine == null) { @@ -90,29 +98,29 @@ public class FilesystemLib { } }); } - @Native public PromiseLib mkdir(Context ctx, String _path) throws IOException { + @Native public static PromiseLib mkdir(Context ctx, String _path) throws IOException { return PromiseLib.await(ctx, () -> { try { - fs.create(Filename.parse(_path).toString(), EntryType.FOLDER); + fs(ctx).create(Filename.parse(_path).toString(), EntryType.FOLDER); return null; } catch (FilesystemException e) { throw e.toEngineException(); } }); } - @Native public PromiseLib mkfile(Context ctx, String _path) throws IOException { + @Native public static PromiseLib mkfile(Context ctx, String _path) throws IOException { return PromiseLib.await(ctx, () -> { try { - fs.create(Filename.parse(_path).toString(), EntryType.FILE); + fs(ctx).create(Filename.parse(_path).toString(), EntryType.FILE); return null; } catch (FilesystemException e) { throw e.toEngineException(); } }); } - @Native public PromiseLib rm(Context ctx, String _path, boolean recursive) throws IOException { + @Native public static PromiseLib rm(Context ctx, String _path, boolean recursive) throws IOException { return PromiseLib.await(ctx, () -> { try { - if (!recursive) fs.create(Filename.parse(_path).toString(), EntryType.NONE); + if (!recursive) fs(ctx).create(Filename.parse(_path).toString(), EntryType.NONE); else { var stack = new Stack(); stack.push(_path); @@ -121,13 +129,13 @@ public class FilesystemLib { var path = Filename.parse(stack.pop()).toString(); FileStat stat; - try { stat = fs.stat(path); } + try { stat = fs(ctx).stat(path); } catch (FilesystemException e) { continue; } if (stat.type == EntryType.FOLDER) { - for (var el : fs.open(path, Mode.READ).readToString().split("\n")) stack.push(el); + for (var el : fs(ctx).open(path, Mode.READ).readToString().split("\n")) stack.push(el); } - else fs.create(path, EntryType.NONE); + else fs(ctx).create(path, EntryType.NONE); } } return null; @@ -135,10 +143,10 @@ public class FilesystemLib { catch (FilesystemException e) { throw e.toEngineException(); } }); } - @Native public PromiseLib stat(Context ctx, String _path) throws IOException { + @Native public static PromiseLib stat(Context ctx, String _path) throws IOException { return PromiseLib.await(ctx, () -> { try { - var stat = fs.stat(_path); + var stat = fs(ctx).stat(_path); var res = new ObjectValue(); res.defineProperty(ctx, "type", stat.type.name); @@ -148,14 +156,10 @@ public class FilesystemLib { catch (FilesystemException e) { throw e.toEngineException(); } }); } - @Native public PromiseLib exists(Context ctx, String _path) throws IOException { + @Native public static PromiseLib exists(Context ctx, String _path) throws IOException { return PromiseLib.await(ctx, () -> { - try { fs.stat(_path); return true; } + try { fs(ctx).stat(_path); return true; } catch (FilesystemException e) { return false; } }); } - - public FilesystemLib(RootFilesystem fs) { - this.fs = fs; - } } diff --git a/src/me/topchetoeu/jscript/lib/Internals.java b/src/me/topchetoeu/jscript/lib/Internals.java index 1409289..d9473a5 100644 --- a/src/me/topchetoeu/jscript/lib/Internals.java +++ b/src/me/topchetoeu/jscript/lib/Internals.java @@ -120,6 +120,7 @@ public class Internals { glob.define(null, "Math", false, wp.getNamespace(MathLib.class)); glob.define(null, "JSON", false, wp.getNamespace(JSONLib.class)); glob.define(null, "Encoding", false, wp.getNamespace(EncodingLib.class)); + glob.define(null, "Filesystem", false, wp.getNamespace(FilesystemLib.class)); glob.define(null, "Date", false, wp.getConstr(DateLib.class)); glob.define(null, "Object", false, wp.getConstr(ObjectLib.class)); diff --git a/src/me/topchetoeu/jscript/permissions/PermissionsManager.java b/src/me/topchetoeu/jscript/permissions/PermissionsManager.java index 1505fb8..bea8355 100644 --- a/src/me/topchetoeu/jscript/permissions/PermissionsManager.java +++ b/src/me/topchetoeu/jscript/permissions/PermissionsManager.java @@ -2,38 +2,33 @@ package me.topchetoeu.jscript.permissions; import java.util.ArrayList; -public class PermissionsManager { - public static final PermissionsManager ALL_PERMS = new PermissionsManager().add(new Permission("**")); +public class PermissionsManager implements PermissionsProvider { + public static final PermissionsProvider ALL_PERMS = new PermissionsManager().add(new Permission("**")); public final ArrayList allowed = new ArrayList<>(); public final ArrayList denied = new ArrayList<>(); - public PermissionsManager add(Permission perm) { + public PermissionsProvider add(Permission perm) { allowed.add(perm); return this; } - public PermissionsManager add(String perm) { + public PermissionsProvider add(String perm) { allowed.add(new Permission(perm)); return this; } - public boolean has(Permission perm, char delim) { + @Override + public boolean hasPermission(Permission perm, char delim) { for (var el : denied) if (el.match(perm, delim)) return false; for (var el : allowed) if (el.match(perm, delim)) return true; return false; } - public boolean has(Permission perm) { + @Override + public boolean hasPermission(Permission perm) { for (var el : denied) if (el.match(perm)) return false; for (var el : allowed) if (el.match(perm)) return true; return false; } - - public boolean has(String perm, char delim) { - return has(new Permission(perm), delim); - } - public boolean has(String perm) { - return has(new Permission(perm)); - } } diff --git a/src/me/topchetoeu/jscript/permissions/PermissionsProvider.java b/src/me/topchetoeu/jscript/permissions/PermissionsProvider.java new file mode 100644 index 0000000..73c5ee6 --- /dev/null +++ b/src/me/topchetoeu/jscript/permissions/PermissionsProvider.java @@ -0,0 +1,13 @@ +package me.topchetoeu.jscript.permissions; + +public interface PermissionsProvider { + boolean hasPermission(Permission perm, char delim); + boolean hasPermission(Permission perm); + + default boolean hasPermission(String perm, char delim) { + return hasPermission(new Permission(perm), delim); + } + default boolean hasPermission(String perm) { + return hasPermission(new Permission(perm)); + } +} \ No newline at end of file -- 2.45.2 From eb14bb080ca686531132a43665e202fda9771ed5 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 25 Nov 2023 20:09:47 +0200 Subject: [PATCH 35/36] fix: debugger breakpoints not registered --- src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java index 7ed5946..eaae038 100644 --- a/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java +++ b/src/me/topchetoeu/jscript/engine/debug/SimpleDebugger.java @@ -474,8 +474,9 @@ public class SimpleDebugger implements Debugger { @Override public void setBreakpointByUrl(V8Message msg) { var line = (int)msg.params.number("lineNumber") + 1; var col = (int)msg.params.number("columnNumber", 0) + 1; - var cond = msg.params.string("condition", null); + var cond = msg.params.string("condition", "").trim(); + if (cond.equals("")) cond = null; if (cond != null) cond = "(" + cond + ")"; Pattern regex; -- 2.45.2 From ab56908171021ded08674af3b4d40350b18c5daf Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 25 Nov 2023 20:09:59 +0200 Subject: [PATCH 36/36] fix: faulty insufficient permissions error --- src/me/topchetoeu/jscript/filesystem/PhysicalFilesystem.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/me/topchetoeu/jscript/filesystem/PhysicalFilesystem.java b/src/me/topchetoeu/jscript/filesystem/PhysicalFilesystem.java index 5a75929..80bcf2f 100644 --- a/src/me/topchetoeu/jscript/filesystem/PhysicalFilesystem.java +++ b/src/me/topchetoeu/jscript/filesystem/PhysicalFilesystem.java @@ -37,7 +37,6 @@ public class PhysicalFilesystem implements Filesystem { var _path = getPath(path); var f = _path.toFile(); - checkMode(_path, Mode.READ_WRITE); switch (type) { case FILE: try { -- 2.45.2