major changes in preparations for environments

This commit is contained in:
TopchetoEU 2023-09-04 14:30:57 +03:00
parent d1b37074a6
commit 29d3f378a5
Signed by: topchetoeu
GPG Key ID: 6531B8583E5F6ED4
54 changed files with 2512 additions and 2161 deletions

View File

@ -1,154 +1,154 @@
package me.topchetoeu.jscript.engine.debug; package me.topchetoeu.jscript.engine.debug;
import java.io.IOException; import java.io.IOException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.ServerSocket; import java.net.ServerSocket;
import java.net.Socket; import java.net.Socket;
import java.security.MessageDigest; import java.security.MessageDigest;
import java.util.Base64; import java.util.Base64;
import me.topchetoeu.jscript.engine.Engine; import me.topchetoeu.jscript.engine.Engine;
import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type; import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type;
import me.topchetoeu.jscript.engine.debug.handlers.DebuggerHandles; import me.topchetoeu.jscript.engine.debug.handlers.DebuggerHandles;
import me.topchetoeu.jscript.exceptions.SyntaxException; import me.topchetoeu.jscript.exceptions.SyntaxException;
public class DebugServer { public class DebugServer {
public static String browserDisplayName = "jscript"; public static String browserDisplayName = "jscript";
public static String targetName = "target"; public static String targetName = "target";
public final Engine engine; public final Engine engine;
private static void send(Socket socket, String val) throws IOException { private static void send(Socket socket, String val) throws IOException {
Http.writeResponse(socket.getOutputStream(), 200, "OK", "application/json", val.getBytes()); Http.writeResponse(socket.getOutputStream(), 200, "OK", "application/json", val.getBytes());
} }
// SILENCE JAVA // SILENCE JAVA
private MessageDigest getDigestInstance() { private MessageDigest getDigestInstance() {
try { try {
return MessageDigest.getInstance("sha1"); return MessageDigest.getInstance("sha1");
} }
catch (Throwable a) { return null; } catch (Throwable a) { return null; }
} }
private static Thread runAsync(Runnable func, String name) { private static Thread runAsync(Runnable func, String name) {
var res = new Thread(func); var res = new Thread(func);
res.setName(name); res.setName(name);
res.start(); res.start();
return res; return res;
} }
private void handle(WebSocket ws) throws InterruptedException, IOException { private void handle(WebSocket ws) throws InterruptedException, IOException {
WebSocketMessage raw; WebSocketMessage raw;
while ((raw = ws.receive()) != null) { while ((raw = ws.receive()) != null) {
if (raw.type != Type.Text) { if (raw.type != Type.Text) {
ws.send(new V8Error("Expected a text message.")); ws.send(new V8Error("Expected a text message."));
continue; continue;
} }
V8Message msg; V8Message msg;
try { try {
msg = new V8Message(raw.textData()); msg = new V8Message(raw.textData());
} }
catch (SyntaxException e) { catch (SyntaxException e) {
ws.send(new V8Error(e.getMessage())); ws.send(new V8Error(e.getMessage()));
return; return;
} }
switch (msg.name) { switch (msg.name) {
case "Debugger.enable": DebuggerHandles.enable(msg, engine, ws); continue; case "Debugger.enable": DebuggerHandles.enable(msg, engine, ws); continue;
case "Debugger.disable": DebuggerHandles.disable(msg, engine, ws); continue; case "Debugger.disable": DebuggerHandles.disable(msg, engine, ws); continue;
case "Debugger.stepInto": DebuggerHandles.stepInto(msg, engine, ws); continue; case "Debugger.stepInto": DebuggerHandles.stepInto(msg, engine, ws); continue;
} }
} }
} }
private void onWsConnect(HttpRequest req, Socket socket) throws IOException { private void onWsConnect(HttpRequest req, Socket socket) throws IOException {
var key = req.headers.get("sec-websocket-key"); var key = req.headers.get("sec-websocket-key");
if (key == null) { if (key == null) {
Http.writeResponse( Http.writeResponse(
socket.getOutputStream(), 426, "Upgrade Required", "text/txt", socket.getOutputStream(), 426, "Upgrade Required", "text/txt",
"Expected a WS upgrade".getBytes() "Expected a WS upgrade".getBytes()
); );
return; return;
} }
var resKey = Base64.getEncoder().encodeToString(getDigestInstance().digest( var resKey = Base64.getEncoder().encodeToString(getDigestInstance().digest(
(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes() (key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes()
)); ));
Http.writeCode(socket.getOutputStream(), 101, "Switching Protocols"); Http.writeCode(socket.getOutputStream(), 101, "Switching Protocols");
Http.writeHeader(socket.getOutputStream(), "Connection", "Upgrade"); Http.writeHeader(socket.getOutputStream(), "Connection", "Upgrade");
Http.writeHeader(socket.getOutputStream(), "Sec-WebSocket-Accept", resKey); Http.writeHeader(socket.getOutputStream(), "Sec-WebSocket-Accept", resKey);
Http.writeLastHeader(socket.getOutputStream(), "Upgrade", "WebSocket"); Http.writeLastHeader(socket.getOutputStream(), "Upgrade", "WebSocket");
var ws = new WebSocket(socket); var ws = new WebSocket(socket);
runAsync(() -> { runAsync(() -> {
try { try {
handle(ws); handle(ws);
} }
catch (InterruptedException e) { return; } catch (InterruptedException e) { return; }
catch (IOException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }
finally { ws.close(); } finally { ws.close(); }
}, "Debug Server Message Reader"); }, "Debug Server Message Reader");
runAsync(() -> { runAsync(() -> {
try { try {
handle(ws); handle(ws);
} }
catch (InterruptedException e) { return; } catch (InterruptedException e) { return; }
catch (IOException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }
finally { ws.close(); } finally { ws.close(); }
}, "Debug Server Event Writer"); }, "Debug Server Event Writer");
} }
public void open(InetSocketAddress address) throws IOException { public void open(InetSocketAddress address) throws IOException {
ServerSocket server = new ServerSocket(); ServerSocket server = new ServerSocket();
server.bind(address); server.bind(address);
try { try {
while (true) { while (true) {
var socket = server.accept(); var socket = server.accept();
var req = Http.readRequest(socket.getInputStream()); var req = Http.readRequest(socket.getInputStream());
switch (req.path) { switch (req.path) {
case "/json/version": case "/json/version":
send(socket, "{\"Browser\":\"" + browserDisplayName + "\",\"Protocol-Version\":\"1.2\"}"); send(socket, "{\"Browser\":\"" + browserDisplayName + "\",\"Protocol-Version\":\"1.2\"}");
break; break;
case "/json/list": case "/json/list":
case "/json": case "/json":
var addr = "ws://" + address.getHostString() + ":" + address.getPort() + "/devtools/page/" + targetName; var addr = "ws://" + address.getHostString() + ":" + address.getPort() + "/devtools/page/" + targetName;
send(socket, "{\"id\":\"" + browserDisplayName + "\",\"webSocketDebuggerUrl\":\"" + addr + "\"}"); send(socket, "{\"id\":\"" + browserDisplayName + "\",\"webSocketDebuggerUrl\":\"" + addr + "\"}");
break; break;
case "/json/new": case "/json/new":
case "/json/activate": case "/json/activate":
case "/json/protocol": case "/json/protocol":
case "/json/close": case "/json/close":
case "/devtools/inspector.html": case "/devtools/inspector.html":
Http.writeResponse( Http.writeResponse(
socket.getOutputStream(), socket.getOutputStream(),
501, "Not Implemented", "text/txt", 501, "Not Implemented", "text/txt",
"This feature isn't (and won't be) implemented.".getBytes() "This feature isn't (and won't be) implemented.".getBytes()
); );
break; break;
default: default:
if (req.path.equals("/devtools/page/" + targetName)) onWsConnect(req, socket); if (req.path.equals("/devtools/page/" + targetName)) onWsConnect(req, socket);
else { else {
Http.writeResponse( Http.writeResponse(
socket.getOutputStream(), socket.getOutputStream(),
404, "Not Found", "text/txt", 404, "Not Found", "text/txt",
"Not found :/".getBytes() "Not found :/".getBytes()
); );
} }
break; break;
} }
} }
} }
finally { server.close(); } finally { server.close(); }
} }
public DebugServer(Engine engine) { public DebugServer(Engine engine) {
this.engine = engine; this.engine = engine;
} }
} }

View File

@ -1,52 +1,52 @@
package me.topchetoeu.jscript.engine.debug; package me.topchetoeu.jscript.engine.debug;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.engine.BreakpointData; import me.topchetoeu.jscript.engine.BreakpointData;
import me.topchetoeu.jscript.engine.DebugCommand; import me.topchetoeu.jscript.engine.DebugCommand;
import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.events.Event; import me.topchetoeu.jscript.events.Event;
public class DebugState { public class DebugState {
private boolean paused = false; private boolean paused = false;
public final HashSet<Location> breakpoints = new HashSet<>(); public final HashSet<Location> breakpoints = new HashSet<>();
public final List<CodeFrame> frames = new ArrayList<>(); public final List<CodeFrame> frames = new ArrayList<>();
public final Map<String, String> sources = new HashMap<>(); public final Map<String, String> sources = new HashMap<>();
public final Event<BreakpointData> breakpointNotifier = new Event<>(); public final Event<BreakpointData> breakpointNotifier = new Event<>();
public final Event<DebugCommand> commandNotifier = new Event<>(); public final Event<DebugCommand> commandNotifier = new Event<>();
public final Event<String> sourceAdded = new Event<>(); public final Event<String> sourceAdded = new Event<>();
public DebugState pushFrame(CodeFrame frame) { public DebugState pushFrame(CodeFrame frame) {
frames.add(frame); frames.add(frame);
return this; return this;
} }
public DebugState popFrame() { public DebugState popFrame() {
if (frames.size() > 0) frames.remove(frames.size() - 1); if (frames.size() > 0) frames.remove(frames.size() - 1);
return this; return this;
} }
public DebugCommand pause(BreakpointData data) throws InterruptedException { public DebugCommand pause(BreakpointData data) throws InterruptedException {
paused = true; paused = true;
breakpointNotifier.next(data); breakpointNotifier.next(data);
return commandNotifier.toAwaitable().await(); return commandNotifier.toAwaitable().await();
} }
public void resume(DebugCommand command) { public void resume(DebugCommand command) {
paused = false; paused = false;
commandNotifier.next(command); commandNotifier.next(command);
} }
// public void addSource()? // public void addSource()?
public boolean paused() { return paused; } public boolean paused() { return paused; }
public boolean isBreakpoint(Location loc) { public boolean isBreakpoint(Location loc) {
return breakpoints.contains(loc); return breakpoints.contains(loc);
} }
} }

View File

@ -1,65 +1,65 @@
package me.topchetoeu.jscript.engine.debug; package me.topchetoeu.jscript.engine.debug;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.HashMap; import java.util.HashMap;
import java.util.IllegalFormatException; import java.util.IllegalFormatException;
// We dont need no http library // We dont need no http library
public class Http { public class Http {
public static void writeCode(OutputStream str, int code, String name) throws IOException { public static void writeCode(OutputStream str, int code, String name) throws IOException {
str.write(("HTTP/1.1 " + code + " " + name + "\r\n").getBytes()); str.write(("HTTP/1.1 " + code + " " + name + "\r\n").getBytes());
} }
public static void writeHeader(OutputStream str, String name, String value) throws IOException { public static void writeHeader(OutputStream str, String name, String value) throws IOException {
str.write((name + ": " + value + "\r\n").getBytes()); str.write((name + ": " + value + "\r\n").getBytes());
} }
public static void writeLastHeader(OutputStream str, String name, String value) throws IOException { public static void writeLastHeader(OutputStream str, String name, String value) throws IOException {
str.write((name + ": " + value + "\r\n").getBytes()); str.write((name + ": " + value + "\r\n").getBytes());
writeHeadersEnd(str); writeHeadersEnd(str);
} }
public static void writeHeadersEnd(OutputStream str) throws IOException { public static void writeHeadersEnd(OutputStream str) throws IOException {
str.write("\n".getBytes()); str.write("\n".getBytes());
} }
public static void writeResponse(OutputStream str, int code, String name, String type, byte[] data) throws IOException { public static void writeResponse(OutputStream str, int code, String name, String type, byte[] data) throws IOException {
writeCode(str, code, name); writeCode(str, code, name);
writeHeader(str, "Content-Type", type); writeHeader(str, "Content-Type", type);
writeLastHeader(str, "Content-Length", data.length + ""); writeLastHeader(str, "Content-Length", data.length + "");
str.write(data); str.write(data);
str.close(); str.close();
} }
public static HttpRequest readRequest(InputStream str) throws IOException { public static HttpRequest readRequest(InputStream str) throws IOException {
var lines = new BufferedReader(new InputStreamReader(str)); var lines = new BufferedReader(new InputStreamReader(str));
var line = lines.readLine(); var line = lines.readLine();
var i1 = line.indexOf(" "); var i1 = line.indexOf(" ");
var i2 = line.lastIndexOf(" "); var i2 = line.lastIndexOf(" ");
var method = line.substring(0, i1).trim().toUpperCase(); var method = line.substring(0, i1).trim().toUpperCase();
var path = line.substring(i1 + 1, i2).trim(); var path = line.substring(i1 + 1, i2).trim();
var headers = new HashMap<String, String>(); var headers = new HashMap<String, String>();
while (!(line = lines.readLine()).isEmpty()) { while (!(line = lines.readLine()).isEmpty()) {
var i = line.indexOf(":"); var i = line.indexOf(":");
if (i < 0) continue; if (i < 0) continue;
var name = line.substring(0, i).trim().toLowerCase(); var name = line.substring(0, i).trim().toLowerCase();
var value = line.substring(i + 1).trim(); var value = line.substring(i + 1).trim();
if (name.length() == 0) continue; if (name.length() == 0) continue;
headers.put(name, value); headers.put(name, value);
} }
if (headers.containsKey("content-length")) { if (headers.containsKey("content-length")) {
try { try {
var i = Integer.parseInt(headers.get("content-length")); var i = Integer.parseInt(headers.get("content-length"));
str.skip(i); str.skip(i);
} }
catch (IllegalFormatException e) { /* ¯\_(ツ)_/¯ */ } catch (IllegalFormatException e) { /* ¯\_(ツ)_/¯ */ }
} }
return new HttpRequest(method, path, headers); return new HttpRequest(method, path, headers);
} }
} }

View File

@ -1,16 +1,16 @@
package me.topchetoeu.jscript.engine.debug; package me.topchetoeu.jscript.engine.debug;
import java.util.Map; import java.util.Map;
public class HttpRequest { public class HttpRequest {
public final String method; public final String method;
public final String path; public final String path;
public final Map<String, String> headers; public final Map<String, String> headers;
public HttpRequest(String method, String path, Map<String, String> headers) { public HttpRequest(String method, String path, Map<String, String> headers) {
this.method = method; this.method = method;
this.path = path; this.path = path;
this.headers = headers; this.headers = headers;
} }
} }

View File

@ -1,19 +1,19 @@
package me.topchetoeu.jscript.engine.debug; package me.topchetoeu.jscript.engine.debug;
import me.topchetoeu.jscript.json.JSON; import me.topchetoeu.jscript.json.JSON;
import me.topchetoeu.jscript.json.JSONMap; import me.topchetoeu.jscript.json.JSONMap;
public class V8Error { public class V8Error {
public final String message; public final String message;
public V8Error(String message) { public V8Error(String message) {
this.message = message; this.message = message;
} }
@Override @Override
public String toString() { public String toString() {
return JSON.stringify(new JSONMap().set("error", new JSONMap() return JSON.stringify(new JSONMap().set("error", new JSONMap()
.set("message", message) .set("message", message)
)); ));
} }
} }

View File

@ -1,22 +1,22 @@
package me.topchetoeu.jscript.engine.debug; package me.topchetoeu.jscript.engine.debug;
import me.topchetoeu.jscript.json.JSON; import me.topchetoeu.jscript.json.JSON;
import me.topchetoeu.jscript.json.JSONMap; import me.topchetoeu.jscript.json.JSONMap;
public class V8Event { public class V8Event {
public final String name; public final String name;
public final JSONMap params; public final JSONMap params;
public V8Event(String name, JSONMap params) { public V8Event(String name, JSONMap params) {
this.name = name; this.name = name;
this.params = params; this.params = params;
} }
@Override @Override
public String toString() { public String toString() {
return JSON.stringify(new JSONMap() return JSON.stringify(new JSONMap()
.set("method", name) .set("method", name)
.set("params", params) .set("params", params)
); );
} }
} }

View File

@ -1,50 +1,50 @@
package me.topchetoeu.jscript.engine.debug; package me.topchetoeu.jscript.engine.debug;
import java.util.Map; import java.util.Map;
import me.topchetoeu.jscript.json.JSON; import me.topchetoeu.jscript.json.JSON;
import me.topchetoeu.jscript.json.JSONElement; import me.topchetoeu.jscript.json.JSONElement;
import me.topchetoeu.jscript.json.JSONMap; import me.topchetoeu.jscript.json.JSONMap;
public class V8Message { public class V8Message {
public final String name; public final String name;
public final int id; public final int id;
public final JSONMap params; public final JSONMap params;
public V8Message(String name, int id, Map<String, JSONElement> params) { public V8Message(String name, int id, Map<String, JSONElement> params) {
this.name = name; this.name = name;
this.params = new JSONMap(params); this.params = new JSONMap(params);
this.id = id; this.id = id;
} }
public V8Result respond(JSONMap result) { public V8Result respond(JSONMap result) {
return new V8Result(id, result); return new V8Result(id, result);
} }
public V8Result respond() { public V8Result respond() {
return new V8Result(id, new JSONMap()); return new V8Result(id, new JSONMap());
} }
public V8Message(JSONMap raw) { public V8Message(JSONMap raw) {
if (!raw.isNumber("id")) throw new IllegalArgumentException("Expected number property 'id'."); if (!raw.isNumber("id")) throw new IllegalArgumentException("Expected number property 'id'.");
if (!raw.isString("method")) throw new IllegalArgumentException("Expected string property 'method'."); if (!raw.isString("method")) throw new IllegalArgumentException("Expected string property 'method'.");
this.name = raw.string("method"); this.name = raw.string("method");
this.id = (int)raw.number("id"); this.id = (int)raw.number("id");
this.params = raw.contains("params") ? raw.map("params") : new JSONMap(); this.params = raw.contains("params") ? raw.map("params") : new JSONMap();
} }
public V8Message(String raw) { public V8Message(String raw) {
this(JSON.parse("json", raw).map()); this(JSON.parse("json", raw).map());
} }
public JSONMap toMap() { public JSONMap toMap() {
var res = new JSONMap(); var res = new JSONMap();
return res; return res;
} }
@Override @Override
public String toString() { public String toString() {
return JSON.stringify(new JSONMap() return JSON.stringify(new JSONMap()
.set("method", name) .set("method", name)
.set("params", params) .set("params", params)
.set("id", id) .set("id", id)
); );
} }
} }

View File

@ -1,22 +1,22 @@
package me.topchetoeu.jscript.engine.debug; package me.topchetoeu.jscript.engine.debug;
import me.topchetoeu.jscript.json.JSON; import me.topchetoeu.jscript.json.JSON;
import me.topchetoeu.jscript.json.JSONMap; import me.topchetoeu.jscript.json.JSONMap;
public class V8Result { public class V8Result {
public final int id; public final int id;
public final JSONMap result; public final JSONMap result;
public V8Result(int id, JSONMap result) { public V8Result(int id, JSONMap result) {
this.id = id; this.id = id;
this.result = result; this.result = result;
} }
@Override @Override
public String toString() { public String toString() {
return JSON.stringify(new JSONMap() return JSON.stringify(new JSONMap()
.set("id", id) .set("id", id)
.set("result", result) .set("result", result)
); );
} }
} }

View File

@ -1,185 +1,185 @@
package me.topchetoeu.jscript.engine.debug; package me.topchetoeu.jscript.engine.debug;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.net.Socket; import java.net.Socket;
import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type; import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type;
public class WebSocket implements AutoCloseable { public class WebSocket implements AutoCloseable {
public long maxLength = 2000000; public long maxLength = 2000000;
private Socket socket; private Socket socket;
private boolean closed = false; private boolean closed = false;
private OutputStream out() throws IOException { private OutputStream out() throws IOException {
return socket.getOutputStream(); return socket.getOutputStream();
} }
private InputStream in() throws IOException { private InputStream in() throws IOException {
return socket.getInputStream(); return socket.getInputStream();
} }
private long readLen(int byteLen) throws IOException { private long readLen(int byteLen) throws IOException {
long res = 0; long res = 0;
if (byteLen == 126) { if (byteLen == 126) {
res |= in().read() << 8; res |= in().read() << 8;
res |= in().read(); res |= in().read();
return res; return res;
} }
else if (byteLen == 127) { else if (byteLen == 127) {
res |= in().read() << 56; res |= in().read() << 56;
res |= in().read() << 48; res |= in().read() << 48;
res |= in().read() << 40; res |= in().read() << 40;
res |= in().read() << 32; res |= in().read() << 32;
res |= in().read() << 24; res |= in().read() << 24;
res |= in().read() << 16; res |= in().read() << 16;
res |= in().read() << 8; res |= in().read() << 8;
res |= in().read(); res |= in().read();
return res; return res;
} }
else return byteLen; else return byteLen;
} }
private byte[] readMask(boolean has) throws IOException { private byte[] readMask(boolean has) throws IOException {
if (has) { if (has) {
return new byte[] { return new byte[] {
(byte)in().read(), (byte)in().read(),
(byte)in().read(), (byte)in().read(),
(byte)in().read(), (byte)in().read(),
(byte)in().read() (byte)in().read()
}; };
} }
else return new byte[4]; else return new byte[4];
} }
private void writeLength(long len) throws IOException { private void writeLength(long len) throws IOException {
if (len < 126) { if (len < 126) {
out().write((int)len); out().write((int)len);
} }
else if (len < 0xFFFF) { else if (len < 0xFFFF) {
out().write(126); out().write(126);
out().write((int)(len >> 8) & 0xFF); out().write((int)(len >> 8) & 0xFF);
out().write((int)len & 0xFF); out().write((int)len & 0xFF);
} }
else { else {
out().write(127); out().write(127);
out().write((int)(len >> 56) & 0xFF); out().write((int)(len >> 56) & 0xFF);
out().write((int)(len >> 48) & 0xFF); out().write((int)(len >> 48) & 0xFF);
out().write((int)(len >> 40) & 0xFF); out().write((int)(len >> 40) & 0xFF);
out().write((int)(len >> 32) & 0xFF); out().write((int)(len >> 32) & 0xFF);
out().write((int)(len >> 24) & 0xFF); out().write((int)(len >> 24) & 0xFF);
out().write((int)(len >> 16) & 0xFF); out().write((int)(len >> 16) & 0xFF);
out().write((int)(len >> 8) & 0xFF); out().write((int)(len >> 8) & 0xFF);
out().write((int)len & 0xFF); out().write((int)len & 0xFF);
} }
} }
private synchronized void write(int type, byte[] data) throws IOException { private synchronized void write(int type, byte[] data) throws IOException {
out().write(type | 0x80); out().write(type | 0x80);
writeLength(data.length); writeLength(data.length);
for (int i = 0; i < data.length; i++) { for (int i = 0; i < data.length; i++) {
out().write(data[i]); out().write(data[i]);
} }
} }
public void send(String data) throws IOException { public void send(String data) throws IOException {
if (closed) throw new IllegalStateException("Object is closed."); if (closed) throw new IllegalStateException("Object is closed.");
write(1, data.getBytes()); write(1, data.getBytes());
} }
public void send(byte[] data) throws IOException { public void send(byte[] data) throws IOException {
if (closed) throw new IllegalStateException("Object is closed."); if (closed) throw new IllegalStateException("Object is closed.");
write(2, data); write(2, data);
} }
public void send(WebSocketMessage msg) throws IOException { public void send(WebSocketMessage msg) throws IOException {
if (msg.type == Type.Binary) send(msg.binaryData()); if (msg.type == Type.Binary) send(msg.binaryData());
else send(msg.textData()); else send(msg.textData());
} }
public void send(Object data) throws IOException { public void send(Object data) throws IOException {
if (closed) throw new IllegalStateException("Object is closed."); if (closed) throw new IllegalStateException("Object is closed.");
write(1, data.toString().getBytes()); write(1, data.toString().getBytes());
} }
public void close(String reason) { public void close(String reason) {
if (socket != null) { if (socket != null) {
try { write(8, reason.getBytes()); } catch (IOException e) { /* ¯\_(ツ)_/¯ */ } try { write(8, reason.getBytes()); } catch (IOException e) { /* ¯\_(ツ)_/¯ */ }
try { socket.close(); } catch (IOException e) { e.printStackTrace(); } try { socket.close(); } catch (IOException e) { e.printStackTrace(); }
} }
socket = null; socket = null;
closed = true; closed = true;
} }
public void close() { public void close() {
close(""); close("");
} }
private WebSocketMessage fail(String reason) { private WebSocketMessage fail(String reason) {
System.out.println("WebSocket Error: " + reason); System.out.println("WebSocket Error: " + reason);
close(reason); close(reason);
return null; return null;
} }
private byte[] readData() throws IOException { private byte[] readData() throws IOException {
var maskLen = in().read(); var maskLen = in().read();
var hasMask = (maskLen & 0x80) != 0; var hasMask = (maskLen & 0x80) != 0;
var len = (int)readLen(maskLen & 0x7F); var len = (int)readLen(maskLen & 0x7F);
var mask = readMask(hasMask); var mask = readMask(hasMask);
if (len > maxLength) fail("WebSocket Error: client exceeded configured max message size"); if (len > maxLength) fail("WebSocket Error: client exceeded configured max message size");
else { else {
var buff = new byte[len]; var buff = new byte[len];
if (in().read(buff) < len) fail("WebSocket Error: payload too short"); if (in().read(buff) < len) fail("WebSocket Error: payload too short");
else { else {
for (int i = 0; i < len; i++) { for (int i = 0; i < len; i++) {
buff[i] ^= mask[(int)(i % 4)]; buff[i] ^= mask[(int)(i % 4)];
} }
return buff; return buff;
} }
} }
return null; return null;
} }
public WebSocketMessage receive() throws InterruptedException { public WebSocketMessage receive() throws InterruptedException {
try { try {
var data = new ByteArrayOutputStream(); var data = new ByteArrayOutputStream();
var type = 0; var type = 0;
while (socket != null && !closed) { while (socket != null && !closed) {
var finId = in().read(); var finId = in().read();
var fin = (finId & 0x80) != 0; var fin = (finId & 0x80) != 0;
int id = finId & 0x0F; int id = finId & 0x0F;
if (id == 0x8) { close(); return null; } if (id == 0x8) { close(); return null; }
if (id >= 0x8) { if (id >= 0x8) {
if (!fin) return fail("WebSocket Error: client-sent control frame was fragmented"); if (!fin) return fail("WebSocket Error: client-sent control frame was fragmented");
if (id == 0x9) write(0xA, data.toByteArray()); if (id == 0x9) write(0xA, data.toByteArray());
continue; continue;
} }
if (type == 0) type = id; if (type == 0) type = id;
if (type == 0) return fail("WebSocket Error: client used opcode 0x00 for first fragment"); if (type == 0) return fail("WebSocket Error: client used opcode 0x00 for first fragment");
var buff = readData(); var buff = readData();
if (buff == null) break; if (buff == null) break;
if (data.size() + buff.length > maxLength) return fail("WebSocket Error: client exceeded configured max message size"); if (data.size() + buff.length > maxLength) return fail("WebSocket Error: client exceeded configured max message size");
data.write(buff); data.write(buff);
if (!fin) continue; if (!fin) continue;
var raw = data.toByteArray(); var raw = data.toByteArray();
if (type == 1) return new WebSocketMessage(new String(raw)); if (type == 1) return new WebSocketMessage(new String(raw));
else return new WebSocketMessage(raw); else return new WebSocketMessage(raw);
} }
} }
catch (IOException e) { close(); } catch (IOException e) { close(); }
return null; return null;
} }
public WebSocket(Socket socket) { public WebSocket(Socket socket) {
this.socket = socket; this.socket = socket;
} }
} }

View File

@ -1,29 +1,29 @@
package me.topchetoeu.jscript.engine.debug; package me.topchetoeu.jscript.engine.debug;
public class WebSocketMessage { public class WebSocketMessage {
public static enum Type { public static enum Type {
Text, Text,
Binary, Binary,
} }
public final Type type; public final Type type;
private final Object data; private final Object data;
public final String textData() { public final String textData() {
if (type != Type.Text) throw new IllegalStateException("Message is not text."); if (type != Type.Text) throw new IllegalStateException("Message is not text.");
return (String)data; return (String)data;
} }
public final byte[] binaryData() { public final byte[] binaryData() {
if (type != Type.Binary) throw new IllegalStateException("Message is not binary."); if (type != Type.Binary) throw new IllegalStateException("Message is not binary.");
return (byte[])data; return (byte[])data;
} }
public WebSocketMessage(String data) { public WebSocketMessage(String data) {
this.type = Type.Text; this.type = Type.Text;
this.data = data; this.data = data;
} }
public WebSocketMessage(byte[] data) { public WebSocketMessage(byte[] data) {
this.type = Type.Binary; this.type = Type.Binary;
this.data = data; this.data = data;
} }
} }

View File

@ -1,29 +1,29 @@
package me.topchetoeu.jscript.engine.debug.handlers; package me.topchetoeu.jscript.engine.debug.handlers;
import java.io.IOException; import java.io.IOException;
import me.topchetoeu.jscript.engine.DebugCommand; import me.topchetoeu.jscript.engine.DebugCommand;
import me.topchetoeu.jscript.engine.Engine; import me.topchetoeu.jscript.engine.Engine;
import me.topchetoeu.jscript.engine.debug.V8Error; import me.topchetoeu.jscript.engine.debug.V8Error;
import me.topchetoeu.jscript.engine.debug.V8Message; import me.topchetoeu.jscript.engine.debug.V8Message;
import me.topchetoeu.jscript.engine.debug.WebSocket; import me.topchetoeu.jscript.engine.debug.WebSocket;
import me.topchetoeu.jscript.json.JSONMap; import me.topchetoeu.jscript.json.JSONMap;
public class DebuggerHandles { public class DebuggerHandles {
public static void enable(V8Message msg, Engine engine, WebSocket ws) throws IOException { 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.")); if (engine.debugState == null) ws.send(new V8Error("Debugging is disabled for this engine."));
else ws.send(msg.respond(new JSONMap().set("debuggerId", 1))); else ws.send(msg.respond(new JSONMap().set("debuggerId", 1)));
} }
public static void disable(V8Message msg, Engine engine, WebSocket ws) throws IOException { public static void disable(V8Message msg, Engine engine, WebSocket ws) throws IOException {
if (engine.debugState == null) ws.send(msg.respond()); if (engine.debugState == null) ws.send(msg.respond());
else ws.send(new V8Error("Debugger may not be disabled.")); else ws.send(new V8Error("Debugger may not be disabled."));
} }
public static void stepInto(V8Message msg, Engine engine, WebSocket ws) throws IOException { 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.")); 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 if (!engine.debugState.paused()) ws.send(new V8Error("Debugger is not paused."));
else { else {
engine.debugState.resume(DebugCommand.STEP_INTO); engine.debugState.resume(DebugCommand.STEP_INTO);
ws.send(msg.respond()); ws.send(msg.respond());
} }
} }
} }

