From 29d3f378a51b10260064034ed71a70a81825311f Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Mon, 4 Sep 2023 14:30:57 +0300 Subject: [PATCH] major changes in preparations for environments --- .../debug/DebugServer.java | 308 ++++---- .../debug/DebugState.java | 104 +-- .../engine => dead-code}/debug/Http.java | 130 ++-- .../debug/HttpRequest.java | 32 +- .../engine => dead-code}/debug/V8Error.java | 38 +- .../engine => dead-code}/debug/V8Event.java | 44 +- .../engine => dead-code}/debug/V8Message.java | 100 +-- .../engine => dead-code}/debug/V8Result.java | 44 +- .../engine => dead-code}/debug/WebSocket.java | 370 ++++----- .../debug/WebSocketMessage.java | 58 +- .../debug/handlers/DebuggerHandles.java | 58 +- .../modules/FileModuleProvider.java | 100 +-- .../engine => dead-code}/modules/Module.java | 114 +-- .../modules/ModuleManager.java | 160 ++-- .../modules/ModuleProvider.java | 16 +- lib/core.ts | 122 ++- lib/lib.d.ts | 66 +- lib/map.ts | 107 ++- lib/modules.ts | 3 +- lib/promise.ts | 202 ++++- lib/regex.ts | 242 +++--- lib/set.ts | 98 ++- lib/timeout.ts | 38 + lib/tsconfig.json | 1 + lib/utils.ts | 16 +- lib/values/array.ts | 27 +- lib/values/boolean.ts | 4 +- lib/values/errors.ts | 53 +- lib/values/function.ts | 44 +- lib/values/number.ts | 16 +- lib/values/object.ts | 121 ++- lib/values/string.ts | 87 ++- lib/values/symbol.ts | 33 +- package-lock.json | 6 + package.json | 1 + src/me/topchetoeu/jscript/Main.java | 81 +- .../jscript/engine/CallContext.java | 21 +- src/me/topchetoeu/jscript/engine/Engine.java | 124 +-- .../jscript/engine/Environment.java | 81 ++ .../jscript/engine/WrappersProvider.java | 8 + .../jscript/engine/frame/CodeFrame.java | 11 +- .../jscript/engine/frame/Runners.java | 16 +- .../jscript/engine/scope/GlobalScope.java | 14 +- .../jscript/engine/scope/LocalScope.java | 16 +- .../jscript/engine/values/CodeFunction.java | 10 +- .../jscript/engine/values/NativeWrapper.java | 2 +- .../jscript/engine/values/ObjectValue.java | 38 +- .../jscript/engine/values/Values.java | 16 +- .../jscript/interop/NativeTypeRegister.java | 7 +- .../topchetoeu/jscript/parsing/Parsing.java | 14 +- .../jscript/polyfills/GeneratorFunction.java | 4 +- .../jscript/polyfills/Internals.java | 317 +++----- ...lyfillEngine.java => PolyfillEngine.java-} | 210 ++--- .../polyfills/{Promise.java => Promise.java-} | 720 +++++++++--------- 54 files changed, 2512 insertions(+), 2161 deletions(-) rename {src/me/topchetoeu/jscript/engine => dead-code}/debug/DebugServer.java (97%) rename {src/me/topchetoeu/jscript/engine => dead-code}/debug/DebugState.java (96%) rename {src/me/topchetoeu/jscript/engine => dead-code}/debug/Http.java (97%) rename {src/me/topchetoeu/jscript/engine => dead-code}/debug/HttpRequest.java (95%) rename {src/me/topchetoeu/jscript/engine => dead-code}/debug/V8Error.java (95%) rename {src/me/topchetoeu/jscript/engine => dead-code}/debug/V8Event.java (95%) rename {src/me/topchetoeu/jscript/engine => dead-code}/debug/V8Message.java (96%) rename {src/me/topchetoeu/jscript/engine => dead-code}/debug/V8Result.java (95%) rename {src/me/topchetoeu/jscript/engine => dead-code}/debug/WebSocket.java (96%) rename {src/me/topchetoeu/jscript/engine => dead-code}/debug/WebSocketMessage.java (96%) rename {src/me/topchetoeu/jscript/engine => dead-code}/debug/handlers/DebuggerHandles.java (97%) rename {src/me/topchetoeu/jscript/engine => dead-code}/modules/FileModuleProvider.java (97%) rename {src/me/topchetoeu/jscript/engine => dead-code}/modules/Module.java (97%) rename {src/me/topchetoeu/jscript/engine => dead-code}/modules/ModuleManager.java (96%) rename {src/me/topchetoeu/jscript/engine => dead-code}/modules/ModuleProvider.java (97%) create mode 100644 lib/timeout.ts create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/me/topchetoeu/jscript/engine/Environment.java create mode 100644 src/me/topchetoeu/jscript/engine/WrappersProvider.java rename src/me/topchetoeu/jscript/polyfills/{PolyfillEngine.java => PolyfillEngine.java-} (97%) rename src/me/topchetoeu/jscript/polyfills/{Promise.java => Promise.java-} (97%) diff --git a/src/me/topchetoeu/jscript/engine/debug/DebugServer.java b/dead-code/debug/DebugServer.java similarity index 97% rename from src/me/topchetoeu/jscript/engine/debug/DebugServer.java rename to dead-code/debug/DebugServer.java index b5e6730..23a6ee9 100644 --- a/src/me/topchetoeu/jscript/engine/debug/DebugServer.java +++ b/dead-code/debug/DebugServer.java @@ -1,154 +1,154 @@ -package me.topchetoeu.jscript.engine.debug; - -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.ServerSocket; -import java.net.Socket; -import java.security.MessageDigest; -import java.util.Base64; - -import me.topchetoeu.jscript.engine.Engine; -import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type; -import me.topchetoeu.jscript.engine.debug.handlers.DebuggerHandles; -import me.topchetoeu.jscript.exceptions.SyntaxException; - -public class DebugServer { - public static String browserDisplayName = "jscript"; - public static String targetName = "target"; - - public final Engine engine; - - private static void send(Socket socket, String val) throws IOException { - Http.writeResponse(socket.getOutputStream(), 200, "OK", "application/json", val.getBytes()); - } - - // SILENCE JAVA - private MessageDigest getDigestInstance() { - try { - return MessageDigest.getInstance("sha1"); - } - catch (Throwable a) { return null; } - } - - private static Thread runAsync(Runnable func, String name) { - var res = new Thread(func); - res.setName(name); - res.start(); - return res; - } - - private void handle(WebSocket ws) throws InterruptedException, IOException { - WebSocketMessage raw; - - while ((raw = ws.receive()) != null) { - if (raw.type != Type.Text) { - ws.send(new V8Error("Expected a text message.")); - continue; - } - - V8Message msg; - - try { - msg = new V8Message(raw.textData()); - } - catch (SyntaxException e) { - ws.send(new V8Error(e.getMessage())); - return; - } - - switch (msg.name) { - case "Debugger.enable": DebuggerHandles.enable(msg, engine, ws); continue; - case "Debugger.disable": DebuggerHandles.disable(msg, engine, ws); continue; - case "Debugger.stepInto": DebuggerHandles.stepInto(msg, engine, ws); continue; - } - } - } - private void onWsConnect(HttpRequest req, Socket socket) throws IOException { - var key = req.headers.get("sec-websocket-key"); - - if (key == null) { - Http.writeResponse( - socket.getOutputStream(), 426, "Upgrade Required", "text/txt", - "Expected a WS upgrade".getBytes() - ); - return; - } - - var resKey = Base64.getEncoder().encodeToString(getDigestInstance().digest( - (key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes() - )); - - Http.writeCode(socket.getOutputStream(), 101, "Switching Protocols"); - Http.writeHeader(socket.getOutputStream(), "Connection", "Upgrade"); - Http.writeHeader(socket.getOutputStream(), "Sec-WebSocket-Accept", resKey); - Http.writeLastHeader(socket.getOutputStream(), "Upgrade", "WebSocket"); - - var ws = new WebSocket(socket); - - runAsync(() -> { - try { - handle(ws); - } - catch (InterruptedException e) { return; } - catch (IOException e) { e.printStackTrace(); } - finally { ws.close(); } - }, "Debug Server Message Reader"); - runAsync(() -> { - try { - handle(ws); - } - catch (InterruptedException e) { return; } - catch (IOException e) { e.printStackTrace(); } - finally { ws.close(); } - }, "Debug Server Event Writer"); - } - - public void open(InetSocketAddress address) throws IOException { - ServerSocket server = new ServerSocket(); - server.bind(address); - - try { - while (true) { - var socket = server.accept(); - var req = Http.readRequest(socket.getInputStream()); - - switch (req.path) { - case "/json/version": - send(socket, "{\"Browser\":\"" + browserDisplayName + "\",\"Protocol-Version\":\"1.2\"}"); - break; - case "/json/list": - case "/json": - var addr = "ws://" + address.getHostString() + ":" + address.getPort() + "/devtools/page/" + targetName; - send(socket, "{\"id\":\"" + browserDisplayName + "\",\"webSocketDebuggerUrl\":\"" + addr + "\"}"); - break; - case "/json/new": - case "/json/activate": - case "/json/protocol": - case "/json/close": - case "/devtools/inspector.html": - Http.writeResponse( - socket.getOutputStream(), - 501, "Not Implemented", "text/txt", - "This feature isn't (and won't be) implemented.".getBytes() - ); - break; - default: - if (req.path.equals("/devtools/page/" + targetName)) onWsConnect(req, socket); - else { - Http.writeResponse( - socket.getOutputStream(), - 404, "Not Found", "text/txt", - "Not found :/".getBytes() - ); - } - break; - } - } - } - finally { server.close(); } - } - - public DebugServer(Engine engine) { - this.engine = engine; - } -} +package me.topchetoeu.jscript.engine.debug; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.security.MessageDigest; +import java.util.Base64; + +import me.topchetoeu.jscript.engine.Engine; +import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type; +import me.topchetoeu.jscript.engine.debug.handlers.DebuggerHandles; +import me.topchetoeu.jscript.exceptions.SyntaxException; + +public class DebugServer { + public static String browserDisplayName = "jscript"; + public static String targetName = "target"; + + public final Engine engine; + + private static void send(Socket socket, String val) throws IOException { + Http.writeResponse(socket.getOutputStream(), 200, "OK", "application/json", val.getBytes()); + } + + // SILENCE JAVA + private MessageDigest getDigestInstance() { + try { + return MessageDigest.getInstance("sha1"); + } + catch (Throwable a) { return null; } + } + + private static Thread runAsync(Runnable func, String name) { + var res = new Thread(func); + res.setName(name); + res.start(); + return res; + } + + private void handle(WebSocket ws) throws InterruptedException, IOException { + WebSocketMessage raw; + + while ((raw = ws.receive()) != null) { + if (raw.type != Type.Text) { + ws.send(new V8Error("Expected a text message.")); + continue; + } + + V8Message msg; + + try { + msg = new V8Message(raw.textData()); + } + catch (SyntaxException e) { + ws.send(new V8Error(e.getMessage())); + return; + } + + switch (msg.name) { + case "Debugger.enable": DebuggerHandles.enable(msg, engine, ws); continue; + case "Debugger.disable": DebuggerHandles.disable(msg, engine, ws); continue; + case "Debugger.stepInto": DebuggerHandles.stepInto(msg, engine, ws); continue; + } + } + } + private void onWsConnect(HttpRequest req, Socket socket) throws IOException { + var key = req.headers.get("sec-websocket-key"); + + if (key == null) { + Http.writeResponse( + socket.getOutputStream(), 426, "Upgrade Required", "text/txt", + "Expected a WS upgrade".getBytes() + ); + return; + } + + var resKey = Base64.getEncoder().encodeToString(getDigestInstance().digest( + (key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes() + )); + + Http.writeCode(socket.getOutputStream(), 101, "Switching Protocols"); + Http.writeHeader(socket.getOutputStream(), "Connection", "Upgrade"); + Http.writeHeader(socket.getOutputStream(), "Sec-WebSocket-Accept", resKey); + Http.writeLastHeader(socket.getOutputStream(), "Upgrade", "WebSocket"); + + var ws = new WebSocket(socket); + + runAsync(() -> { + try { + handle(ws); + } + catch (InterruptedException e) { return; } + catch (IOException e) { e.printStackTrace(); } + finally { ws.close(); } + }, "Debug Server Message Reader"); + runAsync(() -> { + try { + handle(ws); + } + catch (InterruptedException e) { return; } + catch (IOException e) { e.printStackTrace(); } + finally { ws.close(); } + }, "Debug Server Event Writer"); + } + + public void open(InetSocketAddress address) throws IOException { + ServerSocket server = new ServerSocket(); + server.bind(address); + + try { + while (true) { + var socket = server.accept(); + var req = Http.readRequest(socket.getInputStream()); + + switch (req.path) { + case "/json/version": + send(socket, "{\"Browser\":\"" + browserDisplayName + "\",\"Protocol-Version\":\"1.2\"}"); + break; + case "/json/list": + case "/json": + var addr = "ws://" + address.getHostString() + ":" + address.getPort() + "/devtools/page/" + targetName; + send(socket, "{\"id\":\"" + browserDisplayName + "\",\"webSocketDebuggerUrl\":\"" + addr + "\"}"); + break; + case "/json/new": + case "/json/activate": + case "/json/protocol": + case "/json/close": + case "/devtools/inspector.html": + Http.writeResponse( + socket.getOutputStream(), + 501, "Not Implemented", "text/txt", + "This feature isn't (and won't be) implemented.".getBytes() + ); + break; + default: + if (req.path.equals("/devtools/page/" + targetName)) onWsConnect(req, socket); + else { + Http.writeResponse( + socket.getOutputStream(), + 404, "Not Found", "text/txt", + "Not found :/".getBytes() + ); + } + break; + } + } + } + finally { server.close(); } + } + + public DebugServer(Engine engine) { + this.engine = engine; + } +} diff --git a/src/me/topchetoeu/jscript/engine/debug/DebugState.java b/dead-code/debug/DebugState.java similarity index 96% rename from src/me/topchetoeu/jscript/engine/debug/DebugState.java rename to dead-code/debug/DebugState.java index 3cf2209..48c4d90 100644 --- a/src/me/topchetoeu/jscript/engine/debug/DebugState.java +++ b/dead-code/debug/DebugState.java @@ -1,52 +1,52 @@ -package me.topchetoeu.jscript.engine.debug; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; - -import me.topchetoeu.jscript.Location; -import me.topchetoeu.jscript.engine.BreakpointData; -import me.topchetoeu.jscript.engine.DebugCommand; -import me.topchetoeu.jscript.engine.frame.CodeFrame; -import me.topchetoeu.jscript.events.Event; - -public class DebugState { - private boolean paused = false; - - public final HashSet breakpoints = new HashSet<>(); - public final List frames = new ArrayList<>(); - public final Map sources = new HashMap<>(); - - public final Event breakpointNotifier = new Event<>(); - public final Event commandNotifier = new Event<>(); - public final Event sourceAdded = new Event<>(); - - public DebugState pushFrame(CodeFrame frame) { - frames.add(frame); - return this; - } - public DebugState popFrame() { - if (frames.size() > 0) frames.remove(frames.size() - 1); - return this; - } - - public DebugCommand pause(BreakpointData data) throws InterruptedException { - paused = true; - breakpointNotifier.next(data); - return commandNotifier.toAwaitable().await(); - } - public void resume(DebugCommand command) { - paused = false; - commandNotifier.next(command); - } - - // public void addSource()? - - public boolean paused() { return paused; } - - public boolean isBreakpoint(Location loc) { - return breakpoints.contains(loc); - } -} +package me.topchetoeu.jscript.engine.debug; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; + +import me.topchetoeu.jscript.Location; +import me.topchetoeu.jscript.engine.BreakpointData; +import me.topchetoeu.jscript.engine.DebugCommand; +import me.topchetoeu.jscript.engine.frame.CodeFrame; +import me.topchetoeu.jscript.events.Event; + +public class DebugState { + private boolean paused = false; + + public final HashSet breakpoints = new HashSet<>(); + public final List frames = new ArrayList<>(); + public final Map sources = new HashMap<>(); + + public final Event breakpointNotifier = new Event<>(); + public final Event commandNotifier = new Event<>(); + public final Event sourceAdded = new Event<>(); + + public DebugState pushFrame(CodeFrame frame) { + frames.add(frame); + return this; + } + public DebugState popFrame() { + if (frames.size() > 0) frames.remove(frames.size() - 1); + return this; + } + + public DebugCommand pause(BreakpointData data) throws InterruptedException { + paused = true; + breakpointNotifier.next(data); + return commandNotifier.toAwaitable().await(); + } + public void resume(DebugCommand command) { + paused = false; + commandNotifier.next(command); + } + + // public void addSource()? + + public boolean paused() { return paused; } + + public boolean isBreakpoint(Location loc) { + return breakpoints.contains(loc); + } +} diff --git a/src/me/topchetoeu/jscript/engine/debug/Http.java b/dead-code/debug/Http.java similarity index 97% rename from src/me/topchetoeu/jscript/engine/debug/Http.java rename to dead-code/debug/Http.java index 2760dfa..291b745 100644 --- a/src/me/topchetoeu/jscript/engine/debug/Http.java +++ b/dead-code/debug/Http.java @@ -1,65 +1,65 @@ -package me.topchetoeu.jscript.engine.debug; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.util.HashMap; -import java.util.IllegalFormatException; - -// We dont need no http library -public class Http { - public static void writeCode(OutputStream str, int code, String name) throws IOException { - str.write(("HTTP/1.1 " + code + " " + name + "\r\n").getBytes()); - } - public static void writeHeader(OutputStream str, String name, String value) throws IOException { - str.write((name + ": " + value + "\r\n").getBytes()); - } - public static void writeLastHeader(OutputStream str, String name, String value) throws IOException { - str.write((name + ": " + value + "\r\n").getBytes()); - writeHeadersEnd(str); - } - public static void writeHeadersEnd(OutputStream str) throws IOException { - str.write("\n".getBytes()); - } - - public static void writeResponse(OutputStream str, int code, String name, String type, byte[] data) throws IOException { - writeCode(str, code, name); - writeHeader(str, "Content-Type", type); - writeLastHeader(str, "Content-Length", data.length + ""); - str.write(data); - str.close(); - } - - public static HttpRequest readRequest(InputStream str) throws IOException { - var lines = new BufferedReader(new InputStreamReader(str)); - var line = lines.readLine(); - var i1 = line.indexOf(" "); - var i2 = line.lastIndexOf(" "); - - var method = line.substring(0, i1).trim().toUpperCase(); - var path = line.substring(i1 + 1, i2).trim(); - var headers = new HashMap(); - - while (!(line = lines.readLine()).isEmpty()) { - var i = line.indexOf(":"); - if (i < 0) continue; - var name = line.substring(0, i).trim().toLowerCase(); - var value = line.substring(i + 1).trim(); - - if (name.length() == 0) continue; - headers.put(name, value); - } - - if (headers.containsKey("content-length")) { - try { - var i = Integer.parseInt(headers.get("content-length")); - str.skip(i); - } - catch (IllegalFormatException e) { /* ¯\_(ツ)_/¯ */ } - } - - return new HttpRequest(method, path, headers); - } -} +package me.topchetoeu.jscript.engine.debug; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.util.HashMap; +import java.util.IllegalFormatException; + +// We dont need no http library +public class Http { + public static void writeCode(OutputStream str, int code, String name) throws IOException { + str.write(("HTTP/1.1 " + code + " " + name + "\r\n").getBytes()); + } + public static void writeHeader(OutputStream str, String name, String value) throws IOException { + str.write((name + ": " + value + "\r\n").getBytes()); + } + public static void writeLastHeader(OutputStream str, String name, String value) throws IOException { + str.write((name + ": " + value + "\r\n").getBytes()); + writeHeadersEnd(str); + } + public static void writeHeadersEnd(OutputStream str) throws IOException { + str.write("\n".getBytes()); + } + + public static void writeResponse(OutputStream str, int code, String name, String type, byte[] data) throws IOException { + writeCode(str, code, name); + writeHeader(str, "Content-Type", type); + writeLastHeader(str, "Content-Length", data.length + ""); + str.write(data); + str.close(); + } + + public static HttpRequest readRequest(InputStream str) throws IOException { + var lines = new BufferedReader(new InputStreamReader(str)); + var line = lines.readLine(); + var i1 = line.indexOf(" "); + var i2 = line.lastIndexOf(" "); + + var method = line.substring(0, i1).trim().toUpperCase(); + var path = line.substring(i1 + 1, i2).trim(); + var headers = new HashMap(); + + while (!(line = lines.readLine()).isEmpty()) { + var i = line.indexOf(":"); + if (i < 0) continue; + var name = line.substring(0, i).trim().toLowerCase(); + var value = line.substring(i + 1).trim(); + + if (name.length() == 0) continue; + headers.put(name, value); + } + + if (headers.containsKey("content-length")) { + try { + var i = Integer.parseInt(headers.get("content-length")); + str.skip(i); + } + catch (IllegalFormatException e) { /* ¯\_(ツ)_/¯ */ } + } + + return new HttpRequest(method, path, headers); + } +} diff --git a/src/me/topchetoeu/jscript/engine/debug/HttpRequest.java b/dead-code/debug/HttpRequest.java similarity index 95% rename from src/me/topchetoeu/jscript/engine/debug/HttpRequest.java rename to dead-code/debug/HttpRequest.java index e4a1ae6..3fa9708 100644 --- a/src/me/topchetoeu/jscript/engine/debug/HttpRequest.java +++ b/dead-code/debug/HttpRequest.java @@ -1,16 +1,16 @@ -package me.topchetoeu.jscript.engine.debug; - -import java.util.Map; - -public class HttpRequest { - public final String method; - public final String path; - public final Map headers; - - public HttpRequest(String method, String path, Map headers) { - this.method = method; - this.path = path; - this.headers = headers; - } -} - +package me.topchetoeu.jscript.engine.debug; + +import java.util.Map; + +public class HttpRequest { + public final String method; + public final String path; + public final Map headers; + + public HttpRequest(String method, String path, Map headers) { + this.method = method; + this.path = path; + this.headers = headers; + } +} + diff --git a/src/me/topchetoeu/jscript/engine/debug/V8Error.java b/dead-code/debug/V8Error.java similarity index 95% rename from src/me/topchetoeu/jscript/engine/debug/V8Error.java rename to dead-code/debug/V8Error.java index 778160f..854e1cd 100644 --- a/src/me/topchetoeu/jscript/engine/debug/V8Error.java +++ b/dead-code/debug/V8Error.java @@ -1,19 +1,19 @@ -package me.topchetoeu.jscript.engine.debug; - -import me.topchetoeu.jscript.json.JSON; -import me.topchetoeu.jscript.json.JSONMap; - -public class V8Error { - public final String message; - - public V8Error(String message) { - this.message = message; - } - - @Override - public String toString() { - return JSON.stringify(new JSONMap().set("error", new JSONMap() - .set("message", message) - )); - } -} +package me.topchetoeu.jscript.engine.debug; + +import me.topchetoeu.jscript.json.JSON; +import me.topchetoeu.jscript.json.JSONMap; + +public class V8Error { + public final String message; + + public V8Error(String message) { + this.message = message; + } + + @Override + public String toString() { + return JSON.stringify(new JSONMap().set("error", new JSONMap() + .set("message", message) + )); + } +} diff --git a/src/me/topchetoeu/jscript/engine/debug/V8Event.java b/dead-code/debug/V8Event.java similarity index 95% rename from src/me/topchetoeu/jscript/engine/debug/V8Event.java rename to dead-code/debug/V8Event.java index 437c337..a83e20b 100644 --- a/src/me/topchetoeu/jscript/engine/debug/V8Event.java +++ b/dead-code/debug/V8Event.java @@ -1,22 +1,22 @@ -package me.topchetoeu.jscript.engine.debug; - -import me.topchetoeu.jscript.json.JSON; -import me.topchetoeu.jscript.json.JSONMap; - -public class V8Event { - public final String name; - public final JSONMap params; - - public V8Event(String name, JSONMap params) { - this.name = name; - this.params = params; - } - - @Override - public String toString() { - return JSON.stringify(new JSONMap() - .set("method", name) - .set("params", params) - ); - } -} +package me.topchetoeu.jscript.engine.debug; + +import me.topchetoeu.jscript.json.JSON; +import me.topchetoeu.jscript.json.JSONMap; + +public class V8Event { + public final String name; + public final JSONMap params; + + public V8Event(String name, JSONMap params) { + this.name = name; + this.params = params; + } + + @Override + public String toString() { + return JSON.stringify(new JSONMap() + .set("method", name) + .set("params", params) + ); + } +} diff --git a/src/me/topchetoeu/jscript/engine/debug/V8Message.java b/dead-code/debug/V8Message.java similarity index 96% rename from src/me/topchetoeu/jscript/engine/debug/V8Message.java rename to dead-code/debug/V8Message.java index 1c5dfb2..25d4a5a 100644 --- a/src/me/topchetoeu/jscript/engine/debug/V8Message.java +++ b/dead-code/debug/V8Message.java @@ -1,50 +1,50 @@ -package me.topchetoeu.jscript.engine.debug; - -import java.util.Map; - -import me.topchetoeu.jscript.json.JSON; -import me.topchetoeu.jscript.json.JSONElement; -import me.topchetoeu.jscript.json.JSONMap; - -public class V8Message { - public final String name; - public final int id; - public final JSONMap params; - - public V8Message(String name, int id, Map params) { - this.name = name; - this.params = new JSONMap(params); - this.id = id; - } - public V8Result respond(JSONMap result) { - return new V8Result(id, result); - } - public V8Result respond() { - return new V8Result(id, new JSONMap()); - } - - public V8Message(JSONMap raw) { - if (!raw.isNumber("id")) throw new IllegalArgumentException("Expected number property 'id'."); - if (!raw.isString("method")) throw new IllegalArgumentException("Expected string property 'method'."); - - this.name = raw.string("method"); - this.id = (int)raw.number("id"); - this.params = raw.contains("params") ? raw.map("params") : new JSONMap(); - } - public V8Message(String raw) { - this(JSON.parse("json", raw).map()); - } - - public JSONMap toMap() { - var res = new JSONMap(); - return res; - } - @Override - public String toString() { - return JSON.stringify(new JSONMap() - .set("method", name) - .set("params", params) - .set("id", id) - ); - } -} +package me.topchetoeu.jscript.engine.debug; + +import java.util.Map; + +import me.topchetoeu.jscript.json.JSON; +import me.topchetoeu.jscript.json.JSONElement; +import me.topchetoeu.jscript.json.JSONMap; + +public class V8Message { + public final String name; + public final int id; + public final JSONMap params; + + public V8Message(String name, int id, Map params) { + this.name = name; + this.params = new JSONMap(params); + this.id = id; + } + public V8Result respond(JSONMap result) { + return new V8Result(id, result); + } + public V8Result respond() { + return new V8Result(id, new JSONMap()); + } + + public V8Message(JSONMap raw) { + if (!raw.isNumber("id")) throw new IllegalArgumentException("Expected number property 'id'."); + if (!raw.isString("method")) throw new IllegalArgumentException("Expected string property 'method'."); + + this.name = raw.string("method"); + this.id = (int)raw.number("id"); + this.params = raw.contains("params") ? raw.map("params") : new JSONMap(); + } + public V8Message(String raw) { + this(JSON.parse("json", raw).map()); + } + + public JSONMap toMap() { + var res = new JSONMap(); + return res; + } + @Override + public String toString() { + return JSON.stringify(new JSONMap() + .set("method", name) + .set("params", params) + .set("id", id) + ); + } +} diff --git a/src/me/topchetoeu/jscript/engine/debug/V8Result.java b/dead-code/debug/V8Result.java similarity index 95% rename from src/me/topchetoeu/jscript/engine/debug/V8Result.java rename to dead-code/debug/V8Result.java index e2b8bd3..f605cef 100644 --- a/src/me/topchetoeu/jscript/engine/debug/V8Result.java +++ b/dead-code/debug/V8Result.java @@ -1,22 +1,22 @@ -package me.topchetoeu.jscript.engine.debug; - -import me.topchetoeu.jscript.json.JSON; -import me.topchetoeu.jscript.json.JSONMap; - -public class V8Result { - public final int id; - public final JSONMap result; - - public V8Result(int id, JSONMap result) { - this.id = id; - this.result = result; - } - - @Override - public String toString() { - return JSON.stringify(new JSONMap() - .set("id", id) - .set("result", result) - ); - } -} +package me.topchetoeu.jscript.engine.debug; + +import me.topchetoeu.jscript.json.JSON; +import me.topchetoeu.jscript.json.JSONMap; + +public class V8Result { + public final int id; + public final JSONMap result; + + public V8Result(int id, JSONMap result) { + this.id = id; + this.result = result; + } + + @Override + public String toString() { + return JSON.stringify(new JSONMap() + .set("id", id) + .set("result", result) + ); + } +} diff --git a/src/me/topchetoeu/jscript/engine/debug/WebSocket.java b/dead-code/debug/WebSocket.java similarity index 96% rename from src/me/topchetoeu/jscript/engine/debug/WebSocket.java rename to dead-code/debug/WebSocket.java index b282d67..a237121 100644 --- a/src/me/topchetoeu/jscript/engine/debug/WebSocket.java +++ b/dead-code/debug/WebSocket.java @@ -1,185 +1,185 @@ -package me.topchetoeu.jscript.engine.debug; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.net.Socket; - -import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type; - -public class WebSocket implements AutoCloseable { - public long maxLength = 2000000; - - private Socket socket; - private boolean closed = false; - - private OutputStream out() throws IOException { - return socket.getOutputStream(); - } - private InputStream in() throws IOException { - return socket.getInputStream(); - } - - private long readLen(int byteLen) throws IOException { - long res = 0; - - if (byteLen == 126) { - res |= in().read() << 8; - res |= in().read(); - return res; - } - else if (byteLen == 127) { - res |= in().read() << 56; - res |= in().read() << 48; - res |= in().read() << 40; - res |= in().read() << 32; - res |= in().read() << 24; - res |= in().read() << 16; - res |= in().read() << 8; - res |= in().read(); - return res; - } - else return byteLen; - } - private byte[] readMask(boolean has) throws IOException { - if (has) { - return new byte[] { - (byte)in().read(), - (byte)in().read(), - (byte)in().read(), - (byte)in().read() - }; - } - else return new byte[4]; - } - - private void writeLength(long len) throws IOException { - if (len < 126) { - out().write((int)len); - } - else if (len < 0xFFFF) { - out().write(126); - out().write((int)(len >> 8) & 0xFF); - out().write((int)len & 0xFF); - } - else { - out().write(127); - out().write((int)(len >> 56) & 0xFF); - out().write((int)(len >> 48) & 0xFF); - out().write((int)(len >> 40) & 0xFF); - out().write((int)(len >> 32) & 0xFF); - out().write((int)(len >> 24) & 0xFF); - out().write((int)(len >> 16) & 0xFF); - out().write((int)(len >> 8) & 0xFF); - out().write((int)len & 0xFF); - } - } - private synchronized void write(int type, byte[] data) throws IOException { - out().write(type | 0x80); - writeLength(data.length); - for (int i = 0; i < data.length; i++) { - out().write(data[i]); - } - } - - public void send(String data) throws IOException { - if (closed) throw new IllegalStateException("Object is closed."); - write(1, data.getBytes()); - } - public void send(byte[] data) throws IOException { - if (closed) throw new IllegalStateException("Object is closed."); - write(2, data); - } - public void send(WebSocketMessage msg) throws IOException { - if (msg.type == Type.Binary) send(msg.binaryData()); - else send(msg.textData()); - } - public void send(Object data) throws IOException { - if (closed) throw new IllegalStateException("Object is closed."); - write(1, data.toString().getBytes()); - } - - public void close(String reason) { - if (socket != null) { - try { write(8, reason.getBytes()); } catch (IOException e) { /* ¯\_(ツ)_/¯ */ } - try { socket.close(); } catch (IOException e) { e.printStackTrace(); } - } - - socket = null; - closed = true; - } - public void close() { - close(""); - } - - private WebSocketMessage fail(String reason) { - System.out.println("WebSocket Error: " + reason); - close(reason); - return null; - } - - private byte[] readData() throws IOException { - var maskLen = in().read(); - var hasMask = (maskLen & 0x80) != 0; - var len = (int)readLen(maskLen & 0x7F); - var mask = readMask(hasMask); - - if (len > maxLength) fail("WebSocket Error: client exceeded configured max message size"); - else { - var buff = new byte[len]; - - if (in().read(buff) < len) fail("WebSocket Error: payload too short"); - else { - for (int i = 0; i < len; i++) { - buff[i] ^= mask[(int)(i % 4)]; - } - return buff; - } - } - - return null; - } - - public WebSocketMessage receive() throws InterruptedException { - try { - var data = new ByteArrayOutputStream(); - var type = 0; - - while (socket != null && !closed) { - var finId = in().read(); - var fin = (finId & 0x80) != 0; - int id = finId & 0x0F; - - if (id == 0x8) { close(); return null; } - if (id >= 0x8) { - if (!fin) return fail("WebSocket Error: client-sent control frame was fragmented"); - if (id == 0x9) write(0xA, data.toByteArray()); - continue; - } - - if (type == 0) type = id; - if (type == 0) return fail("WebSocket Error: client used opcode 0x00 for first fragment"); - - var buff = readData(); - if (buff == null) break; - - if (data.size() + buff.length > maxLength) return fail("WebSocket Error: client exceeded configured max message size"); - data.write(buff); - - if (!fin) continue; - var raw = data.toByteArray(); - - if (type == 1) return new WebSocketMessage(new String(raw)); - else return new WebSocketMessage(raw); - } - } - catch (IOException e) { close(); } - - return null; - } - - public WebSocket(Socket socket) { - this.socket = socket; - } -} +package me.topchetoeu.jscript.engine.debug; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.Socket; + +import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type; + +public class WebSocket implements AutoCloseable { + public long maxLength = 2000000; + + private Socket socket; + private boolean closed = false; + + private OutputStream out() throws IOException { + return socket.getOutputStream(); + } + private InputStream in() throws IOException { + return socket.getInputStream(); + } + + private long readLen(int byteLen) throws IOException { + long res = 0; + + if (byteLen == 126) { + res |= in().read() << 8; + res |= in().read(); + return res; + } + else if (byteLen == 127) { + res |= in().read() << 56; + res |= in().read() << 48; + res |= in().read() << 40; + res |= in().read() << 32; + res |= in().read() << 24; + res |= in().read() << 16; + res |= in().read() << 8; + res |= in().read(); + return res; + } + else return byteLen; + } + private byte[] readMask(boolean has) throws IOException { + if (has) { + return new byte[] { + (byte)in().read(), + (byte)in().read(), + (byte)in().read(), + (byte)in().read() + }; + } + else return new byte[4]; + } + + private void writeLength(long len) throws IOException { + if (len < 126) { + out().write((int)len); + } + else if (len < 0xFFFF) { + out().write(126); + out().write((int)(len >> 8) & 0xFF); + out().write((int)len & 0xFF); + } + else { + out().write(127); + out().write((int)(len >> 56) & 0xFF); + out().write((int)(len >> 48) & 0xFF); + out().write((int)(len >> 40) & 0xFF); + out().write((int)(len >> 32) & 0xFF); + out().write((int)(len >> 24) & 0xFF); + out().write((int)(len >> 16) & 0xFF); + out().write((int)(len >> 8) & 0xFF); + out().write((int)len & 0xFF); + } + } + private synchronized void write(int type, byte[] data) throws IOException { + out().write(type | 0x80); + writeLength(data.length); + for (int i = 0; i < data.length; i++) { + out().write(data[i]); + } + } + + public void send(String data) throws IOException { + if (closed) throw new IllegalStateException("Object is closed."); + write(1, data.getBytes()); + } + public void send(byte[] data) throws IOException { + if (closed) throw new IllegalStateException("Object is closed."); + write(2, data); + } + public void send(WebSocketMessage msg) throws IOException { + if (msg.type == Type.Binary) send(msg.binaryData()); + else send(msg.textData()); + } + public void send(Object data) throws IOException { + if (closed) throw new IllegalStateException("Object is closed."); + write(1, data.toString().getBytes()); + } + + public void close(String reason) { + if (socket != null) { + try { write(8, reason.getBytes()); } catch (IOException e) { /* ¯\_(ツ)_/¯ */ } + try { socket.close(); } catch (IOException e) { e.printStackTrace(); } + } + + socket = null; + closed = true; + } + public void close() { + close(""); + } + + private WebSocketMessage fail(String reason) { + System.out.println("WebSocket Error: " + reason); + close(reason); + return null; + } + + private byte[] readData() throws IOException { + var maskLen = in().read(); + var hasMask = (maskLen & 0x80) != 0; + var len = (int)readLen(maskLen & 0x7F); + var mask = readMask(hasMask); + + if (len > maxLength) fail("WebSocket Error: client exceeded configured max message size"); + else { + var buff = new byte[len]; + + if (in().read(buff) < len) fail("WebSocket Error: payload too short"); + else { + for (int i = 0; i < len; i++) { + buff[i] ^= mask[(int)(i % 4)]; + } + return buff; + } + } + + return null; + } + + public WebSocketMessage receive() throws InterruptedException { + try { + var data = new ByteArrayOutputStream(); + var type = 0; + + while (socket != null && !closed) { + var finId = in().read(); + var fin = (finId & 0x80) != 0; + int id = finId & 0x0F; + + if (id == 0x8) { close(); return null; } + if (id >= 0x8) { + if (!fin) return fail("WebSocket Error: client-sent control frame was fragmented"); + if (id == 0x9) write(0xA, data.toByteArray()); + continue; + } + + if (type == 0) type = id; + if (type == 0) return fail("WebSocket Error: client used opcode 0x00 for first fragment"); + + var buff = readData(); + if (buff == null) break; + + if (data.size() + buff.length > maxLength) return fail("WebSocket Error: client exceeded configured max message size"); + data.write(buff); + + if (!fin) continue; + var raw = data.toByteArray(); + + if (type == 1) return new WebSocketMessage(new String(raw)); + else return new WebSocketMessage(raw); + } + } + catch (IOException e) { close(); } + + return null; + } + + public WebSocket(Socket socket) { + this.socket = socket; + } +} diff --git a/src/me/topchetoeu/jscript/engine/debug/WebSocketMessage.java b/dead-code/debug/WebSocketMessage.java similarity index 96% rename from src/me/topchetoeu/jscript/engine/debug/WebSocketMessage.java rename to dead-code/debug/WebSocketMessage.java index beb06de..e0b82b5 100644 --- a/src/me/topchetoeu/jscript/engine/debug/WebSocketMessage.java +++ b/dead-code/debug/WebSocketMessage.java @@ -1,29 +1,29 @@ -package me.topchetoeu.jscript.engine.debug; - -public class WebSocketMessage { - public static enum Type { - Text, - Binary, - } - - public final Type type; - private final Object data; - - public final String textData() { - if (type != Type.Text) throw new IllegalStateException("Message is not text."); - return (String)data; - } - public final byte[] binaryData() { - if (type != Type.Binary) throw new IllegalStateException("Message is not binary."); - return (byte[])data; - } - - public WebSocketMessage(String data) { - this.type = Type.Text; - this.data = data; - } - public WebSocketMessage(byte[] data) { - this.type = Type.Binary; - this.data = data; - } -} +package me.topchetoeu.jscript.engine.debug; + +public class WebSocketMessage { + public static enum Type { + Text, + Binary, + } + + public final Type type; + private final Object data; + + public final String textData() { + if (type != Type.Text) throw new IllegalStateException("Message is not text."); + return (String)data; + } + public final byte[] binaryData() { + if (type != Type.Binary) throw new IllegalStateException("Message is not binary."); + return (byte[])data; + } + + public WebSocketMessage(String data) { + this.type = Type.Text; + this.data = data; + } + public WebSocketMessage(byte[] data) { + this.type = Type.Binary; + this.data = data; + } +} diff --git a/src/me/topchetoeu/jscript/engine/debug/handlers/DebuggerHandles.java b/dead-code/debug/handlers/DebuggerHandles.java similarity index 97% rename from src/me/topchetoeu/jscript/engine/debug/handlers/DebuggerHandles.java rename to dead-code/debug/handlers/DebuggerHandles.java index fce42c3..a329c5d 100644 --- a/src/me/topchetoeu/jscript/engine/debug/handlers/DebuggerHandles.java +++ b/dead-code/debug/handlers/DebuggerHandles.java @@ -1,29 +1,29 @@ -package me.topchetoeu.jscript.engine.debug.handlers; - -import java.io.IOException; - -import me.topchetoeu.jscript.engine.DebugCommand; -import me.topchetoeu.jscript.engine.Engine; -import me.topchetoeu.jscript.engine.debug.V8Error; -import me.topchetoeu.jscript.engine.debug.V8Message; -import me.topchetoeu.jscript.engine.debug.WebSocket; -import me.topchetoeu.jscript.json.JSONMap; - -public class DebuggerHandles { - public static void enable(V8Message msg, Engine engine, WebSocket ws) throws IOException { - if (engine.debugState == null) ws.send(new V8Error("Debugging is disabled for this engine.")); - else ws.send(msg.respond(new JSONMap().set("debuggerId", 1))); - } - public static void disable(V8Message msg, Engine engine, WebSocket ws) throws IOException { - if (engine.debugState == null) ws.send(msg.respond()); - else ws.send(new V8Error("Debugger may not be disabled.")); - } - public static void stepInto(V8Message msg, Engine engine, WebSocket ws) throws IOException { - if (engine.debugState == null) ws.send(new V8Error("Debugging is disabled for this engine.")); - else if (!engine.debugState.paused()) ws.send(new V8Error("Debugger is not paused.")); - else { - engine.debugState.resume(DebugCommand.STEP_INTO); - ws.send(msg.respond()); - } - } -} +package me.topchetoeu.jscript.engine.debug.handlers; + +import java.io.IOException; + +import me.topchetoeu.jscript.engine.DebugCommand; +import me.topchetoeu.jscript.engine.Engine; +import me.topchetoeu.jscript.engine.debug.V8Error; +import me.topchetoeu.jscript.engine.debug.V8Message; +import me.topchetoeu.jscript.engine.debug.WebSocket; +import me.topchetoeu.jscript.json.JSONMap; + +public class DebuggerHandles { + public static void enable(V8Message msg, Engine engine, WebSocket ws) throws IOException { + if (engine.debugState == null) ws.send(new V8Error("Debugging is disabled for this engine.")); + else ws.send(msg.respond(new JSONMap().set("debuggerId", 1))); + } + public static void disable(V8Message msg, Engine engine, WebSocket ws) throws IOException { + if (engine.debugState == null) ws.send(msg.respond()); + else ws.send(new V8Error("Debugger may not be disabled.")); + } + public static void stepInto(V8Message msg, Engine engine, WebSocket ws) throws IOException { + if (engine.debugState == null) ws.send(new V8Error("Debugging is disabled for this engine.")); + else if (!engine.debugState.paused()) ws.send(new V8Error("Debugger is not paused.")); + else { + engine.debugState.resume(DebugCommand.STEP_INTO); + ws.send(msg.respond()); + } + } +} diff --git a/src/me/topchetoeu/jscript/engine/modules/FileModuleProvider.java b/dead-code/modules/FileModuleProvider.java similarity index 97% rename from src/me/topchetoeu/jscript/engine/modules/FileModuleProvider.java rename to dead-code/modules/FileModuleProvider.java index 59c1f41..ac9f4f6 100644 --- a/src/me/topchetoeu/jscript/engine/modules/FileModuleProvider.java +++ b/dead-code/modules/FileModuleProvider.java @@ -1,50 +1,50 @@ -package me.topchetoeu.jscript.engine.modules; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.nio.file.Path; - -import me.topchetoeu.jscript.polyfills.PolyfillEngine; - -public class FileModuleProvider implements ModuleProvider { - public File root; - public final boolean allowOutside; - - private boolean checkInside(Path modFile) { - return modFile.toAbsolutePath().startsWith(root.toPath().toAbsolutePath()); - } - - @Override - public Module getModule(File cwd, String name) { - var realName = getRealName(cwd, name); - if (realName == null) return null; - var path = Path.of(realName + ".js").normalize(); - - try { - var res = PolyfillEngine.streamToString(new FileInputStream(path.toFile())); - return new Module(realName, path.toString(), res); - } - catch (IOException e) { - return null; - } - } - @Override - public String getRealName(File cwd, String name) { - var path = Path.of(".", Path.of(cwd.toString(), name).normalize().toString()); - var fileName = path.getFileName().toString(); - if (fileName == null) return null; - if (!fileName.equals("index") && path.toFile().isDirectory()) return getRealName(cwd, name + "/index"); - path = Path.of(path.toString() + ".js"); - if (!allowOutside && !checkInside(path)) return null; - if (!path.toFile().isFile() || !path.toFile().canRead()) return null; - var res = path.toString().replace('\\', '/'); - var i = res.lastIndexOf('.'); - return res.substring(0, i); - } - - public FileModuleProvider(File root, boolean allowOutside) { - this.root = root.toPath().normalize().toFile(); - this.allowOutside = allowOutside; - } -} +package me.topchetoeu.jscript.engine.modules; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.nio.file.Path; + +import me.topchetoeu.jscript.polyfills.PolyfillEngine; + +public class FileModuleProvider implements ModuleProvider { + public File root; + public final boolean allowOutside; + + private boolean checkInside(Path modFile) { + return modFile.toAbsolutePath().startsWith(root.toPath().toAbsolutePath()); + } + + @Override + public Module getModule(File cwd, String name) { + var realName = getRealName(cwd, name); + if (realName == null) return null; + var path = Path.of(realName + ".js").normalize(); + + try { + var res = PolyfillEngine.streamToString(new FileInputStream(path.toFile())); + return new Module(realName, path.toString(), res); + } + catch (IOException e) { + return null; + } + } + @Override + public String getRealName(File cwd, String name) { + var path = Path.of(".", Path.of(cwd.toString(), name).normalize().toString()); + var fileName = path.getFileName().toString(); + if (fileName == null) return null; + if (!fileName.equals("index") && path.toFile().isDirectory()) return getRealName(cwd, name + "/index"); + path = Path.of(path.toString() + ".js"); + if (!allowOutside && !checkInside(path)) return null; + if (!path.toFile().isFile() || !path.toFile().canRead()) return null; + var res = path.toString().replace('\\', '/'); + var i = res.lastIndexOf('.'); + return res.substring(0, i); + } + + public FileModuleProvider(File root, boolean allowOutside) { + this.root = root.toPath().normalize().toFile(); + this.allowOutside = allowOutside; + } +} diff --git a/src/me/topchetoeu/jscript/engine/modules/Module.java b/dead-code/modules/Module.java similarity index 97% rename from src/me/topchetoeu/jscript/engine/modules/Module.java rename to dead-code/modules/Module.java index 44bd681..f6883d3 100644 --- a/src/me/topchetoeu/jscript/engine/modules/Module.java +++ b/dead-code/modules/Module.java @@ -1,57 +1,57 @@ -package me.topchetoeu.jscript.engine.modules; - -import java.io.File; - -import me.topchetoeu.jscript.engine.CallContext; -import me.topchetoeu.jscript.engine.CallContext.DataKey; -import me.topchetoeu.jscript.engine.scope.Variable; -import me.topchetoeu.jscript.engine.values.ObjectValue; -import me.topchetoeu.jscript.interop.NativeGetter; -import me.topchetoeu.jscript.interop.NativeSetter; - -public class Module { - public class ExportsVariable implements Variable { - @Override - public boolean readonly() { return false; } - @Override - public Object get(CallContext ctx) { return exports; } - @Override - public void set(CallContext ctx, Object val) { exports = val; } - } - - public static DataKey KEY = new DataKey<>(); - - public final String filename; - public final String source; - public final String name; - private Object exports = new ObjectValue(); - private boolean executing = false; - - @NativeGetter("name") - public String name() { return name; } - @NativeGetter("exports") - public Object exports() { return exports; } - @NativeSetter("exports") - public void setExports(Object val) { exports = val; } - - public void execute(CallContext ctx) throws InterruptedException { - if (executing) return; - - executing = true; - var scope = ctx.engine().global().globalChild(); - scope.define(null, "module", true, this); - scope.define("exports", new ExportsVariable()); - - var parent = new File(filename).getParentFile(); - if (parent == null) parent = new File("."); - - ctx.engine().compile(scope, filename, source).call(ctx.copy().setData(KEY, this), null); - executing = false; - } - - public Module(String name, String filename, String source) { - this.name = name; - this.filename = filename; - this.source = source; - } -} +package me.topchetoeu.jscript.engine.modules; + +import java.io.File; + +import me.topchetoeu.jscript.engine.CallContext; +import me.topchetoeu.jscript.engine.CallContext.DataKey; +import me.topchetoeu.jscript.engine.scope.Variable; +import me.topchetoeu.jscript.engine.values.ObjectValue; +import me.topchetoeu.jscript.interop.NativeGetter; +import me.topchetoeu.jscript.interop.NativeSetter; + +public class Module { + public class ExportsVariable implements Variable { + @Override + public boolean readonly() { return false; } + @Override + public Object get(CallContext ctx) { return exports; } + @Override + public void set(CallContext ctx, Object val) { exports = val; } + } + + public static DataKey KEY = new DataKey<>(); + + public final String filename; + public final String source; + public final String name; + private Object exports = new ObjectValue(); + private boolean executing = false; + + @NativeGetter("name") + public String name() { return name; } + @NativeGetter("exports") + public Object exports() { return exports; } + @NativeSetter("exports") + public void setExports(Object val) { exports = val; } + + public void execute(CallContext ctx) throws InterruptedException { + if (executing) return; + + executing = true; + var scope = ctx.engine().global().globalChild(); + scope.define(null, "module", true, this); + scope.define("exports", new ExportsVariable()); + + var parent = new File(filename).getParentFile(); + if (parent == null) parent = new File("."); + + ctx.engine().compile(scope, filename, source).call(ctx.copy().setData(KEY, this), null); + executing = false; + } + + public Module(String name, String filename, String source) { + this.name = name; + this.filename = filename; + this.source = source; + } +} diff --git a/src/me/topchetoeu/jscript/engine/modules/ModuleManager.java b/dead-code/modules/ModuleManager.java similarity index 96% rename from src/me/topchetoeu/jscript/engine/modules/ModuleManager.java rename to dead-code/modules/ModuleManager.java index 4def5e1..ab5c20a 100644 --- a/src/me/topchetoeu/jscript/engine/modules/ModuleManager.java +++ b/dead-code/modules/ModuleManager.java @@ -1,80 +1,80 @@ -package me.topchetoeu.jscript.engine.modules; - -import java.io.File; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -import me.topchetoeu.jscript.engine.CallContext; - -public class ModuleManager { - private final List providers = new ArrayList<>(); - private final HashMap cache = new HashMap<>(); - public final FileModuleProvider files; - - public void addProvider(ModuleProvider provider) { - this.providers.add(provider); - } - - public boolean isCached(File cwd, String name) { - name = name.replace("\\", "/"); - - // Absolute paths are forbidden - if (name.startsWith("/")) return false; - // Look for files if we have a relative path - if (name.startsWith("../") || name.startsWith("./")) { - var realName = files.getRealName(cwd, name); - if (cache.containsKey(realName)) return true; - else return false; - } - - for (var provider : providers) { - var realName = provider.getRealName(cwd, name); - if (realName == null) continue; - if (cache.containsKey(realName)) return true; - } - - return false; - } - public Module tryLoad(CallContext ctx, String name) throws InterruptedException { - name = name.replace('\\', '/'); - - var pcwd = Path.of("."); - - if (ctx.hasData(Module.KEY)) { - pcwd = Path.of(((Module)ctx.getData(Module.KEY)).filename).getParent(); - if (pcwd == null) pcwd = Path.of("."); - } - - - var cwd = pcwd.toFile(); - - if (name.startsWith("/")) return null; - if (name.startsWith("../") || name.startsWith("./")) { - var realName = files.getRealName(cwd, name); - if (realName == null) return null; - if (cache.containsKey(realName)) return cache.get(realName); - var mod = files.getModule(cwd, name); - cache.put(mod.name(), mod); - mod.execute(ctx); - return mod; - } - - for (var provider : providers) { - var realName = provider.getRealName(cwd, name); - if (realName == null) continue; - if (cache.containsKey(realName)) return cache.get(realName); - var mod = provider.getModule(cwd, name); - cache.put(mod.name(), mod); - mod.execute(ctx); - return mod; - } - - return null; - } - - public ModuleManager(File root) { - files = new FileModuleProvider(root, false); - } -} +package me.topchetoeu.jscript.engine.modules; + +import java.io.File; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +import me.topchetoeu.jscript.engine.CallContext; + +public class ModuleManager { + private final List providers = new ArrayList<>(); + private final HashMap cache = new HashMap<>(); + public final FileModuleProvider files; + + public void addProvider(ModuleProvider provider) { + this.providers.add(provider); + } + + public boolean isCached(File cwd, String name) { + name = name.replace("\\", "/"); + + // Absolute paths are forbidden + if (name.startsWith("/")) return false; + // Look for files if we have a relative path + if (name.startsWith("../") || name.startsWith("./")) { + var realName = files.getRealName(cwd, name); + if (cache.containsKey(realName)) return true; + else return false; + } + + for (var provider : providers) { + var realName = provider.getRealName(cwd, name); + if (realName == null) continue; + if (cache.containsKey(realName)) return true; + } + + return false; + } + public Module tryLoad(CallContext ctx, String name) throws InterruptedException { + name = name.replace('\\', '/'); + + var pcwd = Path.of("."); + + if (ctx.hasData(Module.KEY)) { + pcwd = Path.of(((Module)ctx.getData(Module.KEY)).filename).getParent(); + if (pcwd == null) pcwd = Path.of("."); + } + + + var cwd = pcwd.toFile(); + + if (name.startsWith("/")) return null; + if (name.startsWith("../") || name.startsWith("./")) { + var realName = files.getRealName(cwd, name); + if (realName == null) return null; + if (cache.containsKey(realName)) return cache.get(realName); + var mod = files.getModule(cwd, name); + cache.put(mod.name(), mod); + mod.execute(ctx); + return mod; + } + + for (var provider : providers) { + var realName = provider.getRealName(cwd, name); + if (realName == null) continue; + if (cache.containsKey(realName)) return cache.get(realName); + var mod = provider.getModule(cwd, name); + cache.put(mod.name(), mod); + mod.execute(ctx); + return mod; + } + + return null; + } + + public ModuleManager(File root) { + files = new FileModuleProvider(root, false); + } +} diff --git a/src/me/topchetoeu/jscript/engine/modules/ModuleProvider.java b/dead-code/modules/ModuleProvider.java similarity index 97% rename from src/me/topchetoeu/jscript/engine/modules/ModuleProvider.java rename to dead-code/modules/ModuleProvider.java index ad122c1..3305b15 100644 --- a/src/me/topchetoeu/jscript/engine/modules/ModuleProvider.java +++ b/dead-code/modules/ModuleProvider.java @@ -1,9 +1,9 @@ -package me.topchetoeu.jscript.engine.modules; - -import java.io.File; - -public interface ModuleProvider { - Module getModule(File cwd, String name); - String getRealName(File cwd, String name); - default boolean hasModule(File cwd, String name) { return getRealName(cwd, name) != null; } +package me.topchetoeu.jscript.engine.modules; + +import java.io.File; + +public interface ModuleProvider { + Module getModule(File cwd, String name); + String getRealName(File cwd, String name); + default boolean hasModule(File cwd, String name) { return getRealName(cwd, name) != null; } } \ No newline at end of file diff --git a/lib/core.ts b/lib/core.ts index 75d0ff8..f28ad57 100644 --- a/lib/core.ts +++ b/lib/core.ts @@ -1,68 +1,64 @@ -var env: Environment; +interface Environment { + global: typeof globalThis & Record; + proto(name: string): object; + setProto(name: string, val: object): void; +} +interface Internals { + markSpecial(...funcs: Function[]): void; + getEnv(func: Function): Environment | undefined; + setEnv(func: T, env: Environment): T; + apply(func: Function, thisArg: any, args: any[]): any; + delay(timeout: number, callback: Function): () => void; + pushMessage(micro: boolean, func: Function, thisArg: any, args: any[]): void; -// @ts-ignore -return (_env: Environment) => { - env = _env; - env.global.assert = (cond, msg) => { - try { - if (!cond()) throw 'condition not satisfied'; - log('Passed ' + msg); - return true; - } - catch (e) { - log('Failed ' + msg + ' because of: ' + e); - return false; - } - } - try { - run('values/object'); - run('values/symbol'); - run('values/function'); - run('values/errors'); - run('values/string'); - run('values/number'); - run('values/boolean'); - run('values/array'); + strlen(val: string): number; + char(val: string): number; + stringFromStrings(arr: string[]): string; + stringFromChars(arr: number[]): string; + symbol(name?: string): symbol; + symbolToString(sym: symbol): string; - env.internals.special(Object, Function, Error, Array); + isArray(obj: any): boolean; + generator(func: (_yield: (val: T) => unknown) => (...args: any[]) => unknown): GeneratorFunction; + defineField(obj: object, key: any, val: any, writable: boolean, enumerable: boolean, configurable: boolean): boolean; + defineProp(obj: object, key: any, get: Function | undefined, set: Function | undefined, enumerable: boolean, configurable: boolean): boolean; + keys(obj: object, onlyString: boolean): any[]; + ownProp(obj: any, key: string): PropertyDescriptor; + ownPropKeys(obj: any): any[]; + lock(obj: object, type: 'ext' | 'seal' | 'freeze'): void; + extensible(obj: object): boolean; - env.global.setTimeout = (func, delay, ...args) => { - if (typeof func !== 'function') throw new TypeError("func must be a function."); - delay = (delay ?? 0) - 0; - return env.internals.setTimeout(() => func(...args), delay) - }; - env.global.setInterval = (func, delay, ...args) => { - if (typeof func !== 'function') throw new TypeError("func must be a function."); - delay = (delay ?? 0) - 0; - return env.internals.setInterval(() => func(...args), delay) - }; - - env.global.clearTimeout = (id) => { - id = id | 0; - env.internals.clearTimeout(id); - }; - env.global.clearInterval = (id) => { - id = id | 0; - env.internals.clearInterval(id); - }; + sort(arr: any[], comaprator: (a: any, b: any) => number): void; +} + +var env: Environment = arguments[0], internals: Internals = arguments[1]; + +try { + run('values/object'); + run('values/symbol'); + run('values/function'); + run('values/errors'); + run('values/string'); + run('values/number'); + run('values/boolean'); + run('values/array'); + run('promise'); + run('map'); + run('set'); + run('regex'); + run('timeout'); - run('promise'); - run('map'); - run('set'); - run('regex'); - run('require'); - - log('Loaded polyfills!'); + log('Loaded polyfills!'); +} +catch (e: any) { + let err = 'Uncaught error while loading polyfills: '; + + if (typeof Error !== 'undefined' && e instanceof Error && e.toString !== {}.toString) err += e; + else if ('message' in e) { + if ('name' in e) err += e.name + ": " + e.message; + else err += 'Error: ' + e.message; } - catch (e: any) { - if (!_env.captureErr) throw e; - var err = 'Uncaught error while loading polyfills: '; - if (typeof Error !== 'undefined' && e instanceof Error && e.toString !== {}.toString) err += e; - else if ('message' in e) { - if ('name' in e) err += e.name + ": " + e.message; - else err += 'Error: ' + e.message; - } - else err += e; - log(e); - } -}; \ No newline at end of file + else err += e; + + log(e); +} \ No newline at end of file diff --git a/lib/lib.d.ts b/lib/lib.d.ts index e1c5c7d..4e83de2 100644 --- a/lib/lib.d.ts +++ b/lib/lib.d.ts @@ -46,8 +46,8 @@ type IteratorReturnResult = type IteratorResult = IteratorYieldResult | IteratorReturnResult; interface Thenable { - then(this: Promise, onFulfilled: PromiseThenFunc, onRejected?: PromiseRejectFunc): Promise>; - then(this: Promise, onFulfilled: undefined, onRejected?: PromiseRejectFunc): Promise; + then(onFulfilled: PromiseThenFunc, onRejected?: PromiseRejectFunc): Promise>; + then(onFulfilled: undefined, onRejected?: PromiseRejectFunc): Promise; } interface RegExpResultIndices extends Array<[number, number]> { @@ -100,7 +100,6 @@ interface IterableIterator extends Iterator { } interface AsyncIterator { - // NOTE: 'next' is defined using a tuple to ensure we report the correct assignability errors in all places. next(...args: [] | [TNext]): Promise>; return?(value?: TReturn | Thenable): Promise>; throw?(e?: any): Promise>; @@ -112,65 +111,29 @@ interface AsyncIterableIterator extends AsyncIterator { [Symbol.asyncIterator](): AsyncIterableIterator; } -interface Generator extends Iterator { +interface Generator extends Iterator { [Symbol.iterator](): Generator; - return(value?: TReturn): IteratorResult; - throw(e?: any): IteratorResult; + return(value: TReturn): IteratorResult; + throw(e: any): IteratorResult; } interface GeneratorFunction { - /** - * Creates a new Generator object. - * @param args A list of arguments the function accepts. - */ new (...args: any[]): Generator; - /** - * Creates a new Generator object. - * @param args A list of arguments the function accepts. - */ (...args: any[]): Generator; - /** - * The length of the arguments. - */ readonly length: number; - /** - * Returns the name of the function. - */ readonly name: string; - /** - * A reference to the prototype. - */ readonly prototype: Generator; } -interface AsyncGenerator extends AsyncIterator { - // NOTE: 'next' is defined using a tuple to ensure we report the correct assignability errors in all places. - next(...args: [] | [TNext]): Promise>; +interface AsyncGenerator extends AsyncIterator { return(value: TReturn | Thenable): Promise>; throw(e: any): Promise>; [Symbol.asyncIterator](): AsyncGenerator; } interface AsyncGeneratorFunction { - /** - * Creates a new AsyncGenerator object. - * @param args A list of arguments the function accepts. - */ new (...args: any[]): AsyncGenerator; - /** - * Creates a new AsyncGenerator object. - * @param args A list of arguments the function accepts. - */ (...args: any[]): AsyncGenerator; - /** - * The length of the arguments. - */ readonly length: number; - /** - * Returns the name of the function. - */ readonly name: string; - /** - * A reference to the prototype. - */ readonly prototype: AsyncGenerator; } @@ -225,7 +188,6 @@ interface MathObject { interface Array extends IterableIterator { [i: number]: T; - constructor: ArrayConstructor; length: number; toString(): string; @@ -283,7 +245,6 @@ interface ArrayConstructor { interface Boolean { valueOf(): boolean; - constructor: BooleanConstructor; } interface BooleanConstructor { (val: any): boolean; @@ -292,10 +253,10 @@ interface BooleanConstructor { } interface Error { - constructor: ErrorConstructor; name: string; message: string; stack: string[]; + toString(): string; } interface ErrorConstructor { (msg?: any): Error; @@ -309,7 +270,6 @@ interface TypeErrorConstructor extends ErrorConstructor { prototype: Error; } interface TypeError extends Error { - constructor: TypeErrorConstructor; name: 'TypeError'; } @@ -319,7 +279,6 @@ interface RangeErrorConstructor extends ErrorConstructor { prototype: Error; } interface RangeError extends Error { - constructor: RangeErrorConstructor; name: 'RangeError'; } @@ -329,7 +288,6 @@ interface SyntaxErrorConstructor extends ErrorConstructor { prototype: Error; } interface SyntaxError extends Error { - constructor: SyntaxErrorConstructor; name: 'SyntaxError'; } @@ -341,7 +299,6 @@ interface Function { toString(): string; prototype: any; - constructor: FunctionConstructor; readonly length: number; name: string; } @@ -375,7 +332,6 @@ interface FunctionConstructor extends Function { interface Number { toString(): string; valueOf(): number; - constructor: NumberConstructor; } interface NumberConstructor { (val: any): number; @@ -477,8 +433,6 @@ interface String { includes(term: string, start?: number): boolean; length: number; - - constructor: StringConstructor; } interface StringConstructor { (val: any): string; @@ -491,7 +445,6 @@ interface StringConstructor { interface Symbol { valueOf(): symbol; - constructor: SymbolConstructor; } interface SymbolConstructor { (val?: any): symbol; @@ -511,7 +464,6 @@ interface SymbolConstructor { } interface Promise extends Thenable { - constructor: PromiseConstructor; catch(func: PromiseRejectFunc): Promise; finally(func: () => void): Promise; } @@ -522,7 +474,8 @@ interface PromiseConstructor { resolve(val: T): Promise>; reject(val: any): Promise; - any(promises: (Promise|T)[]): Promise; + isAwaitable(val: unknown): val is Thenable; + any(promises: T[]): Promise>; race(promises: (Promise|T)[]): Promise; all(promises: T): Promise<{ [Key in keyof T]: Awaited }>; allSettled(...promises: T): Promise<[...{ [P in keyof T]: PromiseResult>}]>; @@ -544,7 +497,6 @@ declare var parseInt: typeof Number.parseInt; declare var parseFloat: typeof Number.parseFloat; declare function log(...vals: any[]): void; -declare function assert(condition: () => unknown, message?: string): boolean; declare var Array: ArrayConstructor; declare var Boolean: BooleanConstructor; diff --git a/lib/map.ts b/lib/map.ts index 2c45e0c..f318687 100644 --- a/lib/map.ts +++ b/lib/map.ts @@ -1,29 +1,92 @@ define("map", () => { - var Map = env.global.Map = env.internals.Map; + const syms = { values: internals.symbol('Map.values') } as { readonly values: unique symbol }; - Map.prototype[Symbol.iterator] = function() { - return this.entries(); - }; + class Map { + [syms.values]: any = {}; - var entries = Map.prototype.entries; - var keys = Map.prototype.keys; - var values = Map.prototype.values; + public [Symbol.iterator](): IterableIterator<[KeyT, ValueT]> { + return this.entries(); + } - Map.prototype.entries = function() { - var it = entries.call(this); - it[Symbol.iterator] = () => it; - return it; - }; - Map.prototype.keys = function() { - var it = keys.call(this); - it[Symbol.iterator] = () => it; - return it; - }; - Map.prototype.values = function() { - var it = values.call(this); - it[Symbol.iterator] = () => it; - return it; - }; + public clear() { + this[syms.values] = {}; + } + public delete(key: KeyT) { + if ((key as any) in this[syms.values]) { + delete this[syms.values]; + return true; + } + else return false; + } + + public entries(): IterableIterator<[KeyT, ValueT]> { + const keys = internals.ownPropKeys(this[syms.values]); + let i = 0; + + return { + next: () => { + if (i >= keys.length) return { done: true }; + else return { done: false, value: [ keys[i], this[syms.values][keys[i++]] ] } + }, + [Symbol.iterator]() { return this; } + } + } + public keys(): IterableIterator { + const keys = internals.ownPropKeys(this[syms.values]); + let i = 0; + + return { + next: () => { + if (i >= keys.length) return { done: true }; + else return { done: false, value: keys[i] } + }, + [Symbol.iterator]() { return this; } + } + } + public values(): IterableIterator { + const keys = internals.ownPropKeys(this[syms.values]); + let i = 0; + + return { + next: () => { + if (i >= keys.length) return { done: true }; + else return { done: false, value: this[syms.values][keys[i++]] } + }, + [Symbol.iterator]() { return this; } + } + } + + public get(key: KeyT) { + return this[syms.values][key]; + } + public set(key: KeyT, val: ValueT) { + this[syms.values][key] = val; + return this; + } + public has(key: KeyT) { + return (key as any) in this[syms.values][key]; + } + + public get size() { + return internals.ownPropKeys(this[syms.values]).length; + } + + public forEach(func: (key: KeyT, val: ValueT, map: Map) => void, thisArg?: any) { + const keys = internals.ownPropKeys(this[syms.values]); + + for (let i = 0; i < keys.length; i++) { + func(keys[i], this[syms.values][keys[i]], this); + } + } + + public constructor(iterable: Iterable<[KeyT, ValueT]>) { + const it = iterable[Symbol.iterator](); + + for (let el = it.next(); !el.done; el = it.next()) { + this[syms.values][el.value[0]] = el.value[1]; + } + } + } env.global.Map = Map; }); diff --git a/lib/modules.ts b/lib/modules.ts index 8eb68a0..c15e496 100644 --- a/lib/modules.ts +++ b/lib/modules.ts @@ -5,7 +5,8 @@ var { define, run } = (() => { modules[name] = func; } function run(name: string) { - return modules[name](); + if (typeof modules[name] === 'function') return modules[name](); + else throw "The module '" + name + "' doesn't exist."; } return { define, run }; diff --git a/lib/promise.ts b/lib/promise.ts index 9eb1404..903c7c2 100644 --- a/lib/promise.ts +++ b/lib/promise.ts @@ -1,3 +1,203 @@ define("promise", () => { - (env.global.Promise = env.internals.Promise).prototype[Symbol.typeName] = 'Promise'; + const syms = { + callbacks: internals.symbol('Promise.callbacks'), + state: internals.symbol('Promise.state'), + value: internals.symbol('Promise.value'), + handled: internals.symbol('Promise.handled'), + } as { + readonly callbacks: unique symbol, + readonly state: unique symbol, + readonly value: unique symbol, + readonly handled: unique symbol, + } + + type Callback = [ PromiseFulfillFunc, PromiseRejectFunc ]; + enum State { + Pending, + Fulfilled, + Rejected, + } + + function isAwaitable(val: unknown): val is Thenable { + return ( + typeof val === 'object' && + val !== null && + 'then' in val && + typeof val.then === 'function' + ); + } + function resolve(promise: Promise, v: any, state: State) { + if (promise[syms.state] === State.Pending) { + if (typeof v === 'object' && v !== null && 'then' in v && typeof v.then === 'function') { + v.then( + (res: any) => resolve(promise, res, state), + (res: any) => resolve(promise, res, State.Rejected) + ); + return; + } + promise[syms.value] = v; + promise[syms.state] = state; + + for (let i = 0; i < promise[syms.callbacks]!.length; i++) { + promise[syms.handled] = true; + promise[syms.callbacks]![i][state - 1](v); + } + + promise[syms.callbacks] = undefined; + + internals.pushMessage(true, internals.setEnv(() => { + if (!promise[syms.handled] && state === State.Rejected) { + log('Uncaught (in promise) ' + promise[syms.value]); + } + }, env), undefined, []); + } + } + + class Promise { + public static isAwaitable(val: unknown): val is Thenable { + return isAwaitable(val); + } + + public static resolve(val: T): Promise> { + return new Promise(res => res(val as any)); + } + public static reject(val: T): Promise> { + return new Promise((_, rej) => rej(val as any)); + } + + public static race(vals: T[]): Promise> { + if (typeof vals.length !== 'number') throw new TypeError('vals must be an array. Note that Promise.race is not variadic.'); + return new Promise((res, rej) => { + for (let i = 0; i < vals.length; i++) { + const val = vals[i]; + if (this.isAwaitable(val)) val.then(res, rej); + else res(val as any); + } + }); + } + public static any(vals: T[]): Promise> { + if (typeof vals.length !== 'number') throw new TypeError('vals must be an array. Note that Promise.any is not variadic.'); + return new Promise((res, rej) => { + let n = 0; + + for (let i = 0; i < vals.length; i++) { + const val = vals[i]; + if (this.isAwaitable(val)) val.then(res, (err) => { + n++; + if (n === vals.length) throw Error('No promise resolved.'); + }); + else res(val as any); + } + + if (vals.length === 0) throw Error('No promise resolved.'); + }); + } + public static all(vals: any[]): Promise { + if (typeof vals.length !== 'number') throw new TypeError('vals must be an array. Note that Promise.all is not variadic.'); + return new Promise((res, rej) => { + const result: any[] = []; + let n = 0; + + for (let i = 0; i < vals.length; i++) { + const val = vals[i]; + if (this.isAwaitable(val)) val.then( + val => { + n++; + result[i] = val; + if (n === vals.length) res(result); + }, + rej + ); + else { + n++; + result[i] = val; + } + } + + if (vals.length === n) res(result); + }); + } + public static allSettled(vals: any[]): Promise { + if (typeof vals.length !== 'number') throw new TypeError('vals must be an array. Note that Promise.allSettled is not variadic.'); + return new Promise((res, rej) => { + const result: any[] = []; + let n = 0; + + for (let i = 0; i < vals.length; i++) { + const value = vals[i]; + if (this.isAwaitable(value)) value.then( + value => { + n++; + result[i] = { status: 'fulfilled', value }; + if (n === vals.length) res(result); + }, + reason => { + n++; + result[i] = { status: 'rejected', reason }; + if (n === vals.length) res(result); + }, + ); + else { + n++; + result[i] = { status: 'fulfilled', value }; + } + } + + if (vals.length === n) res(result); + }); + } + + [syms.callbacks]?: Callback[] = []; + [syms.handled] = false; + [syms.state] = State.Pending; + [syms.value]?: T | unknown; + + public then(onFulfil?: PromiseFulfillFunc, onReject?: PromiseRejectFunc) { + return new Promise((resolve, reject) => { + onFulfil ??= v => v; + onReject ??= v => v; + + const callback = (func: (val: any) => any) => (v: any) => { + try { resolve(func(v)); } + catch (e) { reject(e); } + } + switch (this[syms.state]) { + case State.Pending: + this[syms.callbacks]![this[syms.callbacks]!.length] = [callback(onFulfil), callback(onReject)]; + break; + case State.Fulfilled: + this[syms.handled] = true; + callback(onFulfil)(this[syms.value]); + break; + case State.Rejected: + this[syms.handled] = true; + callback(onReject)(this[syms.value]); + break; + } + }) + } + public catch(func: PromiseRejectFunc) { + return this.then(undefined, func); + } + public finally(func: () => void) { + return this.then( + v => { + func(); + return v; + }, + v => { + func(); + throw v; + } + ); + } + + public constructor(func: PromiseFunc) { + internals.pushMessage(true, func, undefined, [ + ((v) => resolve(this, v, State.Fulfilled)) as PromiseFulfillFunc, + ((err) => resolve(this, err, State.Rejected)) as PromiseRejectFunc + ]); + } + } + env.global.Promise = Promise as any; }); diff --git a/lib/regex.ts b/lib/regex.ts index cb1217f..ed5ce84 100644 --- a/lib/regex.ts +++ b/lib/regex.ts @@ -1,143 +1,143 @@ define("regex", () => { - var RegExp = env.global.RegExp = env.internals.RegExp; + // var RegExp = env.global.RegExp = env.internals.RegExp; - setProps(RegExp.prototype as RegExp, env, { - [Symbol.typeName]: 'RegExp', + // setProps(RegExp.prototype as RegExp, env, { + // [Symbol.typeName]: 'RegExp', - test(val) { - return !!this.exec(val); - }, - toString() { - return '/' + this.source + '/' + this.flags; - }, + // test(val) { + // return !!this.exec(val); + // }, + // toString() { + // return '/' + this.source + '/' + this.flags; + // }, - [Symbol.match](target) { - if (this.global) { - const res: string[] = []; - let val; - while (val = this.exec(target)) { - res.push(val[0]); - } - this.lastIndex = 0; - return res; - } - else { - const res = this.exec(target); - if (!this.sticky) this.lastIndex = 0; - return res; - } - }, - [Symbol.matchAll](target) { - let pattern: RegExp | undefined = new this.constructor(this, this.flags + "g") as RegExp; + // [Symbol.match](target) { + // if (this.global) { + // const res: string[] = []; + // let val; + // while (val = this.exec(target)) { + // res.push(val[0]); + // } + // this.lastIndex = 0; + // return res; + // } + // else { + // const res = this.exec(target); + // if (!this.sticky) this.lastIndex = 0; + // return res; + // } + // }, + // [Symbol.matchAll](target) { + // let pattern: RegExp | undefined = new this.constructor(this, this.flags + "g") as RegExp; - return { - next: (): IteratorResult => { - const val = pattern?.exec(target); + // return { + // next: (): IteratorResult => { + // const val = pattern?.exec(target); - if (val === null || val === undefined) { - pattern = undefined; - return { done: true }; - } - else return { value: val }; - }, - [Symbol.iterator]() { return this; } - } - }, - [Symbol.split](target, limit, sensible) { - const pattern = new this.constructor(this, this.flags + "g") as RegExp; - let match: RegExpResult | null; - let lastEnd = 0; - const res: string[] = []; + // if (val === null || val === undefined) { + // pattern = undefined; + // return { done: true }; + // } + // else return { value: val }; + // }, + // [Symbol.iterator]() { return this; } + // } + // }, + // [Symbol.split](target, limit, sensible) { + // const pattern = new this.constructor(this, this.flags + "g") as RegExp; + // let match: RegExpResult | null; + // let lastEnd = 0; + // const res: string[] = []; - while ((match = pattern.exec(target)) !== null) { - let added: string[] = []; + // while ((match = pattern.exec(target)) !== null) { + // let added: string[] = []; - if (match.index >= target.length) break; + // if (match.index >= target.length) break; - if (match[0].length === 0) { - added = [ target.substring(lastEnd, pattern.lastIndex), ]; - if (pattern.lastIndex < target.length) added.push(...match.slice(1)); - } - else if (match.index - lastEnd > 0) { - added = [ target.substring(lastEnd, match.index), ...match.slice(1) ]; - } - else { - for (let i = 1; i < match.length; i++) { - res[res.length - match.length + i] = match[i]; - } - } + // if (match[0].length === 0) { + // added = [ target.substring(lastEnd, pattern.lastIndex), ]; + // if (pattern.lastIndex < target.length) added.push(...match.slice(1)); + // } + // else if (match.index - lastEnd > 0) { + // added = [ target.substring(lastEnd, match.index), ...match.slice(1) ]; + // } + // else { + // for (let i = 1; i < match.length; i++) { + // res[res.length - match.length + i] = match[i]; + // } + // } - if (sensible) { - if (limit !== undefined && res.length + added.length >= limit) break; - else res.push(...added); - } - else { - for (let i = 0; i < added.length; i++) { - if (limit !== undefined && res.length >= limit) return res; - else res.push(added[i]); - } - } + // if (sensible) { + // if (limit !== undefined && res.length + added.length >= limit) break; + // else res.push(...added); + // } + // else { + // for (let i = 0; i < added.length; i++) { + // if (limit !== undefined && res.length >= limit) return res; + // else res.push(added[i]); + // } + // } - lastEnd = pattern.lastIndex; - } + // lastEnd = pattern.lastIndex; + // } - if (lastEnd < target.length) { - res.push(target.substring(lastEnd)); - } + // if (lastEnd < target.length) { + // res.push(target.substring(lastEnd)); + // } - return res; - }, - [Symbol.replace](target, replacement) { - const pattern = new this.constructor(this, this.flags + "d") as RegExp; - let match: RegExpResult | null; - let lastEnd = 0; - const res: string[] = []; + // return res; + // }, + // [Symbol.replace](target, replacement) { + // const pattern = new this.constructor(this, this.flags + "d") as RegExp; + // let match: RegExpResult | null; + // let lastEnd = 0; + // const res: string[] = []; - // log(pattern.toString()); + // // log(pattern.toString()); - while ((match = pattern.exec(target)) !== null) { - const indices = match.indices![0]; - res.push(target.substring(lastEnd, indices[0])); - if (replacement instanceof Function) { - res.push(replacement(target.substring(indices[0], indices[1]), ...match.slice(1), indices[0], target)); - } - else { - res.push(replacement); - } - lastEnd = indices[1]; - if (!pattern.global) break; - } + // while ((match = pattern.exec(target)) !== null) { + // const indices = match.indices![0]; + // res.push(target.substring(lastEnd, indices[0])); + // if (replacement instanceof Function) { + // res.push(replacement(target.substring(indices[0], indices[1]), ...match.slice(1), indices[0], target)); + // } + // else { + // res.push(replacement); + // } + // lastEnd = indices[1]; + // if (!pattern.global) break; + // } - if (lastEnd < target.length) { - res.push(target.substring(lastEnd)); - } + // if (lastEnd < target.length) { + // res.push(target.substring(lastEnd)); + // } - return res.join(''); - }, - [Symbol.search](target, reverse, start) { - const pattern: RegExp | undefined = new this.constructor(this, this.flags + "g") as RegExp; + // return res.join(''); + // }, + // [Symbol.search](target, reverse, start) { + // const pattern: RegExp | undefined = new this.constructor(this, this.flags + "g") as RegExp; - if (!reverse) { - pattern.lastIndex = (start as any) | 0; - const res = pattern.exec(target); - if (res) return res.index; - else return -1; - } - else { - start ??= target.length; - start |= 0; - let res: RegExpResult | null = null; + // if (!reverse) { + // pattern.lastIndex = (start as any) | 0; + // const res = pattern.exec(target); + // if (res) return res.index; + // else return -1; + // } + // else { + // start ??= target.length; + // start |= 0; + // let res: RegExpResult | null = null; - while (true) { - const tmp = pattern.exec(target); - if (tmp === null || tmp.index > start) break; - res = tmp; - } + // while (true) { + // const tmp = pattern.exec(target); + // if (tmp === null || tmp.index > start) break; + // res = tmp; + // } - if (res && res.index <= start) return res.index; - else return -1; - } - }, - }); + // if (res && res.index <= start) return res.index; + // else return -1; + // } + // }, + // }); }); \ No newline at end of file diff --git a/lib/set.ts b/lib/set.ts index 3a21671..7bf9a19 100644 --- a/lib/set.ts +++ b/lib/set.ts @@ -1,26 +1,80 @@ define("set", () => { - var Set = env.global.Set = env.internals.Set; - Set.prototype[Symbol.iterator] = function() { - return this.values(); - }; + const syms = { values: internals.symbol('Map.values') } as { readonly values: unique symbol }; - var entries = Set.prototype.entries; - var keys = Set.prototype.keys; - var values = Set.prototype.values; + class Set { + [syms.values]: any = {}; - Set.prototype.entries = function() { - var it = entries.call(this); - it[Symbol.iterator] = () => it; - return it; - }; - Set.prototype.keys = function() { - var it = keys.call(this); - it[Symbol.iterator] = () => it; - return it; - }; - Set.prototype.values = function() { - var it = values.call(this); - it[Symbol.iterator] = () => it; - return it; - }; + public [Symbol.iterator](): IterableIterator<[T, T]> { + return this.entries(); + } + + public clear() { + this[syms.values] = {}; + } + public delete(key: T) { + if ((key as any) in this[syms.values]) { + delete this[syms.values]; + return true; + } + else return false; + } + + public entries(): IterableIterator<[T, T]> { + const keys = internals.ownPropKeys(this[syms.values]); + let i = 0; + + return { + next: () => { + if (i >= keys.length) return { done: true }; + else return { done: false, value: [ keys[i], keys[i] ] } + }, + [Symbol.iterator]() { return this; } + } + } + public keys(): IterableIterator { + const keys = internals.ownPropKeys(this[syms.values]); + let i = 0; + + return { + next: () => { + if (i >= keys.length) return { done: true }; + else return { done: false, value: keys[i] } + }, + [Symbol.iterator]() { return this; } + } + } + public values(): IterableIterator { + return this.keys(); + } + + public add(val: T) { + this[syms.values][val] = undefined; + return this; + } + public has(key: T) { + return (key as any) in this[syms.values][key]; + } + + public get size() { + return internals.ownPropKeys(this[syms.values]).length; + } + + public forEach(func: (key: T, val: T, map: Set) => void, thisArg?: any) { + const keys = internals.ownPropKeys(this[syms.values]); + + for (let i = 0; i < keys.length; i++) { + func(keys[i], this[syms.values][keys[i]], this); + } + } + + public constructor(iterable: Iterable) { + const it = iterable[Symbol.iterator](); + + for (let el = it.next(); !el.done; el = it.next()) { + this[syms.values][el.value] = undefined; + } + } + } + + env.global.Set = Set; }); diff --git a/lib/timeout.ts b/lib/timeout.ts new file mode 100644 index 0000000..8db82a0 --- /dev/null +++ b/lib/timeout.ts @@ -0,0 +1,38 @@ +define("timeout", () => { + const timeouts: Record void> = { }; + const intervals: Record void> = { }; + let timeoutI = 0, intervalI = 0; + + env.global.setTimeout = (func, delay, ...args) => { + if (typeof func !== 'function') throw new TypeError("func must be a function."); + delay = (delay ?? 0) - 0; + const cancelFunc = internals.delay(delay, () => internals.apply(func, undefined, args)); + timeouts[++timeoutI] = cancelFunc; + return timeoutI; + }; + env.global.setInterval = (func, delay, ...args) => { + if (typeof func !== 'function') throw new TypeError("func must be a function."); + delay = (delay ?? 0) - 0; + + const i = ++intervalI; + intervals[i] = internals.delay(delay, callback); + + return i; + + function callback() { + internals.apply(func, undefined, args); + intervals[i] = internals.delay(delay!, callback); + } + }; + + env.global.clearTimeout = (id) => { + const func = timeouts[id]; + if (func) func(); + timeouts[id] = undefined!; + }; + env.global.clearInterval = (id) => { + const func = intervals[id]; + if (func) func(); + intervals[id] = undefined!; + }; +}); \ No newline at end of file diff --git a/lib/tsconfig.json b/lib/tsconfig.json index 5172e2d..8c3e2af 100644 --- a/lib/tsconfig.json +++ b/lib/tsconfig.json @@ -15,6 +15,7 @@ "map.ts", "set.ts", "regex.ts", + "timeout.ts", "core.ts" ], "compilerOptions": { diff --git a/lib/utils.ts b/lib/utils.ts index 90fdba4..6fdf694 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -1,9 +1,3 @@ -interface Environment { - global: typeof globalThis & Record; - captureErr: boolean; - internals: any; -} - function setProps< TargetT extends object, DescT extends { @@ -11,11 +5,11 @@ function setProps< ((this: TargetT, ...args: ArgsT) => RetT) : TargetT[x] } ->(target: TargetT, env: Environment, desc: DescT) { - var props = env.internals.keys(desc, false); +>(target: TargetT, desc: DescT) { + var props = internals.keys(desc, false); for (var i = 0; i < props.length; i++) { var key = props[i]; - env.internals.defineField( + internals.defineField( target, key, (desc as any)[key], true, // writable false, // enumerable @@ -23,8 +17,8 @@ function setProps< ); } } -function setConstr(target: T, constr: ConstrT, env: Environment) { - env.internals.defineField( +function setConstr(target: object, constr: Function) { + internals.defineField( target, 'constructor', constr, true, // writable false, // enumerable diff --git a/lib/values/array.ts b/lib/values/array.ts index 43290b4..9eea6d7 100644 --- a/lib/values/array.ts +++ b/lib/values/array.ts @@ -11,20 +11,20 @@ define("values/array", () => { res[i] = arguments[i]; } } - + return res; } as ArrayConstructor; - - Array.prototype = ([] as any).__proto__ as Array; - setConstr(Array.prototype, Array, env); - + + env.setProto('array', Array.prototype); (Array.prototype as any)[Symbol.typeName] = "Array"; - - setProps(Array.prototype, env, { + setConstr(Array.prototype, Array); + + setProps(Array.prototype, { [Symbol.iterator]: function() { return this.values(); }, - + [Symbol.typeName]: "Array", + values() { var i = 0; @@ -67,7 +67,7 @@ define("values/array", () => { concat() { var res = [] as any[]; res.push.apply(res, this); - + for (var i = 0; i < arguments.length; i++) { var arg = arguments[i]; if (arg instanceof Array) { @@ -296,7 +296,7 @@ define("values/array", () => { if (typeof func !== 'function') throw new TypeError('Expected func to be undefined or a function.'); - env.internals.sort(this, func); + internals.sort(this, func); return this; }, splice(start, deleteCount, ...items) { @@ -328,8 +328,9 @@ define("values/array", () => { return res; } }); - - setProps(Array, env, { - isArray(val: any) { return env.internals.isArr(val); } + + setProps(Array, { + isArray(val: any) { return internals.isArray(val); } }); + internals.markSpecial(Array); }); \ No newline at end of file diff --git a/lib/values/boolean.ts b/lib/values/boolean.ts index a8868a4..c0d45a2 100644 --- a/lib/values/boolean.ts +++ b/lib/values/boolean.ts @@ -7,6 +7,6 @@ define("values/boolean", () => { else (this as any).value = val; } as BooleanConstructor; - Boolean.prototype = (false as any).__proto__ as Boolean; - setConstr(Boolean.prototype, Boolean, env); + env.setProto('bool', Boolean.prototype); + setConstr(Boolean.prototype, Boolean); }); diff --git a/lib/values/errors.ts b/lib/values/errors.ts index 95ad33f..4bfc7e5 100644 --- a/lib/values/errors.ts +++ b/lib/values/errors.ts @@ -8,36 +8,39 @@ define("values/errors", () => { stack: [] as string[], }, Error.prototype); } as ErrorConstructor; - - Error.prototype = env.internals.err ?? {}; - Error.prototype.name = 'Error'; - setConstr(Error.prototype, Error, env); - Error.prototype.toString = function() { - if (!(this instanceof Error)) return ''; - - if (this.message === '') return this.name; - else return this.name + ': ' + this.message; - }; + setConstr(Error.prototype, Error); + setProps(Error.prototype, { + name: 'Error', + toString: internals.setEnv(function(this: Error) { + if (!(this instanceof Error)) return ''; + + if (this.message === '') return this.name; + else return this.name + ': ' + this.message; + }, env) + }); + env.setProto('error', Error.prototype); + internals.markSpecial(Error); - function makeError(name: string, proto: any): T { - var err = function (msg: string) { + function makeError(name: string, proto: string): T1 { + function constr (msg: string) { var res = new Error(msg); - (res as any).__proto__ = err.prototype; + (res as any).__proto__ = constr.prototype; return res; - } as T; + } - err.prototype = proto; - err.prototype.name = name; - setConstr(err.prototype, err as ErrorConstructor, env); - (err.prototype as any).__proto__ = Error.prototype; - (err as any).__proto__ = Error; - env.internals.special(err); + (constr as any).__proto__ = Error; + (constr.prototype as any).__proto__ = env.proto('error'); + setConstr(constr.prototype, constr as ErrorConstructor); + setProps(constr.prototype, { name: name }); - return err; + internals.markSpecial(constr); + env.setProto(proto, constr.prototype); + + return constr as T1; } - - env.global.RangeError = makeError('RangeError', env.internals.range ?? {}); - env.global.TypeError = makeError('TypeError', env.internals.type ?? {}); - env.global.SyntaxError = makeError('SyntaxError', env.internals.syntax ?? {}); + + env.global.RangeError = makeError('RangeError', 'rangeErr'); + env.global.TypeError = makeError('TypeError', 'typeErr'); + env.global.SyntaxError = makeError('SyntaxError', 'syntaxErr'); }); \ No newline at end of file diff --git a/lib/values/function.ts b/lib/values/function.ts index e00f5c9..c49a4f2 100644 --- a/lib/values/function.ts +++ b/lib/values/function.ts @@ -3,15 +3,15 @@ define("values/function", () => { throw 'Using the constructor Function() is forbidden.'; } as unknown as FunctionConstructor; - Function.prototype = (Function as any).__proto__ as Function; - setConstr(Function.prototype, Function, env); + env.setProto('function', Function.prototype); + setConstr(Function.prototype, Function); - setProps(Function.prototype, env, { + setProps(Function.prototype, { apply(thisArg, args) { if (typeof args !== 'object') throw 'Expected arguments to be an array-like object.'; var len = args.length - 0; let newArgs: any[]; - if (Array.isArray(args)) newArgs = args; + if (internals.isArray(args)) newArgs = args; else { newArgs = []; @@ -21,21 +21,20 @@ define("values/function", () => { } } - return env.internals.apply(this, thisArg, newArgs); + return internals.apply(this, thisArg, newArgs); }, call(thisArg, ...args) { return this.apply(thisArg, args); }, bind(thisArg, ...args) { - var func = this; + const func = this; + const res = function() { + const resArgs = []; - var res = function() { - var resArgs = []; - - for (var i = 0; i < args.length; i++) { + for (let i = 0; i < args.length; i++) { resArgs[i] = args[i]; } - for (var i = 0; i < arguments.length; i++) { + for (let i = 0; i < arguments.length; i++) { resArgs[i + args.length] = arguments[i]; } @@ -48,7 +47,7 @@ define("values/function", () => { return 'function (...) { ... }'; }, }); - setProps(Function, env, { + setProps(Function, { async(func) { if (typeof func !== 'function') throw new TypeError('Expected func to be function.'); @@ -56,7 +55,7 @@ define("values/function", () => { const args = arguments; return new Promise((res, rej) => { - const gen = Function.generator(func as any).apply(this, args as any); + const gen = internals.apply(internals.generator(func as any), this, args as any); (function next(type: 'none' | 'err' | 'ret', val?: any) { try { @@ -83,11 +82,11 @@ define("values/function", () => { asyncGenerator(func) { if (typeof func !== 'function') throw new TypeError('Expected func to be function.'); - return function(this: any) { - const gen = Function.generator((_yield) => func( + return function(this: any, ...args: any[]) { + const gen = internals.apply(internals.generator((_yield) => func( val => _yield(['await', val]) as any, val => _yield(['yield', val]) - )).apply(this, arguments as any); + )), this, args) as Generator<['await' | 'yield', any]>; const next = (resolve: Function, reject: Function, type: 'none' | 'val' | 'ret' | 'err', val?: any) => { let res; @@ -124,17 +123,18 @@ define("values/function", () => { }, generator(func) { if (typeof func !== 'function') throw new TypeError('Expected func to be function.'); - const gen = env.internals.makeGenerator(func); - return (...args: any[]) => { - const it = gen(args); + const gen = internals.generator(func); + return function(this: any, ...args: any[]) { + const it = internals.apply(gen, this, args); return { - next: it.next, - return: it.return, - throw: it.throw, + next: (...args) => internals.apply(it.next, it, args), + return: (val) => internals.apply(it.next, it, [val]), + throw: (val) => internals.apply(it.next, it, [val]), [Symbol.iterator]() { return this; } } } } }) + internals.markSpecial(Function); }); \ No newline at end of file diff --git a/lib/values/number.ts b/lib/values/number.ts index 11366a4..57e8d48 100644 --- a/lib/values/number.ts +++ b/lib/values/number.ts @@ -7,10 +7,10 @@ define("values/number", () => { else (this as any).value = val; } as NumberConstructor; - Number.prototype = (0 as any).__proto__ as Number; - setConstr(Number.prototype, Number, env); + env.setProto('number', Number.prototype); + setConstr(Number.prototype, Number); - setProps(Number.prototype, env, { + setProps(Number.prototype, { valueOf() { if (typeof this === 'number') return this; else return (this as any).value; @@ -21,13 +21,13 @@ define("values/number", () => { } }); - setProps(Number, env, { - parseInt(val) { return Math.trunc(Number.parseFloat(val)); }, - parseFloat(val) { return env.internals.parseFloat(val); }, + setProps(Number, { + parseInt(val) { return Math.trunc(val as any - 0); }, + parseFloat(val) { return val as any - 0; }, }); - env.global.Object.defineProperty(env.global, 'parseInt', { value: Number.parseInt, writable: false }); - env.global.Object.defineProperty(env.global, 'parseFloat', { value: Number.parseFloat, writable: false }); + env.global.parseInt = Number.parseInt; + env.global.parseFloat = Number.parseFloat; env.global.Object.defineProperty(env.global, 'NaN', { value: 0 / 0, writable: false }); env.global.Object.defineProperty(env.global, 'Infinity', { value: 1 / 0, writable: false }); }); \ No newline at end of file diff --git a/lib/values/object.ts b/lib/values/object.ts index 4e63a39..4c8cdc3 100644 --- a/lib/values/object.ts +++ b/lib/values/object.ts @@ -8,8 +8,9 @@ define("values/object", () => { return arg; } as ObjectConstructor; - Object.prototype = ({} as any).__proto__ as Object; - setConstr(Object.prototype, Object as any, env); + env.setProto('object', Object.prototype); + (Object.prototype as any).__proto__ = null; + setConstr(Object.prototype, Object as any); function throwNotObject(obj: any, name: string) { if (obj === null || typeof obj !== 'object' && typeof obj !== 'function') { @@ -20,8 +21,8 @@ define("values/object", () => { return typeof obj === 'object' && obj !== null || typeof obj === 'function'; } - setProps(Object, env, { - assign: function(dst, ...src) { + setProps(Object, { + assign(dst, ...src) { throwNotObject(dst, 'assign'); for (let i = 0; i < src.length; i++) { const obj = src[i]; @@ -40,10 +41,10 @@ define("values/object", () => { defineProperty(obj, key, attrib) { throwNotObject(obj, 'defineProperty'); if (typeof attrib !== 'object') throw new TypeError('Expected attributes to be an object.'); - + if ('value' in attrib) { if ('get' in attrib || 'set' in attrib) throw new TypeError('Cannot specify a value and accessors for a property.'); - if (!env.internals.defineField( + if (!internals.defineField( obj, key, attrib.value, !!attrib.writable, @@ -54,8 +55,8 @@ define("values/object", () => { else { if (typeof attrib.get !== 'function' && attrib.get !== undefined) throw new TypeError('Get accessor must be a function.'); if (typeof attrib.set !== 'function' && attrib.set !== undefined) throw new TypeError('Set accessor must be a function.'); - - if (!env.internals.defineProp( + + if (!internals.defineProp( obj, key, attrib.get, attrib.set, @@ -63,50 +64,87 @@ define("values/object", () => { !!attrib.configurable )) throw new TypeError('Can\'t define property \'' + key + '\'.'); } - + return obj; }, defineProperties(obj, attrib) { throwNotObject(obj, 'defineProperties'); if (typeof attrib !== 'object' && typeof attrib !== 'function') throw 'Expected second argument to be an object.'; - + for (var key in attrib) { Object.defineProperty(obj, key, attrib[key]); } - + return obj; }, keys(obj, onlyString) { - onlyString = !!(onlyString ?? true); - return env.internals.keys(obj, onlyString); + return internals.keys(obj, !!(onlyString ?? true)); }, entries(obj, onlyString) { - return Object.keys(obj, onlyString).map(v => [ v, (obj as any)[v] ]); + const res = []; + const keys = internals.keys(obj, !!(onlyString ?? true)); + + for (let i = 0; i < keys.length; i++) { + res[i] = [ keys[i], (obj as any)[keys[i]] ]; + } + + return keys; }, values(obj, onlyString) { - return Object.keys(obj, onlyString).map(v => (obj as any)[v]); + const res = []; + const keys = internals.keys(obj, !!(onlyString ?? true)); + + for (let i = 0; i < keys.length; i++) { + res[i] = (obj as any)[keys[i]]; + } + + return keys; }, getOwnPropertyDescriptor(obj, key) { - return env.internals.ownProp(obj, key); + return internals.ownProp(obj, key) as any; }, getOwnPropertyDescriptors(obj) { - return Object.fromEntries([ - ...Object.getOwnPropertyNames(obj), - ...Object.getOwnPropertySymbols(obj) - ].map(v => [ v, Object.getOwnPropertyDescriptor(obj, v) ])) as any; + const res = []; + const keys = internals.ownPropKeys(obj); + + for (let i = 0; i < keys.length; i++) { + res[i] = internals.ownProp(obj, keys[i]); + } + + return res; }, getOwnPropertyNames(obj) { - return env.internals.ownPropKeys(obj, false); + const arr = internals.ownPropKeys(obj); + const res = []; + + for (let i = 0; i < arr.length; i++) { + if (typeof arr[i] === 'symbol') continue; + res[res.length] = arr[i]; + } + + return res as any; }, getOwnPropertySymbols(obj) { - return env.internals.ownPropKeys(obj, true); + const arr = internals.ownPropKeys(obj); + const res = []; + + for (let i = 0; i < arr.length; i++) { + if (typeof arr[i] !== 'symbol') continue; + res[res.length] = arr[i]; + } + + return res as any; }, hasOwn(obj, key) { - if (Object.getOwnPropertyNames(obj).includes(key)) return true; - if (Object.getOwnPropertySymbols(obj).includes(key)) return true; + const keys = internals.ownPropKeys(obj); + + for (let i = 0; i < keys.length; i++) { + if (keys[i] === key) return true; + } + return false; }, @@ -130,41 +168,51 @@ define("values/object", () => { preventExtensions(obj) { throwNotObject(obj, 'preventExtensions'); - env.internals.preventExtensions(obj); + internals.lock(obj, 'ext'); return obj; }, seal(obj) { throwNotObject(obj, 'seal'); - env.internals.seal(obj); + internals.lock(obj, 'seal'); return obj; }, freeze(obj) { throwNotObject(obj, 'freeze'); - env.internals.freeze(obj); + internals.lock(obj, 'freeze'); return obj; }, isExtensible(obj) { if (!check(obj)) return false; - return env.internals.extensible(obj); + return internals.extensible(obj); }, isSealed(obj) { if (!check(obj)) return true; - if (Object.isExtensible(obj)) return false; - return Object.getOwnPropertyNames(obj).every(v => !Object.getOwnPropertyDescriptor(obj, v).configurable); + if (internals.extensible(obj)) return false; + const keys = internals.ownPropKeys(obj); + + for (let i = 0; i < keys.length; i++) { + if (internals.ownProp(obj, keys[i]).configurable) return false; + } + + return true; }, isFrozen(obj) { if (!check(obj)) return true; - if (Object.isExtensible(obj)) return false; - return Object.getOwnPropertyNames(obj).every(v => { - var prop = Object.getOwnPropertyDescriptor(obj, v); + if (internals.extensible(obj)) return false; + const keys = internals.ownPropKeys(obj); + + for (let i = 0; i < keys.length; i++) { + const prop = internals.ownProp(obj, keys[i]); + if (prop.configurable) return false; if ('writable' in prop && prop.writable) return false; - return !prop.configurable; - }); + } + + return true; } }); - setProps(Object.prototype, env, { + setProps(Object.prototype, { valueOf() { return this; }, @@ -175,4 +223,5 @@ define("values/object", () => { return Object.hasOwn(this, key); }, }); + internals.markSpecial(Object); }); \ No newline at end of file diff --git a/lib/values/string.ts b/lib/values/string.ts index 95023ff..b1af549 100644 --- a/lib/values/string.ts +++ b/lib/values/string.ts @@ -7,10 +7,10 @@ define("values/string", () => { else (this as any).value = val; } as StringConstructor; - String.prototype = ('' as any).__proto__ as String; - setConstr(String.prototype, String, env); + env.setProto('string', String.prototype); + setConstr(String.prototype, String); - setProps(String.prototype, env, { + setProps(String.prototype, { toString() { if (typeof this === 'string') return this; else return (this as any).value; @@ -27,7 +27,14 @@ define("values/string", () => { } start = start ?? 0 | 0; end = (end ?? this.length) | 0; - return env.internals.substring(this, start, end); + + const res = []; + + for (let i = start; i < end; i++) { + if (i >= 0 && i < this.length) res[res.length] = this[i]; + } + + return internals.stringFromStrings(res); }, substr(start, length) { start = start ?? 0 | 0; @@ -36,14 +43,41 @@ define("values/string", () => { if (start < 0) start = 0; length = (length ?? this.length - start) | 0; - return this.substring(start, length + start); + const end = length + start; + const res = []; + + for (let i = start; i < end; i++) { + if (i >= 0 && i < this.length) res[res.length] = this[i]; + } + + return internals.stringFromStrings(res); }, toLowerCase() { - return env.internals.toLower(this + ''); + // TODO: Implement localization + const res = []; + + for (let i = 0; i < this.length; i++) { + const c = internals.char(this[i]); + + if (c >= 65 && c <= 90) res[i] = c - 65 + 97; + else res[i] = c; + } + + return internals.stringFromChars(res); }, toUpperCase() { - return env.internals.toUpper(this + ''); + // TODO: Implement localization + const res = []; + + for (let i = 0; i < this.length; i++) { + const c = internals.char(this[i]); + + if (c >= 97 && c <= 122) res[i] = c - 97 + 65; + else res[i] = c; + } + + return internals.stringFromChars(res); }, charAt(pos) { @@ -57,9 +91,14 @@ define("values/string", () => { return this[pos]; }, charCodeAt(pos) { - var res = this.charAt(pos); - if (res === '') return NaN; - else return env.internals.toCharCode(res); + if (typeof this !== 'string') { + if (this instanceof String) return (this as any).value.charAt(pos); + else throw new Error('This function may be used only with primitive or object strings.'); + } + + pos = pos | 0; + if (pos < 0 || pos >= this.length) return 0 / 0; + return internals.char(this[pos]); }, startsWith(term, pos) { @@ -68,7 +107,15 @@ define("values/string", () => { else throw new Error('This function may be used only with primitive or object strings.'); } pos = pos! | 0; - return env.internals.startsWith(this, term + '', pos); + term = term + ""; + + if (pos < 0 || this.length < term.length + pos) return false; + + for (let i = 0; i < term.length; i++) { + if (this[i + pos] !== term[i]) return false; + } + + return true; }, endsWith(term, pos) { if (typeof this !== 'string') { @@ -76,7 +123,17 @@ define("values/string", () => { else throw new Error('This function may be used only with primitive or object strings.'); } pos = (pos ?? this.length) | 0; - return env.internals.endsWith(this, term + '', pos); + term = term + ""; + + const start = pos - term.length; + + if (start < 0 || this.length < term.length + start) return false; + + for (let i = 0; i < term.length; i++) { + if (this[i + start] !== term[i]) return false; + } + + return true; }, indexOf(term: any, start) { @@ -189,9 +246,9 @@ define("values/string", () => { } }); - setProps(String, env, { + setProps(String, { fromCharCode(val) { - return env.internals.fromCharCode(val | 0); + return internals.stringFromChars([val | 0]); }, }) @@ -202,7 +259,7 @@ define("values/string", () => { else throw new Error('This function may be used only with primitive or object strings.'); } - return env.internals.strlen(this); + return internals.strlen(this); }, configurable: true, enumerable: false, diff --git a/lib/values/symbol.ts b/lib/values/symbol.ts index 0258b75..4e47c16 100644 --- a/lib/values/symbol.ts +++ b/lib/values/symbol.ts @@ -1,31 +1,34 @@ define("values/symbol", () => { + const symbols: Record = { }; + var Symbol = env.global.Symbol = function(this: any, val?: string) { if (this !== undefined && this !== null) throw new TypeError("Symbol may not be called with 'new'."); if (typeof val !== 'string' && val !== undefined) throw new TypeError('val must be a string or undefined.'); - return env.internals.symbol(val, true); + return internals.symbol(val); } as SymbolConstructor; - Symbol.prototype = env.internals.symbolProto; - setConstr(Symbol.prototype, Symbol, env); - (Symbol as any).typeName = Symbol("Symbol.name"); - (Symbol as any).replace = Symbol('Symbol.replace'); - (Symbol as any).match = Symbol('Symbol.match'); - (Symbol as any).matchAll = Symbol('Symbol.matchAll'); - (Symbol as any).split = Symbol('Symbol.split'); - (Symbol as any).search = Symbol('Symbol.search'); - (Symbol as any).iterator = Symbol('Symbol.iterator'); - (Symbol as any).asyncIterator = Symbol('Symbol.asyncIterator'); + env.setProto('symbol', Symbol.prototype); + setConstr(Symbol.prototype, Symbol); - setProps(Symbol, env, { + setProps(Symbol, { for(key) { if (typeof key !== 'string' && key !== undefined) throw new TypeError('key must be a string or undefined.'); - return env.internals.symbol(key, false); + if (key in symbols) return symbols[key]; + else return symbols[key] = internals.symbol(key); }, keyFor(sym) { if (typeof sym !== 'symbol') throw new TypeError('sym must be a symbol.'); - return env.internals.symStr(sym); + return internals.symbolToString(sym); }, - typeName: Symbol('Symbol.name') as any, + + typeName: Symbol("Symbol.name") as any, + replace: Symbol('Symbol.replace') as any, + match: Symbol('Symbol.match') as any, + matchAll: Symbol('Symbol.matchAll') as any, + split: Symbol('Symbol.split') as any, + search: Symbol('Symbol.search') as any, + iterator: Symbol('Symbol.iterator') as any, + asyncIterator: Symbol('Symbol.asyncIterator') as any, }); env.global.Object.defineProperty(Object.prototype, Symbol.typeName, { value: 'Object' }); diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..4bbacb0 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,6 @@ +{ + "name": "java-jscript", + "lockfileVersion": 3, + "requires": true, + "packages": {} +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/package.json @@ -0,0 +1 @@ +{} diff --git a/src/me/topchetoeu/jscript/Main.java b/src/me/topchetoeu/jscript/Main.java index b72f080..7fe117b 100644 --- a/src/me/topchetoeu/jscript/Main.java +++ b/src/me/topchetoeu/jscript/Main.java @@ -2,25 +2,54 @@ package me.topchetoeu.jscript; import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; import java.nio.file.Files; import java.nio.file.Path; import java.util.Map; +import me.topchetoeu.jscript.engine.CallContext; import me.topchetoeu.jscript.engine.Engine; +import me.topchetoeu.jscript.engine.Environment; +import me.topchetoeu.jscript.engine.values.NativeFunction; import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.events.Observer; import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.SyntaxException; +import me.topchetoeu.jscript.interop.NativeTypeRegister; +import me.topchetoeu.jscript.polyfills.Internals; public class Main { static Thread task; static Engine engine; + static Environment env; + + public static String streamToString(InputStream in) { + try { + StringBuilder out = new StringBuilder(); + BufferedReader br = new BufferedReader(new InputStreamReader(in)); + + for(var line = br.readLine(); line != null; line = br.readLine()) { + out.append(line).append('\n'); + } + + br.close(); + return out.toString(); + } + catch (IOException e) { + return null; + } + } + public static String resourceToString(String name) { + var str = Main.class.getResourceAsStream("/me/topchetoeu/jscript/" + name); + if (str == null) return null; + return streamToString(str); + } private static Observer valuePrinter = new Observer() { public void next(Object data) { try { - Values.printValue(engine.context(), data); + Values.printValue(null, data); } catch (InterruptedException e) { } System.out.println(); @@ -28,20 +57,24 @@ public class Main { public void error(RuntimeException err) { try { - if (err instanceof EngineException) { - System.out.println("Uncaught " + ((EngineException)err).toString(engine.context())); + try { + if (err instanceof EngineException) { + System.out.println("Uncaught " + ((EngineException)err).toString(new CallContext(engine, env))); + } + else if (err instanceof SyntaxException) { + System.out.println("Syntax error:" + ((SyntaxException)err).msg); + } + else if (err.getCause() instanceof InterruptedException) return; + else { + System.out.println("Internal error ocurred:"); + err.printStackTrace(); + } } - else if (err instanceof SyntaxException) { - System.out.println("Syntax error:" + ((SyntaxException)err).msg); + catch (EngineException ex) { + System.out.println("Uncaught "); + Values.printValue(null, ((EngineException)err).value); + System.out.println(); } - else if (err.getCause() instanceof InterruptedException) return; - else { - System.out.println("Internal error ocurred:"); - err.printStackTrace(); - } - } - catch (EngineException ex) { - System.out.println("Uncaught [error while converting to string]"); } catch (InterruptedException ex) { return; @@ -53,23 +86,33 @@ public class Main { System.out.println(String.format("Running %s v%s by %s", Metadata.NAME, Metadata.VERSION, Metadata.AUTHOR)); var in = new BufferedReader(new InputStreamReader(System.in)); engine = new Engine(); - var scope = engine.global().globalChild(); + env = new Environment(null, null, null); var exited = new boolean[1]; - scope.define("exit", ctx -> { + env.global.define("exit", ctx -> { exited[0] = true; task.interrupt(); throw new InterruptedException(); }); - scope.define("go", ctx -> { + env.global.define("go", ctx -> { try { - var func = engine.compile(scope, "do.js", new String(Files.readAllBytes(Path.of("do.js")))); + var func = engine.compile(ctx, "do.js", new String(Files.readAllBytes(Path.of("do.js")))); return func.call(ctx); } catch (IOException e) { throw new EngineException("Couldn't open do.js"); } }); + env.global.define(true, new NativeFunction("log", (el, t, _args) -> { + for (var obj : _args) Values.printValue(el, obj); + System.out.println(); + return null; + })); + + var builderEnv = env.child(); + builderEnv.wrappersProvider = new NativeTypeRegister(); + + engine.pushMsg(false, Map.of(), builderEnv, "core.js", resourceToString("js/core.js"), null, env, new Internals()); task = engine.start(); var reader = new Thread(() -> { @@ -79,11 +122,11 @@ public class Main { var raw = in.readLine(); if (raw == null) break; - engine.pushMsg(false, scope, Map.of(), "", raw, null).toObservable().once(valuePrinter); + engine.pushMsg(false, Map.of(), env, "", raw, null).toObservable().once(valuePrinter); } catch (EngineException e) { try { - System.out.println("Uncaught " + e.toString(engine.context())); + System.out.println("Uncaught " + e.toString(null)); } catch (EngineException ex) { System.out.println("Uncaught [error while converting to string]"); diff --git a/src/me/topchetoeu/jscript/engine/CallContext.java b/src/me/topchetoeu/jscript/engine/CallContext.java index f787872..e62bd12 100644 --- a/src/me/topchetoeu/jscript/engine/CallContext.java +++ b/src/me/topchetoeu/jscript/engine/CallContext.java @@ -4,18 +4,20 @@ import java.util.Collections; import java.util.Hashtable; import java.util.Map; +@SuppressWarnings("unchecked") public class CallContext { public static final class DataKey {} public final Engine engine; - private final Map, Object> data = new Hashtable<>(); + public Environment environment; + private final Map, Object> data; - public Engine engine() { return engine; } public Map, Object> data() { return Collections.unmodifiableMap(data); } public CallContext copy() { - return new CallContext(engine).mergeData(data); + return new CallContext(engine, environment).mergeData(data); } + public CallContext mergeData(Map, Object> objs) { data.putAll(objs); return this; @@ -25,7 +27,6 @@ public class CallContext { else data.put(key, val); return this; } - @SuppressWarnings("unchecked") public T addData(DataKey key, T val) { if (data.containsKey(key)) return (T)data.get(key); else { @@ -34,15 +35,14 @@ public class CallContext { return val; } } - public boolean hasData(DataKey key) { return data.containsKey(key); } public T getData(DataKey key) { return getData(key, null); } - @SuppressWarnings("unchecked") public T getData(DataKey key, T defaultVal) { if (!hasData(key)) return defaultVal; else return (T)data.get(key); } + public boolean hasData(DataKey key) { return data.containsKey(key); } public CallContext changeData(DataKey key, int n, int start) { return setData(key, getData(key, start) + n); @@ -54,7 +54,14 @@ public class CallContext { return changeData(key, 1, 0); } - public CallContext(Engine engine) { + public CallContext(Engine engine, Environment env) { this.engine = engine; + this.data = new Hashtable<>(); + this.environment = env; + } + public CallContext(CallContext parent, Environment env) { + this.engine = parent.engine; + this.data = parent.data; + this.environment = env; } } diff --git a/src/me/topchetoeu/jscript/engine/Engine.java b/src/me/topchetoeu/jscript/engine/Engine.java index 111e66a..917d3db 100644 --- a/src/me/topchetoeu/jscript/engine/Engine.java +++ b/src/me/topchetoeu/jscript/engine/Engine.java @@ -1,43 +1,43 @@ package me.topchetoeu.jscript.engine; -import java.util.HashMap; import java.util.Map; import java.util.concurrent.LinkedBlockingDeque; import me.topchetoeu.jscript.engine.CallContext.DataKey; -import me.topchetoeu.jscript.engine.debug.DebugState; -import me.topchetoeu.jscript.engine.modules.ModuleManager; -import me.topchetoeu.jscript.engine.scope.GlobalScope; import me.topchetoeu.jscript.engine.values.FunctionValue; -import me.topchetoeu.jscript.engine.values.ObjectValue; -import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto; +import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.events.Awaitable; import me.topchetoeu.jscript.events.DataNotifier; import me.topchetoeu.jscript.exceptions.EngineException; -import me.topchetoeu.jscript.interop.NativeTypeRegister; import me.topchetoeu.jscript.parsing.Parsing; public class Engine { - private static class RawFunction { - public final GlobalScope scope; + private class UncompiledFunction extends FunctionValue { public final String filename; public final String raw; - public RawFunction(GlobalScope scope, String filename, String raw) { - this.scope = scope; + @Override + public Object call(CallContext ctx, Object thisArg, Object... args) throws InterruptedException { + return compile(ctx, filename, raw).call(ctx, thisArg, args); + } + + public UncompiledFunction(String filename, String raw) { + super(filename, 0); this.filename = filename; this.raw = raw; } } private static class Task { - public final Object func; + public final FunctionValue func; public final Object thisArg; public final Object[] args; public final Map, Object> data; public final DataNotifier notifier = new DataNotifier<>(); + public final Environment env; - public Task(Object func, Map, Object> data, Object thisArg, Object[] args) { + public Task(Environment env, FunctionValue func, Map, Object> data, Object thisArg, Object[] args) { + this.env = env; this.func = func; this.data = data; this.thisArg = thisArg; @@ -45,72 +45,22 @@ public class Engine { } } - public static final DataKey DEBUG_STATE_KEY = new DataKey<>(); private static int nextId = 0; - private Map, Object> callCtxVals = new HashMap<>(); - private GlobalScope global = new GlobalScope(); - private ObjectValue arrayProto = new ObjectValue(); - private ObjectValue boolProto = new ObjectValue(); - private ObjectValue funcProto = new ObjectValue(); - private ObjectValue numProto = new ObjectValue(); - private ObjectValue objProto = new ObjectValue(PlaceholderProto.NONE); - private ObjectValue strProto = new ObjectValue(); - private ObjectValue symProto = new ObjectValue(); - private ObjectValue errProto = new ObjectValue(); - private ObjectValue syntaxErrProto = new ObjectValue(PlaceholderProto.ERROR); - private ObjectValue typeErrProto = new ObjectValue(PlaceholderProto.ERROR); - private ObjectValue rangeErrProto = new ObjectValue(PlaceholderProto.ERROR); - private NativeTypeRegister typeRegister; + // private Map, Object> callCtxVals = new HashMap<>(); + // private NativeTypeRegister typeRegister; private Thread thread; private LinkedBlockingDeque macroTasks = new LinkedBlockingDeque<>(); private LinkedBlockingDeque microTasks = new LinkedBlockingDeque<>(); public final int id = ++nextId; - public final DebugState debugState = new DebugState(); - public ObjectValue arrayProto() { return arrayProto; } - public ObjectValue booleanProto() { return boolProto; } - public ObjectValue functionProto() { return funcProto; } - public ObjectValue numberProto() { return numProto; } - public ObjectValue objectProto() { return objProto; } - public ObjectValue stringProto() { return strProto; } - public ObjectValue symbolProto() { return symProto; } - public ObjectValue errorProto() { return errProto; } - public ObjectValue syntaxErrorProto() { return syntaxErrProto; } - public ObjectValue typeErrorProto() { return typeErrProto; } - public ObjectValue rangeErrorProto() { return rangeErrProto; } - - public GlobalScope global() { return global; } - public NativeTypeRegister typeRegister() { return typeRegister; } - - public void copyFrom(Engine other) { - global = other.global; - typeRegister = other.typeRegister; - arrayProto = other.arrayProto; - boolProto = other.boolProto; - funcProto = other.funcProto; - numProto = other.numProto; - objProto = other.objProto; - strProto = other.strProto; - symProto = other.symProto; - errProto = other.errProto; - syntaxErrProto = other.syntaxErrProto; - typeErrProto = other.typeErrProto; - rangeErrProto = other.rangeErrProto; - } + // public NativeTypeRegister typeRegister() { return typeRegister; } private void runTask(Task task) throws InterruptedException { try { - FunctionValue func; - if (task.func instanceof FunctionValue) func = (FunctionValue)task.func; - else { - var raw = (RawFunction)task.func; - func = compile(raw.scope, raw.filename, raw.raw); - } - - task.notifier.next(func.call(context().mergeData(task.data), task.thisArg, task.args)); + task.notifier.next(task.func.call(new CallContext(this, task.env).mergeData(task.data), task.thisArg, task.args)); } catch (InterruptedException e) { task.notifier.error(new RuntimeException(e)); @@ -160,42 +110,22 @@ public class Engine { return this.thread != null; } - public Object makeRegex(String pattern, String flags) { - throw EngineException.ofError("Regular expressions not supported."); - } - public ModuleManager modules() { - return null; - } - public ObjectValue getPrototype(Class clazz) { - return typeRegister.getProto(clazz); - } - public FunctionValue getConstructor(Class clazz) { - return typeRegister.getConstr(clazz); - } - public CallContext context() { return new CallContext(this).mergeData(callCtxVals); } - - public Awaitable pushMsg(boolean micro, FunctionValue func, Map, Object> data, Object thisArg, Object... args) { - var msg = new Task(func, data, thisArg, args); + public Awaitable pushMsg(boolean micro, Map, Object> data, Environment env, FunctionValue func, Object thisArg, Object... args) { + var msg = new Task(env, func, data, thisArg, args); if (micro) microTasks.addLast(msg); else macroTasks.addLast(msg); return msg.notifier; } - public Awaitable pushMsg(boolean micro, GlobalScope scope, Map, Object> data, String filename, String raw, Object thisArg, Object... args) { - var msg = new Task(new RawFunction(scope, filename, raw), data, thisArg, args); - if (micro) microTasks.addLast(msg); - else macroTasks.addLast(msg); - return msg.notifier; + public Awaitable pushMsg(boolean micro, Map, Object> data, Environment env, String filename, String raw, Object thisArg, Object... args) { + return pushMsg(micro, data, env, new UncompiledFunction(filename, raw), thisArg, args); } - public FunctionValue compile(GlobalScope scope, String filename, String raw) throws InterruptedException { - return Parsing.compile(scope, filename, raw); + public FunctionValue compile(CallContext ctx, String filename, String raw) throws InterruptedException { + var res = Values.toString(ctx, ctx.environment.compile.call(ctx, null, raw, filename)); + return Parsing.compile(ctx.environment, filename, res); } - public Engine(NativeTypeRegister register) { - this.typeRegister = register; - this.callCtxVals.put(DEBUG_STATE_KEY, debugState); - } - public Engine() { - this(new NativeTypeRegister()); - } + // public Engine() { + // this.typeRegister = new NativeTypeRegister(); + // } } diff --git a/src/me/topchetoeu/jscript/engine/Environment.java b/src/me/topchetoeu/jscript/engine/Environment.java new file mode 100644 index 0000000..8224833 --- /dev/null +++ b/src/me/topchetoeu/jscript/engine/Environment.java @@ -0,0 +1,81 @@ +package me.topchetoeu.jscript.engine; + +import java.util.HashMap; + +import me.topchetoeu.jscript.engine.scope.GlobalScope; +import me.topchetoeu.jscript.engine.values.FunctionValue; +import me.topchetoeu.jscript.engine.values.NativeFunction; +import me.topchetoeu.jscript.engine.values.ObjectValue; +import me.topchetoeu.jscript.exceptions.EngineException; +import me.topchetoeu.jscript.interop.Native; +import me.topchetoeu.jscript.interop.NativeGetter; +import me.topchetoeu.jscript.interop.NativeSetter; + +public class Environment { + private HashMap prototypes = new HashMap<>(); + public GlobalScope global; + public WrappersProvider wrappersProvider; + + @Native public FunctionValue compile; + @Native public FunctionValue regexConstructor = new NativeFunction("RegExp", (ctx, thisArg, args) -> { + throw EngineException.ofError("Regular expressions not supported."); + }); + @Native public ObjectValue proto(String name) { + return prototypes.get(name); + } + @Native public void setProto(String name, ObjectValue val) { + prototypes.put(name, val); + } + // @Native public ObjectValue arrayPrototype = new ObjectValue(); + // @Native public ObjectValue boolPrototype = new ObjectValue(); + // @Native public ObjectValue functionPrototype = new ObjectValue(); + // @Native public ObjectValue numberPrototype = new ObjectValue(); + // @Native public ObjectValue objectPrototype = new ObjectValue(PlaceholderProto.NONE); + // @Native public ObjectValue stringPrototype = new ObjectValue(); + // @Native public ObjectValue symbolPrototype = new ObjectValue(); + // @Native public ObjectValue errorPrototype = new ObjectValue(); + // @Native public ObjectValue syntaxErrPrototype = new ObjectValue(PlaceholderProto.ERROR); + // @Native public ObjectValue typeErrPrototype = new ObjectValue(PlaceholderProto.ERROR); + // @Native public ObjectValue rangeErrPrototype = new ObjectValue(PlaceholderProto.ERROR); + + @NativeGetter("global") + public ObjectValue getGlobal() { + return global.obj; + } + @NativeSetter("global") + public void setGlobal(ObjectValue val) { + global = new GlobalScope(val); + } + + @Native + public Environment fork() { + var res = new Environment(compile, wrappersProvider, global); + res.regexConstructor = regexConstructor; + res.prototypes = new HashMap<>(prototypes); + return res; + } + + @Native + public Environment child() { + var res = fork(); + res.global = res.global.globalChild(); + return res; + } + + public Environment(FunctionValue compile, WrappersProvider nativeConverter, GlobalScope global) { + if (compile == null) compile = new NativeFunction("compile", (ctx, thisArg, args) -> args.length == 0 ? "" : args[0]); + if (nativeConverter == null) nativeConverter = new WrappersProvider() { + public ObjectValue getConstr(Class obj) { + throw EngineException.ofType("Java objects not passable to Javascript."); + } + public ObjectValue getProto(Class obj) { + throw EngineException.ofType("Java objects not passable to Javascript."); + } + }; + if (global == null) global = new GlobalScope(); + + this.wrappersProvider = nativeConverter; + this.compile = compile; + this.global = global; + } +} diff --git a/src/me/topchetoeu/jscript/engine/WrappersProvider.java b/src/me/topchetoeu/jscript/engine/WrappersProvider.java new file mode 100644 index 0000000..6205da8 --- /dev/null +++ b/src/me/topchetoeu/jscript/engine/WrappersProvider.java @@ -0,0 +1,8 @@ +package me.topchetoeu.jscript.engine; + +import me.topchetoeu.jscript.engine.values.ObjectValue; + +public interface WrappersProvider { + public ObjectValue getProto(Class obj); + public ObjectValue getConstr(Class obj); +} diff --git a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java index 702a9a4..5a6e188 100644 --- a/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java +++ b/src/me/topchetoeu/jscript/engine/frame/CodeFrame.java @@ -6,7 +6,6 @@ import java.util.List; import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.engine.CallContext; import me.topchetoeu.jscript.engine.DebugCommand; -import me.topchetoeu.jscript.engine.Engine; import me.topchetoeu.jscript.engine.CallContext.DataKey; import me.topchetoeu.jscript.engine.scope.LocalScope; import me.topchetoeu.jscript.engine.scope.ValueVariable; @@ -106,14 +105,14 @@ public class CodeFrame { if (ctx.getData(STACK_N_KEY, 0) >= ctx.addData(MAX_STACK_KEY, 10000)) throw EngineException.ofRange("Stack overflow!"); ctx.changeData(STACK_N_KEY); - var debugState = ctx.getData(Engine.DEBUG_STATE_KEY); - if (debugState != null) debugState.pushFrame(this); + // var debugState = ctx.getData(Engine.DEBUG_STATE_KEY); + // if (debugState != null) debugState.pushFrame(this); } public void end(CallContext ctx) { - var debugState = ctx.getData(Engine.DEBUG_STATE_KEY); - - if (debugState != null) debugState.popFrame(); ctx.changeData(STACK_N_KEY, -1); + + // var debugState = ctx.getData(Engine.DEBUG_STATE_KEY); + // if (debugState != null) debugState.popFrame(); } private Object nextNoTry(CallContext ctx) throws InterruptedException { diff --git a/src/me/topchetoeu/jscript/engine/frame/Runners.java b/src/me/topchetoeu/jscript/engine/frame/Runners.java index c59cb0f..631706e 100644 --- a/src/me/topchetoeu/jscript/engine/frame/Runners.java +++ b/src/me/topchetoeu/jscript/engine/frame/Runners.java @@ -69,7 +69,7 @@ public class Runners { public static Object execMakeVar(CallContext ctx, Instruction instr, CodeFrame frame) throws InterruptedException { var name = (String)instr.get(0); - frame.function.globals.define(name); + frame.function.environment.global.define(name); frame.codePtr++; return NO_RETURN; } @@ -164,7 +164,7 @@ public class Runners { public static Object execLoadVar(CallContext ctx, Instruction instr, CodeFrame frame) throws InterruptedException { var i = instr.get(0); - if (i instanceof String) frame.push(ctx, frame.function.globals.get(ctx, (String)i)); + if (i instanceof String) frame.push(ctx, frame.function.environment.global.get(ctx, (String)i)); else frame.push(ctx, frame.scope.get((int)i).get(ctx)); frame.codePtr++; @@ -176,7 +176,7 @@ public class Runners { return NO_RETURN; } public static Object execLoadGlob(CallContext ctx, Instruction instr, CodeFrame frame) { - frame.push(ctx, frame.function.globals.obj); + frame.push(ctx, frame.function.environment.global.obj); frame.codePtr++; return NO_RETURN; } @@ -202,7 +202,7 @@ public class Runners { var body = new Instruction[end - start]; System.arraycopy(frame.function.body, start, body, 0, end - start); - var func = new CodeFunction("", localsN, len, frame.function.globals, captures, body); + var func = new CodeFunction("", localsN, len, frame.function.environment, captures, body); frame.push(ctx, func); frame.codePtr += n; @@ -227,7 +227,7 @@ public class Runners { return execLoadMember(ctx, state, instr, frame); } public static Object execLoadRegEx(CallContext ctx, Instruction instr, CodeFrame frame) throws InterruptedException { - frame.push(ctx, ctx.engine().makeRegex(instr.get(0), instr.get(1))); + frame.push(ctx, ctx.environment.regexConstructor.call(ctx, null, instr.get(0), instr.get(1))); frame.codePtr++; return NO_RETURN; } @@ -252,7 +252,7 @@ public class Runners { var val = (boolean)instr.get(1) ? frame.peek() : frame.pop(); var i = instr.get(0); - if (i instanceof String) frame.function.globals.set(ctx, (String)i, val); + if (i instanceof String) frame.function.environment.global.set(ctx, (String)i, val); else frame.scope.get((int)i).set(ctx, val); frame.codePtr++; @@ -299,8 +299,8 @@ public class Runners { Object obj; if (name != null) { - if (frame.function.globals.has(ctx, name)) { - obj = frame.function.globals.get(ctx, name); + if (frame.function.environment.global.has(ctx, name)) { + obj = frame.function.environment.global.get(ctx, name); } else obj = null; } diff --git a/src/me/topchetoeu/jscript/engine/scope/GlobalScope.java b/src/me/topchetoeu/jscript/engine/scope/GlobalScope.java index e318c24..38fac3a 100644 --- a/src/me/topchetoeu/jscript/engine/scope/GlobalScope.java +++ b/src/me/topchetoeu/jscript/engine/scope/GlobalScope.java @@ -11,10 +11,9 @@ import me.topchetoeu.jscript.exceptions.EngineException; public class GlobalScope implements ScopeRecord { public final ObjectValue obj; - public final GlobalScope parent; @Override - public GlobalScope parent() { return parent; } + public GlobalScope parent() { return null; } public boolean has(CallContext ctx, String name) throws InterruptedException { return obj.hasMember(ctx, name, false); @@ -24,7 +23,9 @@ public class GlobalScope implements ScopeRecord { } public GlobalScope globalChild() { - return new GlobalScope(this); + var obj = new ObjectValue(); + obj.setPrototype(null, this.obj); + return new GlobalScope(obj); } public LocalScopeRecord child() { return new LocalScopeRecord(this); @@ -78,12 +79,9 @@ public class GlobalScope implements ScopeRecord { } public GlobalScope() { - this.parent = null; this.obj = new ObjectValue(); } - public GlobalScope(GlobalScope parent) { - this.parent = null; - this.obj = new ObjectValue(); - this.obj.setPrototype(null, parent.obj); + public GlobalScope(ObjectValue val) { + this.obj = val; } } diff --git a/src/me/topchetoeu/jscript/engine/scope/LocalScope.java b/src/me/topchetoeu/jscript/engine/scope/LocalScope.java index efa0bb4..3232f47 100644 --- a/src/me/topchetoeu/jscript/engine/scope/LocalScope.java +++ b/src/me/topchetoeu/jscript/engine/scope/LocalScope.java @@ -4,7 +4,6 @@ import java.util.ArrayList; public class LocalScope { private String[] names; - private LocalScope parent; public final ValueVariable[] captures; public final ValueVariable[] locals; public final ArrayList catchVars = new ArrayList<>(); @@ -33,18 +32,11 @@ public class LocalScope { return captures.length + locals.length; } - public GlobalScope toGlobal(GlobalScope global) { - GlobalScope res; - - if (parent == null) res = new GlobalScope(global); - else res = new GlobalScope(parent.toGlobal(global)); - + public void toGlobal(GlobalScope global) { var names = getNames(); for (var i = 0; i < names.length; i++) { - res.define(names[i], locals[i]); + global.define(names[i], locals[i]); } - - return res; } public LocalScope(int n, ValueVariable[] captures) { @@ -55,8 +47,4 @@ public class LocalScope { locals[i] = new ValueVariable(false, null); } } - public LocalScope(int n, ValueVariable[] captures, LocalScope parent) { - this(n, captures); - this.parent = parent; - } } diff --git a/src/me/topchetoeu/jscript/engine/values/CodeFunction.java b/src/me/topchetoeu/jscript/engine/values/CodeFunction.java index 48a430e..e2ff92e 100644 --- a/src/me/topchetoeu/jscript/engine/values/CodeFunction.java +++ b/src/me/topchetoeu/jscript/engine/values/CodeFunction.java @@ -6,8 +6,8 @@ import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction.Type; import me.topchetoeu.jscript.engine.CallContext; +import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.frame.CodeFrame; -import me.topchetoeu.jscript.engine.scope.GlobalScope; import me.topchetoeu.jscript.engine.scope.ValueVariable; public class CodeFunction extends FunctionValue { @@ -17,7 +17,7 @@ public class CodeFunction extends FunctionValue { public final LinkedHashMap breakableLocToIndex = new LinkedHashMap<>(); public final LinkedHashMap breakableIndexToLoc = new LinkedHashMap<>(); public final ValueVariable[] captures; - public final GlobalScope globals; + public Environment environment; public Location loc() { for (var instr : body) { @@ -34,13 +34,13 @@ public class CodeFunction extends FunctionValue { @Override public Object call(CallContext ctx, Object thisArg, Object... args) throws InterruptedException { - return new CodeFrame(ctx, thisArg, args, this).run(ctx); + return new CodeFrame(ctx, thisArg, args, this).run(new CallContext(ctx, environment)); } - public CodeFunction(String name, int localsN, int length, GlobalScope globals, ValueVariable[] captures, Instruction[] body) { + public CodeFunction(String name, int localsN, int length, Environment environment, ValueVariable[] captures, Instruction[] body) { super(name, length); this.captures = captures; - this.globals = globals; + this.environment = environment; this.localsN = localsN; this.length = length; this.body = body; diff --git a/src/me/topchetoeu/jscript/engine/values/NativeWrapper.java b/src/me/topchetoeu/jscript/engine/values/NativeWrapper.java index 7a5e1d0..50efae2 100644 --- a/src/me/topchetoeu/jscript/engine/values/NativeWrapper.java +++ b/src/me/topchetoeu/jscript/engine/values/NativeWrapper.java @@ -8,7 +8,7 @@ public class NativeWrapper extends ObjectValue { @Override public ObjectValue getPrototype(CallContext ctx) throws InterruptedException { - if (prototype == NATIVE_PROTO) return ctx.engine.getPrototype(wrapped.getClass()); + if (prototype == NATIVE_PROTO) return ctx.environment.wrappersProvider.getProto(wrapped.getClass()); else return super.getPrototype(ctx); } diff --git a/src/me/topchetoeu/jscript/engine/values/ObjectValue.java b/src/me/topchetoeu/jscript/engine/values/ObjectValue.java index 4899448..ca0fb55 100644 --- a/src/me/topchetoeu/jscript/engine/values/ObjectValue.java +++ b/src/me/topchetoeu/jscript/engine/values/ObjectValue.java @@ -147,13 +147,13 @@ public class ObjectValue { public ObjectValue getPrototype(CallContext ctx) throws InterruptedException { try { - if (prototype == OBJ_PROTO) return ctx.engine().objectProto(); - if (prototype == ARR_PROTO) return ctx.engine().arrayProto(); - if (prototype == FUNC_PROTO) return ctx.engine().functionProto(); - if (prototype == ERR_PROTO) return ctx.engine().errorProto(); - if (prototype == RANGE_ERR_PROTO) return ctx.engine().rangeErrorProto(); - if (prototype == SYNTAX_ERR_PROTO) return ctx.engine().syntaxErrorProto(); - if (prototype == TYPE_ERR_PROTO) return ctx.engine().typeErrorProto(); + if (prototype == OBJ_PROTO) return ctx.environment.proto("object"); + if (prototype == ARR_PROTO) return ctx.environment.proto("array"); + if (prototype == FUNC_PROTO) return ctx.environment.proto("function"); + if (prototype == ERR_PROTO) return ctx.environment.proto("error"); + if (prototype == RANGE_ERR_PROTO) return ctx.environment.proto("rangeErr"); + if (prototype == SYNTAX_ERR_PROTO) return ctx.environment.proto("syntaxErr"); + if (prototype == TYPE_ERR_PROTO) return ctx.environment.proto("typeErr"); } catch (NullPointerException e) { return null; @@ -165,18 +165,21 @@ public class ObjectValue { val = Values.normalize(ctx, val); if (!extensible()) return false; - if (val == null || val == Values.NULL) prototype = null; + if (val == null || val == Values.NULL) { + prototype = null; + return true; + } else if (Values.isObject(val)) { var obj = Values.object(val); - if (ctx != null && ctx.engine() != null) { - if (obj == ctx.engine().objectProto()) prototype = OBJ_PROTO; - else if (obj == ctx.engine().arrayProto()) prototype = ARR_PROTO; - else if (obj == ctx.engine().functionProto()) prototype = FUNC_PROTO; - else if (obj == ctx.engine().errorProto()) prototype = ERR_PROTO; - else if (obj == ctx.engine().syntaxErrorProto()) prototype = SYNTAX_ERR_PROTO; - else if (obj == ctx.engine().typeErrorProto()) prototype = TYPE_ERR_PROTO; - else if (obj == ctx.engine().rangeErrorProto()) prototype = RANGE_ERR_PROTO; + if (ctx != null && ctx.environment != null) { + if (obj == ctx.environment.proto("object")) prototype = OBJ_PROTO; + else if (obj == ctx.environment.proto("array")) prototype = ARR_PROTO; + else if (obj == ctx.environment.proto("function")) prototype = FUNC_PROTO; + else if (obj == ctx.environment.proto("error")) prototype = ERR_PROTO; + else if (obj == ctx.environment.proto("syntaxErr")) prototype = SYNTAX_ERR_PROTO; + else if (obj == ctx.environment.proto("typeErr")) prototype = TYPE_ERR_PROTO; + else if (obj == ctx.environment.proto("rangeErr")) prototype = RANGE_ERR_PROTO; else prototype = obj; } else prototype = obj; @@ -277,7 +280,8 @@ public class ObjectValue { if (hasField(ctx, key)) return true; if (properties.containsKey(key)) return true; if (own) return false; - return prototype != null && getPrototype(ctx).hasMember(ctx, key, own); + var proto = getPrototype(ctx); + return proto != null && proto.hasMember(ctx, key, own); } public final boolean deleteMember(CallContext ctx, Object key) throws InterruptedException { key = Values.normalize(ctx, key); diff --git a/src/me/topchetoeu/jscript/engine/values/Values.java b/src/me/topchetoeu/jscript/engine/values/Values.java index 22d4654..5f7f4ca 100644 --- a/src/me/topchetoeu/jscript/engine/values/Values.java +++ b/src/me/topchetoeu/jscript/engine/values/Values.java @@ -321,15 +321,15 @@ public class Values { if (isObject(obj)) return object(obj).getPrototype(ctx); if (ctx == null) return null; - if (obj instanceof String) return ctx.engine().stringProto(); - else if (obj instanceof Number) return ctx.engine().numberProto(); - else if (obj instanceof Boolean) return ctx.engine().booleanProto(); - else if (obj instanceof Symbol) return ctx.engine().symbolProto(); + if (obj instanceof String) return ctx.environment.proto("string"); + else if (obj instanceof Number) return ctx.environment.proto("number"); + else if (obj instanceof Boolean) return ctx.environment.proto("bool"); + else if (obj instanceof Symbol) return ctx.environment.proto("symbol"); return null; } public static boolean setPrototype(CallContext ctx, Object obj, Object proto) throws InterruptedException { - obj = normalize(ctx, obj); proto = normalize(ctx, proto); + obj = normalize(ctx, obj); return isObject(obj) && object(obj).setPrototype(ctx, proto); } public static List getMembers(CallContext ctx, Object obj, boolean own, boolean includeNonEnumerable) throws InterruptedException { @@ -420,7 +420,7 @@ public class Values { if (val instanceof Class) { if (ctx == null) return null; - else return ctx.engine.getConstructor((Class)val); + else return ctx.environment.wrappersProvider.getConstr((Class)val); } return new NativeWrapper(val); @@ -498,7 +498,7 @@ public class Values { public static Iterable toJavaIterable(CallContext ctx, Object obj) throws InterruptedException { return () -> { try { - var constr = getMember(ctx, ctx.engine().symbolProto(), "constructor"); + var constr = getMember(ctx, ctx.environment.proto("symbol"), "constructor"); var symbol = getMember(ctx, constr, "iterator"); var iteratorFunc = getMember(ctx, obj, symbol); @@ -567,7 +567,7 @@ public class Values { var it = iterable.iterator(); try { - var key = getMember(ctx, getMember(ctx, ctx.engine().symbolProto(), "constructor"), "iterator"); + var key = getMember(ctx, getMember(ctx, ctx.environment.proto("symbol"), "constructor"), "iterator"); res.defineProperty(ctx, key, new NativeFunction("", (_ctx, thisArg, args) -> thisArg)); } catch (IllegalArgumentException | NullPointerException e) { } diff --git a/src/me/topchetoeu/jscript/interop/NativeTypeRegister.java b/src/me/topchetoeu/jscript/interop/NativeTypeRegister.java index aceaa79..ac6eb85 100644 --- a/src/me/topchetoeu/jscript/interop/NativeTypeRegister.java +++ b/src/me/topchetoeu/jscript/interop/NativeTypeRegister.java @@ -3,12 +3,13 @@ package me.topchetoeu.jscript.interop; import java.lang.reflect.Modifier; import java.util.HashMap; +import me.topchetoeu.jscript.engine.WrappersProvider; import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.NativeFunction; import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.exceptions.EngineException; -public class NativeTypeRegister { +public class NativeTypeRegister implements WrappersProvider { private final HashMap, FunctionValue> constructors = new HashMap<>(); private final HashMap, ObjectValue> prototypes = new HashMap<>(); @@ -80,9 +81,7 @@ public class NativeTypeRegister { var name = nat.value(); if (name.equals("")) name = cl.getSimpleName(); - var getter = new OverloadFunction("get " + name).add(Overload.getter(member ? clazz : null, (ctx, thisArg, args) -> { - return ctx.engine().typeRegister().getConstr(cl); - })); + var getter = new NativeFunction("get " + name, (ctx, thisArg, args) -> cl); target.defineProperty(null, name, getter, null, true, false); } diff --git a/src/me/topchetoeu/jscript/parsing/Parsing.java b/src/me/topchetoeu/jscript/parsing/Parsing.java index 2a22195..ea10930 100644 --- a/src/me/topchetoeu/jscript/parsing/Parsing.java +++ b/src/me/topchetoeu/jscript/parsing/Parsing.java @@ -13,8 +13,8 @@ import me.topchetoeu.jscript.compilation.VariableDeclareStatement.Pair; import me.topchetoeu.jscript.compilation.control.*; import me.topchetoeu.jscript.compilation.control.SwitchStatement.SwitchCase; import me.topchetoeu.jscript.compilation.values.*; +import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.Operation; -import me.topchetoeu.jscript.engine.scope.GlobalScope; import me.topchetoeu.jscript.engine.scope.ValueVariable; import me.topchetoeu.jscript.engine.values.CodeFunction; import me.topchetoeu.jscript.engine.values.Values; @@ -1842,8 +1842,8 @@ public class Parsing { return list.toArray(Statement[]::new); } - public static CodeFunction compile(GlobalScope scope, Statement... statements) { - var target = scope.globalChild(); + public static CodeFunction compile(Environment environment, Statement... statements) { + var target = environment.global.globalChild(); var subscope = target.child(); var res = new ArrayList(); var body = new CompoundStatement(null, statements); @@ -1870,14 +1870,14 @@ public class Parsing { } else res.add(Instruction.ret()); - return new CodeFunction("", subscope.localsCount(), 0, scope, new ValueVariable[0], res.toArray(Instruction[]::new)); + return new CodeFunction("", subscope.localsCount(), 0, environment, new ValueVariable[0], res.toArray(Instruction[]::new)); } - public static CodeFunction compile(GlobalScope scope, String filename, String raw) { + public static CodeFunction compile(Environment environment, String filename, String raw) { try { - return compile(scope, parse(filename, raw)); + return compile(environment, parse(filename, raw)); } catch (SyntaxException e) { - return new CodeFunction(null, 2, 0, scope, new ValueVariable[0], new Instruction[] { Instruction.throwSyntax(e) }); + return new CodeFunction(null, 2, 0, environment, new ValueVariable[0], new Instruction[] { Instruction.throwSyntax(e) }); } } } diff --git a/src/me/topchetoeu/jscript/polyfills/GeneratorFunction.java b/src/me/topchetoeu/jscript/polyfills/GeneratorFunction.java index f27dba7..159a01a 100644 --- a/src/me/topchetoeu/jscript/polyfills/GeneratorFunction.java +++ b/src/me/topchetoeu/jscript/polyfills/GeneratorFunction.java @@ -11,7 +11,7 @@ import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.interop.Native; public class GeneratorFunction extends FunctionValue { - public final CodeFunction factory; + public final FunctionValue factory; public static class Generator { private boolean yielding = true; @@ -92,7 +92,7 @@ public class GeneratorFunction extends FunctionValue { return handler; } - public GeneratorFunction(CodeFunction factory) { + public GeneratorFunction(FunctionValue factory) { super(factory.name, factory.length); this.factory = factory; } diff --git a/src/me/topchetoeu/jscript/polyfills/Internals.java b/src/me/topchetoeu/jscript/polyfills/Internals.java index da6628a..c6bee13 100644 --- a/src/me/topchetoeu/jscript/polyfills/Internals.java +++ b/src/me/topchetoeu/jscript/polyfills/Internals.java @@ -1,150 +1,130 @@ package me.topchetoeu.jscript.polyfills; -import java.util.HashMap; - import me.topchetoeu.jscript.engine.CallContext; +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.engine.values.Symbol; import me.topchetoeu.jscript.engine.values.Values; -import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.interop.Native; -import me.topchetoeu.jscript.interop.NativeGetter; public class Internals { - private HashMap intervals = new HashMap<>(); - private HashMap timeouts = new HashMap<>(); - private HashMap symbols = new HashMap<>(); - private int nextId = 0; - - @Native - public double parseFloat(CallContext ctx, Object val) throws InterruptedException { - return Values.toNumber(ctx, val); - } - - @Native - public boolean isArr(Object val) { - return val instanceof ArrayValue; - } - - @NativeGetter("symbolProto") - public ObjectValue symbolProto(CallContext ctx) { - return ctx.engine().symbolProto(); - } - @Native - public final Object apply(CallContext ctx, FunctionValue func, Object th, ArrayValue args) throws InterruptedException { - return func.call(ctx, th, args.toArray()); - } - @Native - public boolean defineProp(CallContext ctx, ObjectValue obj, Object key, FunctionValue getter, FunctionValue setter, boolean enumerable, boolean configurable) { - return obj.defineProperty(ctx, key, getter, setter, configurable, enumerable); - } - @Native - public boolean defineField(CallContext ctx, ObjectValue obj, Object key, Object val, boolean writable, boolean enumerable, boolean configurable) { - return obj.defineProperty(ctx, key, val, writable, configurable, enumerable); - } - - @Native - public int strlen(String str) { - return str.length(); - } - @Native - public String substring(String str, int start, int end) { - if (start > end) return substring(str, end, start); - - if (start < 0) start = 0; - if (start >= str.length()) return ""; - - if (end < 0) end = 0; - if (end > str.length()) end = str.length(); - - return str.substring(start, end); - } - @Native - public String toLower(String str) { - return str.toLowerCase(); - } - @Native - public String toUpper(String str) { - return str.toUpperCase(); - } - @Native - public int toCharCode(String str) { - return str.codePointAt(0); - } - @Native - public String fromCharCode(int code) { - return Character.toString((char)code); - } - @Native - public boolean startsWith(String str, String term, int offset) { - return str.startsWith(term, offset); - } - @Native - public boolean endsWith(String str, String term, int offset) { - try { - return str.substring(0, offset).endsWith(term); + @Native public void markSpecial(FunctionValue... funcs) { + for (var func : funcs) { + func.special = true; } - catch (IndexOutOfBoundsException e) { return false; } - } - - @Native - public int setInterval(CallContext ctx, FunctionValue func, double delay) { + @Native public Environment getEnv(Object func) { + if (func instanceof CodeFunction) return ((CodeFunction)func).environment; + else return null; + } + @Native public Object setEnv(Object func, Environment env) { + if (func instanceof CodeFunction) ((CodeFunction)func).environment = env; + return func; + } + @Native public Object apply(CallContext ctx, FunctionValue func, Object thisArg, ArrayValue args) throws InterruptedException { + return func.call(ctx, thisArg, args.toArray()); + } + @Native public FunctionValue delay(CallContext ctx, double delay, FunctionValue callback) throws InterruptedException { var thread = new Thread((Runnable)() -> { var ms = (long)delay; var ns = (int)((delay - ms) * 10000000); - while (true) { - try { - Thread.sleep(ms, ns); - } - catch (InterruptedException e) { return; } - - ctx.engine().pushMsg(false, func, ctx.data(), null); - } - }); - thread.start(); - - intervals.put(++nextId, thread); - - return nextId; - } - @Native - public int setTimeout(CallContext ctx, FunctionValue func, double delay) { - var thread = new Thread((Runnable)() -> { - var ms = (long)delay; - var ns = (int)((delay - ms) * 1000000); - try { Thread.sleep(ms, ns); } catch (InterruptedException e) { return; } - ctx.engine().pushMsg(false, func, ctx.data(), null); + ctx.engine.pushMsg(false, ctx.data(), ctx.environment, callback, null); }); thread.start(); - timeouts.put(++nextId, thread); - - return nextId; + return new NativeFunction((_ctx, thisArg, args) -> { + thread.interrupt(); + return null; + }); + } + @Native public void pushMessage(CallContext ctx, boolean micro, FunctionValue func, Object thisArg, Object[] args) { + ctx.engine.pushMsg(micro, ctx.data(), ctx.environment, func, thisArg, args); } - @Native - public void clearInterval(int id) { - var thread = intervals.remove(id); - if (thread != null) thread.interrupt(); + @Native public int strlen(String str) { + return str.length(); } - @Native - public void clearTimeout(int id) { - var thread = timeouts.remove(id); - if (thread != null) thread.interrupt(); + @Native("char") public int _char(String str) { + return str.charAt(0); + } + @Native public String stringFromChars(char[] str) { + return new String(str); + } + @Native public String stringFromStrings(String[] str) { + var res = new char[str.length]; + + for (var i = 0; i < str.length; i++) res[i] = str[i].charAt(0); + + return stringFromChars(res); + } + @Native public Symbol symbol(String str) { + return new Symbol(str); + } + @Native public String symbolToString(Symbol str) { + return str.value; } - @Native - public void sort(CallContext ctx, ArrayValue arr, FunctionValue cmp) { + @Native public boolean isArray(Object obj) { + return obj instanceof ArrayValue; + } + @Native public GeneratorFunction generator(FunctionValue obj) { + return new GeneratorFunction(obj); + } + + @Native public boolean defineField(CallContext ctx, ObjectValue obj, Object key, Object val, boolean writable, boolean enumerable, boolean configurable) { + return obj.defineProperty(ctx, key, val, writable, configurable, enumerable); + } + @Native public boolean defineProp(CallContext ctx, ObjectValue obj, Object key, FunctionValue getter, FunctionValue setter, boolean enumerable, boolean configurable) { + return obj.defineProperty(ctx, key, getter, setter, configurable, enumerable); + } + + @Native public ArrayValue keys(CallContext ctx, Object obj, boolean onlyString) throws InterruptedException { + var res = new ArrayValue(); + + var i = 0; + var list = Values.getMembers(ctx, obj, true, false); + + for (var el : list) res.set(ctx, i++, el); + + return res; + } + @Native public ArrayValue ownPropKeys(CallContext ctx, Object obj, boolean symbols) throws InterruptedException { + var res = new ArrayValue(); + + if (Values.isObject(obj)) { + var i = 0; + var list = Values.object(obj).keys(true); + + for (var el : list) res.set(ctx, i++, el); + } + + return res; + } + @Native public ObjectValue ownProp(CallContext ctx, ObjectValue val, Object key) throws InterruptedException { + return val.getMemberDescriptor(ctx, key); + } + @Native public void lock(ObjectValue val, String type) { + switch (type) { + case "ext": val.preventExtensions(); break; + case "seal": val.seal(); break; + case "freeze": val.freeze(); break; + } + } + @Native public boolean extensible(ObjectValue val) { + return val.extensible(); + } + + @Native public void sort(CallContext ctx, ArrayValue arr, FunctionValue cmp) { arr.sort((a, b) -> { try { var res = Values.toNumber(ctx, cmp.call(ctx, null, a, b)); @@ -158,109 +138,4 @@ public class Internals { } }); } - - @Native - public void special(FunctionValue... funcs) { - for (var func : funcs) { - func.special = true; - } - } - - @Native - public Symbol symbol(String name, boolean unique) { - if (!unique && symbols.containsKey(name)) { - return symbols.get(name); - } - else { - var val = new Symbol(name); - if (!unique) symbols.put(name, val); - return val; - } - } - @Native - public String symStr(Symbol symbol) { - return symbol.value; - } - - @Native - public void freeze(ObjectValue val) { - val.freeze(); - } - @Native - public void seal(ObjectValue val) { - val.seal(); - } - @Native - public void preventExtensions(ObjectValue val) { - val.preventExtensions(); - } - - @Native - public boolean extensible(Object val) { - return Values.isObject(val) && Values.object(val).extensible(); - } - - @Native - public ArrayValue keys(CallContext ctx, Object obj, boolean onlyString) throws InterruptedException { - var res = new ArrayValue(); - - var i = 0; - var list = Values.getMembers(ctx, obj, true, false); - - for (var el : list) { - if (el instanceof Symbol && onlyString) continue; - res.set(ctx, i++, el); - } - - return res; - } - @Native - public ArrayValue ownPropKeys(CallContext ctx, Object obj, boolean symbols) throws InterruptedException { - var res = new ArrayValue(); - - if (Values.isObject(obj)) { - var i = 0; - var list = Values.object(obj).keys(true); - - for (var el : list) { - if (el instanceof Symbol == symbols) res.set(ctx, i++, el); - } - } - - return res; - } - @Native - public ObjectValue ownProp(CallContext ctx, ObjectValue val, Object key) throws InterruptedException { - return val.getMemberDescriptor(ctx, key); - } - - @Native - public Object require(CallContext ctx, Object name) throws InterruptedException { - var res = ctx.engine().modules().tryLoad(ctx, Values.toString(ctx, name)); - if (res == null) throw EngineException.ofError("The module '" + name + "' doesn\'t exist."); - return res.exports(); - } - - @Native - public GeneratorFunction makeGenerator(FunctionValue func) { - if (func instanceof CodeFunction) return new GeneratorFunction((CodeFunction)func); - else throw EngineException.ofType("Can't create a generator with a non-js function."); - } - - @NativeGetter("err") - public ObjectValue errProto(CallContext ctx) { - return ctx.engine.errorProto(); - } - @NativeGetter("syntax") - public ObjectValue syntaxProto(CallContext ctx) { - return ctx.engine.syntaxErrorProto(); - } - @NativeGetter("range") - public ObjectValue rangeProto(CallContext ctx) { - return ctx.engine.rangeErrorProto(); - } - @NativeGetter("type") - public ObjectValue typeProto(CallContext ctx) { - return ctx.engine.typeErrorProto(); - } } diff --git a/src/me/topchetoeu/jscript/polyfills/PolyfillEngine.java b/src/me/topchetoeu/jscript/polyfills/PolyfillEngine.java- similarity index 97% rename from src/me/topchetoeu/jscript/polyfills/PolyfillEngine.java rename to src/me/topchetoeu/jscript/polyfills/PolyfillEngine.java- index f3a7706..0ea635e 100644 --- a/src/me/topchetoeu/jscript/polyfills/PolyfillEngine.java +++ b/src/me/topchetoeu/jscript/polyfills/PolyfillEngine.java- @@ -1,105 +1,105 @@ -package me.topchetoeu.jscript.polyfills; - -import java.io.BufferedReader; -import java.io.File; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; - -import me.topchetoeu.jscript.engine.Engine; -import me.topchetoeu.jscript.engine.modules.ModuleManager; - -public class PolyfillEngine extends Engine { - public static String streamToString(InputStream in) { - try { - StringBuilder out = new StringBuilder(); - BufferedReader br = new BufferedReader(new InputStreamReader(in)); - - for(var line = br.readLine(); line != null; line = br.readLine()) { - out.append(line).append('\n'); - } - - br.close(); - return out.toString(); - } - catch (IOException e) { - return null; - } - } - public static String resourceToString(String name) { - var str = PolyfillEngine.class.getResourceAsStream("/me/topchetoeu/jscript/" + name); - if (str == null) return null; - return streamToString(str); - } - - public final ModuleManager modules; - - @Override - public Object makeRegex(String pattern, String flags) { - return new RegExp(pattern, flags); - } - - @Override - public ModuleManager modules() { - return modules; - } - public PolyfillEngine(File root) { - super(); - - this.modules = new ModuleManager(root); - - // exposeNamespace("Math", Math.class); - // exposeNamespace("JSON", JSON.class); - // exposeClass("Promise", Promise.class); - // exposeClass("RegExp", RegExp.class); - // exposeClass("Date", Date.class); - // exposeClass("Map", Map.class); - // exposeClass("Set", Set.class); - - // global().define("Object", "Function", "String", "Number", "Boolean", "Symbol"); - // global().define("Array", "require"); - // global().define("Error", "SyntaxError", "TypeError", "RangeError"); - // global().define("setTimeout", "setInterval", "clearTimeout", "clearInterval"); - // global().define("debugger"); - - // global().define(true, new NativeFunction("measure", (ctx, thisArg, values) -> { - // var start = System.nanoTime(); - // try { - // return Values.call(ctx, values[0], ctx); - // } - // finally { - // System.out.println(String.format("Function took %s ms", (System.nanoTime() - start) / 1000000.)); - // } - // })); - // global().define(true, new NativeFunction("isNaN", (ctx, thisArg, args) -> { - // if (args.length == 0) return true; - // else return Double.isNaN(Values.toNumber(ctx, args[0])); - // })); - // global().define(true, new NativeFunction("log", (el, t, args) -> { - // for (var obj : args) Values.printValue(el, obj); - // System.out.println(); - // return null; - // })); - - // var scope = global().globalChild(); - // scope.define("gt", true, global().obj); - // scope.define("lgt", true, scope.obj); - // scope.define("setProps", "setConstr"); - // scope.define("internals", true, new Internals()); - // scope.define(true, new NativeFunction("run", (ctx, thisArg, args) -> { - // var filename = (String)args[0]; - // boolean pollute = args.length > 1 && args[1].equals(true); - // FunctionValue func; - // var src = resourceToString("js/" + filename); - // if (src == null) throw new RuntimeException("The source '" + filename + "' doesn't exist."); - - // if (pollute) func = Parsing.compile(global(), filename, src); - // else func = Parsing.compile(scope.globalChild(), filename, src); - - // func.call(ctx); - // return null; - // })); - - // pushMsg(false, scope.globalChild(), java.util.Map.of(), "core.js", resourceToString("js/core.js"), null); - } -} +package me.topchetoeu.jscript.polyfills; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; + +import me.topchetoeu.jscript.engine.Engine; +import me.topchetoeu.jscript.engine.modules.ModuleManager; + +public class PolyfillEngine extends Engine { + public static String streamToString(InputStream in) { + try { + StringBuilder out = new StringBuilder(); + BufferedReader br = new BufferedReader(new InputStreamReader(in)); + + for(var line = br.readLine(); line != null; line = br.readLine()) { + out.append(line).append('\n'); + } + + br.close(); + return out.toString(); + } + catch (IOException e) { + return null; + } + } + public static String resourceToString(String name) { + var str = PolyfillEngine.class.getResourceAsStream("/me/topchetoeu/jscript/" + name); + if (str == null) return null; + return streamToString(str); + } + + public final ModuleManager modules; + + @Override + public Object makeRegex(String pattern, String flags) { + return new RegExp(pattern, flags); + } + + @Override + public ModuleManager modules() { + return modules; + } + public PolyfillEngine(File root) { + super(); + + this.modules = new ModuleManager(root); + + // exposeNamespace("Math", Math.class); + // exposeNamespace("JSON", JSON.class); + // exposeClass("Promise", Promise.class); + // exposeClass("RegExp", RegExp.class); + // exposeClass("Date", Date.class); + // exposeClass("Map", Map.class); + // exposeClass("Set", Set.class); + + // global().define("Object", "Function", "String", "Number", "Boolean", "Symbol"); + // global().define("Array", "require"); + // global().define("Error", "SyntaxError", "TypeError", "RangeError"); + // global().define("setTimeout", "setInterval", "clearTimeout", "clearInterval"); + // global().define("debugger"); + + // global().define(true, new NativeFunction("measure", (ctx, thisArg, values) -> { + // var start = System.nanoTime(); + // try { + // return Values.call(ctx, values[0], ctx); + // } + // finally { + // System.out.println(String.format("Function took %s ms", (System.nanoTime() - start) / 1000000.)); + // } + // })); + // global().define(true, new NativeFunction("isNaN", (ctx, thisArg, args) -> { + // if (args.length == 0) return true; + // else return Double.isNaN(Values.toNumber(ctx, args[0])); + // })); + // global().define(true, new NativeFunction("log", (el, t, args) -> { + // for (var obj : args) Values.printValue(el, obj); + // System.out.println(); + // return null; + // })); + + // var scope = global().globalChild(); + // scope.define("gt", true, global().obj); + // scope.define("lgt", true, scope.obj); + // scope.define("setProps", "setConstr"); + // scope.define("internals", true, new Internals()); + // scope.define(true, new NativeFunction("run", (ctx, thisArg, args) -> { + // var filename = (String)args[0]; + // boolean pollute = args.length > 1 && args[1].equals(true); + // FunctionValue func; + // var src = resourceToString("js/" + filename); + // if (src == null) throw new RuntimeException("The source '" + filename + "' doesn't exist."); + + // if (pollute) func = Parsing.compile(global(), filename, src); + // else func = Parsing.compile(scope.globalChild(), filename, src); + + // func.call(ctx); + // return null; + // })); + + // pushMsg(false, scope.globalChild(), java.util.Map.of(), "core.js", resourceToString("js/core.js"), null); + } +} diff --git a/src/me/topchetoeu/jscript/polyfills/Promise.java b/src/me/topchetoeu/jscript/polyfills/Promise.java- similarity index 97% rename from src/me/topchetoeu/jscript/polyfills/Promise.java rename to src/me/topchetoeu/jscript/polyfills/Promise.java- index 6a4481e..f911f9b 100644 --- a/src/me/topchetoeu/jscript/polyfills/Promise.java +++ b/src/me/topchetoeu/jscript/polyfills/Promise.java- @@ -1,360 +1,360 @@ -package me.topchetoeu.jscript.polyfills; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import me.topchetoeu.jscript.engine.CallContext; -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.exceptions.EngineException; -import me.topchetoeu.jscript.interop.Native; - -public class Promise { - private static class Handle { - public final CallContext ctx; - public final FunctionValue fulfilled; - public final FunctionValue rejected; - - public Handle(CallContext ctx, FunctionValue fulfilled, FunctionValue rejected) { - this.ctx = ctx; - this.fulfilled = fulfilled; - this.rejected = rejected; - } - } - - @Native("resolve") - public static Promise ofResolved(CallContext ctx, Object val) { - if (Values.isWrapper(val, Promise.class)) return Values.wrapper(val, Promise.class); - var res = new Promise(); - res.fulfill(ctx, val); - return res; - } - public static Promise ofResolved(Object val) { - if (Values.isWrapper(val, Promise.class)) return Values.wrapper(val, Promise.class); - var res = new Promise(); - res.fulfill(val); - return res; - } - - @Native("reject") - public static Promise ofRejected(CallContext ctx, Object val) { - var res = new Promise(); - res.reject(ctx, val); - return res; - } - public static Promise ofRejected(Object val) { - var res = new Promise(); - res.fulfill(val); - return res; - } - - @Native - public static Promise any(CallContext ctx, Object _promises) { - if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); - var promises = Values.array(_promises); - if (promises.size() == 0) return ofResolved(new ArrayValue()); - var n = new int[] { promises.size() }; - var res = new Promise(); - - var errors = new ArrayValue(); - - for (var i = 0; i < promises.size(); i++) { - var index = i; - var val = promises.get(i); - if (Values.isWrapper(val, Promise.class)) Values.wrapper(val, Promise.class).then( - ctx, - new NativeFunction(null, (e, th, args) -> { res.fulfill(e, args[0]); return null; }), - new NativeFunction(null, (e, th, args) -> { - errors.set(ctx, index, args[0]); - n[0]--; - if (n[0] <= 0) res.reject(e, errors); - return null; - }) - ); - else { - res.fulfill(ctx, val); - break; - } - } - - return res; - } - @Native - public static Promise race(CallContext ctx, Object _promises) { - if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); - var promises = Values.array(_promises); - if (promises.size() == 0) return ofResolved(new ArrayValue()); - var res = new Promise(); - - for (var i = 0; i < promises.size(); i++) { - var val = promises.get(i); - if (Values.isWrapper(val, Promise.class)) Values.wrapper(val, Promise.class).then( - ctx, - new NativeFunction(null, (e, th, args) -> { res.fulfill(e, args[0]); return null; }), - new NativeFunction(null, (e, th, args) -> { res.reject(e, args[0]); return null; }) - ); - else { - res.fulfill(val); - break; - } - } - - return res; - } - @Native - public static Promise all(CallContext ctx, Object _promises) { - if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); - var promises = Values.array(_promises); - if (promises.size() == 0) return ofResolved(new ArrayValue()); - var n = new int[] { promises.size() }; - var res = new Promise(); - - var result = new ArrayValue(); - - for (var i = 0; i < promises.size(); i++) { - var index = i; - var val = promises.get(i); - if (Values.isWrapper(val, Promise.class)) Values.wrapper(val, Promise.class).then( - ctx, - new NativeFunction(null, (e, th, args) -> { - result.set(ctx, index, args[0]); - n[0]--; - if (n[0] <= 0) res.fulfill(e, result); - return null; - }), - new NativeFunction(null, (e, th, args) -> { res.reject(e, args[0]); return null; }) - ); - else { - result.set(ctx, i, val); - break; - } - } - - if (n[0] <= 0) res.fulfill(ctx, result); - - return res; - } - @Native - public static Promise allSettled(CallContext ctx, Object _promises) { - if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); - var promises = Values.array(_promises); - if (promises.size() == 0) return ofResolved(new ArrayValue()); - var n = new int[] { promises.size() }; - var res = new Promise(); - - var result = new ArrayValue(); - - for (var i = 0; i < promises.size(); i++) { - var index = i; - var val = promises.get(i); - if (Values.isWrapper(val, Promise.class)) Values.wrapper(val, Promise.class).then( - ctx, - new NativeFunction(null, (e, th, args) -> { - result.set(ctx, index, new ObjectValue(ctx, Map.of( - "status", "fulfilled", - "value", args[0] - ))); - n[0]--; - if (n[0] <= 0) res.fulfill(e, result); - return null; - }), - new NativeFunction(null, (e, th, args) -> { - result.set(ctx, index, new ObjectValue(ctx, Map.of( - "status", "rejected", - "reason", args[0] - ))); - n[0]--; - if (n[0] <= 0) res.fulfill(e, result); - return null; - }) - ); - else { - result.set(ctx, i, new ObjectValue(ctx, Map.of( - "status", "fulfilled", - "value", val - ))); - n[0]--; - } - } - - if (n[0] <= 0) res.fulfill(ctx, result); - - return res; - } - - private List handles = new ArrayList<>(); - - private static final int STATE_PENDING = 0; - private static final int STATE_FULFILLED = 1; - private static final int STATE_REJECTED = 2; - - private int state = STATE_PENDING; - private Object val; - - /** - * Thread safe - call from any thread - */ - public void fulfill(Object val) { - if (Values.isWrapper(val, Promise.class)) throw new IllegalArgumentException("A promise may not be a fulfil value."); - if (state != STATE_PENDING) return; - - this.state = STATE_FULFILLED; - this.val = val; - for (var el : handles) el.ctx.engine().pushMsg(true, el.fulfilled, el.ctx.data(), null, val); - handles = null; - } - /** - * Thread safe - call from any thread - */ - public void fulfill(CallContext ctx, Object val) { - if (Values.isWrapper(val, Promise.class)) Values.wrapper(val, Promise.class).then(ctx, - new NativeFunction(null, (e, th, args) -> { - this.fulfill(e, args[0]); - return null; - }), - new NativeFunction(null, (e, th, args) -> { - this.reject(e, args[0]); - return null; - }) - ); - else this.fulfill(val); - } - /** - * Thread safe - call from any thread - */ - public void reject(Object val) { - if (Values.isWrapper(val, Promise.class)) throw new IllegalArgumentException("A promise may not be a reject value."); - if (state != STATE_PENDING) return; - - this.state = STATE_REJECTED; - this.val = val; - for (var el : handles) el.ctx.engine().pushMsg(true, el.rejected, el.ctx.data(), null, val); - handles = null; - } - /** - * Thread safe - call from any thread - */ - public void reject(CallContext ctx, Object val) { - if (Values.isWrapper(val, Promise.class)) Values.wrapper(val, Promise.class).then(ctx, - new NativeFunction(null, (e, th, args) -> { - this.reject(e, args[0]); - return null; - }), - new NativeFunction(null, (e, th, args) -> { - this.reject(e, args[0]); - return null; - }) - ); - else this.reject(val); - } - - private void handle(CallContext ctx, FunctionValue fulfill, FunctionValue reject) { - if (state == STATE_FULFILLED) ctx.engine().pushMsg(true, fulfill, ctx.data(), null, val); - else if (state == STATE_REJECTED) ctx.engine().pushMsg(true, reject, ctx.data(), null, val); - else handles.add(new Handle(ctx, fulfill, reject)); - } - - /** - * Thread safe - you can call this from anywhere - * HOWEVER, it's strongly recommended to use this only in javascript - */ - @Native - public Promise then(CallContext ctx, Object onFulfill, Object onReject) { - if (!(onFulfill instanceof FunctionValue)) onFulfill = null; - if (!(onReject instanceof FunctionValue)) onReject = null; - - var res = new Promise(); - - var fulfill = (FunctionValue)onFulfill; - var reject = (FunctionValue)onReject; - - handle(ctx, - new NativeFunction(null, (e, th, args) -> { - if (fulfill == null) res.fulfill(e, args[0]); - else { - try { res.fulfill(e, fulfill.call(e, null, args[0])); } - catch (EngineException err) { res.reject(e, err.value); } - } - return null; - }), - new NativeFunction(null, (e, th, args) -> { - if (reject == null) res.reject(e, args[0]); - else { - try { res.fulfill(e, reject.call(e, null, args[0])); } - catch (EngineException err) { res.reject(e, err.value); } - } - return null; - }) - ); - - return res; - } - /** - * Thread safe - you can call this from anywhere - * HOWEVER, it's strongly recommended to use this only in javascript - */ - @Native("catch") - public Promise _catch(CallContext ctx, Object onReject) { - return then(ctx, null, onReject); - } - /** - * Thread safe - you can call this from anywhere - * HOWEVER, it's strongly recommended to use this only in javascript - */ - @Native("finally") - public Promise _finally(CallContext ctx, Object handle) { - return then(ctx, - new NativeFunction(null, (e, th, args) -> { - if (handle instanceof FunctionValue) ((FunctionValue)handle).call(ctx); - return args[0]; - }), - new NativeFunction(null, (e, th, args) -> { - if (handle instanceof FunctionValue) ((FunctionValue)handle).call(ctx); - throw new EngineException(args[0]); - }) - ); - } - - /** - * NOT THREAD SAFE - must be called from the engine executor thread - */ - @Native - public Promise(CallContext ctx, FunctionValue func) throws InterruptedException { - if (!(func instanceof FunctionValue)) throw EngineException.ofType("A function must be passed to the promise constructor."); - try { - func.call( - ctx, null, - new NativeFunction(null, (e, th, args) -> { - fulfill(e, args.length > 0 ? args[0] : null); - return null; - }), - new NativeFunction(null, (e, th, args) -> { - reject(e, args.length > 0 ? args[0] : null); - return null; - }) - ); - } - catch (EngineException e) { - reject(ctx, e.value); - } - } - - @Override @Native - public String toString() { - if (state == STATE_PENDING) return "Promise (pending)"; - else if (state == STATE_FULFILLED) return "Promise (fulfilled)"; - else return "Promise (rejected)"; - } - - private Promise(int state, Object val) { - this.state = state; - this.val = val; - } - public Promise() { - this(STATE_PENDING, null); - } -} +package me.topchetoeu.jscript.polyfills; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import me.topchetoeu.jscript.engine.CallContext; +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.exceptions.EngineException; +import me.topchetoeu.jscript.interop.Native; + +public class Promise { + private static class Handle { + public final CallContext ctx; + public final FunctionValue fulfilled; + public final FunctionValue rejected; + + public Handle(CallContext ctx, FunctionValue fulfilled, FunctionValue rejected) { + this.ctx = ctx; + this.fulfilled = fulfilled; + this.rejected = rejected; + } + } + + @Native("resolve") + public static Promise ofResolved(CallContext ctx, Object val) { + if (Values.isWrapper(val, Promise.class)) return Values.wrapper(val, Promise.class); + var res = new Promise(); + res.fulfill(ctx, val); + return res; + } + public static Promise ofResolved(Object val) { + if (Values.isWrapper(val, Promise.class)) return Values.wrapper(val, Promise.class); + var res = new Promise(); + res.fulfill(val); + return res; + } + + @Native("reject") + public static Promise ofRejected(CallContext ctx, Object val) { + var res = new Promise(); + res.reject(ctx, val); + return res; + } + public static Promise ofRejected(Object val) { + var res = new Promise(); + res.fulfill(val); + return res; + } + + @Native + public static Promise any(CallContext ctx, Object _promises) { + if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); + var promises = Values.array(_promises); + if (promises.size() == 0) return ofResolved(new ArrayValue()); + var n = new int[] { promises.size() }; + var res = new Promise(); + + var errors = new ArrayValue(); + + for (var i = 0; i < promises.size(); i++) { + var index = i; + var val = promises.get(i); + if (Values.isWrapper(val, Promise.class)) Values.wrapper(val, Promise.class).then( + ctx, + new NativeFunction(null, (e, th, args) -> { res.fulfill(e, args[0]); return null; }), + new NativeFunction(null, (e, th, args) -> { + errors.set(ctx, index, args[0]); + n[0]--; + if (n[0] <= 0) res.reject(e, errors); + return null; + }) + ); + else { + res.fulfill(ctx, val); + break; + } + } + + return res; + } + @Native + public static Promise race(CallContext ctx, Object _promises) { + if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); + var promises = Values.array(_promises); + if (promises.size() == 0) return ofResolved(new ArrayValue()); + var res = new Promise(); + + for (var i = 0; i < promises.size(); i++) { + var val = promises.get(i); + if (Values.isWrapper(val, Promise.class)) Values.wrapper(val, Promise.class).then( + ctx, + new NativeFunction(null, (e, th, args) -> { res.fulfill(e, args[0]); return null; }), + new NativeFunction(null, (e, th, args) -> { res.reject(e, args[0]); return null; }) + ); + else { + res.fulfill(val); + break; + } + } + + return res; + } + @Native + public static Promise all(CallContext ctx, Object _promises) { + if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); + var promises = Values.array(_promises); + if (promises.size() == 0) return ofResolved(new ArrayValue()); + var n = new int[] { promises.size() }; + var res = new Promise(); + + var result = new ArrayValue(); + + for (var i = 0; i < promises.size(); i++) { + var index = i; + var val = promises.get(i); + if (Values.isWrapper(val, Promise.class)) Values.wrapper(val, Promise.class).then( + ctx, + new NativeFunction(null, (e, th, args) -> { + result.set(ctx, index, args[0]); + n[0]--; + if (n[0] <= 0) res.fulfill(e, result); + return null; + }), + new NativeFunction(null, (e, th, args) -> { res.reject(e, args[0]); return null; }) + ); + else { + result.set(ctx, i, val); + break; + } + } + + if (n[0] <= 0) res.fulfill(ctx, result); + + return res; + } + @Native + public static Promise allSettled(CallContext ctx, Object _promises) { + if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); + var promises = Values.array(_promises); + if (promises.size() == 0) return ofResolved(new ArrayValue()); + var n = new int[] { promises.size() }; + var res = new Promise(); + + var result = new ArrayValue(); + + for (var i = 0; i < promises.size(); i++) { + var index = i; + var val = promises.get(i); + if (Values.isWrapper(val, Promise.class)) Values.wrapper(val, Promise.class).then( + ctx, + new NativeFunction(null, (e, th, args) -> { + result.set(ctx, index, new ObjectValue(ctx, Map.of( + "status", "fulfilled", + "value", args[0] + ))); + n[0]--; + if (n[0] <= 0) res.fulfill(e, result); + return null; + }), + new NativeFunction(null, (e, th, args) -> { + result.set(ctx, index, new ObjectValue(ctx, Map.of( + "status", "rejected", + "reason", args[0] + ))); + n[0]--; + if (n[0] <= 0) res.fulfill(e, result); + return null; + }) + ); + else { + result.set(ctx, i, new ObjectValue(ctx, Map.of( + "status", "fulfilled", + "value", val + ))); + n[0]--; + } + } + + if (n[0] <= 0) res.fulfill(ctx, result); + + return res; + } + + private List handles = new ArrayList<>(); + + private static final int STATE_PENDING = 0; + private static final int STATE_FULFILLED = 1; + private static final int STATE_REJECTED = 2; + + private int state = STATE_PENDING; + private Object val; + + /** + * Thread safe - call from any thread + */ + public void fulfill(Object val) { + if (Values.isWrapper(val, Promise.class)) throw new IllegalArgumentException("A promise may not be a fulfil value."); + if (state != STATE_PENDING) return; + + this.state = STATE_FULFILLED; + this.val = val; + for (var el : handles) el.ctx.engine().pushMsg(true, el.fulfilled, el.ctx.data(), null, val); + handles = null; + } + /** + * Thread safe - call from any thread + */ + public void fulfill(CallContext ctx, Object val) { + if (Values.isWrapper(val, Promise.class)) Values.wrapper(val, Promise.class).then(ctx, + new NativeFunction(null, (e, th, args) -> { + this.fulfill(e, args[0]); + return null; + }), + new NativeFunction(null, (e, th, args) -> { + this.reject(e, args[0]); + return null; + }) + ); + else this.fulfill(val); + } + /** + * Thread safe - call from any thread + */ + public void reject(Object val) { + if (Values.isWrapper(val, Promise.class)) throw new IllegalArgumentException("A promise may not be a reject value."); + if (state != STATE_PENDING) return; + + this.state = STATE_REJECTED; + this.val = val; + for (var el : handles) el.ctx.engine().pushMsg(true, el.rejected, el.ctx.data(), null, val); + handles = null; + } + /** + * Thread safe - call from any thread + */ + public void reject(CallContext ctx, Object val) { + if (Values.isWrapper(val, Promise.class)) Values.wrapper(val, Promise.class).then(ctx, + new NativeFunction(null, (e, th, args) -> { + this.reject(e, args[0]); + return null; + }), + new NativeFunction(null, (e, th, args) -> { + this.reject(e, args[0]); + return null; + }) + ); + else this.reject(val); + } + + private void handle(CallContext ctx, FunctionValue fulfill, FunctionValue reject) { + if (state == STATE_FULFILLED) ctx.engine().pushMsg(true, fulfill, ctx.data(), null, val); + else if (state == STATE_REJECTED) ctx.engine().pushMsg(true, reject, ctx.data(), null, val); + else handles.add(new Handle(ctx, fulfill, reject)); + } + + /** + * Thread safe - you can call this from anywhere + * HOWEVER, it's strongly recommended to use this only in javascript + */ + @Native + public Promise then(CallContext ctx, Object onFulfill, Object onReject) { + if (!(onFulfill instanceof FunctionValue)) onFulfill = null; + if (!(onReject instanceof FunctionValue)) onReject = null; + + var res = new Promise(); + + var fulfill = (FunctionValue)onFulfill; + var reject = (FunctionValue)onReject; + + handle(ctx, + new NativeFunction(null, (e, th, args) -> { + if (fulfill == null) res.fulfill(e, args[0]); + else { + try { res.fulfill(e, fulfill.call(e, null, args[0])); } + catch (EngineException err) { res.reject(e, err.value); } + } + return null; + }), + new NativeFunction(null, (e, th, args) -> { + if (reject == null) res.reject(e, args[0]); + else { + try { res.fulfill(e, reject.call(e, null, args[0])); } + catch (EngineException err) { res.reject(e, err.value); } + } + return null; + }) + ); + + return res; + } + /** + * Thread safe - you can call this from anywhere + * HOWEVER, it's strongly recommended to use this only in javascript + */ + @Native("catch") + public Promise _catch(CallContext ctx, Object onReject) { + return then(ctx, null, onReject); + } + /** + * Thread safe - you can call this from anywhere + * HOWEVER, it's strongly recommended to use this only in javascript + */ + @Native("finally") + public Promise _finally(CallContext ctx, Object handle) { + return then(ctx, + new NativeFunction(null, (e, th, args) -> { + if (handle instanceof FunctionValue) ((FunctionValue)handle).call(ctx); + return args[0]; + }), + new NativeFunction(null, (e, th, args) -> { + if (handle instanceof FunctionValue) ((FunctionValue)handle).call(ctx); + throw new EngineException(args[0]); + }) + ); + } + + /** + * NOT THREAD SAFE - must be called from the engine executor thread + */ + @Native + public Promise(CallContext ctx, FunctionValue func) throws InterruptedException { + if (!(func instanceof FunctionValue)) throw EngineException.ofType("A function must be passed to the promise constructor."); + try { + func.call( + ctx, null, + new NativeFunction(null, (e, th, args) -> { + fulfill(e, args.length > 0 ? args[0] : null); + return null; + }), + new NativeFunction(null, (e, th, args) -> { + reject(e, args.length > 0 ? args[0] : null); + return null; + }) + ); + } + catch (EngineException e) { + reject(ctx, e.value); + } + } + + @Override @Native + public String toString() { + if (state == STATE_PENDING) return "Promise (pending)"; + else if (state == STATE_FULFILLED) return "Promise (fulfilled)"; + else return "Promise (rejected)"; + } + + private Promise(int state, Object val) { + this.state = state; + this.val = val; + } + public Promise() { + this(STATE_PENDING, null); + } +}