View File

@ -1,50 +1,50 @@
package me.topchetoeu.jscript.engine.modules; package me.topchetoeu.jscript.engine.modules;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import me.topchetoeu.jscript.polyfills.PolyfillEngine; import me.topchetoeu.jscript.polyfills.PolyfillEngine;
public class FileModuleProvider implements ModuleProvider { public class FileModuleProvider implements ModuleProvider {
public File root; public File root;
public final boolean allowOutside; public final boolean allowOutside;
private boolean checkInside(Path modFile) { private boolean checkInside(Path modFile) {
return modFile.toAbsolutePath().startsWith(root.toPath().toAbsolutePath()); return modFile.toAbsolutePath().startsWith(root.toPath().toAbsolutePath());
} }
@Override @Override
public Module getModule(File cwd, String name) { public Module getModule(File cwd, String name) {
var realName = getRealName(cwd, name); var realName = getRealName(cwd, name);
if (realName == null) return null; if (realName == null) return null;
var path = Path.of(realName + ".js").normalize(); var path = Path.of(realName + ".js").normalize();
try { try {
var res = PolyfillEngine.streamToString(new FileInputStream(path.toFile())); var res = PolyfillEngine.streamToString(new FileInputStream(path.toFile()));
return new Module(realName, path.toString(), res); return new Module(realName, path.toString(), res);
} }
catch (IOException e) { catch (IOException e) {
return null; return null;
} }
} }
@Override @Override
public String getRealName(File cwd, String name) { public String getRealName(File cwd, String name) {
var path = Path.of(".", Path.of(cwd.toString(), name).normalize().toString()); var path = Path.of(".", Path.of(cwd.toString(), name).normalize().toString());
var fileName = path.getFileName().toString(); var fileName = path.getFileName().toString();
if (fileName == null) return null; if (fileName == null) return null;
if (!fileName.equals("index") && path.toFile().isDirectory()) return getRealName(cwd, name + "/index"); if (!fileName.equals("index") && path.toFile().isDirectory()) return getRealName(cwd, name + "/index");
path = Path.of(path.toString() + ".js"); path = Path.of(path.toString() + ".js");
if (!allowOutside && !checkInside(path)) return null; if (!allowOutside && !checkInside(path)) return null;
if (!path.toFile().isFile() || !path.toFile().canRead()) return null; if (!path.toFile().isFile() || !path.toFile().canRead()) return null;
var res = path.toString().replace('\\', '/'); var res = path.toString().replace('\\', '/');
var i = res.lastIndexOf('.'); var i = res.lastIndexOf('.');
return res.substring(0, i); return res.substring(0, i);
} }
public FileModuleProvider(File root, boolean allowOutside) { public FileModuleProvider(File root, boolean allowOutside) {
this.root = root.toPath().normalize().toFile(); this.root = root.toPath().normalize().toFile();
this.allowOutside = allowOutside; this.allowOutside = allowOutside;
} }
} }

View File

@ -1,57 +1,57 @@
package me.topchetoeu.jscript.engine.modules; package me.topchetoeu.jscript.engine.modules;
import java.io.File; import java.io.File;
import me.topchetoeu.jscript.engine.CallContext; import me.topchetoeu.jscript.engine.CallContext;
import me.topchetoeu.jscript.engine.CallContext.DataKey; import me.topchetoeu.jscript.engine.CallContext.DataKey;
import me.topchetoeu.jscript.engine.scope.Variable; import me.topchetoeu.jscript.engine.scope.Variable;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.interop.NativeGetter; import me.topchetoeu.jscript.interop.NativeGetter;
import me.topchetoeu.jscript.interop.NativeSetter; import me.topchetoeu.jscript.interop.NativeSetter;
public class Module { public class Module {
public class ExportsVariable implements Variable { public class ExportsVariable implements Variable {
@Override @Override
public boolean readonly() { return false; } public boolean readonly() { return false; }
@Override @Override
public Object get(CallContext ctx) { return exports; } public Object get(CallContext ctx) { return exports; }
@Override @Override
public void set(CallContext ctx, Object val) { exports = val; } public void set(CallContext ctx, Object val) { exports = val; }
} }
public static DataKey<Module> KEY = new DataKey<>(); public static DataKey<Module> KEY = new DataKey<>();
public final String filename; public final String filename;
public final String source; public final String source;
public final String name; public final String name;
private Object exports = new ObjectValue(); private Object exports = new ObjectValue();
private boolean executing = false; private boolean executing = false;
@NativeGetter("name") @NativeGetter("name")
public String name() { return name; } public String name() { return name; }
@NativeGetter("exports") @NativeGetter("exports")
public Object exports() { return exports; } public Object exports() { return exports; }
@NativeSetter("exports") @NativeSetter("exports")
public void setExports(Object val) { exports = val; } public void setExports(Object val) { exports = val; }
public void execute(CallContext ctx) throws InterruptedException { public void execute(CallContext ctx) throws InterruptedException {
if (executing) return; if (executing) return;
executing = true; executing = true;
var scope = ctx.engine().global().globalChild(); var scope = ctx.engine().global().globalChild();
scope.define(null, "module", true, this); scope.define(null, "module", true, this);
scope.define("exports", new ExportsVariable()); scope.define("exports", new ExportsVariable());
var parent = new File(filename).getParentFile(); var parent = new File(filename).getParentFile();
if (parent == null) parent = new File("."); if (parent == null) parent = new File(".");
ctx.engine().compile(scope, filename, source).call(ctx.copy().setData(KEY, this), null); ctx.engine().compile(scope, filename, source).call(ctx.copy().setData(KEY, this), null);
executing = false; executing = false;
} }
public Module(String name, String filename, String source) { public Module(String name, String filename, String source) {
this.name = name; this.name = name;
this.filename = filename; this.filename = filename;
this.source = source; this.source = source;
} }
} }

View File

@ -1,80 +1,80 @@
package me.topchetoeu.jscript.engine.modules; package me.topchetoeu.jscript.engine.modules;
import java.io.File; import java.io.File;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import me.topchetoeu.jscript.engine.CallContext; import me.topchetoeu.jscript.engine.CallContext;
public class ModuleManager { public class ModuleManager {
private final List<ModuleProvider> providers = new ArrayList<>(); private final List<ModuleProvider> providers = new ArrayList<>();
private final HashMap<String, Module> cache = new HashMap<>(); private final HashMap<String, Module> cache = new HashMap<>();
public final FileModuleProvider files; public final FileModuleProvider files;
public void addProvider(ModuleProvider provider) { public void addProvider(ModuleProvider provider) {
this.providers.add(provider); this.providers.add(provider);
} }
public boolean isCached(File cwd, String name) { public boolean isCached(File cwd, String name) {
name = name.replace("\\", "/"); name = name.replace("\\", "/");
// Absolute paths are forbidden // Absolute paths are forbidden
if (name.startsWith("/")) return false; if (name.startsWith("/")) return false;
// Look for files if we have a relative path // Look for files if we have a relative path
if (name.startsWith("../") || name.startsWith("./")) { if (name.startsWith("../") || name.startsWith("./")) {
var realName = files.getRealName(cwd, name); var realName = files.getRealName(cwd, name);
if (cache.containsKey(realName)) return true; if (cache.containsKey(realName)) return true;
else return false; else return false;
} }
for (var provider : providers) { for (var provider : providers) {
var realName = provider.getRealName(cwd, name); var realName = provider.getRealName(cwd, name);
if (realName == null) continue; if (realName == null) continue;
if (cache.containsKey(realName)) return true; if (cache.containsKey(realName)) return true;
} }
return false; return false;
} }
public Module tryLoad(CallContext ctx, String name) throws InterruptedException { public Module tryLoad(CallContext ctx, String name) throws InterruptedException {
name = name.replace('\\', '/'); name = name.replace('\\', '/');
var pcwd = Path.of("."); var pcwd = Path.of(".");
if (ctx.hasData(Module.KEY)) { if (ctx.hasData(Module.KEY)) {
pcwd = Path.of(((Module)ctx.getData(Module.KEY)).filename).getParent(); pcwd = Path.of(((Module)ctx.getData(Module.KEY)).filename).getParent();
if (pcwd == null) pcwd = Path.of("."); if (pcwd == null) pcwd = Path.of(".");
} }
var cwd = pcwd.toFile(); var cwd = pcwd.toFile();
if (name.startsWith("/")) return null; if (name.startsWith("/")) return null;
if (name.startsWith("../") || name.startsWith("./")) { if (name.startsWith("../") || name.startsWith("./")) {
var realName = files.getRealName(cwd, name); var realName = files.getRealName(cwd, name);
if (realName == null) return null; if (realName == null) return null;
if (cache.containsKey(realName)) return cache.get(realName); if (cache.containsKey(realName)) return cache.get(realName);
var mod = files.getModule(cwd, name); var mod = files.getModule(cwd, name);
cache.put(mod.name(), mod); cache.put(mod.name(), mod);
mod.execute(ctx); mod.execute(ctx);
return mod; return mod;
} }
for (var provider : providers) { for (var provider : providers) {
var realName = provider.getRealName(cwd, name); var realName = provider.getRealName(cwd, name);
if (realName == null) continue; if (realName == null) continue;
if (cache.containsKey(realName)) return cache.get(realName); if (cache.containsKey(realName)) return cache.get(realName);
var mod = provider.getModule(cwd, name); var mod = provider.getModule(cwd, name);
cache.put(mod.name(), mod); cache.put(mod.name(), mod);
mod.execute(ctx); mod.execute(ctx);
return mod; return mod;
} }
return null; return null;
} }
public ModuleManager(File root) { public ModuleManager(File root) {
files = new FileModuleProvider(root, false); files = new FileModuleProvider(root, false);
} }
} }

View File

@ -1,9 +1,9 @@
package me.topchetoeu.jscript.engine.modules; package me.topchetoeu.jscript.engine.modules;
import java.io.File; import java.io.File;
public interface ModuleProvider { public interface ModuleProvider {
Module getModule(File cwd, String name); Module getModule(File cwd, String name);
String getRealName(File cwd, String name); String getRealName(File cwd, String name);
default boolean hasModule(File cwd, String name) { return getRealName(cwd, name) != null; } default boolean hasModule(File cwd, String name) { return getRealName(cwd, name) != null; }
} }

View File

@ -1,68 +1,64 @@
var env: Environment; interface Environment {
global: typeof globalThis & Record<string, any>;
proto(name: string): object;
setProto(name: string, val: object): void;
}
interface Internals {
markSpecial(...funcs: Function[]): void;
getEnv(func: Function): Environment | undefined;
setEnv<T>(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 strlen(val: string): number;
return (_env: Environment) => { char(val: string): number;
env = _env; stringFromStrings(arr: string[]): string;
env.global.assert = (cond, msg) => { stringFromChars(arr: number[]): string;
try { symbol(name?: string): symbol;
if (!cond()) throw 'condition not satisfied'; symbolToString(sym: symbol): string;
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');
env.internals.special(Object, Function, Error, Array); isArray(obj: any): boolean;
generator(func: (_yield: <T>(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<any, any>;
ownPropKeys(obj: any): any[];
lock(obj: object, type: 'ext' | 'seal' | 'freeze'): void;
extensible(obj: object): boolean;
env.global.setTimeout = (func, delay, ...args) => { sort(arr: any[], comaprator: (a: any, b: any) => number): void;
if (typeof func !== 'function') throw new TypeError("func must be a function."); }
delay = (delay ?? 0) - 0;
return env.internals.setTimeout(() => func(...args), delay) var env: Environment = arguments[0], internals: Internals = arguments[1];
};
env.global.setInterval = (func, delay, ...args) => { try {
if (typeof func !== 'function') throw new TypeError("func must be a function."); run('values/object');
delay = (delay ?? 0) - 0; run('values/symbol');
return env.internals.setInterval(() => func(...args), delay) run('values/function');
}; run('values/errors');
run('values/string');
env.global.clearTimeout = (id) => { run('values/number');
id = id | 0; run('values/boolean');
env.internals.clearTimeout(id); run('values/array');
}; run('promise');
env.global.clearInterval = (id) => { run('map');
id = id | 0; run('set');
env.internals.clearInterval(id); run('regex');
}; run('timeout');
run('promise'); log('Loaded polyfills!');
run('map'); }
run('set'); catch (e: any) {
run('regex'); let err = 'Uncaught error while loading polyfills: ';
run('require');
if (typeof Error !== 'undefined' && e instanceof Error && e.toString !== {}.toString) err += e;
log('Loaded polyfills!'); else if ('message' in e) {
if ('name' in e) err += e.name + ": " + e.message;
else err += 'Error: ' + e.message;
} }
catch (e: any) { else err += e;
if (!_env.captureErr) throw e;
var err = 'Uncaught error while loading polyfills: '; log(e);
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);
}
};

66
lib/lib.d.ts vendored
View File

@ -46,8 +46,8 @@ type IteratorReturnResult<TReturn> =
type IteratorResult<T, TReturn = any> = IteratorYieldResult<T> | IteratorReturnResult<TReturn>; type IteratorResult<T, TReturn = any> = IteratorYieldResult<T> | IteratorReturnResult<TReturn>;
interface Thenable<T> { interface Thenable<T> {
then<NextT>(this: Promise<T>, onFulfilled: PromiseThenFunc<T, NextT>, onRejected?: PromiseRejectFunc): Promise<Awaited<NextT>>; then<NextT>(onFulfilled: PromiseThenFunc<T, NextT>, onRejected?: PromiseRejectFunc): Promise<Awaited<NextT>>;
then(this: Promise<T>, onFulfilled: undefined, onRejected?: PromiseRejectFunc): Promise<T>; then(onFulfilled: undefined, onRejected?: PromiseRejectFunc): Promise<T>;
} }
interface RegExpResultIndices extends Array<[number, number]> { interface RegExpResultIndices extends Array<[number, number]> {
@ -100,7 +100,6 @@ interface IterableIterator<T> extends Iterator<T> {
} }
interface AsyncIterator<T, TReturn = any, TNext = undefined> { interface AsyncIterator<T, TReturn = any, TNext = undefined> {
// NOTE: 'next' is defined using a tuple to ensure we report the correct assignability errors in all places.
next(...args: [] | [TNext]): Promise<IteratorResult<T, TReturn>>; next(...args: [] | [TNext]): Promise<IteratorResult<T, TReturn>>;
return?(value?: TReturn | Thenable<TReturn>): Promise<IteratorResult<T, TReturn>>; return?(value?: TReturn | Thenable<TReturn>): Promise<IteratorResult<T, TReturn>>;
throw?(e?: any): Promise<IteratorResult<T, TReturn>>; throw?(e?: any): Promise<IteratorResult<T, TReturn>>;
@ -112,65 +111,29 @@ interface AsyncIterableIterator<T> extends AsyncIterator<T> {
[Symbol.asyncIterator](): AsyncIterableIterator<T>; [Symbol.asyncIterator](): AsyncIterableIterator<T>;
} }
interface Generator<T = unknown, TReturn = any, TNext = unknown> extends Iterator<T, TReturn, TNext> { interface Generator<T = unknown, TReturn = unknown, TNext = unknown> extends Iterator<T, TReturn, TNext> {
[Symbol.iterator](): Generator<T, TReturn, TNext>; [Symbol.iterator](): Generator<T, TReturn, TNext>;
return(value?: TReturn): IteratorResult<T, TReturn>; return(value: TReturn): IteratorResult<T, TReturn>;
throw(e?: any): IteratorResult<T, TReturn>; throw(e: any): IteratorResult<T, TReturn>;
} }
interface GeneratorFunction { interface GeneratorFunction {
/**
* Creates a new Generator object.
* @param args A list of arguments the function accepts.
*/
new (...args: any[]): Generator; new (...args: any[]): Generator;
/**
* Creates a new Generator object.
* @param args A list of arguments the function accepts.
*/
(...args: any[]): Generator; (...args: any[]): Generator;
/**
* The length of the arguments.
*/
readonly length: number; readonly length: number;
/**
* Returns the name of the function.
*/
readonly name: string; readonly name: string;
/**
* A reference to the prototype.
*/
readonly prototype: Generator; readonly prototype: Generator;
} }
interface AsyncGenerator<T = unknown, TReturn = any, TNext = unknown> extends AsyncIterator<T, TReturn, TNext> { interface AsyncGenerator<T = unknown, TReturn = unknown, TNext = unknown> extends AsyncIterator<T, TReturn, TNext> {
// NOTE: 'next' is defined using a tuple to ensure we report the correct assignability errors in all places.
next(...args: [] | [TNext]): Promise<IteratorResult<T, TReturn>>;
return(value: TReturn | Thenable<TReturn>): Promise<IteratorResult<T, TReturn>>; return(value: TReturn | Thenable<TReturn>): Promise<IteratorResult<T, TReturn>>;
throw(e: any): Promise<IteratorResult<T, TReturn>>; throw(e: any): Promise<IteratorResult<T, TReturn>>;
[Symbol.asyncIterator](): AsyncGenerator<T, TReturn, TNext>; [Symbol.asyncIterator](): AsyncGenerator<T, TReturn, TNext>;
} }
interface AsyncGeneratorFunction { interface AsyncGeneratorFunction {
/**
* Creates a new AsyncGenerator object.
* @param args A list of arguments the function accepts.
*/
new (...args: any[]): AsyncGenerator; new (...args: any[]): AsyncGenerator;
/**
* Creates a new AsyncGenerator object.
* @param args A list of arguments the function accepts.
*/
(...args: any[]): AsyncGenerator; (...args: any[]): AsyncGenerator;
/**
* The length of the arguments.
*/
readonly length: number; readonly length: number;
/**
* Returns the name of the function.
*/
readonly name: string; readonly name: string;
/**
* A reference to the prototype.
*/
readonly prototype: AsyncGenerator; readonly prototype: AsyncGenerator;
} }
@ -225,7 +188,6 @@ interface MathObject {
interface Array<T> extends IterableIterator<T> { interface Array<T> extends IterableIterator<T> {
[i: number]: T; [i: number]: T;
constructor: ArrayConstructor;
length: number; length: number;
toString(): string; toString(): string;
@ -283,7 +245,6 @@ interface ArrayConstructor {
interface Boolean { interface Boolean {
valueOf(): boolean; valueOf(): boolean;
constructor: BooleanConstructor;
} }
interface BooleanConstructor { interface BooleanConstructor {
(val: any): boolean; (val: any): boolean;
@ -292,10 +253,10 @@ interface BooleanConstructor {
} }
interface Error { interface Error {
constructor: ErrorConstructor;
name: string; name: string;
message: string; message: string;
stack: string[]; stack: string[];
toString(): string;
} }
interface ErrorConstructor { interface ErrorConstructor {
(msg?: any): Error; (msg?: any): Error;
@ -309,7 +270,6 @@ interface TypeErrorConstructor extends ErrorConstructor {
prototype: Error; prototype: Error;
} }
interface TypeError extends Error { interface TypeError extends Error {
constructor: TypeErrorConstructor;
name: 'TypeError'; name: 'TypeError';
} }
@ -319,7 +279,6 @@ interface RangeErrorConstructor extends ErrorConstructor {
prototype: Error; prototype: Error;
} }
interface RangeError extends Error { interface RangeError extends Error {
constructor: RangeErrorConstructor;
name: 'RangeError'; name: 'RangeError';
} }
@ -329,7 +288,6 @@ interface SyntaxErrorConstructor extends ErrorConstructor {
prototype: Error; prototype: Error;
} }
interface SyntaxError extends Error { interface SyntaxError extends Error {
constructor: SyntaxErrorConstructor;
name: 'SyntaxError'; name: 'SyntaxError';
} }
@ -341,7 +299,6 @@ interface Function {
toString(): string; toString(): string;
prototype: any; prototype: any;
constructor: FunctionConstructor;
readonly length: number; readonly length: number;
name: string; name: string;
} }
@ -375,7 +332,6 @@ interface FunctionConstructor extends Function {
interface Number { interface Number {
toString(): string; toString(): string;
valueOf(): number; valueOf(): number;
constructor: NumberConstructor;
} }
interface NumberConstructor { interface NumberConstructor {
(val: any): number; (val: any): number;
@ -477,8 +433,6 @@ interface String {
includes(term: string, start?: number): boolean; includes(term: string, start?: number): boolean;
length: number; length: number;
constructor: StringConstructor;
} }
interface StringConstructor { interface StringConstructor {
(val: any): string; (val: any): string;
@ -491,7 +445,6 @@ interface StringConstructor {
interface Symbol { interface Symbol {
valueOf(): symbol; valueOf(): symbol;
constructor: SymbolConstructor;
} }
interface SymbolConstructor { interface SymbolConstructor {
(val?: any): symbol; (val?: any): symbol;
@ -511,7 +464,6 @@ interface SymbolConstructor {
} }
interface Promise<T> extends Thenable<T> { interface Promise<T> extends Thenable<T> {
constructor: PromiseConstructor;
catch(func: PromiseRejectFunc): Promise<T>; catch(func: PromiseRejectFunc): Promise<T>;
finally(func: () => void): Promise<T>; finally(func: () => void): Promise<T>;
} }
@ -522,7 +474,8 @@ interface PromiseConstructor {
resolve<T>(val: T): Promise<Awaited<T>>; resolve<T>(val: T): Promise<Awaited<T>>;
reject(val: any): Promise<never>; reject(val: any): Promise<never>;
any<T>(promises: (Promise<T>|T)[]): Promise<T>; isAwaitable(val: unknown): val is Thenable<any>;
any<T>(promises: T[]): Promise<Awaited<T>>;
race<T>(promises: (Promise<T>|T)[]): Promise<T>; race<T>(promises: (Promise<T>|T)[]): Promise<T>;
all<T extends any[]>(promises: T): Promise<{ [Key in keyof T]: Awaited<T[Key]> }>; all<T extends any[]>(promises: T): Promise<{ [Key in keyof T]: Awaited<T[Key]> }>;
allSettled<T extends any[]>(...promises: T): Promise<[...{ [P in keyof T]: PromiseResult<Awaited<T[P]>>}]>; allSettled<T extends any[]>(...promises: T): Promise<[...{ [P in keyof T]: PromiseResult<Awaited<T[P]>>}]>;
@ -544,7 +497,6 @@ declare var parseInt: typeof Number.parseInt;
declare var parseFloat: typeof Number.parseFloat; declare var parseFloat: typeof Number.parseFloat;
declare function log(...vals: any[]): void; declare function log(...vals: any[]): void;
declare function assert(condition: () => unknown, message?: string): boolean;
declare var Array: ArrayConstructor; declare var Array: ArrayConstructor;
declare var Boolean: BooleanConstructor; declare var Boolean: BooleanConstructor;

View File

@ -1,29 +1,92 @@
define("map", () => { 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() { class Map<KeyT, ValueT> {
return this.entries(); [syms.values]: any = {};
};
var entries = Map.prototype.entries; public [Symbol.iterator](): IterableIterator<[KeyT, ValueT]> {
var keys = Map.prototype.keys; return this.entries();
var values = Map.prototype.values; }
Map.prototype.entries = function() { public clear() {
var it = entries.call(this); this[syms.values] = {};
it[Symbol.iterator] = () => it; }
return it; public delete(key: KeyT) {
}; if ((key as any) in this[syms.values]) {
Map.prototype.keys = function() { delete this[syms.values];
var it = keys.call(this); return true;
it[Symbol.iterator] = () => it; }
return it; else return false;
}; }
Map.prototype.values = function() {
var it = values.call(this); public entries(): IterableIterator<[KeyT, ValueT]> {
it[Symbol.iterator] = () => it; const keys = internals.ownPropKeys(this[syms.values]);
return it; 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<KeyT> {
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<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: 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<KeyT, ValueT>) => 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; env.global.Map = Map;
}); });

View File

@ -5,7 +5,8 @@ var { define, run } = (() => {
modules[name] = func; modules[name] = func;
} }
function run(name: string) { 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 }; return { define, run };

View File

@ -1,3 +1,203 @@
define("promise", () => { 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<T> = [ PromiseFulfillFunc<T>, PromiseRejectFunc ];
enum State {
Pending,
Fulfilled,
Rejected,
}
function isAwaitable(val: unknown): val is Thenable<any> {
return (
typeof val === 'object' &&
val !== null &&
'then' in val &&
typeof val.then === 'function'
);
}
function resolve(promise: Promise<any>, 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<T> {
public static isAwaitable(val: unknown): val is Thenable<any> {
return isAwaitable(val);
}
public static resolve<T>(val: T): Promise<Awaited<T>> {
return new Promise(res => res(val as any));
}
public static reject<T>(val: T): Promise<Awaited<T>> {
return new Promise((_, rej) => rej(val as any));
}
public static race<T>(vals: T[]): Promise<Awaited<T>> {
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<T>(vals: T[]): Promise<Awaited<T>> {
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<any[]> {
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<any[]> {
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<T>[] = [];
[syms.handled] = false;
[syms.state] = State.Pending;
[syms.value]?: T | unknown;
public then(onFulfil?: PromiseFulfillFunc<T>, 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<T>) {
internals.pushMessage(true, func, undefined, [
((v) => resolve(this, v, State.Fulfilled)) as PromiseFulfillFunc<T>,
((err) => resolve(this, err, State.Rejected)) as PromiseRejectFunc
]);
}
}
env.global.Promise = Promise as any;
}); });

View File

@ -1,143 +1,143 @@
define("regex", () => { define("regex", () => {
var RegExp = env.global.RegExp = env.internals.RegExp; // var RegExp = env.global.RegExp = env.internals.RegExp;
setProps(RegExp.prototype as RegExp, env, { // setProps(RegExp.prototype as RegExp, env, {
[Symbol.typeName]: 'RegExp', // [Symbol.typeName]: 'RegExp',
test(val) { // test(val) {
return !!this.exec(val); // return !!this.exec(val);
}, // },
toString() { // toString() {
return '/' + this.source + '/' + this.flags; // return '/' + this.source + '/' + this.flags;
}, // },
[Symbol.match](target) { // [Symbol.match](target) {
if (this.global) { // if (this.global) {
const res: string[] = []; // const res: string[] = [];
let val; // let val;
while (val = this.exec(target)) { // while (val = this.exec(target)) {
res.push(val[0]); // res.push(val[0]);
} // }
this.lastIndex = 0; // this.lastIndex = 0;
return res; // return res;
} // }
else { // else {
const res = this.exec(target); // const res = this.exec(target);
if (!this.sticky) this.lastIndex = 0; // if (!this.sticky) this.lastIndex = 0;
return res; // return res;
} // }
}, // },
[Symbol.matchAll](target) { // [Symbol.matchAll](target) {
let pattern: RegExp | undefined = new this.constructor(this, this.flags + "g") as RegExp; // let pattern: RegExp | undefined = new this.constructor(this, this.flags + "g") as RegExp;
return { // return {
next: (): IteratorResult<RegExpResult, undefined> => { // next: (): IteratorResult<RegExpResult, undefined> => {
const val = pattern?.exec(target); // const val = pattern?.exec(target);
if (val === null || val === undefined) { // if (val === null || val === undefined) {
pattern = undefined; // pattern = undefined;
return { done: true }; // return { done: true };
} // }
else return { value: val }; // else return { value: val };
}, // },
[Symbol.iterator]() { return this; } // [Symbol.iterator]() { return this; }
} // }
}, // },
[Symbol.split](target, limit, sensible) { // [Symbol.split](target, limit, sensible) {
const pattern = new this.constructor(this, this.flags + "g") as RegExp; // const pattern = new this.constructor(this, this.flags + "g") as RegExp;
let match: RegExpResult | null; // let match: RegExpResult | null;
let lastEnd = 0; // let lastEnd = 0;
const res: string[] = []; // const res: string[] = [];
while ((match = pattern.exec(target)) !== null) { // while ((match = pattern.exec(target)) !== null) {
let added: string[] = []; // let added: string[] = [];
if (match.index >= target.length) break; // if (match.index >= target.length) break;
if (match[0].length === 0) { // if (match[0].length === 0) {
added = [ target.substring(lastEnd, pattern.lastIndex), ]; // added = [ target.substring(lastEnd, pattern.lastIndex), ];
if (pattern.lastIndex < target.length) added.push(...match.slice(1)); // if (pattern.lastIndex < target.length) added.push(...match.slice(1));
} // }
else if (match.index - lastEnd > 0) { // else if (match.index - lastEnd > 0) {
added = [ target.substring(lastEnd, match.index), ...match.slice(1) ]; // added = [ target.substring(lastEnd, match.index), ...match.slice(1) ];
} // }
else { // else {
for (let i = 1; i < match.length; i++) { // for (let i = 1; i < match.length; i++) {
res[res.length - match.length + i] = match[i]; // res[res.length - match.length + i] = match[i];
} // }
} // }
if (sensible) { // if (sensible) {
if (limit !== undefined && res.length + added.length >= limit) break; // if (limit !== undefined && res.length + added.length >= limit) break;
else res.push(...added); // else res.push(...added);
} // }
else { // else {
for (let i = 0; i < added.length; i++) { // for (let i = 0; i < added.length; i++) {
if (limit !== undefined && res.length >= limit) return res; // if (limit !== undefined && res.length >= limit) return res;
else res.push(added[i]); // else res.push(added[i]);
} // }
} // }
lastEnd = pattern.lastIndex; // lastEnd = pattern.lastIndex;
} // }
if (lastEnd < target.length) { // if (lastEnd < target.length) {
res.push(target.substring(lastEnd)); // res.push(target.substring(lastEnd));
} // }
return res; // return res;
}, // },
[Symbol.replace](target, replacement) { // [Symbol.replace](target, replacement) {
const pattern = new this.constructor(this, this.flags + "d") as RegExp; // const pattern = new this.constructor(this, this.flags + "d") as RegExp;
let match: RegExpResult | null; // let match: RegExpResult | null;
let lastEnd = 0; // let lastEnd = 0;
const res: string[] = []; // const res: string[] = [];
// log(pattern.toString()); // // log(pattern.toString());
while ((match = pattern.exec(target)) !== null) { // while ((match = pattern.exec(target)) !== null) {
const indices = match.indices![0]; // const indices = match.indices![0];
res.push(target.substring(lastEnd, indices[0])); // res.push(target.substring(lastEnd, indices[0]));
if (replacement instanceof Function) { // if (replacement instanceof Function) {
res.push(replacement(target.substring(indices[0], indices[1]), ...match.slice(1), indices[0], target)); // res.push(replacement(target.substring(indices[0], indices[1]), ...match.slice(1), indices[0], target));
} // }
else { // else {
res.push(replacement); // res.push(replacement);
} // }
lastEnd = indices[1]; // lastEnd = indices[1];
if (!pattern.global) break; // if (!pattern.global) break;
} // }
if (lastEnd < target.length) { // if (lastEnd < target.length) {
res.push(target.substring(lastEnd)); // res.push(target.substring(lastEnd));
} // }
return res.join(''); // return res.join('');
}, // },
[Symbol.search](target, reverse, start) { // [Symbol.search](target, reverse, start) {
const pattern: RegExp | undefined = new this.constructor(this, this.flags + "g") as RegExp; // const pattern: RegExp | undefined = new this.constructor(this, this.flags + "g") as RegExp;
if (!reverse) { // if (!reverse) {
pattern.lastIndex = (start as any) | 0; // pattern.lastIndex = (start as any) | 0;
const res = pattern.exec(target); // const res = pattern.exec(target);
if (res) return res.index; // if (res) return res.index;
else return -1; // else return -1;
} // }
else { // else {
start ??= target.length; // start ??= target.length;
start |= 0; // start |= 0;
let res: RegExpResult | null = null; // let res: RegExpResult | null = null;
while (true) { // while (true) {
const tmp = pattern.exec(target); // const tmp = pattern.exec(target);
if (tmp === null || tmp.index > start) break; // if (tmp === null || tmp.index > start) break;
res = tmp; // res = tmp;
} // }
if (res && res.index <= start) return res.index; // if (res && res.index <= start) return res.index;
else return -1; // else return -1;
} // }
}, // },
}); // });
}); });

View File

@ -1,26 +1,80 @@
define("set", () => { define("set", () => {
var Set = env.global.Set = env.internals.Set; const syms = { values: internals.symbol('Map.values') } as { readonly values: unique symbol };
Set.prototype[Symbol.iterator] = function() {
return this.values();
};
var entries = Set.prototype.entries; class Set<T> {
var keys = Set.prototype.keys; [syms.values]: any = {};
var values = Set.prototype.values;
Set.prototype.entries = function() { public [Symbol.iterator](): IterableIterator<[T, T]> {
var it = entries.call(this); return this.entries();
it[Symbol.iterator] = () => it; }
return it;
}; public clear() {
Set.prototype.keys = function() { this[syms.values] = {};
var it = keys.call(this); }
it[Symbol.iterator] = () => it; public delete(key: T) {
return it; if ((key as any) in this[syms.values]) {
}; delete this[syms.values];
Set.prototype.values = function() { return true;
var it = values.call(this); }
it[Symbol.iterator] = () => it; else return false;
return it; }
};
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<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] }
},
[Symbol.iterator]() { return this; }
}
}
public values(): IterableIterator<T> {
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<T>) => 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<T>) {
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;
}); });

38
lib/timeout.ts Normal file
View File

@ -0,0 +1,38 @@
define("timeout", () => {
const timeouts: Record<number, () => void> = { };
const intervals: Record<number, () => 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!;
};
});

View File

@ -15,6 +15,7 @@
"map.ts", "map.ts",
"set.ts", "set.ts",
"regex.ts", "regex.ts",
"timeout.ts",
"core.ts" "core.ts"
], ],
"compilerOptions": { "compilerOptions": {

View File

@ -1,9 +1,3 @@
interface Environment {
global: typeof globalThis & Record<string, any>;
captureErr: boolean;
internals: any;
}
function setProps< function setProps<
TargetT extends object, TargetT extends object,
DescT extends { DescT extends {
@ -11,11 +5,11 @@ function setProps<
((this: TargetT, ...args: ArgsT) => RetT) : ((this: TargetT, ...args: ArgsT) => RetT) :
TargetT[x] TargetT[x]
} }
>(target: TargetT, env: Environment, desc: DescT) { >(target: TargetT, desc: DescT) {
var props = env.internals.keys(desc, false); var props = internals.keys(desc, false);
for (var i = 0; i < props.length; i++) { for (var i = 0; i < props.length; i++) {
var key = props[i]; var key = props[i];
env.internals.defineField( internals.defineField(
target, key, (desc as any)[key], target, key, (desc as any)[key],
true, // writable true, // writable
false, // enumerable false, // enumerable
@ -23,8 +17,8 @@ function setProps<
); );
} }
} }
function setConstr<ConstrT, T extends { constructor: ConstrT }>(target: T, constr: ConstrT, env: Environment) { function setConstr(target: object, constr: Function) {
env.internals.defineField( internals.defineField(
target, 'constructor', constr, target, 'constructor', constr,
true, // writable true, // writable
false, // enumerable false, // enumerable

View File

@ -11,20 +11,20 @@ define("values/array", () => {
res[i] = arguments[i]; res[i] = arguments[i];
} }
} }
return res; return res;
} as ArrayConstructor; } as ArrayConstructor;
Array.prototype = ([] as any).__proto__ as Array<any>; env.setProto('array', Array.prototype);
setConstr(Array.prototype, Array, env);
(Array.prototype as any)[Symbol.typeName] = "Array"; (Array.prototype as any)[Symbol.typeName] = "Array";
setConstr(Array.prototype, Array);
setProps(Array.prototype, env, {
setProps(Array.prototype, {
[Symbol.iterator]: function() { [Symbol.iterator]: function() {
return this.values(); return this.values();
}, },
[Symbol.typeName]: "Array",
values() { values() {
var i = 0; var i = 0;
@ -67,7 +67,7 @@ define("values/array", () => {
concat() { concat() {
var res = [] as any[]; var res = [] as any[];
res.push.apply(res, this); res.push.apply(res, this);
for (var i = 0; i < arguments.length; i++) { for (var i = 0; i < arguments.length; i++) {
var arg = arguments[i]; var arg = arguments[i];
if (arg instanceof Array) { 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.'); 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; return this;
}, },
splice(start, deleteCount, ...items) { splice(start, deleteCount, ...items) {
@ -328,8 +328,9 @@ define("values/array", () => {
return res; return res;
} }
}); });
setProps(Array, env, { setProps(Array, {
isArray(val: any) { return env.internals.isArr(val); } isArray(val: any) { return internals.isArray(val); }
}); });
internals.markSpecial(Array);
}); });

View File

@ -7,6 +7,6 @@ define("values/boolean", () => {
else (this as any).value = val; else (this as any).value = val;
} as BooleanConstructor; } as BooleanConstructor;
Boolean.prototype = (false as any).__proto__ as Boolean; env.setProto('bool', Boolean.prototype);
setConstr(Boolean.prototype, Boolean, env); setConstr(Boolean.prototype, Boolean);
}); });

View File

@ -8,36 +8,39 @@ define("values/errors", () => {
stack: [] as string[], stack: [] as string[],
}, Error.prototype); }, Error.prototype);
} as ErrorConstructor; } as ErrorConstructor;
Error.prototype = env.internals.err ?? {};
Error.prototype.name = 'Error';
setConstr(Error.prototype, Error, env);
Error.prototype.toString = function() { setConstr(Error.prototype, Error);
if (!(this instanceof Error)) return ''; setProps(Error.prototype, {
name: 'Error',
if (this.message === '') return this.name; toString: internals.setEnv(function(this: Error) {
else return this.name + ': ' + this.message; 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<T extends ErrorConstructor>(name: string, proto: any): T { function makeError<T1 extends ErrorConstructor>(name: string, proto: string): T1 {
var err = function (msg: string) { function constr (msg: string) {
var res = new Error(msg); var res = new Error(msg);
(res as any).__proto__ = err.prototype; (res as any).__proto__ = constr.prototype;
return res; return res;
} as T; }
err.prototype = proto; (constr as any).__proto__ = Error;
err.prototype.name = name; (constr.prototype as any).__proto__ = env.proto('error');
setConstr(err.prototype, err as ErrorConstructor, env); setConstr(constr.prototype, constr as ErrorConstructor);
(err.prototype as any).__proto__ = Error.prototype; setProps(constr.prototype, { name: name });
(err as any).__proto__ = Error;
env.internals.special(err);
return err; internals.markSpecial(constr);
env.setProto(proto, constr.prototype);
return constr as T1;
} }
env.global.RangeError = makeError('RangeError', env.internals.range ?? {}); env.global.RangeError = makeError('RangeError', 'rangeErr');
env.global.TypeError = makeError('TypeError', env.internals.type ?? {}); env.global.TypeError = makeError('TypeError', 'typeErr');
env.global.SyntaxError = makeError('SyntaxError', env.internals.syntax ?? {}); env.global.SyntaxError = makeError('SyntaxError', 'syntaxErr');
}); });

View File

@ -3,15 +3,15 @@ define("values/function", () => {
throw 'Using the constructor Function() is forbidden.'; throw 'Using the constructor Function() is forbidden.';
} as unknown as FunctionConstructor; } as unknown as FunctionConstructor;
Function.prototype = (Function as any).__proto__ as Function; env.setProto('function', Function.prototype);
setConstr(Function.prototype, Function, env); setConstr(Function.prototype, Function);
setProps(Function.prototype, env, { setProps(Function.prototype, {
apply(thisArg, args) { apply(thisArg, args) {
if (typeof args !== 'object') throw 'Expected arguments to be an array-like object.'; if (typeof args !== 'object') throw 'Expected arguments to be an array-like object.';
var len = args.length - 0; var len = args.length - 0;
let newArgs: any[]; let newArgs: any[];
if (Array.isArray(args)) newArgs = args; if (internals.isArray(args)) newArgs = args;
else { else {
newArgs = []; newArgs = [];
@ -21,21 +21,20 @@ define("values/function", () => {
} }
} }
return env.internals.apply(this, thisArg, newArgs); return internals.apply(this, thisArg, newArgs);
}, },
call(thisArg, ...args) { call(thisArg, ...args) {
return this.apply(thisArg, args); return this.apply(thisArg, args);
}, },
bind(thisArg, ...args) { bind(thisArg, ...args) {
var func = this; const func = this;
const res = function() {
const resArgs = [];
var res = function() { for (let i = 0; i < args.length; i++) {
var resArgs = [];
for (var i = 0; i < args.length; i++) {
resArgs[i] = args[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]; resArgs[i + args.length] = arguments[i];
} }
@ -48,7 +47,7 @@ define("values/function", () => {
return 'function (...) { ... }'; return 'function (...) { ... }';
}, },
}); });
setProps(Function, env, { setProps(Function, {
async(func) { async(func) {
if (typeof func !== 'function') throw new TypeError('Expected func to be function.'); if (typeof func !== 'function') throw new TypeError('Expected func to be function.');
@ -56,7 +55,7 @@ define("values/function", () => {
const args = arguments; const args = arguments;
return new Promise((res, rej) => { 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) { (function next(type: 'none' | 'err' | 'ret', val?: any) {
try { try {
@ -83,11 +82,11 @@ define("values/function", () => {
asyncGenerator(func) { asyncGenerator(func) {
if (typeof func !== 'function') throw new TypeError('Expected func to be function.'); if (typeof func !== 'function') throw new TypeError('Expected func to be function.');
return function(this: any) { return function(this: any, ...args: any[]) {
const gen = Function.generator<any[], ['await' | 'yield', any]>((_yield) => func( const gen = internals.apply(internals.generator((_yield) => func(
val => _yield(['await', val]) as any, val => _yield(['await', val]) as any,
val => _yield(['yield', val]) 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) => { const next = (resolve: Function, reject: Function, type: 'none' | 'val' | 'ret' | 'err', val?: any) => {
let res; let res;
@ -124,17 +123,18 @@ define("values/function", () => {
}, },
generator(func) { generator(func) {
if (typeof func !== 'function') throw new TypeError('Expected func to be function.'); if (typeof func !== 'function') throw new TypeError('Expected func to be function.');
const gen = env.internals.makeGenerator(func); const gen = internals.generator(func);
return (...args: any[]) => { return function(this: any, ...args: any[]) {
const it = gen(args); const it = internals.apply(gen, this, args);
return { return {
next: it.next, next: (...args) => internals.apply(it.next, it, args),
return: it.return, return: (val) => internals.apply(it.next, it, [val]),
throw: it.throw, throw: (val) => internals.apply(it.next, it, [val]),
[Symbol.iterator]() { return this; } [Symbol.iterator]() { return this; }
} }
} }
} }
}) })
internals.markSpecial(Function);
}); });

View File

@ -7,10 +7,10 @@ define("values/number", () => {
else (this as any).value = val; else (this as any).value = val;
} as NumberConstructor; } as NumberConstructor;
Number.prototype = (0 as any).__proto__ as Number; env.setProto('number', Number.prototype);
setConstr(Number.prototype, Number, env); setConstr(Number.prototype, Number);
setProps(Number.prototype, env, { setProps(Number.prototype, {
valueOf() { valueOf() {
if (typeof this === 'number') return this; if (typeof this === 'number') return this;
else return (this as any).value; else return (this as any).value;
@ -21,13 +21,13 @@ define("values/number", () => {
} }
}); });
setProps(Number, env, { setProps(Number, {
parseInt(val) { return Math.trunc(Number.parseFloat(val)); }, parseInt(val) { return Math.trunc(val as any - 0); },
parseFloat(val) { return env.internals.parseFloat(val); }, parseFloat(val) { return val as any - 0; },
}); });
env.global.Object.defineProperty(env.global, 'parseInt', { value: Number.parseInt, writable: false }); env.global.parseInt = Number.parseInt;
env.global.Object.defineProperty(env.global, 'parseFloat', { value: Number.parseFloat, writable: false }); env.global.parseFloat = Number.parseFloat;
env.global.Object.defineProperty(env.global, 'NaN', { value: 0 / 0, writable: false }); env.global.Object.defineProperty(env.global, 'NaN', { value: 0 / 0, writable: false });
env.global.Object.defineProperty(env.global, 'Infinity', { value: 1 / 0, writable: false }); env.global.Object.defineProperty(env.global, 'Infinity', { value: 1 / 0, writable: false });
}); });

View File

@ -8,8 +8,9 @@ define("values/object", () => {
return arg; return arg;
} as ObjectConstructor; } as ObjectConstructor;
Object.prototype = ({} as any).__proto__ as Object; env.setProto('object', Object.prototype);
setConstr(Object.prototype, Object as any, env); (Object.prototype as any).__proto__ = null;
setConstr(Object.prototype, Object as any);
function throwNotObject(obj: any, name: string) { function throwNotObject(obj: any, name: string) {
if (obj === null || typeof obj !== 'object' && typeof obj !== 'function') { 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'; return typeof obj === 'object' && obj !== null || typeof obj === 'function';
} }
setProps(Object, env, { setProps(Object, {
assign: function(dst, ...src) { assign(dst, ...src) {
throwNotObject(dst, 'assign'); throwNotObject(dst, 'assign');
for (let i = 0; i < src.length; i++) { for (let i = 0; i < src.length; i++) {
const obj = src[i]; const obj = src[i];
@ -40,10 +41,10 @@ define("values/object", () => {
defineProperty(obj, key, attrib) { defineProperty(obj, key, attrib) {
throwNotObject(obj, 'defineProperty'); throwNotObject(obj, 'defineProperty');
if (typeof attrib !== 'object') throw new TypeError('Expected attributes to be an object.'); if (typeof attrib !== 'object') throw new TypeError('Expected attributes to be an object.');
if ('value' in attrib) { if ('value' in attrib) {
if ('get' in attrib || 'set' in attrib) throw new TypeError('Cannot specify a value and accessors for a property.'); 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, obj, key,
attrib.value, attrib.value,
!!attrib.writable, !!attrib.writable,
@ -54,8 +55,8 @@ define("values/object", () => {
else { else {
if (typeof attrib.get !== 'function' && attrib.get !== undefined) throw new TypeError('Get accessor must be a function.'); 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 (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, obj, key,
attrib.get, attrib.get,
attrib.set, attrib.set,
@ -63,50 +64,87 @@ define("values/object", () => {
!!attrib.configurable !!attrib.configurable
)) throw new TypeError('Can\'t define property \'' + key + '\'.'); )) throw new TypeError('Can\'t define property \'' + key + '\'.');
} }
return obj; return obj;
}, },
defineProperties(obj, attrib) { defineProperties(obj, attrib) {
throwNotObject(obj, 'defineProperties'); throwNotObject(obj, 'defineProperties');
if (typeof attrib !== 'object' && typeof attrib !== 'function') throw 'Expected second argument to be an object.'; if (typeof attrib !== 'object' && typeof attrib !== 'function') throw 'Expected second argument to be an object.';
for (var key in attrib) { for (var key in attrib) {
Object.defineProperty(obj, key, attrib[key]); Object.defineProperty(obj, key, attrib[key]);
} }
return obj; return obj;
}, },
keys(obj, onlyString) { keys(obj, onlyString) {
onlyString = !!(onlyString ?? true); return internals.keys(obj, !!(onlyString ?? true));
return env.internals.keys(obj, onlyString);
}, },
entries(obj, onlyString) { 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) { 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) { getOwnPropertyDescriptor(obj, key) {
return env.internals.ownProp(obj, key); return internals.ownProp(obj, key) as any;
}, },
getOwnPropertyDescriptors(obj) { getOwnPropertyDescriptors(obj) {
return Object.fromEntries([ const res = [];
...Object.getOwnPropertyNames(obj), const keys = internals.ownPropKeys(obj);
...Object.getOwnPropertySymbols(obj)
].map(v => [ v, Object.getOwnPropertyDescriptor(obj, v) ])) as any; for (let i = 0; i < keys.length; i++) {
res[i] = internals.ownProp(obj, keys[i]);
}
return res;
}, },
getOwnPropertyNames(obj) { 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) { 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) { hasOwn(obj, key) {
if (Object.getOwnPropertyNames(obj).includes(key)) return true; const keys = internals.ownPropKeys(obj);
if (Object.getOwnPropertySymbols(obj).includes(key)) return true;
for (let i = 0; i < keys.length; i++) {
if (keys[i] === key) return true;
}
return false; return false;
}, },
@ -130,41 +168,51 @@ define("values/object", () => {
preventExtensions(obj) { preventExtensions(obj) {
throwNotObject(obj, 'preventExtensions'); throwNotObject(obj, 'preventExtensions');
env.internals.preventExtensions(obj); internals.lock(obj, 'ext');
return obj; return obj;
}, },
seal(obj) { seal(obj) {
throwNotObject(obj, 'seal'); throwNotObject(obj, 'seal');
env.internals.seal(obj); internals.lock(obj, 'seal');
return obj; return obj;
}, },
freeze(obj) { freeze(obj) {
throwNotObject(obj, 'freeze'); throwNotObject(obj, 'freeze');
env.internals.freeze(obj); internals.lock(obj, 'freeze');
return obj; return obj;
}, },
isExtensible(obj) { isExtensible(obj) {
if (!check(obj)) return false; if (!check(obj)) return false;
return env.internals.extensible(obj); return internals.extensible(obj);
}, },
isSealed(obj) { isSealed(obj) {
if (!check(obj)) return true; if (!check(obj)) return true;
if (Object.isExtensible(obj)) return false; if (internals.extensible(obj)) return false;
return Object.getOwnPropertyNames(obj).every(v => !Object.getOwnPropertyDescriptor(obj, v).configurable); 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) { isFrozen(obj) {
if (!check(obj)) return true; if (!check(obj)) return true;
if (Object.isExtensible(obj)) return false; if (internals.extensible(obj)) return false;
return Object.getOwnPropertyNames(obj).every(v => { const keys = internals.ownPropKeys(obj);
var prop = Object.getOwnPropertyDescriptor(obj, v);
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; if ('writable' in prop && prop.writable) return false;
return !prop.configurable; }
});
return true;
} }
}); });
setProps(Object.prototype, env, { setProps(Object.prototype, {
valueOf() { valueOf() {
return this; return this;
}, },
@ -175,4 +223,5 @@ define("values/object", () => {
return Object.hasOwn(this, key); return Object.hasOwn(this, key);
}, },
}); });
internals.markSpecial(Object);
}); });

View File

@ -7,10 +7,10 @@ define("values/string", () => {
else (this as any).value = val; else (this as any).value = val;
} as StringConstructor; } as StringConstructor;
String.prototype = ('' as any).__proto__ as String; env.setProto('string', String.prototype);
setConstr(String.prototype, String, env); setConstr(String.prototype, String);
setProps(String.prototype, env, { setProps(String.prototype, {
toString() { toString() {
if (typeof this === 'string') return this; if (typeof this === 'string') return this;
else return (this as any).value; else return (this as any).value;
@ -27,7 +27,14 @@ define("values/string", () => {
} }
start = start ?? 0 | 0; start = start ?? 0 | 0;
end = (end ?? this.length) | 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) { substr(start, length) {
start = start ?? 0 | 0; start = start ?? 0 | 0;
@ -36,14 +43,41 @@ define("values/string", () => {
if (start < 0) start = 0; if (start < 0) start = 0;
length = (length ?? this.length - 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() { 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() { 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) { charAt(pos) {
@ -57,9 +91,14 @@ define("values/string", () => {
return this[pos]; return this[pos];
}, },
charCodeAt(pos) { charCodeAt(pos) {
var res = this.charAt(pos); if (typeof this !== 'string') {
if (res === '') return NaN; if (this instanceof String) return (this as any).value.charAt(pos);
else return env.internals.toCharCode(res); 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) { 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.'); else throw new Error('This function may be used only with primitive or object strings.');
} }
pos = pos! | 0; 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) { endsWith(term, pos) {
if (typeof this !== 'string') { 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.'); else throw new Error('This function may be used only with primitive or object strings.');
} }
pos = (pos ?? this.length) | 0; 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) { indexOf(term: any, start) {
@ -189,9 +246,9 @@ define("values/string", () => {
} }
}); });
setProps(String, env, { setProps(String, {
fromCharCode(val) { 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.'); 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, configurable: true,
enumerable: false, enumerable: false,

View File

@ -1,31 +1,34 @@
define("values/symbol", () => { define("values/symbol", () => {
const symbols: Record<string, symbol> = { };
var Symbol = env.global.Symbol = function(this: any, val?: string) { 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 (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.'); 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; } as SymbolConstructor;
Symbol.prototype = env.internals.symbolProto; env.setProto('symbol', Symbol.prototype);
setConstr(Symbol.prototype, Symbol, env); setConstr(Symbol.prototype, Symbol);
(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');
setProps(Symbol, env, { setProps(Symbol, {
for(key) { for(key) {
if (typeof key !== 'string' && key !== undefined) throw new TypeError('key must be a string or undefined.'); 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) { keyFor(sym) {
if (typeof sym !== 'symbol') throw new TypeError('sym must be a symbol.'); 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' }); env.global.Object.defineProperty(Object.prototype, Symbol.typeName, { value: 'Object' });

6
package-lock.json generated Normal file
View File

@ -0,0 +1,6 @@
{
"name": "java-jscript",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}

1
package.json Normal file
View File

@ -0,0 +1 @@
{}

View File

@ -2,25 +2,54 @@ package me.topchetoeu.jscript;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Map; import java.util.Map;
import me.topchetoeu.jscript.engine.CallContext;
import me.topchetoeu.jscript.engine.Engine; 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.engine.values.Values;
import me.topchetoeu.jscript.events.Observer; import me.topchetoeu.jscript.events.Observer;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.SyntaxException; import me.topchetoeu.jscript.exceptions.SyntaxException;
import me.topchetoeu.jscript.interop.NativeTypeRegister;
import me.topchetoeu.jscript.polyfills.Internals;
public class Main { public class Main {
static Thread task; static Thread task;
static Engine engine; 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<Object> valuePrinter = new Observer<Object>() { private static Observer<Object> valuePrinter = new Observer<Object>() {
public void next(Object data) { public void next(Object data) {
try { try {
Values.printValue(engine.context(), data); Values.printValue(null, data);
} }
catch (InterruptedException e) { } catch (InterruptedException e) { }
System.out.println(); System.out.println();
@ -28,20 +57,24 @@ public class Main {
public void error(RuntimeException err) { public void error(RuntimeException err) {
try { try {
if (err instanceof EngineException) { try {
System.out.println("Uncaught " + ((EngineException)err).toString(engine.context())); 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) { catch (EngineException ex) {
System.out.println("Syntax error:" + ((SyntaxException)err).msg); 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) { catch (InterruptedException ex) {
return; 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)); 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)); var in = new BufferedReader(new InputStreamReader(System.in));
engine = new Engine(); engine = new Engine();
var scope = engine.global().globalChild(); env = new Environment(null, null, null);
var exited = new boolean[1]; var exited = new boolean[1];
scope.define("exit", ctx -> { env.global.define("exit", ctx -> {
exited[0] = true; exited[0] = true;
task.interrupt(); task.interrupt();
throw new InterruptedException(); throw new InterruptedException();
}); });
scope.define("go", ctx -> { env.global.define("go", ctx -> {
try { 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); return func.call(ctx);
} }
catch (IOException e) { catch (IOException e) {
throw new EngineException("Couldn't open do.js"); 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(); task = engine.start();
var reader = new Thread(() -> { var reader = new Thread(() -> {
@ -79,11 +122,11 @@ public class Main {
var raw = in.readLine(); var raw = in.readLine();
if (raw == null) break; if (raw == null) break;
engine.pushMsg(false, scope, Map.of(), "<stdio>", raw, null).toObservable().once(valuePrinter); engine.pushMsg(false, Map.of(), env, "<stdio>", raw, null).toObservable().once(valuePrinter);
} }
catch (EngineException e) { catch (EngineException e) {
try { try {
System.out.println("Uncaught " + e.toString(engine.context())); System.out.println("Uncaught " + e.toString(null));
} }
catch (EngineException ex) { catch (EngineException ex) {
System.out.println("Uncaught [error while converting to string]"); System.out.println("Uncaught [error while converting to string]");

View File

@ -4,18 +4,20 @@ import java.util.Collections;
import java.util.Hashtable; import java.util.Hashtable;
import java.util.Map; import java.util.Map;
@SuppressWarnings("unchecked")
public class CallContext { public class CallContext {
public static final class DataKey<T> {} public static final class DataKey<T> {}
public final Engine engine; public final Engine engine;
private final Map<DataKey<?>, Object> data = new Hashtable<>(); public Environment environment;
private final Map<DataKey<?>, Object> data;
public Engine engine() { return engine; }
public Map<DataKey<?>, Object> data() { return Collections.unmodifiableMap(data); } public Map<DataKey<?>, Object> data() { return Collections.unmodifiableMap(data); }
public CallContext copy() { public CallContext copy() {
return new CallContext(engine).mergeData(data); return new CallContext(engine, environment).mergeData(data);
} }
public CallContext mergeData(Map<DataKey<?>, Object> objs) { public CallContext mergeData(Map<DataKey<?>, Object> objs) {
data.putAll(objs); data.putAll(objs);
return this; return this;
@ -25,7 +27,6 @@ public class CallContext {
else data.put(key, val); else data.put(key, val);
return this; return this;
} }
@SuppressWarnings("unchecked")
public <T> T addData(DataKey<T> key, T val) { public <T> T addData(DataKey<T> key, T val) {
if (data.containsKey(key)) return (T)data.get(key); if (data.containsKey(key)) return (T)data.get(key);
else { else {
@ -34,15 +35,14 @@ public class CallContext {
return val; return val;
} }
} }
public boolean hasData(DataKey<?> key) { return data.containsKey(key); }
public <T> T getData(DataKey<T> key) { public <T> T getData(DataKey<T> key) {
return getData(key, null); return getData(key, null);
} }
@SuppressWarnings("unchecked")
public <T> T getData(DataKey<T> key, T defaultVal) { public <T> T getData(DataKey<T> key, T defaultVal) {
if (!hasData(key)) return defaultVal; if (!hasData(key)) return defaultVal;
else return (T)data.get(key); else return (T)data.get(key);
} }
public boolean hasData(DataKey<?> key) { return data.containsKey(key); }
public CallContext changeData(DataKey<Integer> key, int n, int start) { public CallContext changeData(DataKey<Integer> key, int n, int start) {
return setData(key, getData(key, start) + n); return setData(key, getData(key, start) + n);
@ -54,7 +54,14 @@ public class CallContext {
return changeData(key, 1, 0); return changeData(key, 1, 0);
} }
public CallContext(Engine engine) { public CallContext(Engine engine, Environment env) {
this.engine = engine; 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;
} }
} }

View File

@ -1,43 +1,43 @@
package me.topchetoeu.jscript.engine; package me.topchetoeu.jscript.engine;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.LinkedBlockingDeque;
import me.topchetoeu.jscript.engine.CallContext.DataKey; 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.FunctionValue;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto;
import me.topchetoeu.jscript.events.Awaitable; import me.topchetoeu.jscript.events.Awaitable;
import me.topchetoeu.jscript.events.DataNotifier; import me.topchetoeu.jscript.events.DataNotifier;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.interop.NativeTypeRegister;
import me.topchetoeu.jscript.parsing.Parsing; import me.topchetoeu.jscript.parsing.Parsing;
public class Engine { public class Engine {
private static class RawFunction { private class UncompiledFunction extends FunctionValue {
public final GlobalScope scope;
public final String filename; public final String filename;
public final String raw; public final String raw;
public RawFunction(GlobalScope scope, String filename, String raw) { @Override
this.scope = scope; 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.filename = filename;
this.raw = raw; this.raw = raw;
} }
} }
private static class Task { private static class Task {
public final Object func; public final FunctionValue func;
public final Object thisArg; public final Object thisArg;
public final Object[] args; public final Object[] args;
public final Map<DataKey<?>, Object> data; public final Map<DataKey<?>, Object> data;
public final DataNotifier<Object> notifier = new DataNotifier<>(); public final DataNotifier<Object> notifier = new DataNotifier<>();
public final Environment env;
public Task(Object func, Map<DataKey<?>, Object> data, Object thisArg, Object[] args) { public Task(Environment env, FunctionValue func, Map<DataKey<?>, Object> data, Object thisArg, Object[] args) {
this.env = env;
this.func = func; this.func = func;
this.data = data; this.data = data;
this.thisArg = thisArg; this.thisArg = thisArg;
@ -45,72 +45,22 @@ public class Engine {
} }
} }
public static final DataKey<DebugState> DEBUG_STATE_KEY = new DataKey<>();
private static int nextId = 0; private static int nextId = 0;
private Map<DataKey<?>, Object> callCtxVals = new HashMap<>(); // private Map<DataKey<?>, Object> callCtxVals = new HashMap<>();
private GlobalScope global = new GlobalScope(); // private NativeTypeRegister typeRegister;
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 Thread thread; private Thread thread;
private LinkedBlockingDeque<Task> macroTasks = new LinkedBlockingDeque<>(); private LinkedBlockingDeque<Task> macroTasks = new LinkedBlockingDeque<>();
private LinkedBlockingDeque<Task> microTasks = new LinkedBlockingDeque<>(); private LinkedBlockingDeque<Task> microTasks = new LinkedBlockingDeque<>();
public final int id = ++nextId; public final int id = ++nextId;
public final DebugState debugState = new DebugState();
public ObjectValue arrayProto() { return arrayProto; } // public NativeTypeRegister typeRegister() { return typeRegister; }
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;
}
private void runTask(Task task) throws InterruptedException { private void runTask(Task task) throws InterruptedException {
try { try {
FunctionValue func; task.notifier.next(task.func.call(new CallContext(this, task.env).mergeData(task.data), task.thisArg, task.args));
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));
} }
catch (InterruptedException e) { catch (InterruptedException e) {
task.notifier.error(new RuntimeException(e)); task.notifier.error(new RuntimeException(e));
@ -160,42 +110,22 @@ public class Engine {
return this.thread != null; return this.thread != null;
} }
public Object makeRegex(String pattern, String flags) { public Awaitable<Object> pushMsg(boolean micro, Map<DataKey<?>, Object> data, Environment env, FunctionValue func, Object thisArg, Object... args) {
throw EngineException.ofError("Regular expressions not supported."); var msg = new Task(env, func, data, thisArg, args);
}
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<Object> pushMsg(boolean micro, FunctionValue func, Map<DataKey<?>, Object> data, Object thisArg, Object... args) {
var msg = new Task(func, data, thisArg, args);
if (micro) microTasks.addLast(msg); if (micro) microTasks.addLast(msg);
else macroTasks.addLast(msg); else macroTasks.addLast(msg);
return msg.notifier; return msg.notifier;
} }
public Awaitable<Object> pushMsg(boolean micro, GlobalScope scope, Map<DataKey<?>, Object> data, String filename, String raw, Object thisArg, Object... args) { public Awaitable<Object> pushMsg(boolean micro, Map<DataKey<?>, Object> data, Environment env, String filename, String raw, Object thisArg, Object... args) {
var msg = new Task(new RawFunction(scope, filename, raw), data, thisArg, args); return pushMsg(micro, data, env, new UncompiledFunction(filename, raw), thisArg, args);
if (micro) microTasks.addLast(msg);
else macroTasks.addLast(msg);
return msg.notifier;
} }
public FunctionValue compile(GlobalScope scope, String filename, String raw) throws InterruptedException { public FunctionValue compile(CallContext ctx, String filename, String raw) throws InterruptedException {
return Parsing.compile(scope, filename, raw); var res = Values.toString(ctx, ctx.environment.compile.call(ctx, null, raw, filename));
return Parsing.compile(ctx.environment, filename, res);
} }
public Engine(NativeTypeRegister register) { // public Engine() {
this.typeRegister = register; // this.typeRegister = new NativeTypeRegister();
this.callCtxVals.put(DEBUG_STATE_KEY, debugState); // }
}
public Engine() {
this(new NativeTypeRegister());
}
} }

View File

@ -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<String, ObjectValue> 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;
}
}

View File

@ -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);
}

View File

@ -6,7 +6,6 @@ import java.util.List;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.engine.CallContext; import me.topchetoeu.jscript.engine.CallContext;
import me.topchetoeu.jscript.engine.DebugCommand; import me.topchetoeu.jscript.engine.DebugCommand;
import me.topchetoeu.jscript.engine.Engine;
import me.topchetoeu.jscript.engine.CallContext.DataKey; import me.topchetoeu.jscript.engine.CallContext.DataKey;
import me.topchetoeu.jscript.engine.scope.LocalScope; import me.topchetoeu.jscript.engine.scope.LocalScope;
import me.topchetoeu.jscript.engine.scope.ValueVariable; 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!"); if (ctx.getData(STACK_N_KEY, 0) >= ctx.addData(MAX_STACK_KEY, 10000)) throw EngineException.ofRange("Stack overflow!");
ctx.changeData(STACK_N_KEY); ctx.changeData(STACK_N_KEY);
var debugState = ctx.getData(Engine.DEBUG_STATE_KEY); // var debugState = ctx.getData(Engine.DEBUG_STATE_KEY);
if (debugState != null) debugState.pushFrame(this); // if (debugState != null) debugState.pushFrame(this);
} }
public void end(CallContext ctx) { public void end(CallContext ctx) {
var debugState = ctx.getData(Engine.DEBUG_STATE_KEY);
if (debugState != null) debugState.popFrame();
ctx.changeData(STACK_N_KEY, -1); 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 { private Object nextNoTry(CallContext ctx) throws InterruptedException {

View File

@ -69,7 +69,7 @@ public class Runners {
public static Object execMakeVar(CallContext ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execMakeVar(CallContext ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
var name = (String)instr.get(0); var name = (String)instr.get(0);
frame.function.globals.define(name); frame.function.environment.global.define(name);
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
@ -164,7 +164,7 @@ public class Runners {
public static Object execLoadVar(CallContext ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execLoadVar(CallContext ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
var i = instr.get(0); 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)); else frame.push(ctx, frame.scope.get((int)i).get(ctx));
frame.codePtr++; frame.codePtr++;
@ -176,7 +176,7 @@ public class Runners {
return NO_RETURN; return NO_RETURN;
} }
public static Object execLoadGlob(CallContext ctx, Instruction instr, CodeFrame frame) { 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++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
@ -202,7 +202,7 @@ public class Runners {
var body = new Instruction[end - start]; var body = new Instruction[end - start];
System.arraycopy(frame.function.body, start, body, 0, 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.push(ctx, func);
frame.codePtr += n; frame.codePtr += n;
@ -227,7 +227,7 @@ public class Runners {
return execLoadMember(ctx, state, instr, frame); return execLoadMember(ctx, state, instr, frame);
} }
public static Object execLoadRegEx(CallContext ctx, Instruction instr, CodeFrame frame) throws InterruptedException { 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++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
@ -252,7 +252,7 @@ public class Runners {
var val = (boolean)instr.get(1) ? frame.peek() : frame.pop(); var val = (boolean)instr.get(1) ? frame.peek() : frame.pop();
var i = instr.get(0); 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); else frame.scope.get((int)i).set(ctx, val);
frame.codePtr++; frame.codePtr++;
@ -299,8 +299,8 @@ public class Runners {
Object obj; Object obj;
if (name != null) { if (name != null) {
if (frame.function.globals.has(ctx, name)) { if (frame.function.environment.global.has(ctx, name)) {
obj = frame.function.globals.get(ctx, name); obj = frame.function.environment.global.get(ctx, name);
} }
else obj = null; else obj = null;
} }

View File

@ -11,10 +11,9 @@ import me.topchetoeu.jscript.exceptions.EngineException;
public class GlobalScope implements ScopeRecord { public class GlobalScope implements ScopeRecord {
public final ObjectValue obj; public final ObjectValue obj;
public final GlobalScope parent;
@Override @Override
public GlobalScope parent() { return parent; } public GlobalScope parent() { return null; }
public boolean has(CallContext ctx, String name) throws InterruptedException { public boolean has(CallContext ctx, String name) throws InterruptedException {
return obj.hasMember(ctx, name, false); return obj.hasMember(ctx, name, false);
@ -24,7 +23,9 @@ public class GlobalScope implements ScopeRecord {
} }
public GlobalScope globalChild() { public GlobalScope globalChild() {
return new GlobalScope(this); var obj = new ObjectValue();
obj.setPrototype(null, this.obj);
return new GlobalScope(obj);
} }
public LocalScopeRecord child() { public LocalScopeRecord child() {
return new LocalScopeRecord(this); return new LocalScopeRecord(this);
@ -78,12 +79,9 @@ public class GlobalScope implements ScopeRecord {
} }
public GlobalScope() { public GlobalScope() {
this.parent = null;
this.obj = new ObjectValue(); this.obj = new ObjectValue();
} }
public GlobalScope(GlobalScope parent) { public GlobalScope(ObjectValue val) {
this.parent = null; this.obj = val;
this.obj = new ObjectValue();
this.obj.setPrototype(null, parent.obj);
} }
} }

View File

@ -4,7 +4,6 @@ import java.util.ArrayList;
public class LocalScope { public class LocalScope {
private String[] names; private String[] names;
private LocalScope parent;
public final ValueVariable[] captures; public final ValueVariable[] captures;
public final ValueVariable[] locals; public final ValueVariable[] locals;
public final ArrayList<ValueVariable> catchVars = new ArrayList<>(); public final ArrayList<ValueVariable> catchVars = new ArrayList<>();
@ -33,18 +32,11 @@ public class LocalScope {
return captures.length + locals.length; return captures.length + locals.length;
} }
public GlobalScope toGlobal(GlobalScope global) { public void toGlobal(GlobalScope global) {
GlobalScope res;
if (parent == null) res = new GlobalScope(global);
else res = new GlobalScope(parent.toGlobal(global));
var names = getNames(); var names = getNames();
for (var i = 0; i < names.length; i++) { 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) { public LocalScope(int n, ValueVariable[] captures) {
@ -55,8 +47,4 @@ public class LocalScope {
locals[i] = new ValueVariable(false, null); locals[i] = new ValueVariable(false, null);
} }
} }
public LocalScope(int n, ValueVariable[] captures, LocalScope parent) {
this(n, captures);
this.parent = parent;
}
} }

View File

@ -6,8 +6,8 @@ import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Instruction.Type; import me.topchetoeu.jscript.compilation.Instruction.Type;
import me.topchetoeu.jscript.engine.CallContext; import me.topchetoeu.jscript.engine.CallContext;
import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.engine.scope.GlobalScope;
import me.topchetoeu.jscript.engine.scope.ValueVariable; import me.topchetoeu.jscript.engine.scope.ValueVariable;
public class CodeFunction extends FunctionValue { public class CodeFunction extends FunctionValue {
@ -17,7 +17,7 @@ public class CodeFunction extends FunctionValue {
public final LinkedHashMap<Location, Integer> breakableLocToIndex = new LinkedHashMap<>(); public final LinkedHashMap<Location, Integer> breakableLocToIndex = new LinkedHashMap<>();
public final LinkedHashMap<Integer, Location> breakableIndexToLoc = new LinkedHashMap<>(); public final LinkedHashMap<Integer, Location> breakableIndexToLoc = new LinkedHashMap<>();
public final ValueVariable[] captures; public final ValueVariable[] captures;
public final GlobalScope globals; public Environment environment;
public Location loc() { public Location loc() {
for (var instr : body) { for (var instr : body) {
@ -34,13 +34,13 @@ public class CodeFunction extends FunctionValue {
@Override @Override
public Object call(CallContext ctx, Object thisArg, Object... args) throws InterruptedException { 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); super(name, length);
this.captures = captures; this.captures = captures;
this.globals = globals; this.environment = environment;
this.localsN = localsN; this.localsN = localsN;
this.length = length; this.length = length;
this.body = body; this.body = body;

View File

@ -8,7 +8,7 @@ public class NativeWrapper extends ObjectValue {
@Override @Override
public ObjectValue getPrototype(CallContext ctx) throws InterruptedException { 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); else return super.getPrototype(ctx);
} }

View File

@ -147,13 +147,13 @@ public class ObjectValue {
public ObjectValue getPrototype(CallContext ctx) throws InterruptedException { public ObjectValue getPrototype(CallContext ctx) throws InterruptedException {
try { try {
if (prototype == OBJ_PROTO) return ctx.engine().objectProto(); if (prototype == OBJ_PROTO) return ctx.environment.proto("object");
if (prototype == ARR_PROTO) return ctx.engine().arrayProto(); if (prototype == ARR_PROTO) return ctx.environment.proto("array");
if (prototype == FUNC_PROTO) return ctx.engine().functionProto(); if (prototype == FUNC_PROTO) return ctx.environment.proto("function");
if (prototype == ERR_PROTO) return ctx.engine().errorProto(); if (prototype == ERR_PROTO) return ctx.environment.proto("error");
if (prototype == RANGE_ERR_PROTO) return ctx.engine().rangeErrorProto(); if (prototype == RANGE_ERR_PROTO) return ctx.environment.proto("rangeErr");
if (prototype == SYNTAX_ERR_PROTO) return ctx.engine().syntaxErrorProto(); if (prototype == SYNTAX_ERR_PROTO) return ctx.environment.proto("syntaxErr");
if (prototype == TYPE_ERR_PROTO) return ctx.engine().typeErrorProto(); if (prototype == TYPE_ERR_PROTO) return ctx.environment.proto("typeErr");
} }
catch (NullPointerException e) { catch (NullPointerException e) {
return null; return null;
@ -165,18 +165,21 @@ public class ObjectValue {
val = Values.normalize(ctx, val); val = Values.normalize(ctx, val);
if (!extensible()) return false; 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)) { else if (Values.isObject(val)) {
var obj = Values.object(val); var obj = Values.object(val);
if (ctx != null && ctx.engine() != null) { if (ctx != null && ctx.environment != null) {
if (obj == ctx.engine().objectProto()) prototype = OBJ_PROTO; if (obj == ctx.environment.proto("object")) prototype = OBJ_PROTO;
else if (obj == ctx.engine().arrayProto()) prototype = ARR_PROTO; else if (obj == ctx.environment.proto("array")) prototype = ARR_PROTO;
else if (obj == ctx.engine().functionProto()) prototype = FUNC_PROTO; else if (obj == ctx.environment.proto("function")) prototype = FUNC_PROTO;
else if (obj == ctx.engine().errorProto()) prototype = ERR_PROTO; else if (obj == ctx.environment.proto("error")) prototype = ERR_PROTO;
else if (obj == ctx.engine().syntaxErrorProto()) prototype = SYNTAX_ERR_PROTO; else if (obj == ctx.environment.proto("syntaxErr")) prototype = SYNTAX_ERR_PROTO;
else if (obj == ctx.engine().typeErrorProto()) prototype = TYPE_ERR_PROTO; else if (obj == ctx.environment.proto("typeErr")) prototype = TYPE_ERR_PROTO;
else if (obj == ctx.engine().rangeErrorProto()) prototype = RANGE_ERR_PROTO; else if (obj == ctx.environment.proto("rangeErr")) prototype = RANGE_ERR_PROTO;
else prototype = obj; else prototype = obj;
} }
else prototype = obj; else prototype = obj;
@ -277,7 +280,8 @@ public class ObjectValue {
if (hasField(ctx, key)) return true; if (hasField(ctx, key)) return true;
if (properties.containsKey(key)) return true; if (properties.containsKey(key)) return true;
if (own) return false; 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 { public final boolean deleteMember(CallContext ctx, Object key) throws InterruptedException {
key = Values.normalize(ctx, key); key = Values.normalize(ctx, key);

View File

@ -321,15 +321,15 @@ public class Values {
if (isObject(obj)) return object(obj).getPrototype(ctx); if (isObject(obj)) return object(obj).getPrototype(ctx);
if (ctx == null) return null; if (ctx == null) return null;
if (obj instanceof String) return ctx.engine().stringProto(); if (obj instanceof String) return ctx.environment.proto("string");
else if (obj instanceof Number) return ctx.engine().numberProto(); else if (obj instanceof Number) return ctx.environment.proto("number");
else if (obj instanceof Boolean) return ctx.engine().booleanProto(); else if (obj instanceof Boolean) return ctx.environment.proto("bool");
else if (obj instanceof Symbol) return ctx.engine().symbolProto(); else if (obj instanceof Symbol) return ctx.environment.proto("symbol");
return null; return null;
} }
public static boolean setPrototype(CallContext ctx, Object obj, Object proto) throws InterruptedException { 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); return isObject(obj) && object(obj).setPrototype(ctx, proto);
} }
public static List<Object> getMembers(CallContext ctx, Object obj, boolean own, boolean includeNonEnumerable) throws InterruptedException { public static List<Object> getMembers(CallContext ctx, Object obj, boolean own, boolean includeNonEnumerable) throws InterruptedException {
@ -420,7 +420,7 @@ public class Values {
if (val instanceof Class) { if (val instanceof Class) {
if (ctx == null) return null; if (ctx == null) return null;
else return ctx.engine.getConstructor((Class<?>)val); else return ctx.environment.wrappersProvider.getConstr((Class<?>)val);
} }
return new NativeWrapper(val); return new NativeWrapper(val);
@ -498,7 +498,7 @@ public class Values {
public static Iterable<Object> toJavaIterable(CallContext ctx, Object obj) throws InterruptedException { public static Iterable<Object> toJavaIterable(CallContext ctx, Object obj) throws InterruptedException {
return () -> { return () -> {
try { 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 symbol = getMember(ctx, constr, "iterator");
var iteratorFunc = getMember(ctx, obj, symbol); var iteratorFunc = getMember(ctx, obj, symbol);
@ -567,7 +567,7 @@ public class Values {
var it = iterable.iterator(); var it = iterable.iterator();
try { 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)); res.defineProperty(ctx, key, new NativeFunction("", (_ctx, thisArg, args) -> thisArg));
} }
catch (IllegalArgumentException | NullPointerException e) { } catch (IllegalArgumentException | NullPointerException e) { }

View File

@ -3,12 +3,13 @@ package me.topchetoeu.jscript.interop;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.HashMap; import java.util.HashMap;
import me.topchetoeu.jscript.engine.WrappersProvider;
import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.NativeFunction; import me.topchetoeu.jscript.engine.values.NativeFunction;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
public class NativeTypeRegister { public class NativeTypeRegister implements WrappersProvider {
private final HashMap<Class<?>, FunctionValue> constructors = new HashMap<>(); private final HashMap<Class<?>, FunctionValue> constructors = new HashMap<>();
private final HashMap<Class<?>, ObjectValue> prototypes = new HashMap<>(); private final HashMap<Class<?>, ObjectValue> prototypes = new HashMap<>();
@ -80,9 +81,7 @@ public class NativeTypeRegister {
var name = nat.value(); var name = nat.value();
if (name.equals("")) name = cl.getSimpleName(); if (name.equals("")) name = cl.getSimpleName();
var getter = new OverloadFunction("get " + name).add(Overload.getter(member ? clazz : null, (ctx, thisArg, args) -> { var getter = new NativeFunction("get " + name, (ctx, thisArg, args) -> cl);
return ctx.engine().typeRegister().getConstr(cl);
}));
target.defineProperty(null, name, getter, null, true, false); target.defineProperty(null, name, getter, null, true, false);
} }

View File

@ -13,8 +13,8 @@ import me.topchetoeu.jscript.compilation.VariableDeclareStatement.Pair;
import me.topchetoeu.jscript.compilation.control.*; import me.topchetoeu.jscript.compilation.control.*;
import me.topchetoeu.jscript.compilation.control.SwitchStatement.SwitchCase; import me.topchetoeu.jscript.compilation.control.SwitchStatement.SwitchCase;
import me.topchetoeu.jscript.compilation.values.*; import me.topchetoeu.jscript.compilation.values.*;
import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.Operation; 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.scope.ValueVariable;
import me.topchetoeu.jscript.engine.values.CodeFunction; import me.topchetoeu.jscript.engine.values.CodeFunction;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.Values;
@ -1842,8 +1842,8 @@ public class Parsing {
return list.toArray(Statement[]::new); return list.toArray(Statement[]::new);
} }
public static CodeFunction compile(GlobalScope scope, Statement... statements) { public static CodeFunction compile(Environment environment, Statement... statements) {
var target = scope.globalChild(); var target = environment.global.globalChild();
var subscope = target.child(); var subscope = target.child();
var res = new ArrayList<Instruction>(); var res = new ArrayList<Instruction>();
var body = new CompoundStatement(null, statements); var body = new CompoundStatement(null, statements);
@ -1870,14 +1870,14 @@ public class Parsing {
} }
else res.add(Instruction.ret()); 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 { try {
return compile(scope, parse(filename, raw)); return compile(environment, parse(filename, raw));
} }
catch (SyntaxException e) { 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) });
} }
} }
} }

View File

@ -11,7 +11,7 @@ import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.Native;
public class GeneratorFunction extends FunctionValue { public class GeneratorFunction extends FunctionValue {
public final CodeFunction factory; public final FunctionValue factory;
public static class Generator { public static class Generator {
private boolean yielding = true; private boolean yielding = true;
@ -92,7 +92,7 @@ public class GeneratorFunction extends FunctionValue {
return handler; return handler;
} }
public GeneratorFunction(CodeFunction factory) { public GeneratorFunction(FunctionValue factory) {
super(factory.name, factory.length); super(factory.name, factory.length);
this.factory = factory; this.factory = factory;
} }

View File

@ -1,150 +1,130 @@
package me.topchetoeu.jscript.polyfills; package me.topchetoeu.jscript.polyfills;
import java.util.HashMap;
import me.topchetoeu.jscript.engine.CallContext; 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.ArrayValue;
import me.topchetoeu.jscript.engine.values.CodeFunction; import me.topchetoeu.jscript.engine.values.CodeFunction;
import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.NativeFunction;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Symbol; import me.topchetoeu.jscript.engine.values.Symbol;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.NativeGetter;
public class Internals { public class Internals {
private HashMap<Integer, Thread> intervals = new HashMap<>(); @Native public void markSpecial(FunctionValue... funcs) {
private HashMap<Integer, Thread> timeouts = new HashMap<>(); for (var func : funcs) {
private HashMap<String, Symbol> symbols = new HashMap<>(); func.special = true;
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);
} }
catch (IndexOutOfBoundsException e) { return false; }
} }
@Native public Environment getEnv(Object func) {
@Native if (func instanceof CodeFunction) return ((CodeFunction)func).environment;
public int setInterval(CallContext ctx, FunctionValue func, double delay) { 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 thread = new Thread((Runnable)() -> {
var ms = (long)delay; var ms = (long)delay;
var ns = (int)((delay - ms) * 10000000); 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 { try {
Thread.sleep(ms, ns); Thread.sleep(ms, ns);
} }
catch (InterruptedException e) { return; } catch (InterruptedException e) { return; }
ctx.engine().pushMsg(false, func, ctx.data(), null); ctx.engine.pushMsg(false, ctx.data(), ctx.environment, callback, null);
}); });
thread.start(); thread.start();
timeouts.put(++nextId, thread); return new NativeFunction((_ctx, thisArg, args) -> {
thread.interrupt();
return nextId; 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 @Native public int strlen(String str) {
public void clearInterval(int id) { return str.length();
var thread = intervals.remove(id);
if (thread != null) thread.interrupt();
} }
@Native @Native("char") public int _char(String str) {
public void clearTimeout(int id) { return str.charAt(0);
var thread = timeouts.remove(id); }
if (thread != null) thread.interrupt(); @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 @Native public boolean isArray(Object obj) {
public void sort(CallContext ctx, ArrayValue arr, FunctionValue cmp) { 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) -> { arr.sort((a, b) -> {
try { try {
var res = Values.toNumber(ctx, cmp.call(ctx, null, a, b)); 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();
}
} }

View File

@ -1,105 +1,105 @@
package me.topchetoeu.jscript.polyfills; package me.topchetoeu.jscript.polyfills;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.InputStreamReader; import java.io.InputStreamReader;
import me.topchetoeu.jscript.engine.Engine; import me.topchetoeu.jscript.engine.Engine;
import me.topchetoeu.jscript.engine.modules.ModuleManager; import me.topchetoeu.jscript.engine.modules.ModuleManager;
public class PolyfillEngine extends Engine { public class PolyfillEngine extends Engine {
public static String streamToString(InputStream in) { public static String streamToString(InputStream in) {
try { try {
StringBuilder out = new StringBuilder(); StringBuilder out = new StringBuilder();
BufferedReader br = new BufferedReader(new InputStreamReader(in)); BufferedReader br = new BufferedReader(new InputStreamReader(in));
for(var line = br.readLine(); line != null; line = br.readLine()) { for(var line = br.readLine(); line != null; line = br.readLine()) {
out.append(line).append('\n'); out.append(line).append('\n');
} }
br.close(); br.close();
return out.toString(); return out.toString();
} }
catch (IOException e) { catch (IOException e) {
return null; return null;
} }
} }
public static String resourceToString(String name) { public static String resourceToString(String name) {
var str = PolyfillEngine.class.getResourceAsStream("/me/topchetoeu/jscript/" + name); var str = PolyfillEngine.class.getResourceAsStream("/me/topchetoeu/jscript/" + name);
if (str == null) return null; if (str == null) return null;
return streamToString(str); return streamToString(str);
} }
public final ModuleManager modules; public final ModuleManager modules;
@Override @Override
public Object makeRegex(String pattern, String flags) { public Object makeRegex(String pattern, String flags) {
return new RegExp(pattern, flags); return new RegExp(pattern, flags);
} }
@Override @Override
public ModuleManager modules() { public ModuleManager modules() {
return modules; return modules;
} }
public PolyfillEngine(File root) { public PolyfillEngine(File root) {
super(); super();
this.modules = new ModuleManager(root); this.modules = new ModuleManager(root);
// exposeNamespace("Math", Math.class); // exposeNamespace("Math", Math.class);
// exposeNamespace("JSON", JSON.class); // exposeNamespace("JSON", JSON.class);
// exposeClass("Promise", Promise.class); // exposeClass("Promise", Promise.class);
// exposeClass("RegExp", RegExp.class); // exposeClass("RegExp", RegExp.class);
// exposeClass("Date", Date.class); // exposeClass("Date", Date.class);
// exposeClass("Map", Map.class); // exposeClass("Map", Map.class);
// exposeClass("Set", Set.class); // exposeClass("Set", Set.class);
// global().define("Object", "Function", "String", "Number", "Boolean", "Symbol"); // global().define("Object", "Function", "String", "Number", "Boolean", "Symbol");
// global().define("Array", "require"); // global().define("Array", "require");
// global().define("Error", "SyntaxError", "TypeError", "RangeError"); // global().define("Error", "SyntaxError", "TypeError", "RangeError");
// global().define("setTimeout", "setInterval", "clearTimeout", "clearInterval"); // global().define("setTimeout", "setInterval", "clearTimeout", "clearInterval");
// global().define("debugger"); // global().define("debugger");
// global().define(true, new NativeFunction("measure", (ctx, thisArg, values) -> { // global().define(true, new NativeFunction("measure", (ctx, thisArg, values) -> {
// var start = System.nanoTime(); // var start = System.nanoTime();
// try { // try {
// return Values.call(ctx, values[0], ctx); // return Values.call(ctx, values[0], ctx);
// } // }
// finally { // finally {
// System.out.println(String.format("Function took %s ms", (System.nanoTime() - start) / 1000000.)); // System.out.println(String.format("Function took %s ms", (System.nanoTime() - start) / 1000000.));
// } // }
// })); // }));
// global().define(true, new NativeFunction("isNaN", (ctx, thisArg, args) -> { // global().define(true, new NativeFunction("isNaN", (ctx, thisArg, args) -> {
// if (args.length == 0) return true; // if (args.length == 0) return true;
// else return Double.isNaN(Values.toNumber(ctx, args[0])); // else return Double.isNaN(Values.toNumber(ctx, args[0]));
// })); // }));
// global().define(true, new NativeFunction("log", (el, t, args) -> { // global().define(true, new NativeFunction("log", (el, t, args) -> {
// for (var obj : args) Values.printValue(el, obj); // for (var obj : args) Values.printValue(el, obj);
// System.out.println(); // System.out.println();
// return null; // return null;
// })); // }));
// var scope = global().globalChild(); // var scope = global().globalChild();
// scope.define("gt", true, global().obj); // scope.define("gt", true, global().obj);
// scope.define("lgt", true, scope.obj); // scope.define("lgt", true, scope.obj);
// scope.define("setProps", "setConstr"); // scope.define("setProps", "setConstr");
// scope.define("internals", true, new Internals()); // scope.define("internals", true, new Internals());
// scope.define(true, new NativeFunction("run", (ctx, thisArg, args) -> { // scope.define(true, new NativeFunction("run", (ctx, thisArg, args) -> {
// var filename = (String)args[0]; // var filename = (String)args[0];
// boolean pollute = args.length > 1 && args[1].equals(true); // boolean pollute = args.length > 1 && args[1].equals(true);
// FunctionValue func; // FunctionValue func;
// var src = resourceToString("js/" + filename); // var src = resourceToString("js/" + filename);
// if (src == null) throw new RuntimeException("The source '" + filename + "' doesn't exist."); // if (src == null) throw new RuntimeException("The source '" + filename + "' doesn't exist.");
// if (pollute) func = Parsing.compile(global(), filename, src); // if (pollute) func = Parsing.compile(global(), filename, src);
// else func = Parsing.compile(scope.globalChild(), filename, src); // else func = Parsing.compile(scope.globalChild(), filename, src);
// func.call(ctx); // func.call(ctx);
// return null; // return null;
// })); // }));
// pushMsg(false, scope.globalChild(), java.util.Map.of(), "core.js", resourceToString("js/core.js"), null); // pushMsg(false, scope.globalChild(), java.util.Map.of(), "core.js", resourceToString("js/core.js"), null);
} }
} }

View File

@ -1,360 +1,360 @@
package me.topchetoeu.jscript.polyfills; package me.topchetoeu.jscript.polyfills;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import me.topchetoeu.jscript.engine.CallContext; import me.topchetoeu.jscript.engine.CallContext;
import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.NativeFunction; import me.topchetoeu.jscript.engine.values.NativeFunction;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.Native;
public class Promise { public class Promise {
private static class Handle { private static class Handle {
public final CallContext ctx; public final CallContext ctx;
public final FunctionValue fulfilled; public final FunctionValue fulfilled;
public final FunctionValue rejected; public final FunctionValue rejected;
public Handle(CallContext ctx, FunctionValue fulfilled, FunctionValue rejected) { public Handle(CallContext ctx, FunctionValue fulfilled, FunctionValue rejected) {
this.ctx = ctx; this.ctx = ctx;
this.fulfilled = fulfilled; this.fulfilled = fulfilled;
this.rejected = rejected; this.rejected = rejected;
} }
} }
@Native("resolve") @Native("resolve")
public static Promise ofResolved(CallContext ctx, Object val) { public static Promise ofResolved(CallContext ctx, Object val) {
if (Values.isWrapper(val, Promise.class)) return Values.wrapper(val, Promise.class); if (Values.isWrapper(val, Promise.class)) return Values.wrapper(val, Promise.class);
var res = new Promise(); var res = new Promise();
res.fulfill(ctx, val); res.fulfill(ctx, val);
return res; return res;
} }
public static Promise ofResolved(Object val) { public static Promise ofResolved(Object val) {
if (Values.isWrapper(val, Promise.class)) return Values.wrapper(val, Promise.class); if (Values.isWrapper(val, Promise.class)) return Values.wrapper(val, Promise.class);
var res = new Promise(); var res = new Promise();
res.fulfill(val); res.fulfill(val);
return res; return res;
} }
@Native("reject") @Native("reject")
public static Promise ofRejected(CallContext ctx, Object val) { public static Promise ofRejected(CallContext ctx, Object val) {
var res = new Promise(); var res = new Promise();
res.reject(ctx, val); res.reject(ctx, val);
return res; return res;
} }
public static Promise ofRejected(Object val) { public static Promise ofRejected(Object val) {
var res = new Promise(); var res = new Promise();
res.fulfill(val); res.fulfill(val);
return res; return res;
} }
@Native @Native
public static Promise any(CallContext ctx, Object _promises) { public static Promise any(CallContext ctx, Object _promises) {
if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array.");
var promises = Values.array(_promises); var promises = Values.array(_promises);
if (promises.size() == 0) return ofResolved(new ArrayValue()); if (promises.size() == 0) return ofResolved(new ArrayValue());
var n = new int[] { promises.size() }; var n = new int[] { promises.size() };
var res = new Promise(); var res = new Promise();
var errors = new ArrayValue(); var errors = new ArrayValue();
for (var i = 0; i < promises.size(); i++) { for (var i = 0; i < promises.size(); i++) {
var index = i; var index = i;
var val = promises.get(i); var val = promises.get(i);
if (Values.isWrapper(val, Promise.class)) Values.wrapper(val, Promise.class).then( if (Values.isWrapper(val, Promise.class)) Values.wrapper(val, Promise.class).then(
ctx, ctx,
new NativeFunction(null, (e, th, args) -> { res.fulfill(e, args[0]); return null; }), new NativeFunction(null, (e, th, args) -> { res.fulfill(e, args[0]); return null; }),
new NativeFunction(null, (e, th, args) -> { new NativeFunction(null, (e, th, args) -> {
errors.set(ctx, index, args[0]); errors.set(ctx, index, args[0]);
n[0]--; n[0]--;
if (n[0] <= 0) res.reject(e, errors); if (n[0] <= 0) res.reject(e, errors);
return null; return null;
}) })
); );
else { else {
res.fulfill(ctx, val); res.fulfill(ctx, val);
break; break;
} }
} }
return res; return res;
} }
@Native @Native
public static Promise race(CallContext ctx, Object _promises) { public static Promise race(CallContext ctx, Object _promises) {
if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array.");
var promises = Values.array(_promises); var promises = Values.array(_promises);
if (promises.size() == 0) return ofResolved(new ArrayValue()); if (promises.size() == 0) return ofResolved(new ArrayValue());
var res = new Promise(); var res = new Promise();
for (var i = 0; i < promises.size(); i++) { for (var i = 0; i < promises.size(); i++) {
var val = promises.get(i); var val = promises.get(i);
if (Values.isWrapper(val, Promise.class)) Values.wrapper(val, Promise.class).then( if (Values.isWrapper(val, Promise.class)) Values.wrapper(val, Promise.class).then(
ctx, ctx,
new NativeFunction(null, (e, th, args) -> { res.fulfill(e, args[0]); return null; }), 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; }) new NativeFunction(null, (e, th, args) -> { res.reject(e, args[0]); return null; })
); );
else { else {
res.fulfill(val); res.fulfill(val);
break; break;
} }
} }
return res; return res;
} }
@Native @Native
public static Promise all(CallContext ctx, Object _promises) { public static Promise all(CallContext ctx, Object _promises) {
if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array.");
var promises = Values.array(_promises); var promises = Values.array(_promises);
if (promises.size() == 0) return ofResolved(new ArrayValue()); if (promises.size() == 0) return ofResolved(new ArrayValue());
var n = new int[] { promises.size() }; var n = new int[] { promises.size() };
var res = new Promise(); var res = new Promise();
var result = new ArrayValue(); var result = new ArrayValue();
for (var i = 0; i < promises.size(); i++) { for (var i = 0; i < promises.size(); i++) {
var index = i; var index = i;
var val = promises.get(i); var val = promises.get(i);
if (Values.isWrapper(val, Promise.class)) Values.wrapper(val, Promise.class).then( if (Values.isWrapper(val, Promise.class)) Values.wrapper(val, Promise.class).then(
ctx, ctx,
new NativeFunction(null, (e, th, args) -> { new NativeFunction(null, (e, th, args) -> {
result.set(ctx, index, args[0]); result.set(ctx, index, args[0]);
n[0]--; n[0]--;
if (n[0] <= 0) res.fulfill(e, result); if (n[0] <= 0) res.fulfill(e, result);
return null; return null;
}), }),
new NativeFunction(null, (e, th, args) -> { res.reject(e, args[0]); return null; }) new NativeFunction(null, (e, th, args) -> { res.reject(e, args[0]); return null; })
); );
else { else {
result.set(ctx, i, val); result.set(ctx, i, val);
break; break;
} }
} }
if (n[0] <= 0) res.fulfill(ctx, result); if (n[0] <= 0) res.fulfill(ctx, result);
return res; return res;
} }
@Native @Native
public static Promise allSettled(CallContext ctx, Object _promises) { public static Promise allSettled(CallContext ctx, Object _promises) {
if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array."); if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array.");
var promises = Values.array(_promises); var promises = Values.array(_promises);
if (promises.size() == 0) return ofResolved(new ArrayValue()); if (promises.size() == 0) return ofResolved(new ArrayValue());
var n = new int[] { promises.size() }; var n = new int[] { promises.size() };
var res = new Promise(); var res = new Promise();
var result = new ArrayValue(); var result = new ArrayValue();
for (var i = 0; i < promises.size(); i++) { for (var i = 0; i < promises.size(); i++) {
var index = i; var index = i;
var val = promises.get(i); var val = promises.get(i);
if (Values.isWrapper(val, Promise.class)) Values.wrapper(val, Promise.class).then( if (Values.isWrapper(val, Promise.class)) Values.wrapper(val, Promise.class).then(
ctx, ctx,
new NativeFunction(null, (e, th, args) -> { new NativeFunction(null, (e, th, args) -> {
result.set(ctx, index, new ObjectValue(ctx, Map.of( result.set(ctx, index, new ObjectValue(ctx, Map.of(
"status", "fulfilled", "status", "fulfilled",
"value", args[0] "value", args[0]
))); )));
n[0]--; n[0]--;
if (n[0] <= 0) res.fulfill(e, result); if (n[0] <= 0) res.fulfill(e, result);
return null; return null;
}), }),
new NativeFunction(null, (e, th, args) -> { new NativeFunction(null, (e, th, args) -> {
result.set(ctx, index, new ObjectValue(ctx, Map.of( result.set(ctx, index, new ObjectValue(ctx, Map.of(
"status", "rejected", "status", "rejected",
"reason", args[0] "reason", args[0]
))); )));
n[0]--; n[0]--;
if (n[0] <= 0) res.fulfill(e, result); if (n[0] <= 0) res.fulfill(e, result);
return null; return null;
}) })
); );
else { else {
result.set(ctx, i, new ObjectValue(ctx, Map.of( result.set(ctx, i, new ObjectValue(ctx, Map.of(
"status", "fulfilled", "status", "fulfilled",
"value", val "value", val
))); )));
n[0]--; n[0]--;
} }
} }
if (n[0] <= 0) res.fulfill(ctx, result); if (n[0] <= 0) res.fulfill(ctx, result);
return res; return res;
} }
private List<Handle> handles = new ArrayList<>(); private List<Handle> handles = new ArrayList<>();
private static final int STATE_PENDING = 0; private static final int STATE_PENDING = 0;
private static final int STATE_FULFILLED = 1; private static final int STATE_FULFILLED = 1;
private static final int STATE_REJECTED = 2; private static final int STATE_REJECTED = 2;
private int state = STATE_PENDING; private int state = STATE_PENDING;
private Object val; private Object val;
/** /**
* Thread safe - call from any thread * Thread safe - call from any thread
*/ */
public void fulfill(Object val) { public void fulfill(Object val) {
if (Values.isWrapper(val, Promise.class)) throw new IllegalArgumentException("A promise may not be a fulfil value."); if (Values.isWrapper(val, Promise.class)) throw new IllegalArgumentException("A promise may not be a fulfil value.");
if (state != STATE_PENDING) return; if (state != STATE_PENDING) return;
this.state = STATE_FULFILLED; this.state = STATE_FULFILLED;
this.val = val; this.val = val;
for (var el : handles) el.ctx.engine().pushMsg(true, el.fulfilled, el.ctx.data(), null, val); for (var el : handles) el.ctx.engine().pushMsg(true, el.fulfilled, el.ctx.data(), null, val);
handles = null; handles = null;
} }
/** /**
* Thread safe - call from any thread * Thread safe - call from any thread
*/ */
public void fulfill(CallContext ctx, Object val) { public void fulfill(CallContext ctx, Object val) {
if (Values.isWrapper(val, Promise.class)) Values.wrapper(val, Promise.class).then(ctx, if (Values.isWrapper(val, Promise.class)) Values.wrapper(val, Promise.class).then(ctx,
new NativeFunction(null, (e, th, args) -> { new NativeFunction(null, (e, th, args) -> {
this.fulfill(e, args[0]); this.fulfill(e, args[0]);
return null; return null;
}), }),
new NativeFunction(null, (e, th, args) -> { new NativeFunction(null, (e, th, args) -> {
this.reject(e, args[0]); this.reject(e, args[0]);
return null; return null;
}) })
); );
else this.fulfill(val); else this.fulfill(val);
} }
/** /**
* Thread safe - call from any thread * Thread safe - call from any thread
*/ */
public void reject(Object val) { public void reject(Object val) {
if (Values.isWrapper(val, Promise.class)) throw new IllegalArgumentException("A promise may not be a reject value."); if (Values.isWrapper(val, Promise.class)) throw new IllegalArgumentException("A promise may not be a reject value.");
if (state != STATE_PENDING) return; if (state != STATE_PENDING) return;
this.state = STATE_REJECTED; this.state = STATE_REJECTED;
this.val = val; this.val = val;
for (var el : handles) el.ctx.engine().pushMsg(true, el.rejected, el.ctx.data(), null, val); for (var el : handles) el.ctx.engine().pushMsg(true, el.rejected, el.ctx.data(), null, val);
handles = null; handles = null;
} }
/** /**
* Thread safe - call from any thread * Thread safe - call from any thread
*/ */
public void reject(CallContext ctx, Object val) { public void reject(CallContext ctx, Object val) {
if (Values.isWrapper(val, Promise.class)) Values.wrapper(val, Promise.class).then(ctx, if (Values.isWrapper(val, Promise.class)) Values.wrapper(val, Promise.class).then(ctx,
new NativeFunction(null, (e, th, args) -> { new NativeFunction(null, (e, th, args) -> {
this.reject(e, args[0]); this.reject(e, args[0]);
return null; return null;
}), }),
new NativeFunction(null, (e, th, args) -> { new NativeFunction(null, (e, th, args) -> {
this.reject(e, args[0]); this.reject(e, args[0]);
return null; return null;
}) })
); );
else this.reject(val); else this.reject(val);
} }
private void handle(CallContext ctx, FunctionValue fulfill, FunctionValue reject) { private void handle(CallContext ctx, FunctionValue fulfill, FunctionValue reject) {
if (state == STATE_FULFILLED) ctx.engine().pushMsg(true, fulfill, ctx.data(), null, val); 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 if (state == STATE_REJECTED) ctx.engine().pushMsg(true, reject, ctx.data(), null, val);
else handles.add(new Handle(ctx, fulfill, reject)); else handles.add(new Handle(ctx, fulfill, reject));
} }
/** /**
* Thread safe - you can call this from anywhere * Thread safe - you can call this from anywhere
* HOWEVER, it's strongly recommended to use this only in javascript * HOWEVER, it's strongly recommended to use this only in javascript
*/ */
@Native @Native
public Promise then(CallContext ctx, Object onFulfill, Object onReject) { public Promise then(CallContext ctx, Object onFulfill, Object onReject) {
if (!(onFulfill instanceof FunctionValue)) onFulfill = null; if (!(onFulfill instanceof FunctionValue)) onFulfill = null;
if (!(onReject instanceof FunctionValue)) onReject = null; if (!(onReject instanceof FunctionValue)) onReject = null;
var res = new Promise(); var res = new Promise();
var fulfill = (FunctionValue)onFulfill; var fulfill = (FunctionValue)onFulfill;
var reject = (FunctionValue)onReject; var reject = (FunctionValue)onReject;
handle(ctx, handle(ctx,
new NativeFunction(null, (e, th, args) -> { new NativeFunction(null, (e, th, args) -> {
if (fulfill == null) res.fulfill(e, args[0]); if (fulfill == null) res.fulfill(e, args[0]);
else { else {
try { res.fulfill(e, fulfill.call(e, null, args[0])); } try { res.fulfill(e, fulfill.call(e, null, args[0])); }
catch (EngineException err) { res.reject(e, err.value); } catch (EngineException err) { res.reject(e, err.value); }
} }
return null; return null;
}), }),
new NativeFunction(null, (e, th, args) -> { new NativeFunction(null, (e, th, args) -> {
if (reject == null) res.reject(e, args[0]); if (reject == null) res.reject(e, args[0]);
else { else {
try { res.fulfill(e, reject.call(e, null, args[0])); } try { res.fulfill(e, reject.call(e, null, args[0])); }
catch (EngineException err) { res.reject(e, err.value); } catch (EngineException err) { res.reject(e, err.value); }
} }
return null; return null;
}) })
); );
return res; return res;
} }
/** /**
* Thread safe - you can call this from anywhere * Thread safe - you can call this from anywhere
* HOWEVER, it's strongly recommended to use this only in javascript * HOWEVER, it's strongly recommended to use this only in javascript
*/ */
@Native("catch") @Native("catch")
public Promise _catch(CallContext ctx, Object onReject) { public Promise _catch(CallContext ctx, Object onReject) {
return then(ctx, null, onReject); return then(ctx, null, onReject);
} }
/** /**
* Thread safe - you can call this from anywhere * Thread safe - you can call this from anywhere
* HOWEVER, it's strongly recommended to use this only in javascript * HOWEVER, it's strongly recommended to use this only in javascript
*/ */
@Native("finally") @Native("finally")
public Promise _finally(CallContext ctx, Object handle) { public Promise _finally(CallContext ctx, Object handle) {
return then(ctx, return then(ctx,
new NativeFunction(null, (e, th, args) -> { new NativeFunction(null, (e, th, args) -> {
if (handle instanceof FunctionValue) ((FunctionValue)handle).call(ctx); if (handle instanceof FunctionValue) ((FunctionValue)handle).call(ctx);
return args[0]; return args[0];
}), }),
new NativeFunction(null, (e, th, args) -> { new NativeFunction(null, (e, th, args) -> {
if (handle instanceof FunctionValue) ((FunctionValue)handle).call(ctx); if (handle instanceof FunctionValue) ((FunctionValue)handle).call(ctx);
throw new EngineException(args[0]); throw new EngineException(args[0]);
}) })
); );
} }
/** /**
* NOT THREAD SAFE - must be called from the engine executor thread * NOT THREAD SAFE - must be called from the engine executor thread
*/ */
@Native @Native
public Promise(CallContext ctx, FunctionValue func) throws InterruptedException { public Promise(CallContext ctx, FunctionValue func) throws InterruptedException {
if (!(func instanceof FunctionValue)) throw EngineException.ofType("A function must be passed to the promise constructor."); if (!(func instanceof FunctionValue)) throw EngineException.ofType("A function must be passed to the promise constructor.");
try { try {
func.call( func.call(
ctx, null, ctx, null,
new NativeFunction(null, (e, th, args) -> { new NativeFunction(null, (e, th, args) -> {
fulfill(e, args.length > 0 ? args[0] : null); fulfill(e, args.length > 0 ? args[0] : null);
return null; return null;
}), }),
new NativeFunction(null, (e, th, args) -> { new NativeFunction(null, (e, th, args) -> {
reject(e, args.length > 0 ? args[0] : null); reject(e, args.length > 0 ? args[0] : null);
return null; return null;
}) })
); );
} }
catch (EngineException e) { catch (EngineException e) {
reject(ctx, e.value); reject(ctx, e.value);
} }
} }
@Override @Native @Override @Native
public String toString() { public String toString() {
if (state == STATE_PENDING) return "Promise (pending)"; if (state == STATE_PENDING) return "Promise (pending)";
else if (state == STATE_FULFILLED) return "Promise (fulfilled)"; else if (state == STATE_FULFILLED) return "Promise (fulfilled)";
else return "Promise (rejected)"; else return "Promise (rejected)";
} }
private Promise(int state, Object val) { private Promise(int state, Object val) {
this.state = state; this.state = state;
this.val = val; this.val = val;
} }
public Promise() { public Promise() {
this(STATE_PENDING, null); this(STATE_PENDING, null);
} }
} }