Compare commits
29 Commits
v0.1.0-alp
...
v0.2.2-alp
| Author | SHA1 | Date | |
|---|---|---|---|
| 005610ca40 | |||
|
9743a6c078
|
|||
|
21a6d20ac5
|
|||
|
4d1846f082
|
|||
| 9547c86b32 | |||
|
1cccfa90a8
|
|||
|
604b752be6
|
|||
|
6c7fe6deaf
|
|||
|
68f3b6d926
|
|||
|
63b04019cf
|
|||
|
9c65bacbac
|
|||
|
0dacaaeb4c
|
|||
|
c1b84689c4
|
|||
|
bd08902196
|
|||
|
22ec95a7b5
|
|||
|
6c71911575
|
|||
|
e16c0fedb1
|
|||
|
cf36b7adc5
|
|||
|
ff9b57aeb9
|
|||
|
47c62128ab
|
|||
|
4aaf2f26db
|
|||
|
f21cdc831c
|
|||
|
86b206051d
|
|||
|
f2cd50726d
|
|||
|
d8071af480
|
|||
|
0b7442a3d8
|
|||
|
356a5a5b78
|
|||
|
da4b35f506
|
|||
|
8c049ac08f
|
1
.github/workflows/tagged-release.yml
vendored
1
.github/workflows/tagged-release.yml
vendored
@@ -14,6 +14,7 @@ jobs:
|
||||
- name: Clone repository
|
||||
uses: GuillaumeFalourd/clone-github-repo-action@main
|
||||
with:
|
||||
branch: 'master' # fuck this political bullshitshit, took me an hour to fix this
|
||||
owner: 'TopchetoEU'
|
||||
repository: 'java-jscript'
|
||||
- name: "Build"
|
||||
|
||||
3
build.js
3
build.js
@@ -54,7 +54,7 @@ async function compileJava() {
|
||||
.replace('${AUTHOR}', conf.author)
|
||||
);
|
||||
const args = ['--release', '11', ];
|
||||
if (argv[1] === 'debug') args.push('-g');
|
||||
if (argv[2] === 'debug') args.push('-g');
|
||||
args.push('-d', 'dst/classes', 'Metadata.java');
|
||||
|
||||
for await (const path of find('src', undefined, v => v.endsWith('.java') && !v.endsWith('Metadata.java'))) args.push(path);
|
||||
@@ -69,7 +69,6 @@ async function compileJava() {
|
||||
try {
|
||||
try { await fs.rm('dst', { recursive: true }); } catch {}
|
||||
await copy('src', 'dst/classes', v => !v.endsWith('.java'));
|
||||
await run('tsc', '-p', 'lib/tsconfig.json', '--outFile', 'dst/classes/me/topchetoeu/jscript/js/core.js'),
|
||||
await compileJava();
|
||||
await run('jar', '-c', '-f', 'dst/jscript.jar', '-e', 'me.topchetoeu.jscript.Main', '-C', 'dst/classes', '.');
|
||||
}
|
||||
|
||||
@@ -1,154 +0,0 @@
|
||||
package me.topchetoeu.jscript.engine.debug;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import java.security.MessageDigest;
|
||||
import java.util.Base64;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Engine;
|
||||
import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type;
|
||||
import me.topchetoeu.jscript.engine.debug.handlers.DebuggerHandles;
|
||||
import me.topchetoeu.jscript.exceptions.SyntaxException;
|
||||
|
||||
public class DebugServer {
|
||||
public static String browserDisplayName = "jscript";
|
||||
public static String targetName = "target";
|
||||
|
||||
public final Engine engine;
|
||||
|
||||
private static void send(Socket socket, String val) throws IOException {
|
||||
Http.writeResponse(socket.getOutputStream(), 200, "OK", "application/json", val.getBytes());
|
||||
}
|
||||
|
||||
// SILENCE JAVA
|
||||
private MessageDigest getDigestInstance() {
|
||||
try {
|
||||
return MessageDigest.getInstance("sha1");
|
||||
}
|
||||
catch (Throwable a) { return null; }
|
||||
}
|
||||
|
||||
private static Thread runAsync(Runnable func, String name) {
|
||||
var res = new Thread(func);
|
||||
res.setName(name);
|
||||
res.start();
|
||||
return res;
|
||||
}
|
||||
|
||||
private void handle(WebSocket ws) throws InterruptedException, IOException {
|
||||
WebSocketMessage raw;
|
||||
|
||||
while ((raw = ws.receive()) != null) {
|
||||
if (raw.type != Type.Text) {
|
||||
ws.send(new V8Error("Expected a text message."));
|
||||
continue;
|
||||
}
|
||||
|
||||
V8Message msg;
|
||||
|
||||
try {
|
||||
msg = new V8Message(raw.textData());
|
||||
}
|
||||
catch (SyntaxException e) {
|
||||
ws.send(new V8Error(e.getMessage()));
|
||||
return;
|
||||
}
|
||||
|
||||
switch (msg.name) {
|
||||
case "Debugger.enable": DebuggerHandles.enable(msg, engine, ws); continue;
|
||||
case "Debugger.disable": DebuggerHandles.disable(msg, engine, ws); continue;
|
||||
case "Debugger.stepInto": DebuggerHandles.stepInto(msg, engine, ws); continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
private void onWsConnect(HttpRequest req, Socket socket) throws IOException {
|
||||
var key = req.headers.get("sec-websocket-key");
|
||||
|
||||
if (key == null) {
|
||||
Http.writeResponse(
|
||||
socket.getOutputStream(), 426, "Upgrade Required", "text/txt",
|
||||
"Expected a WS upgrade".getBytes()
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
var resKey = Base64.getEncoder().encodeToString(getDigestInstance().digest(
|
||||
(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11").getBytes()
|
||||
));
|
||||
|
||||
Http.writeCode(socket.getOutputStream(), 101, "Switching Protocols");
|
||||
Http.writeHeader(socket.getOutputStream(), "Connection", "Upgrade");
|
||||
Http.writeHeader(socket.getOutputStream(), "Sec-WebSocket-Accept", resKey);
|
||||
Http.writeLastHeader(socket.getOutputStream(), "Upgrade", "WebSocket");
|
||||
|
||||
var ws = new WebSocket(socket);
|
||||
|
||||
runAsync(() -> {
|
||||
try {
|
||||
handle(ws);
|
||||
}
|
||||
catch (InterruptedException e) { return; }
|
||||
catch (IOException e) { e.printStackTrace(); }
|
||||
finally { ws.close(); }
|
||||
}, "Debug Server Message Reader");
|
||||
runAsync(() -> {
|
||||
try {
|
||||
handle(ws);
|
||||
}
|
||||
catch (InterruptedException e) { return; }
|
||||
catch (IOException e) { e.printStackTrace(); }
|
||||
finally { ws.close(); }
|
||||
}, "Debug Server Event Writer");
|
||||
}
|
||||
|
||||
public void open(InetSocketAddress address) throws IOException {
|
||||
ServerSocket server = new ServerSocket();
|
||||
server.bind(address);
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
var socket = server.accept();
|
||||
var req = Http.readRequest(socket.getInputStream());
|
||||
|
||||
switch (req.path) {
|
||||
case "/json/version":
|
||||
send(socket, "{\"Browser\":\"" + browserDisplayName + "\",\"Protocol-Version\":\"1.2\"}");
|
||||
break;
|
||||
case "/json/list":
|
||||
case "/json":
|
||||
var addr = "ws://" + address.getHostString() + ":" + address.getPort() + "/devtools/page/" + targetName;
|
||||
send(socket, "{\"id\":\"" + browserDisplayName + "\",\"webSocketDebuggerUrl\":\"" + addr + "\"}");
|
||||
break;
|
||||
case "/json/new":
|
||||
case "/json/activate":
|
||||
case "/json/protocol":
|
||||
case "/json/close":
|
||||
case "/devtools/inspector.html":
|
||||
Http.writeResponse(
|
||||
socket.getOutputStream(),
|
||||
501, "Not Implemented", "text/txt",
|
||||
"This feature isn't (and won't be) implemented.".getBytes()
|
||||
);
|
||||
break;
|
||||
default:
|
||||
if (req.path.equals("/devtools/page/" + targetName)) onWsConnect(req, socket);
|
||||
else {
|
||||
Http.writeResponse(
|
||||
socket.getOutputStream(),
|
||||
404, "Not Found", "text/txt",
|
||||
"Not found :/".getBytes()
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
finally { server.close(); }
|
||||
}
|
||||
|
||||
public DebugServer(Engine engine) {
|
||||
this.engine = engine;
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
package me.topchetoeu.jscript.engine.debug;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.engine.BreakpointData;
|
||||
import me.topchetoeu.jscript.engine.DebugCommand;
|
||||
import me.topchetoeu.jscript.engine.frame.CodeFrame;
|
||||
import me.topchetoeu.jscript.events.Event;
|
||||
|
||||
public class DebugState {
|
||||
private boolean paused = false;
|
||||
|
||||
public final HashSet<Location> breakpoints = new HashSet<>();
|
||||
public final List<CodeFrame> frames = new ArrayList<>();
|
||||
public final Map<String, String> sources = new HashMap<>();
|
||||
|
||||
public final Event<BreakpointData> breakpointNotifier = new Event<>();
|
||||
public final Event<DebugCommand> commandNotifier = new Event<>();
|
||||
public final Event<String> sourceAdded = new Event<>();
|
||||
|
||||
public DebugState pushFrame(CodeFrame frame) {
|
||||
frames.add(frame);
|
||||
return this;
|
||||
}
|
||||
public DebugState popFrame() {
|
||||
if (frames.size() > 0) frames.remove(frames.size() - 1);
|
||||
return this;
|
||||
}
|
||||
|
||||
public DebugCommand pause(BreakpointData data) throws InterruptedException {
|
||||
paused = true;
|
||||
breakpointNotifier.next(data);
|
||||
return commandNotifier.toAwaitable().await();
|
||||
}
|
||||
public void resume(DebugCommand command) {
|
||||
paused = false;
|
||||
commandNotifier.next(command);
|
||||
}
|
||||
|
||||
// public void addSource()?
|
||||
|
||||
public boolean paused() { return paused; }
|
||||
|
||||
public boolean isBreakpoint(Location loc) {
|
||||
return breakpoints.contains(loc);
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package me.topchetoeu.jscript.engine.debug;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.IllegalFormatException;
|
||||
|
||||
// We dont need no http library
|
||||
public class Http {
|
||||
public static void writeCode(OutputStream str, int code, String name) throws IOException {
|
||||
str.write(("HTTP/1.1 " + code + " " + name + "\r\n").getBytes());
|
||||
}
|
||||
public static void writeHeader(OutputStream str, String name, String value) throws IOException {
|
||||
str.write((name + ": " + value + "\r\n").getBytes());
|
||||
}
|
||||
public static void writeLastHeader(OutputStream str, String name, String value) throws IOException {
|
||||
str.write((name + ": " + value + "\r\n").getBytes());
|
||||
writeHeadersEnd(str);
|
||||
}
|
||||
public static void writeHeadersEnd(OutputStream str) throws IOException {
|
||||
str.write("\n".getBytes());
|
||||
}
|
||||
|
||||
public static void writeResponse(OutputStream str, int code, String name, String type, byte[] data) throws IOException {
|
||||
writeCode(str, code, name);
|
||||
writeHeader(str, "Content-Type", type);
|
||||
writeLastHeader(str, "Content-Length", data.length + "");
|
||||
str.write(data);
|
||||
str.close();
|
||||
}
|
||||
|
||||
public static HttpRequest readRequest(InputStream str) throws IOException {
|
||||
var lines = new BufferedReader(new InputStreamReader(str));
|
||||
var line = lines.readLine();
|
||||
var i1 = line.indexOf(" ");
|
||||
var i2 = line.lastIndexOf(" ");
|
||||
|
||||
var method = line.substring(0, i1).trim().toUpperCase();
|
||||
var path = line.substring(i1 + 1, i2).trim();
|
||||
var headers = new HashMap<String, String>();
|
||||
|
||||
while (!(line = lines.readLine()).isEmpty()) {
|
||||
var i = line.indexOf(":");
|
||||
if (i < 0) continue;
|
||||
var name = line.substring(0, i).trim().toLowerCase();
|
||||
var value = line.substring(i + 1).trim();
|
||||
|
||||
if (name.length() == 0) continue;
|
||||
headers.put(name, value);
|
||||
}
|
||||
|
||||
if (headers.containsKey("content-length")) {
|
||||
try {
|
||||
var i = Integer.parseInt(headers.get("content-length"));
|
||||
str.skip(i);
|
||||
}
|
||||
catch (IllegalFormatException e) { /* ¯\_(ツ)_/¯ */ }
|
||||
}
|
||||
|
||||
return new HttpRequest(method, path, headers);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package me.topchetoeu.jscript.engine.debug;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class HttpRequest {
|
||||
public final String method;
|
||||
public final String path;
|
||||
public final Map<String, String> headers;
|
||||
|
||||
public HttpRequest(String method, String path, Map<String, String> headers) {
|
||||
this.method = method;
|
||||
this.path = path;
|
||||
this.headers = headers;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
package me.topchetoeu.jscript.engine.debug;
|
||||
|
||||
import me.topchetoeu.jscript.json.JSON;
|
||||
import me.topchetoeu.jscript.json.JSONMap;
|
||||
|
||||
public class V8Error {
|
||||
public final String message;
|
||||
|
||||
public V8Error(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return JSON.stringify(new JSONMap().set("error", new JSONMap()
|
||||
.set("message", message)
|
||||
));
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package me.topchetoeu.jscript.engine.debug;
|
||||
|
||||
import me.topchetoeu.jscript.json.JSON;
|
||||
import me.topchetoeu.jscript.json.JSONMap;
|
||||
|
||||
public class V8Event {
|
||||
public final String name;
|
||||
public final JSONMap params;
|
||||
|
||||
public V8Event(String name, JSONMap params) {
|
||||
this.name = name;
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return JSON.stringify(new JSONMap()
|
||||
.set("method", name)
|
||||
.set("params", params)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package me.topchetoeu.jscript.engine.debug;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import me.topchetoeu.jscript.json.JSON;
|
||||
import me.topchetoeu.jscript.json.JSONElement;
|
||||
import me.topchetoeu.jscript.json.JSONMap;
|
||||
|
||||
public class V8Message {
|
||||
public final String name;
|
||||
public final int id;
|
||||
public final JSONMap params;
|
||||
|
||||
public V8Message(String name, int id, Map<String, JSONElement> params) {
|
||||
this.name = name;
|
||||
this.params = new JSONMap(params);
|
||||
this.id = id;
|
||||
}
|
||||
public V8Result respond(JSONMap result) {
|
||||
return new V8Result(id, result);
|
||||
}
|
||||
public V8Result respond() {
|
||||
return new V8Result(id, new JSONMap());
|
||||
}
|
||||
|
||||
public V8Message(JSONMap raw) {
|
||||
if (!raw.isNumber("id")) throw new IllegalArgumentException("Expected number property 'id'.");
|
||||
if (!raw.isString("method")) throw new IllegalArgumentException("Expected string property 'method'.");
|
||||
|
||||
this.name = raw.string("method");
|
||||
this.id = (int)raw.number("id");
|
||||
this.params = raw.contains("params") ? raw.map("params") : new JSONMap();
|
||||
}
|
||||
public V8Message(String raw) {
|
||||
this(JSON.parse("json", raw).map());
|
||||
}
|
||||
|
||||
public JSONMap toMap() {
|
||||
var res = new JSONMap();
|
||||
return res;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return JSON.stringify(new JSONMap()
|
||||
.set("method", name)
|
||||
.set("params", params)
|
||||
.set("id", id)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package me.topchetoeu.jscript.engine.debug;
|
||||
|
||||
import me.topchetoeu.jscript.json.JSON;
|
||||
import me.topchetoeu.jscript.json.JSONMap;
|
||||
|
||||
public class V8Result {
|
||||
public final int id;
|
||||
public final JSONMap result;
|
||||
|
||||
public V8Result(int id, JSONMap result) {
|
||||
this.id = id;
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return JSON.stringify(new JSONMap()
|
||||
.set("id", id)
|
||||
.set("result", result)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,185 +0,0 @@
|
||||
package me.topchetoeu.jscript.engine.debug;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.Socket;
|
||||
|
||||
import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type;
|
||||
|
||||
public class WebSocket implements AutoCloseable {
|
||||
public long maxLength = 2000000;
|
||||
|
||||
private Socket socket;
|
||||
private boolean closed = false;
|
||||
|
||||
private OutputStream out() throws IOException {
|
||||
return socket.getOutputStream();
|
||||
}
|
||||
private InputStream in() throws IOException {
|
||||
return socket.getInputStream();
|
||||
}
|
||||
|
||||
private long readLen(int byteLen) throws IOException {
|
||||
long res = 0;
|
||||
|
||||
if (byteLen == 126) {
|
||||
res |= in().read() << 8;
|
||||
res |= in().read();
|
||||
return res;
|
||||
}
|
||||
else if (byteLen == 127) {
|
||||
res |= in().read() << 56;
|
||||
res |= in().read() << 48;
|
||||
res |= in().read() << 40;
|
||||
res |= in().read() << 32;
|
||||
res |= in().read() << 24;
|
||||
res |= in().read() << 16;
|
||||
res |= in().read() << 8;
|
||||
res |= in().read();
|
||||
return res;
|
||||
}
|
||||
else return byteLen;
|
||||
}
|
||||
private byte[] readMask(boolean has) throws IOException {
|
||||
if (has) {
|
||||
return new byte[] {
|
||||
(byte)in().read(),
|
||||
(byte)in().read(),
|
||||
(byte)in().read(),
|
||||
(byte)in().read()
|
||||
};
|
||||
}
|
||||
else return new byte[4];
|
||||
}
|
||||
|
||||
private void writeLength(long len) throws IOException {
|
||||
if (len < 126) {
|
||||
out().write((int)len);
|
||||
}
|
||||
else if (len < 0xFFFF) {
|
||||
out().write(126);
|
||||
out().write((int)(len >> 8) & 0xFF);
|
||||
out().write((int)len & 0xFF);
|
||||
}
|
||||
else {
|
||||
out().write(127);
|
||||
out().write((int)(len >> 56) & 0xFF);
|
||||
out().write((int)(len >> 48) & 0xFF);
|
||||
out().write((int)(len >> 40) & 0xFF);
|
||||
out().write((int)(len >> 32) & 0xFF);
|
||||
out().write((int)(len >> 24) & 0xFF);
|
||||
out().write((int)(len >> 16) & 0xFF);
|
||||
out().write((int)(len >> 8) & 0xFF);
|
||||
out().write((int)len & 0xFF);
|
||||
}
|
||||
}
|
||||
private synchronized void write(int type, byte[] data) throws IOException {
|
||||
out().write(type | 0x80);
|
||||
writeLength(data.length);
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
out().write(data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
public void send(String data) throws IOException {
|
||||
if (closed) throw new IllegalStateException("Object is closed.");
|
||||
write(1, data.getBytes());
|
||||
}
|
||||
public void send(byte[] data) throws IOException {
|
||||
if (closed) throw new IllegalStateException("Object is closed.");
|
||||
write(2, data);
|
||||
}
|
||||
public void send(WebSocketMessage msg) throws IOException {
|
||||
if (msg.type == Type.Binary) send(msg.binaryData());
|
||||
else send(msg.textData());
|
||||
}
|
||||
public void send(Object data) throws IOException {
|
||||
if (closed) throw new IllegalStateException("Object is closed.");
|
||||
write(1, data.toString().getBytes());
|
||||
}
|
||||
|
||||
public void close(String reason) {
|
||||
if (socket != null) {
|
||||
try { write(8, reason.getBytes()); } catch (IOException e) { /* ¯\_(ツ)_/¯ */ }
|
||||
try { socket.close(); } catch (IOException e) { e.printStackTrace(); }
|
||||
}
|
||||
|
||||
socket = null;
|
||||
closed = true;
|
||||
}
|
||||
public void close() {
|
||||
close("");
|
||||
}
|
||||
|
||||
private WebSocketMessage fail(String reason) {
|
||||
System.out.println("WebSocket Error: " + reason);
|
||||
close(reason);
|
||||
return null;
|
||||
}
|
||||
|
||||
private byte[] readData() throws IOException {
|
||||
var maskLen = in().read();
|
||||
var hasMask = (maskLen & 0x80) != 0;
|
||||
var len = (int)readLen(maskLen & 0x7F);
|
||||
var mask = readMask(hasMask);
|
||||
|
||||
if (len > maxLength) fail("WebSocket Error: client exceeded configured max message size");
|
||||
else {
|
||||
var buff = new byte[len];
|
||||
|
||||
if (in().read(buff) < len) fail("WebSocket Error: payload too short");
|
||||
else {
|
||||
for (int i = 0; i < len; i++) {
|
||||
buff[i] ^= mask[(int)(i % 4)];
|
||||
}
|
||||
return buff;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public WebSocketMessage receive() throws InterruptedException {
|
||||
try {
|
||||
var data = new ByteArrayOutputStream();
|
||||
var type = 0;
|
||||
|
||||
while (socket != null && !closed) {
|
||||
var finId = in().read();
|
||||
var fin = (finId & 0x80) != 0;
|
||||
int id = finId & 0x0F;
|
||||
|
||||
if (id == 0x8) { close(); return null; }
|
||||
if (id >= 0x8) {
|
||||
if (!fin) return fail("WebSocket Error: client-sent control frame was fragmented");
|
||||
if (id == 0x9) write(0xA, data.toByteArray());
|
||||
continue;
|
||||
}
|
||||
|
||||
if (type == 0) type = id;
|
||||
if (type == 0) return fail("WebSocket Error: client used opcode 0x00 for first fragment");
|
||||
|
||||
var buff = readData();
|
||||
if (buff == null) break;
|
||||
|
||||
if (data.size() + buff.length > maxLength) return fail("WebSocket Error: client exceeded configured max message size");
|
||||
data.write(buff);
|
||||
|
||||
if (!fin) continue;
|
||||
var raw = data.toByteArray();
|
||||
|
||||
if (type == 1) return new WebSocketMessage(new String(raw));
|
||||
else return new WebSocketMessage(raw);
|
||||
}
|
||||
}
|
||||
catch (IOException e) { close(); }
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public WebSocket(Socket socket) {
|
||||
this.socket = socket;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package me.topchetoeu.jscript.engine.debug;
|
||||
|
||||
public class WebSocketMessage {
|
||||
public static enum Type {
|
||||
Text,
|
||||
Binary,
|
||||
}
|
||||
|
||||
public final Type type;
|
||||
private final Object data;
|
||||
|
||||
public final String textData() {
|
||||
if (type != Type.Text) throw new IllegalStateException("Message is not text.");
|
||||
return (String)data;
|
||||
}
|
||||
public final byte[] binaryData() {
|
||||
if (type != Type.Binary) throw new IllegalStateException("Message is not binary.");
|
||||
return (byte[])data;
|
||||
}
|
||||
|
||||
public WebSocketMessage(String data) {
|
||||
this.type = Type.Text;
|
||||
this.data = data;
|
||||
}
|
||||
public WebSocketMessage(byte[] data) {
|
||||
this.type = Type.Binary;
|
||||
this.data = data;
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package me.topchetoeu.jscript.engine.debug.handlers;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import me.topchetoeu.jscript.engine.DebugCommand;
|
||||
import me.topchetoeu.jscript.engine.Engine;
|
||||
import me.topchetoeu.jscript.engine.debug.V8Error;
|
||||
import me.topchetoeu.jscript.engine.debug.V8Message;
|
||||
import me.topchetoeu.jscript.engine.debug.WebSocket;
|
||||
import me.topchetoeu.jscript.json.JSONMap;
|
||||
|
||||
public class DebuggerHandles {
|
||||
public static void enable(V8Message msg, Engine engine, WebSocket ws) throws IOException {
|
||||
if (engine.debugState == null) ws.send(new V8Error("Debugging is disabled for this engine."));
|
||||
else ws.send(msg.respond(new JSONMap().set("debuggerId", 1)));
|
||||
}
|
||||
public static void disable(V8Message msg, Engine engine, WebSocket ws) throws IOException {
|
||||
if (engine.debugState == null) ws.send(msg.respond());
|
||||
else ws.send(new V8Error("Debugger may not be disabled."));
|
||||
}
|
||||
public static void stepInto(V8Message msg, Engine engine, WebSocket ws) throws IOException {
|
||||
if (engine.debugState == null) ws.send(new V8Error("Debugging is disabled for this engine."));
|
||||
else if (!engine.debugState.paused()) ws.send(new V8Error("Debugger is not paused."));
|
||||
else {
|
||||
engine.debugState.resume(DebugCommand.STEP_INTO);
|
||||
ws.send(msg.respond());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
package me.topchetoeu.jscript.engine.modules;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import me.topchetoeu.jscript.polyfills.PolyfillEngine;
|
||||
|
||||
public class FileModuleProvider implements ModuleProvider {
|
||||
public File root;
|
||||
public final boolean allowOutside;
|
||||
|
||||
private boolean checkInside(Path modFile) {
|
||||
return modFile.toAbsolutePath().startsWith(root.toPath().toAbsolutePath());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Module getModule(File cwd, String name) {
|
||||
var realName = getRealName(cwd, name);
|
||||
if (realName == null) return null;
|
||||
var path = Path.of(realName + ".js").normalize();
|
||||
|
||||
try {
|
||||
var res = PolyfillEngine.streamToString(new FileInputStream(path.toFile()));
|
||||
return new Module(realName, path.toString(), res);
|
||||
}
|
||||
catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public String getRealName(File cwd, String name) {
|
||||
var path = Path.of(".", Path.of(cwd.toString(), name).normalize().toString());
|
||||
var fileName = path.getFileName().toString();
|
||||
if (fileName == null) return null;
|
||||
if (!fileName.equals("index") && path.toFile().isDirectory()) return getRealName(cwd, name + "/index");
|
||||
path = Path.of(path.toString() + ".js");
|
||||
if (!allowOutside && !checkInside(path)) return null;
|
||||
if (!path.toFile().isFile() || !path.toFile().canRead()) return null;
|
||||
var res = path.toString().replace('\\', '/');
|
||||
var i = res.lastIndexOf('.');
|
||||
return res.substring(0, i);
|
||||
}
|
||||
|
||||
public FileModuleProvider(File root, boolean allowOutside) {
|
||||
this.root = root.toPath().normalize().toFile();
|
||||
this.allowOutside = allowOutside;
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
package me.topchetoeu.jscript.engine.modules;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import me.topchetoeu.jscript.engine.CallContext;
|
||||
import me.topchetoeu.jscript.engine.CallContext.DataKey;
|
||||
import me.topchetoeu.jscript.engine.scope.Variable;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
import me.topchetoeu.jscript.interop.NativeSetter;
|
||||
|
||||
public class Module {
|
||||
public class ExportsVariable implements Variable {
|
||||
@Override
|
||||
public boolean readonly() { return false; }
|
||||
@Override
|
||||
public Object get(CallContext ctx) { return exports; }
|
||||
@Override
|
||||
public void set(CallContext ctx, Object val) { exports = val; }
|
||||
}
|
||||
|
||||
public static DataKey<Module> KEY = new DataKey<>();
|
||||
|
||||
public final String filename;
|
||||
public final String source;
|
||||
public final String name;
|
||||
private Object exports = new ObjectValue();
|
||||
private boolean executing = false;
|
||||
|
||||
@NativeGetter("name")
|
||||
public String name() { return name; }
|
||||
@NativeGetter("exports")
|
||||
public Object exports() { return exports; }
|
||||
@NativeSetter("exports")
|
||||
public void setExports(Object val) { exports = val; }
|
||||
|
||||
public void execute(CallContext ctx) throws InterruptedException {
|
||||
if (executing) return;
|
||||
|
||||
executing = true;
|
||||
var scope = ctx.engine().global().globalChild();
|
||||
scope.define(null, "module", true, this);
|
||||
scope.define("exports", new ExportsVariable());
|
||||
|
||||
var parent = new File(filename).getParentFile();
|
||||
if (parent == null) parent = new File(".");
|
||||
|
||||
ctx.engine().compile(scope, filename, source).call(ctx.copy().setData(KEY, this), null);
|
||||
executing = false;
|
||||
}
|
||||
|
||||
public Module(String name, String filename, String source) {
|
||||
this.name = name;
|
||||
this.filename = filename;
|
||||
this.source = source;
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
package me.topchetoeu.jscript.engine.modules;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.engine.CallContext;
|
||||
|
||||
public class ModuleManager {
|
||||
private final List<ModuleProvider> providers = new ArrayList<>();
|
||||
private final HashMap<String, Module> cache = new HashMap<>();
|
||||
public final FileModuleProvider files;
|
||||
|
||||
public void addProvider(ModuleProvider provider) {
|
||||
this.providers.add(provider);
|
||||
}
|
||||
|
||||
public boolean isCached(File cwd, String name) {
|
||||
name = name.replace("\\", "/");
|
||||
|
||||
// Absolute paths are forbidden
|
||||
if (name.startsWith("/")) return false;
|
||||
// Look for files if we have a relative path
|
||||
if (name.startsWith("../") || name.startsWith("./")) {
|
||||
var realName = files.getRealName(cwd, name);
|
||||
if (cache.containsKey(realName)) return true;
|
||||
else return false;
|
||||
}
|
||||
|
||||
for (var provider : providers) {
|
||||
var realName = provider.getRealName(cwd, name);
|
||||
if (realName == null) continue;
|
||||
if (cache.containsKey(realName)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
public Module tryLoad(CallContext ctx, String name) throws InterruptedException {
|
||||
name = name.replace('\\', '/');
|
||||
|
||||
var pcwd = Path.of(".");
|
||||
|
||||
if (ctx.hasData(Module.KEY)) {
|
||||
pcwd = Path.of(((Module)ctx.getData(Module.KEY)).filename).getParent();
|
||||
if (pcwd == null) pcwd = Path.of(".");
|
||||
}
|
||||
|
||||
|
||||
var cwd = pcwd.toFile();
|
||||
|
||||
if (name.startsWith("/")) return null;
|
||||
if (name.startsWith("../") || name.startsWith("./")) {
|
||||
var realName = files.getRealName(cwd, name);
|
||||
if (realName == null) return null;
|
||||
if (cache.containsKey(realName)) return cache.get(realName);
|
||||
var mod = files.getModule(cwd, name);
|
||||
cache.put(mod.name(), mod);
|
||||
mod.execute(ctx);
|
||||
return mod;
|
||||
}
|
||||
|
||||
for (var provider : providers) {
|
||||
var realName = provider.getRealName(cwd, name);
|
||||
if (realName == null) continue;
|
||||
if (cache.containsKey(realName)) return cache.get(realName);
|
||||
var mod = provider.getModule(cwd, name);
|
||||
cache.put(mod.name(), mod);
|
||||
mod.execute(ctx);
|
||||
return mod;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public ModuleManager(File root) {
|
||||
files = new FileModuleProvider(root, false);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +0,0 @@
|
||||
package me.topchetoeu.jscript.engine.modules;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public interface ModuleProvider {
|
||||
Module getModule(File cwd, String name);
|
||||
String getRealName(File cwd, String name);
|
||||
default boolean hasModule(File cwd, String name) { return getRealName(cwd, name) != null; }
|
||||
}
|
||||
71
lib/core.ts
71
lib/core.ts
@@ -1,71 +0,0 @@
|
||||
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;
|
||||
|
||||
strlen(val: string): number;
|
||||
char(val: string): number;
|
||||
stringFromStrings(arr: string[]): string;
|
||||
stringFromChars(arr: number[]): string;
|
||||
symbol(name?: string): symbol;
|
||||
symbolToString(sym: symbol): string;
|
||||
|
||||
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;
|
||||
|
||||
sort(arr: any[], comaprator: (a: any, b: any) => number): void;
|
||||
|
||||
constructor: {
|
||||
log(...args: any[]): void;
|
||||
}
|
||||
}
|
||||
|
||||
var env: Environment = arguments[0], internals: Internals = arguments[1];
|
||||
globalThis.log = internals.constructor.log;
|
||||
|
||||
try {
|
||||
run('values/object');
|
||||
run('values/symbol');
|
||||
run('values/function');
|
||||
run('values/errors');
|
||||
run('values/string');
|
||||
run('values/number');
|
||||
run('values/boolean');
|
||||
run('values/array');
|
||||
run('promise');
|
||||
run('map');
|
||||
run('set');
|
||||
run('regex');
|
||||
run('timeout');
|
||||
|
||||
env.global.log = log;
|
||||
|
||||
log('Loaded polyfills!');
|
||||
}
|
||||
catch (e: any) {
|
||||
let err = 'Uncaught error while loading polyfills: ';
|
||||
|
||||
if (typeof Error !== 'undefined' && e instanceof Error && e.toString !== {}.toString) err += e;
|
||||
else if ('message' in e) {
|
||||
if ('name' in e) err += e.name + ": " + e.message;
|
||||
else err += 'Error: ' + e.message;
|
||||
}
|
||||
else err += e;
|
||||
|
||||
log(e);
|
||||
}
|
||||
93
lib/map.ts
93
lib/map.ts
@@ -1,93 +0,0 @@
|
||||
define("map", () => {
|
||||
const syms = { values: internals.symbol('Map.values') } as { readonly values: unique symbol };
|
||||
const Object = env.global.Object;
|
||||
|
||||
class Map<KeyT, ValueT> {
|
||||
[syms.values]: any = {};
|
||||
|
||||
public [env.global.Symbol.iterator](): IterableIterator<[KeyT, ValueT]> {
|
||||
return this.entries();
|
||||
}
|
||||
|
||||
public clear() {
|
||||
this[syms.values] = {};
|
||||
}
|
||||
public delete(key: KeyT) {
|
||||
if ((key as any) in this[syms.values]) {
|
||||
delete this[syms.values];
|
||||
return true;
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
|
||||
public entries(): IterableIterator<[KeyT, ValueT]> {
|
||||
const keys = internals.ownPropKeys(this[syms.values]);
|
||||
let i = 0;
|
||||
|
||||
return {
|
||||
next: () => {
|
||||
if (i >= keys.length) return { done: true };
|
||||
else return { done: false, value: [ keys[i], this[syms.values][keys[i++]] ] }
|
||||
},
|
||||
[env.global.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] }
|
||||
},
|
||||
[env.global.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++]] }
|
||||
},
|
||||
[env.global.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[env.global.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;
|
||||
});
|
||||
@@ -1,13 +0,0 @@
|
||||
var { define, run } = (() => {
|
||||
const modules: Record<string, Function> = {};
|
||||
|
||||
function define(name: string, func: Function) {
|
||||
modules[name] = func;
|
||||
}
|
||||
function run(name: string) {
|
||||
if (typeof modules[name] === 'function') return modules[name]();
|
||||
else throw "The module '" + name + "' doesn't exist.";
|
||||
}
|
||||
|
||||
return { define, run };
|
||||
})();
|
||||
203
lib/promise.ts
203
lib/promise.ts
@@ -1,203 +0,0 @@
|
||||
define("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;
|
||||
});
|
||||
143
lib/regex.ts
143
lib/regex.ts
@@ -1,143 +0,0 @@
|
||||
define("regex", () => {
|
||||
// var RegExp = env.global.RegExp = env.internals.RegExp;
|
||||
|
||||
// setProps(RegExp.prototype as RegExp, env, {
|
||||
// [Symbol.typeName]: 'RegExp',
|
||||
|
||||
// test(val) {
|
||||
// return !!this.exec(val);
|
||||
// },
|
||||
// toString() {
|
||||
// return '/' + this.source + '/' + this.flags;
|
||||
// },
|
||||
|
||||
// [Symbol.match](target) {
|
||||
// if (this.global) {
|
||||
// const res: string[] = [];
|
||||
// let val;
|
||||
// while (val = this.exec(target)) {
|
||||
// res.push(val[0]);
|
||||
// }
|
||||
// this.lastIndex = 0;
|
||||
// return res;
|
||||
// }
|
||||
// else {
|
||||
// const res = this.exec(target);
|
||||
// if (!this.sticky) this.lastIndex = 0;
|
||||
// return res;
|
||||
// }
|
||||
// },
|
||||
// [Symbol.matchAll](target) {
|
||||
// let pattern: RegExp | undefined = new this.constructor(this, this.flags + "g") as RegExp;
|
||||
|
||||
// return {
|
||||
// next: (): IteratorResult<RegExpResult, undefined> => {
|
||||
// const val = pattern?.exec(target);
|
||||
|
||||
// if (val === null || val === undefined) {
|
||||
// pattern = undefined;
|
||||
// return { done: true };
|
||||
// }
|
||||
// else return { value: val };
|
||||
// },
|
||||
// [Symbol.iterator]() { return this; }
|
||||
// }
|
||||
// },
|
||||
// [Symbol.split](target, limit, sensible) {
|
||||
// const pattern = new this.constructor(this, this.flags + "g") as RegExp;
|
||||
// let match: RegExpResult | null;
|
||||
// let lastEnd = 0;
|
||||
// const res: string[] = [];
|
||||
|
||||
// while ((match = pattern.exec(target)) !== null) {
|
||||
// let added: string[] = [];
|
||||
|
||||
// if (match.index >= target.length) break;
|
||||
|
||||
// if (match[0].length === 0) {
|
||||
// added = [ target.substring(lastEnd, pattern.lastIndex), ];
|
||||
// if (pattern.lastIndex < target.length) added.push(...match.slice(1));
|
||||
// }
|
||||
// else if (match.index - lastEnd > 0) {
|
||||
// added = [ target.substring(lastEnd, match.index), ...match.slice(1) ];
|
||||
// }
|
||||
// else {
|
||||
// for (let i = 1; i < match.length; i++) {
|
||||
// res[res.length - match.length + i] = match[i];
|
||||
// }
|
||||
// }
|
||||
|
||||
// if (sensible) {
|
||||
// if (limit !== undefined && res.length + added.length >= limit) break;
|
||||
// else res.push(...added);
|
||||
// }
|
||||
// else {
|
||||
// for (let i = 0; i < added.length; i++) {
|
||||
// if (limit !== undefined && res.length >= limit) return res;
|
||||
// else res.push(added[i]);
|
||||
// }
|
||||
// }
|
||||
|
||||
// lastEnd = pattern.lastIndex;
|
||||
// }
|
||||
|
||||
// if (lastEnd < target.length) {
|
||||
// res.push(target.substring(lastEnd));
|
||||
// }
|
||||
|
||||
// return res;
|
||||
// },
|
||||
// [Symbol.replace](target, replacement) {
|
||||
// const pattern = new this.constructor(this, this.flags + "d") as RegExp;
|
||||
// let match: RegExpResult | null;
|
||||
// let lastEnd = 0;
|
||||
// const res: string[] = [];
|
||||
|
||||
// // log(pattern.toString());
|
||||
|
||||
// while ((match = pattern.exec(target)) !== null) {
|
||||
// const indices = match.indices![0];
|
||||
// res.push(target.substring(lastEnd, indices[0]));
|
||||
// if (replacement instanceof Function) {
|
||||
// res.push(replacement(target.substring(indices[0], indices[1]), ...match.slice(1), indices[0], target));
|
||||
// }
|
||||
// else {
|
||||
// res.push(replacement);
|
||||
// }
|
||||
// lastEnd = indices[1];
|
||||
// if (!pattern.global) break;
|
||||
// }
|
||||
|
||||
// if (lastEnd < target.length) {
|
||||
// res.push(target.substring(lastEnd));
|
||||
// }
|
||||
|
||||
// return res.join('');
|
||||
// },
|
||||
// [Symbol.search](target, reverse, start) {
|
||||
// const pattern: RegExp | undefined = new this.constructor(this, this.flags + "g") as RegExp;
|
||||
|
||||
|
||||
// if (!reverse) {
|
||||
// pattern.lastIndex = (start as any) | 0;
|
||||
// const res = pattern.exec(target);
|
||||
// if (res) return res.index;
|
||||
// else return -1;
|
||||
// }
|
||||
// else {
|
||||
// start ??= target.length;
|
||||
// start |= 0;
|
||||
// let res: RegExpResult | null = null;
|
||||
|
||||
// while (true) {
|
||||
// const tmp = pattern.exec(target);
|
||||
// if (tmp === null || tmp.index > start) break;
|
||||
// res = tmp;
|
||||
// }
|
||||
|
||||
// if (res && res.index <= start) return res.index;
|
||||
// else return -1;
|
||||
// }
|
||||
// },
|
||||
// });
|
||||
});
|
||||
81
lib/set.ts
81
lib/set.ts
@@ -1,81 +0,0 @@
|
||||
define("set", () => {
|
||||
const syms = { values: internals.symbol('Map.values') } as { readonly values: unique symbol };
|
||||
const Object = env.global.Object;
|
||||
|
||||
class Set<T> {
|
||||
[syms.values]: any = {};
|
||||
|
||||
public [env.global.Symbol.iterator](): IterableIterator<[T, T]> {
|
||||
return this.entries();
|
||||
}
|
||||
|
||||
public clear() {
|
||||
this[syms.values] = {};
|
||||
}
|
||||
public delete(key: T) {
|
||||
if ((key as any) in this[syms.values]) {
|
||||
delete this[syms.values];
|
||||
return true;
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
|
||||
public entries(): IterableIterator<[T, T]> {
|
||||
const keys = internals.ownPropKeys(this[syms.values]);
|
||||
let i = 0;
|
||||
|
||||
return {
|
||||
next: () => {
|
||||
if (i >= keys.length) return { done: true };
|
||||
else return { done: false, value: [ keys[i], keys[i] ] }
|
||||
},
|
||||
[env.global.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] }
|
||||
},
|
||||
[env.global.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[env.global.Symbol.iterator]();
|
||||
|
||||
for (let el = it.next(); !el.done; el = it.next()) {
|
||||
this[syms.values][el.value] = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
env.global.Set = Set;
|
||||
});
|
||||
@@ -1,38 +0,0 @@
|
||||
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!;
|
||||
};
|
||||
});
|
||||
@@ -1,34 +0,0 @@
|
||||
{
|
||||
"files": [
|
||||
"lib.d.ts",
|
||||
"modules.ts",
|
||||
"utils.ts",
|
||||
"values/object.ts",
|
||||
"values/symbol.ts",
|
||||
"values/function.ts",
|
||||
"values/errors.ts",
|
||||
"values/string.ts",
|
||||
"values/number.ts",
|
||||
"values/boolean.ts",
|
||||
"values/array.ts",
|
||||
"promise.ts",
|
||||
"map.ts",
|
||||
"set.ts",
|
||||
"regex.ts",
|
||||
"timeout.ts",
|
||||
"core.ts"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"outFile": "../bin/me/topchetoeu/jscript/js/core.js",
|
||||
// "declarationDir": "",
|
||||
// "declarationDir": "bin/me/topchetoeu/jscript/dts",
|
||||
"target": "ES5",
|
||||
"lib": [],
|
||||
"module": "None",
|
||||
"stripInternal": true,
|
||||
"downlevelIteration": true,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
}
|
||||
}
|
||||
38
lib/utils.ts
38
lib/utils.ts
@@ -1,38 +0,0 @@
|
||||
function setProps<
|
||||
TargetT extends object,
|
||||
DescT extends {
|
||||
[x in Exclude<keyof TargetT, 'constructor'> ]?: TargetT[x] extends ((...args: infer ArgsT) => infer RetT) ?
|
||||
((this: TargetT, ...args: ArgsT) => RetT) :
|
||||
TargetT[x]
|
||||
}
|
||||
>(target: TargetT, desc: DescT) {
|
||||
var props = internals.keys(desc, false);
|
||||
for (var i = 0; i < props.length; i++) {
|
||||
var key = props[i];
|
||||
internals.defineField(
|
||||
target, key, (desc as any)[key],
|
||||
true, // writable
|
||||
false, // enumerable
|
||||
true // configurable
|
||||
);
|
||||
}
|
||||
}
|
||||
function setConstr(target: object, constr: Function) {
|
||||
internals.defineField(
|
||||
target, 'constructor', constr,
|
||||
true, // writable
|
||||
false, // enumerable
|
||||
true // configurable
|
||||
);
|
||||
}
|
||||
|
||||
function wrapI(max: number, i: number) {
|
||||
i |= 0;
|
||||
if (i < 0) i = max + i;
|
||||
return i;
|
||||
}
|
||||
function clampI(max: number, i: number) {
|
||||
if (i < 0) i = 0;
|
||||
if (i > max) i = max;
|
||||
return i;
|
||||
}
|
||||
@@ -1,336 +0,0 @@
|
||||
define("values/array", () => {
|
||||
var Array = env.global.Array = function(len?: number) {
|
||||
var res = [];
|
||||
|
||||
if (typeof len === 'number' && arguments.length === 1) {
|
||||
if (len < 0) throw 'Invalid array length.';
|
||||
res.length = len;
|
||||
}
|
||||
else {
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
res[i] = arguments[i];
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
} as ArrayConstructor;
|
||||
|
||||
env.setProto('array', Array.prototype);
|
||||
(Array.prototype as any)[env.global.Symbol.typeName] = "Array";
|
||||
setConstr(Array.prototype, Array);
|
||||
|
||||
setProps(Array.prototype, {
|
||||
[env.global.Symbol.iterator]: function() {
|
||||
return this.values();
|
||||
},
|
||||
[env.global.Symbol.typeName]: "Array",
|
||||
|
||||
values() {
|
||||
var i = 0;
|
||||
|
||||
return {
|
||||
next: () => {
|
||||
while (i < this.length) {
|
||||
if (i++ in this) return { done: false, value: this[i - 1] };
|
||||
}
|
||||
return { done: true, value: undefined };
|
||||
},
|
||||
[env.global.Symbol.iterator]() { return this; }
|
||||
};
|
||||
},
|
||||
keys() {
|
||||
var i = 0;
|
||||
|
||||
return {
|
||||
next: () => {
|
||||
while (i < this.length) {
|
||||
if (i++ in this) return { done: false, value: i - 1 };
|
||||
}
|
||||
return { done: true, value: undefined };
|
||||
},
|
||||
[env.global.Symbol.iterator]() { return this; }
|
||||
};
|
||||
},
|
||||
entries() {
|
||||
var i = 0;
|
||||
|
||||
return {
|
||||
next: () => {
|
||||
while (i < this.length) {
|
||||
if (i++ in this) return { done: false, value: [i - 1, this[i - 1]] };
|
||||
}
|
||||
return { done: true, value: undefined };
|
||||
},
|
||||
[env.global.Symbol.iterator]() { return this; }
|
||||
};
|
||||
},
|
||||
concat() {
|
||||
var res = [] as any[];
|
||||
res.push.apply(res, this);
|
||||
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
var arg = arguments[i];
|
||||
if (arg instanceof Array) {
|
||||
res.push.apply(res, arg);
|
||||
}
|
||||
else {
|
||||
res.push(arg);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
},
|
||||
every(func, thisArg) {
|
||||
if (typeof func !== 'function') throw new TypeError("Given argument not a function.");
|
||||
func = func.bind(thisArg);
|
||||
|
||||
for (var i = 0; i < this.length; i++) {
|
||||
if (!func(this[i], i, this)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
some(func, thisArg) {
|
||||
if (typeof func !== 'function') throw new TypeError("Given argument not a function.");
|
||||
func = func.bind(thisArg);
|
||||
|
||||
for (var i = 0; i < this.length; i++) {
|
||||
if (func(this[i], i, this)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
fill(val, start, end) {
|
||||
if (arguments.length < 3) end = this.length;
|
||||
if (arguments.length < 2) start = 0;
|
||||
|
||||
start = clampI(this.length, wrapI(this.length + 1, start ?? 0));
|
||||
end = clampI(this.length, wrapI(this.length + 1, end ?? this.length));
|
||||
|
||||
for (; start < end; start++) {
|
||||
this[start] = val;
|
||||
}
|
||||
|
||||
return this;
|
||||
},
|
||||
filter(func, thisArg) {
|
||||
if (typeof func !== 'function') throw new TypeError("Given argument is not a function.");
|
||||
|
||||
var res = [];
|
||||
for (var i = 0; i < this.length; i++) {
|
||||
if (i in this && func.call(thisArg, this[i], i, this)) res.push(this[i]);
|
||||
}
|
||||
return res;
|
||||
},
|
||||
find(func, thisArg) {
|
||||
if (typeof func !== 'function') throw new TypeError("Given argument is not a function.");
|
||||
|
||||
for (var i = 0; i < this.length; i++) {
|
||||
if (i in this && func.call(thisArg, this[i], i, this)) return this[i];
|
||||
}
|
||||
|
||||
return undefined;
|
||||
},
|
||||
findIndex(func, thisArg) {
|
||||
if (typeof func !== 'function') throw new TypeError("Given argument is not a function.");
|
||||
|
||||
for (var i = 0; i < this.length; i++) {
|
||||
if (i in this && func.call(thisArg, this[i], i, this)) return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
},
|
||||
findLast(func, thisArg) {
|
||||
if (typeof func !== 'function') throw new TypeError("Given argument is not a function.");
|
||||
|
||||
for (var i = this.length - 1; i >= 0; i--) {
|
||||
if (i in this && func.call(thisArg, this[i], i, this)) return this[i];
|
||||
}
|
||||
|
||||
return undefined;
|
||||
},
|
||||
findLastIndex(func, thisArg) {
|
||||
if (typeof func !== 'function') throw new TypeError("Given argument is not a function.");
|
||||
|
||||
for (var i = this.length - 1; i >= 0; i--) {
|
||||
if (i in this && func.call(thisArg, this[i], i, this)) return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
},
|
||||
flat(depth) {
|
||||
var res = [] as any[];
|
||||
var buff = [];
|
||||
res.push(...this);
|
||||
|
||||
for (var i = 0; i < (depth ?? 1); i++) {
|
||||
var anyArrays = false;
|
||||
for (var el of res) {
|
||||
if (el instanceof Array) {
|
||||
buff.push(...el);
|
||||
anyArrays = true;
|
||||
}
|
||||
else buff.push(el);
|
||||
}
|
||||
|
||||
res = buff;
|
||||
buff = [];
|
||||
if (!anyArrays) break;
|
||||
}
|
||||
|
||||
return res;
|
||||
},
|
||||
flatMap(func, th) {
|
||||
return this.map(func, th).flat();
|
||||
},
|
||||
forEach(func, thisArg) {
|
||||
for (var i = 0; i < this.length; i++) {
|
||||
if (i in this) func.call(thisArg, this[i], i, this);
|
||||
}
|
||||
},
|
||||
map(func, thisArg) {
|
||||
if (typeof func !== 'function') throw new TypeError("Given argument is not a function.");
|
||||
|
||||
var res = [];
|
||||
for (var i = 0; i < this.length; i++) {
|
||||
if (i in this) res[i] = func.call(thisArg, this[i], i, this);
|
||||
}
|
||||
return res;
|
||||
},
|
||||
pop() {
|
||||
if (this.length === 0) return undefined;
|
||||
var val = this[this.length - 1];
|
||||
this.length--;
|
||||
return val;
|
||||
},
|
||||
push() {
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
this[this.length] = arguments[i];
|
||||
}
|
||||
return arguments.length;
|
||||
},
|
||||
shift() {
|
||||
if (this.length === 0) return undefined;
|
||||
var res = this[0];
|
||||
|
||||
for (var i = 0; i < this.length - 1; i++) {
|
||||
this[i] = this[i + 1];
|
||||
}
|
||||
|
||||
this.length--;
|
||||
|
||||
return res;
|
||||
},
|
||||
unshift() {
|
||||
for (var i = this.length - 1; i >= 0; i--) {
|
||||
this[i + arguments.length] = this[i];
|
||||
}
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
this[i] = arguments[i];
|
||||
}
|
||||
|
||||
return arguments.length;
|
||||
},
|
||||
slice(start, end) {
|
||||
start = clampI(this.length, wrapI(this.length + 1, start ?? 0));
|
||||
end = clampI(this.length, wrapI(this.length + 1, end ?? this.length));
|
||||
|
||||
var res: any[] = [];
|
||||
var n = end - start;
|
||||
if (n <= 0) return res;
|
||||
|
||||
for (var i = 0; i < n; i++) {
|
||||
res[i] = this[start + i];
|
||||
}
|
||||
|
||||
return res;
|
||||
},
|
||||
toString() {
|
||||
let res = '';
|
||||
for (let i = 0; i < this.length; i++) {
|
||||
if (i > 0) res += ',';
|
||||
if (i in this && this[i] !== undefined && this[i] !== null) res += this[i];
|
||||
}
|
||||
|
||||
return res;
|
||||
},
|
||||
indexOf(el, start) {
|
||||
start = start! | 0;
|
||||
for (var i = Math.max(0, start); i < this.length; i++) {
|
||||
if (i in this && this[i] == el) return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
},
|
||||
lastIndexOf(el, start) {
|
||||
start = start! | 0;
|
||||
for (var i = this.length; i >= start; i--) {
|
||||
if (i in this && this[i] == el) return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
},
|
||||
includes(el, start) {
|
||||
return this.indexOf(el, start) >= 0;
|
||||
},
|
||||
join(val = ',') {
|
||||
let res = '', first = true;
|
||||
|
||||
for (let i = 0; i < this.length; i++) {
|
||||
if (!(i in this)) continue;
|
||||
if (!first) res += val;
|
||||
first = false;
|
||||
res += this[i];
|
||||
}
|
||||
return res;
|
||||
},
|
||||
sort(func) {
|
||||
func ??= (a, b) => {
|
||||
const _a = a + '';
|
||||
const _b = b + '';
|
||||
|
||||
if (_a > _b) return 1;
|
||||
if (_a < _b) return -1;
|
||||
return 0;
|
||||
};
|
||||
|
||||
if (typeof func !== 'function') throw new TypeError('Expected func to be undefined or a function.');
|
||||
|
||||
internals.sort(this, func);
|
||||
return this;
|
||||
},
|
||||
splice(start, deleteCount, ...items) {
|
||||
start = clampI(this.length, wrapI(this.length, start ?? 0));
|
||||
deleteCount = (deleteCount ?? Infinity | 0);
|
||||
if (start + deleteCount >= this.length) deleteCount = this.length - start;
|
||||
|
||||
const res = this.slice(start, start + deleteCount);
|
||||
const moveN = items.length - deleteCount;
|
||||
const len = this.length;
|
||||
|
||||
if (moveN < 0) {
|
||||
for (let i = start - moveN; i < len; i++) {
|
||||
this[i + moveN] = this[i];
|
||||
}
|
||||
}
|
||||
else if (moveN > 0) {
|
||||
for (let i = len - 1; i >= start; i--) {
|
||||
this[i + moveN] = this[i];
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < items.length; i++) {
|
||||
this[i + start] = items[i];
|
||||
}
|
||||
|
||||
this.length = len + moveN;
|
||||
|
||||
return res;
|
||||
}
|
||||
});
|
||||
|
||||
setProps(Array, {
|
||||
isArray(val: any) { return internals.isArray(val); }
|
||||
});
|
||||
internals.markSpecial(Array);
|
||||
});
|
||||
@@ -1,12 +0,0 @@
|
||||
define("values/boolean", () => {
|
||||
var Boolean = env.global.Boolean = function (this: Boolean | undefined, arg) {
|
||||
var val;
|
||||
if (arguments.length === 0) val = false;
|
||||
else val = !!arg;
|
||||
if (this === undefined || this === null) return val;
|
||||
else (this as any).value = val;
|
||||
} as BooleanConstructor;
|
||||
|
||||
env.setProto('bool', Boolean.prototype);
|
||||
setConstr(Boolean.prototype, Boolean);
|
||||
});
|
||||
@@ -1,46 +0,0 @@
|
||||
define("values/errors", () => {
|
||||
var Error = env.global.Error = function Error(msg: string) {
|
||||
if (msg === undefined) msg = '';
|
||||
else msg += '';
|
||||
|
||||
return Object.setPrototypeOf({
|
||||
message: msg,
|
||||
stack: [] as string[],
|
||||
}, Error.prototype);
|
||||
} as ErrorConstructor;
|
||||
|
||||
setConstr(Error.prototype, Error);
|
||||
setProps(Error.prototype, {
|
||||
name: 'Error',
|
||||
toString: internals.setEnv(function(this: Error) {
|
||||
if (!(this instanceof Error)) return '';
|
||||
|
||||
if (this.message === '') return this.name;
|
||||
else return this.name + ': ' + this.message;
|
||||
}, env)
|
||||
});
|
||||
env.setProto('error', Error.prototype);
|
||||
internals.markSpecial(Error);
|
||||
|
||||
function makeError<T1 extends ErrorConstructor>(name: string, proto: string): T1 {
|
||||
function constr (msg: string) {
|
||||
var res = new Error(msg);
|
||||
(res as any).__proto__ = constr.prototype;
|
||||
return res;
|
||||
}
|
||||
|
||||
(constr as any).__proto__ = Error;
|
||||
(constr.prototype as any).__proto__ = env.proto('error');
|
||||
setConstr(constr.prototype, constr as ErrorConstructor);
|
||||
setProps(constr.prototype, { name: name });
|
||||
|
||||
internals.markSpecial(constr);
|
||||
env.setProto(proto, constr.prototype);
|
||||
|
||||
return constr as T1;
|
||||
}
|
||||
|
||||
env.global.RangeError = makeError('RangeError', 'rangeErr');
|
||||
env.global.TypeError = makeError('TypeError', 'typeErr');
|
||||
env.global.SyntaxError = makeError('SyntaxError', 'syntaxErr');
|
||||
});
|
||||
@@ -1,140 +0,0 @@
|
||||
define("values/function", () => {
|
||||
var Function = env.global.Function = function() {
|
||||
throw 'Using the constructor Function() is forbidden.';
|
||||
} as unknown as FunctionConstructor;
|
||||
|
||||
env.setProto('function', Function.prototype);
|
||||
setConstr(Function.prototype, Function);
|
||||
|
||||
setProps(Function.prototype, {
|
||||
apply(thisArg, args) {
|
||||
if (typeof args !== 'object') throw 'Expected arguments to be an array-like object.';
|
||||
var len = args.length - 0;
|
||||
let newArgs: any[];
|
||||
if (internals.isArray(args)) newArgs = args;
|
||||
else {
|
||||
newArgs = [];
|
||||
|
||||
while (len >= 0) {
|
||||
len--;
|
||||
newArgs[len] = args[len];
|
||||
}
|
||||
}
|
||||
|
||||
return internals.apply(this, thisArg, newArgs);
|
||||
},
|
||||
call(thisArg, ...args) {
|
||||
return this.apply(thisArg, args);
|
||||
},
|
||||
bind(thisArg, ...args) {
|
||||
const func = this;
|
||||
const res = function() {
|
||||
const resArgs = [];
|
||||
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
resArgs[i] = args[i];
|
||||
}
|
||||
for (let i = 0; i < arguments.length; i++) {
|
||||
resArgs[i + args.length] = arguments[i];
|
||||
}
|
||||
|
||||
return func.apply(thisArg, resArgs);
|
||||
};
|
||||
res.name = "<bound> " + func.name;
|
||||
return res;
|
||||
},
|
||||
toString() {
|
||||
return 'function (...) { ... }';
|
||||
},
|
||||
});
|
||||
setProps(Function, {
|
||||
async(func) {
|
||||
if (typeof func !== 'function') throw new TypeError('Expected func to be function.');
|
||||
|
||||
return function (this: any) {
|
||||
const args = arguments;
|
||||
|
||||
return new Promise((res, rej) => {
|
||||
const gen = internals.apply(internals.generator(func as any), this, args as any);
|
||||
|
||||
(function next(type: 'none' | 'err' | 'ret', val?: any) {
|
||||
try {
|
||||
let result;
|
||||
|
||||
switch (type) {
|
||||
case 'err': result = gen.throw(val); break;
|
||||
case 'ret': result = gen.next(val); break;
|
||||
case 'none': result = gen.next(); break;
|
||||
}
|
||||
if (result.done) res(result.value);
|
||||
else Promise.resolve(result.value).then(
|
||||
v => next('ret', v),
|
||||
v => next('err', v)
|
||||
)
|
||||
}
|
||||
catch (e) {
|
||||
rej(e);
|
||||
}
|
||||
})('none');
|
||||
});
|
||||
};
|
||||
},
|
||||
asyncGenerator(func) {
|
||||
if (typeof func !== 'function') throw new TypeError('Expected func to be function.');
|
||||
|
||||
return function(this: any, ...args: any[]) {
|
||||
const gen = internals.apply(internals.generator((_yield) => func(
|
||||
val => _yield(['await', val]) as any,
|
||||
val => _yield(['yield', val])
|
||||
)), this, args) as Generator<['await' | 'yield', any]>;
|
||||
|
||||
const next = (resolve: Function, reject: Function, type: 'none' | 'val' | 'ret' | 'err', val?: any) => {
|
||||
let res;
|
||||
|
||||
try {
|
||||
switch (type) {
|
||||
case 'val': res = gen.next(val); break;
|
||||
case 'ret': res = gen.return(val); break;
|
||||
case 'err': res = gen.throw(val); break;
|
||||
default: res = gen.next(); break;
|
||||
}
|
||||
}
|
||||
catch (e) { return reject(e); }
|
||||
|
||||
if (res.done) return { done: true, res: <any>res };
|
||||
else if (res.value[0] === 'await') Promise.resolve(res.value[1]).then(
|
||||
v => next(resolve, reject, 'val', v),
|
||||
v => next(resolve, reject, 'err', v),
|
||||
)
|
||||
else resolve({ done: false, value: res.value[1] });
|
||||
};
|
||||
|
||||
return {
|
||||
next() {
|
||||
const args = arguments;
|
||||
if (arguments.length === 0) return new Promise((res, rej) => next(res, rej, 'none'));
|
||||
else return new Promise((res, rej) => next(res, rej, 'val', args[0]));
|
||||
},
|
||||
return: (value) => new Promise((res, rej) => next(res, rej, 'ret', value)),
|
||||
throw: (value) => new Promise((res, rej) => next(res, rej, 'err', value)),
|
||||
[env.global.Symbol.asyncIterator]() { return this; }
|
||||
}
|
||||
}
|
||||
},
|
||||
generator(func) {
|
||||
if (typeof func !== 'function') throw new TypeError('Expected func to be function.');
|
||||
const gen = internals.generator(func);
|
||||
return function(this: any, ...args: any[]) {
|
||||
const it = internals.apply(gen, this, args);
|
||||
|
||||
return {
|
||||
next: (...args) => internals.apply(it.next, it, args),
|
||||
return: (val) => internals.apply(it.next, it, [val]),
|
||||
throw: (val) => internals.apply(it.next, it, [val]),
|
||||
[env.global.Symbol.iterator]() { return this; }
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
internals.markSpecial(Function);
|
||||
});
|
||||
@@ -1,33 +0,0 @@
|
||||
define("values/number", () => {
|
||||
var Number = env.global.Number = function(this: Number | undefined, arg: any) {
|
||||
var val;
|
||||
if (arguments.length === 0) val = 0;
|
||||
else val = arg - 0;
|
||||
if (this === undefined || this === null) return val;
|
||||
else (this as any).value = val;
|
||||
} as NumberConstructor;
|
||||
|
||||
env.setProto('number', Number.prototype);
|
||||
setConstr(Number.prototype, Number);
|
||||
|
||||
setProps(Number.prototype, {
|
||||
valueOf() {
|
||||
if (typeof this === 'number') return this;
|
||||
else return (this as any).value;
|
||||
},
|
||||
toString() {
|
||||
if (typeof this === 'number') return this + '';
|
||||
else return (this as any).value + '';
|
||||
}
|
||||
});
|
||||
|
||||
setProps(Number, {
|
||||
parseInt(val) { return Math.trunc(val as any - 0); },
|
||||
parseFloat(val) { return val as any - 0; },
|
||||
});
|
||||
|
||||
env.global.parseInt = Number.parseInt;
|
||||
env.global.parseFloat = Number.parseFloat;
|
||||
env.global.Object.defineProperty(env.global, 'NaN', { value: 0 / 0, writable: false });
|
||||
env.global.Object.defineProperty(env.global, 'Infinity', { value: 1 / 0, writable: false });
|
||||
});
|
||||
@@ -1,226 +0,0 @@
|
||||
define("values/object", () => {
|
||||
var Object = env.global.Object = function(arg: any) {
|
||||
if (arg === undefined || arg === null) return {};
|
||||
else if (typeof arg === 'boolean') return new Boolean(arg);
|
||||
else if (typeof arg === 'number') return new Number(arg);
|
||||
else if (typeof arg === 'string') return new String(arg);
|
||||
return arg;
|
||||
} as ObjectConstructor;
|
||||
|
||||
env.setProto('object', Object.prototype);
|
||||
(Object.prototype as any).__proto__ = null;
|
||||
setConstr(Object.prototype, Object as any);
|
||||
|
||||
function throwNotObject(obj: any, name: string) {
|
||||
if (obj === null || typeof obj !== 'object' && typeof obj !== 'function') {
|
||||
throw new TypeError(`Object.${name} may only be used for objects.`);
|
||||
}
|
||||
}
|
||||
function check(obj: any) {
|
||||
return typeof obj === 'object' && obj !== null || typeof obj === 'function';
|
||||
}
|
||||
|
||||
setProps(Object, {
|
||||
assign(dst, ...src) {
|
||||
throwNotObject(dst, 'assign');
|
||||
for (let i = 0; i < src.length; i++) {
|
||||
const obj = src[i];
|
||||
throwNotObject(obj, 'assign');
|
||||
for (const key of Object.keys(obj)) {
|
||||
(dst as any)[key] = (obj as any)[key];
|
||||
}
|
||||
}
|
||||
return dst;
|
||||
},
|
||||
create(obj, props) {
|
||||
props ??= {};
|
||||
return Object.defineProperties({ __proto__: obj }, props as any) as any;
|
||||
},
|
||||
|
||||
defineProperty(obj, key, attrib) {
|
||||
throwNotObject(obj, 'defineProperty');
|
||||
if (typeof attrib !== 'object') throw new TypeError('Expected attributes to be an object.');
|
||||
|
||||
if ('value' in attrib) {
|
||||
if ('get' in attrib || 'set' in attrib) throw new TypeError('Cannot specify a value and accessors for a property.');
|
||||
if (!internals.defineField(
|
||||
obj, key,
|
||||
attrib.value,
|
||||
!!attrib.writable,
|
||||
!!attrib.enumerable,
|
||||
!!attrib.configurable
|
||||
)) throw new TypeError('Can\'t define property \'' + key + '\'.');
|
||||
}
|
||||
else {
|
||||
if (typeof attrib.get !== 'function' && attrib.get !== undefined) throw new TypeError('Get accessor must be a function.');
|
||||
if (typeof attrib.set !== 'function' && attrib.set !== undefined) throw new TypeError('Set accessor must be a function.');
|
||||
|
||||
if (!internals.defineProp(
|
||||
obj, key,
|
||||
attrib.get,
|
||||
attrib.set,
|
||||
!!attrib.enumerable,
|
||||
!!attrib.configurable
|
||||
)) throw new TypeError('Can\'t define property \'' + key + '\'.');
|
||||
}
|
||||
|
||||
return obj;
|
||||
},
|
||||
defineProperties(obj, attrib) {
|
||||
throwNotObject(obj, 'defineProperties');
|
||||
if (typeof attrib !== 'object' && typeof attrib !== 'function') throw 'Expected second argument to be an object.';
|
||||
|
||||
for (var key in attrib) {
|
||||
Object.defineProperty(obj, key, attrib[key]);
|
||||
}
|
||||
|
||||
return obj;
|
||||
},
|
||||
|
||||
keys(obj, onlyString) {
|
||||
return internals.keys(obj, !!(onlyString ?? true));
|
||||
},
|
||||
entries(obj, onlyString) {
|
||||
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) {
|
||||
const res = [];
|
||||
const keys = internals.keys(obj, !!(onlyString ?? true));
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
res[i] = (obj as any)[keys[i]];
|
||||
}
|
||||
|
||||
return keys;
|
||||
},
|
||||
|
||||
getOwnPropertyDescriptor(obj, key) {
|
||||
return internals.ownProp(obj, key) as any;
|
||||
},
|
||||
getOwnPropertyDescriptors(obj) {
|
||||
const res = [];
|
||||
const keys = internals.ownPropKeys(obj);
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
res[i] = internals.ownProp(obj, keys[i]);
|
||||
}
|
||||
|
||||
return res;
|
||||
},
|
||||
|
||||
getOwnPropertyNames(obj) {
|
||||
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) {
|
||||
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) {
|
||||
const keys = internals.ownPropKeys(obj);
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
if (keys[i] === key) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
getPrototypeOf(obj) {
|
||||
return obj.__proto__;
|
||||
},
|
||||
setPrototypeOf(obj, proto) {
|
||||
(obj as any).__proto__ = proto;
|
||||
return obj;
|
||||
},
|
||||
|
||||
fromEntries(iterable) {
|
||||
const res = {} as any;
|
||||
|
||||
for (const el of iterable) {
|
||||
res[el[0]] = el[1];
|
||||
}
|
||||
|
||||
return res;
|
||||
},
|
||||
|
||||
preventExtensions(obj) {
|
||||
throwNotObject(obj, 'preventExtensions');
|
||||
internals.lock(obj, 'ext');
|
||||
return obj;
|
||||
},
|
||||
seal(obj) {
|
||||
throwNotObject(obj, 'seal');
|
||||
internals.lock(obj, 'seal');
|
||||
return obj;
|
||||
},
|
||||
freeze(obj) {
|
||||
throwNotObject(obj, 'freeze');
|
||||
internals.lock(obj, 'freeze');
|
||||
return obj;
|
||||
},
|
||||
|
||||
isExtensible(obj) {
|
||||
if (!check(obj)) return false;
|
||||
return internals.extensible(obj);
|
||||
},
|
||||
isSealed(obj) {
|
||||
if (!check(obj)) return true;
|
||||
if (internals.extensible(obj)) return false;
|
||||
const keys = internals.ownPropKeys(obj);
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
if (internals.ownProp(obj, keys[i]).configurable) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
isFrozen(obj) {
|
||||
if (!check(obj)) return true;
|
||||
if (internals.extensible(obj)) return false;
|
||||
const keys = internals.ownPropKeys(obj);
|
||||
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const prop = internals.ownProp(obj, keys[i]);
|
||||
if (prop.configurable) return false;
|
||||
if ('writable' in prop && prop.writable) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
setProps(Object.prototype, {
|
||||
valueOf() {
|
||||
return this;
|
||||
},
|
||||
toString() {
|
||||
return '[object ' + (this[env.global.Symbol.typeName] ?? 'Unknown') + ']';
|
||||
},
|
||||
hasOwnProperty(key) {
|
||||
return Object.hasOwn(this, key);
|
||||
},
|
||||
});
|
||||
internals.markSpecial(Object);
|
||||
});
|
||||
@@ -1,267 +0,0 @@
|
||||
define("values/string", () => {
|
||||
var String = env.global.String = function(this: String | undefined, arg: any) {
|
||||
var val;
|
||||
if (arguments.length === 0) val = '';
|
||||
else val = arg + '';
|
||||
if (this === undefined || this === null) return val;
|
||||
else (this as any).value = val;
|
||||
} as StringConstructor;
|
||||
|
||||
env.setProto('string', String.prototype);
|
||||
setConstr(String.prototype, String);
|
||||
|
||||
setProps(String.prototype, {
|
||||
toString() {
|
||||
if (typeof this === 'string') return this;
|
||||
else return (this as any).value;
|
||||
},
|
||||
valueOf() {
|
||||
if (typeof this === 'string') return this;
|
||||
else return (this as any).value;
|
||||
},
|
||||
|
||||
substring(start, end) {
|
||||
if (typeof this !== 'string') {
|
||||
if (this instanceof String) return (this as any).value.substring(start, end);
|
||||
else throw new Error('This function may be used only with primitive or object strings.');
|
||||
}
|
||||
start = start ?? 0 | 0;
|
||||
end = (end ?? this.length) | 0;
|
||||
|
||||
const res = [];
|
||||
|
||||
for (let i = start; i < end; i++) {
|
||||
if (i >= 0 && i < this.length) res[res.length] = this[i];
|
||||
}
|
||||
|
||||
return internals.stringFromStrings(res);
|
||||
},
|
||||
substr(start, length) {
|
||||
start = start ?? 0 | 0;
|
||||
|
||||
if (start >= this.length) start = this.length - 1;
|
||||
if (start < 0) start = 0;
|
||||
|
||||
length = (length ?? this.length - start) | 0;
|
||||
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() {
|
||||
// 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() {
|
||||
// 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) {
|
||||
if (typeof this !== 'string') {
|
||||
if (this instanceof String) return (this as any).value.charAt(pos);
|
||||
else throw new Error('This function may be used only with primitive or object strings.');
|
||||
}
|
||||
|
||||
pos = pos | 0;
|
||||
if (pos < 0 || pos >= this.length) return '';
|
||||
return this[pos];
|
||||
},
|
||||
charCodeAt(pos) {
|
||||
if (typeof this !== 'string') {
|
||||
if (this instanceof String) return (this as any).value.charAt(pos);
|
||||
else throw new Error('This function may be used only with primitive or object strings.');
|
||||
}
|
||||
|
||||
pos = pos | 0;
|
||||
if (pos < 0 || pos >= this.length) return 0 / 0;
|
||||
return internals.char(this[pos]);
|
||||
},
|
||||
|
||||
startsWith(term, pos) {
|
||||
if (typeof this !== 'string') {
|
||||
if (this instanceof String) return (this as any).value.startsWith(term, pos);
|
||||
else throw new Error('This function may be used only with primitive or object strings.');
|
||||
}
|
||||
pos = pos! | 0;
|
||||
term = term + "";
|
||||
|
||||
if (pos < 0 || this.length < term.length + pos) return false;
|
||||
|
||||
for (let i = 0; i < term.length; i++) {
|
||||
if (this[i + pos] !== term[i]) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
endsWith(term, pos) {
|
||||
if (typeof this !== 'string') {
|
||||
if (this instanceof String) return (this as any).value.endsWith(term, pos);
|
||||
else throw new Error('This function may be used only with primitive or object strings.');
|
||||
}
|
||||
pos = (pos ?? this.length) | 0;
|
||||
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) {
|
||||
if (typeof this !== 'string') {
|
||||
if (this instanceof String) return (this as any).value.indexOf(term, start);
|
||||
else throw new Error('This function may be used only with primitive or object strings.');
|
||||
}
|
||||
|
||||
if (typeof term[env.global.Symbol.search] !== 'function') term = RegExp.escape(term);
|
||||
|
||||
return term[env.global.Symbol.search](this, false, start);
|
||||
},
|
||||
lastIndexOf(term: any, start) {
|
||||
if (typeof this !== 'string') {
|
||||
if (this instanceof String) return (this as any).value.indexOf(term, start);
|
||||
else throw new Error('This function may be used only with primitive or object strings.');
|
||||
}
|
||||
|
||||
if (typeof term[env.global.Symbol.search] !== 'function') term = RegExp.escape(term);
|
||||
|
||||
return term[env.global.Symbol.search](this, true, start);
|
||||
},
|
||||
includes(term, start) {
|
||||
return this.indexOf(term, start) >= 0;
|
||||
},
|
||||
|
||||
replace(pattern: any, val) {
|
||||
if (typeof this !== 'string') {
|
||||
if (this instanceof String) return (this as any).value.replace(pattern, val);
|
||||
else throw new Error('This function may be used only with primitive or object strings.');
|
||||
}
|
||||
|
||||
if (typeof pattern[env.global.Symbol.replace] !== 'function') pattern = RegExp.escape(pattern);
|
||||
|
||||
return pattern[env.global.Symbol.replace](this, val);
|
||||
},
|
||||
replaceAll(pattern: any, val) {
|
||||
if (typeof this !== 'string') {
|
||||
if (this instanceof String) return (this as any).value.replace(pattern, val);
|
||||
else throw new Error('This function may be used only with primitive or object strings.');
|
||||
}
|
||||
|
||||
if (typeof pattern[env.global.Symbol.replace] !== 'function') pattern = RegExp.escape(pattern, "g");
|
||||
if (pattern instanceof RegExp && !pattern.global) pattern = new pattern.constructor(pattern.source, pattern.flags + "g");
|
||||
|
||||
return pattern[env.global.Symbol.replace](this, val);
|
||||
},
|
||||
|
||||
match(pattern: any) {
|
||||
if (typeof this !== 'string') {
|
||||
if (this instanceof String) return (this as any).value.match(pattern);
|
||||
else throw new Error('This function may be used only with primitive or object strings.');
|
||||
}
|
||||
|
||||
if (typeof pattern[env.global.Symbol.match] !== 'function') pattern = RegExp.escape(pattern);
|
||||
|
||||
return pattern[env.global.Symbol.match](this);
|
||||
},
|
||||
matchAll(pattern: any) {
|
||||
if (typeof this !== 'string') {
|
||||
if (this instanceof String) return (this as any).value.matchAll(pattern);
|
||||
else throw new Error('This function may be used only with primitive or object strings.');
|
||||
}
|
||||
|
||||
if (typeof pattern[env.global.Symbol.match] !== 'function') pattern = RegExp.escape(pattern, "g");
|
||||
if (pattern instanceof RegExp && !pattern.global) pattern = new pattern.constructor(pattern.source, pattern.flags + "g");
|
||||
|
||||
return pattern[env.global.Symbol.match](this);
|
||||
},
|
||||
|
||||
split(pattern: any, lim, sensible) {
|
||||
if (typeof this !== 'string') {
|
||||
if (this instanceof String) return (this as any).value.split(pattern, lim, sensible);
|
||||
else throw new Error('This function may be used only with primitive or object strings.');
|
||||
}
|
||||
|
||||
if (typeof pattern[env.global.Symbol.split] !== 'function') pattern = RegExp.escape(pattern, "g");
|
||||
|
||||
return pattern[env.global.Symbol.split](this, lim, sensible);
|
||||
},
|
||||
slice(start, end) {
|
||||
if (typeof this !== 'string') {
|
||||
if (this instanceof String) return (this as any).value.slice(start, end);
|
||||
else throw new Error('This function may be used only with primitive or object strings.');
|
||||
}
|
||||
|
||||
start = wrapI(this.length, start ?? 0 | 0);
|
||||
end = wrapI(this.length, end ?? this.length | 0);
|
||||
|
||||
if (start > end) return '';
|
||||
|
||||
return this.substring(start, end);
|
||||
},
|
||||
|
||||
concat(...args) {
|
||||
if (typeof this !== 'string') {
|
||||
if (this instanceof String) return (this as any).value.concat(...args);
|
||||
else throw new Error('This function may be used only with primitive or object strings.');
|
||||
}
|
||||
|
||||
var res = this;
|
||||
for (var arg of args) res += arg;
|
||||
return res;
|
||||
},
|
||||
|
||||
trim() {
|
||||
return this
|
||||
.replace(/^\s+/g, '')
|
||||
.replace(/\s+$/g, '');
|
||||
}
|
||||
});
|
||||
|
||||
setProps(String, {
|
||||
fromCharCode(val) {
|
||||
return internals.stringFromChars([val | 0]);
|
||||
},
|
||||
})
|
||||
|
||||
env.global.Object.defineProperty(String.prototype, 'length', {
|
||||
get() {
|
||||
if (typeof this !== 'string') {
|
||||
if (this instanceof String) return (this as any).value.length;
|
||||
else throw new Error('This function may be used only with primitive or object strings.');
|
||||
}
|
||||
|
||||
return internals.strlen(this);
|
||||
},
|
||||
configurable: true,
|
||||
enumerable: false,
|
||||
});
|
||||
});
|
||||
@@ -1,36 +0,0 @@
|
||||
define("values/symbol", () => {
|
||||
const symbols: Record<string, symbol> = { };
|
||||
|
||||
var Symbol = env.global.Symbol = function(this: any, val?: string) {
|
||||
if (this !== undefined && this !== null) throw new TypeError("Symbol may not be called with 'new'.");
|
||||
if (typeof val !== 'string' && val !== undefined) throw new TypeError('val must be a string or undefined.');
|
||||
return internals.symbol(val);
|
||||
} as SymbolConstructor;
|
||||
|
||||
env.setProto('symbol', Symbol.prototype);
|
||||
setConstr(Symbol.prototype, Symbol);
|
||||
|
||||
setProps(Symbol, {
|
||||
for(key) {
|
||||
if (typeof key !== 'string' && key !== undefined) throw new TypeError('key must be a string or undefined.');
|
||||
if (key in symbols) return symbols[key];
|
||||
else return symbols[key] = internals.symbol(key);
|
||||
},
|
||||
keyFor(sym) {
|
||||
if (typeof sym !== 'symbol') throw new TypeError('sym must be a symbol.');
|
||||
return internals.symbolToString(sym);
|
||||
},
|
||||
|
||||
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,
|
||||
});
|
||||
|
||||
internals.defineField(env.global.Object.prototype, Symbol.typeName, 'Object', false, false, false);
|
||||
internals.defineField(env.global, Symbol.typeName, 'Window', false, false, false);
|
||||
});
|
||||
6
package-lock.json
generated
6
package-lock.json
generated
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"name": "java-jscript",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -7,21 +7,20 @@ import java.io.InputStreamReader;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import me.topchetoeu.jscript.engine.MessageContext;
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.Message;
|
||||
import me.topchetoeu.jscript.engine.Engine;
|
||||
import me.topchetoeu.jscript.engine.FunctionContext;
|
||||
import me.topchetoeu.jscript.engine.Environment;
|
||||
import me.topchetoeu.jscript.engine.values.NativeFunction;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.events.Observer;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.exceptions.SyntaxException;
|
||||
import me.topchetoeu.jscript.interop.NativeTypeRegister;
|
||||
import me.topchetoeu.jscript.polyfills.Internals;
|
||||
|
||||
public class Main {
|
||||
static Thread task;
|
||||
static Engine engine;
|
||||
static FunctionContext env;
|
||||
static Environment env;
|
||||
|
||||
public static String streamToString(InputStream in) {
|
||||
try {
|
||||
@@ -47,37 +46,14 @@ public class Main {
|
||||
|
||||
private static Observer<Object> valuePrinter = new Observer<Object>() {
|
||||
public void next(Object data) {
|
||||
try {
|
||||
Values.printValue(null, data);
|
||||
}
|
||||
try { Values.printValue(null, data); }
|
||||
catch (InterruptedException e) { }
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
public void error(RuntimeException err) {
|
||||
try {
|
||||
try {
|
||||
if (err instanceof EngineException) {
|
||||
System.out.println("Uncaught " + ((EngineException)err).toString(new Context(null, new MessageContext(engine))));
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
catch (EngineException ex) {
|
||||
System.out.println("Uncaught ");
|
||||
Values.printValue(null, ((EngineException)err).value);
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
catch (InterruptedException ex) {
|
||||
return;
|
||||
}
|
||||
try { Values.printError(err, null); }
|
||||
catch (InterruptedException ex) { return; }
|
||||
}
|
||||
};
|
||||
|
||||
@@ -85,26 +61,30 @@ public class Main {
|
||||
System.out.println(String.format("Running %s v%s by %s", Metadata.NAME, Metadata.VERSION, Metadata.AUTHOR));
|
||||
var in = new BufferedReader(new InputStreamReader(System.in));
|
||||
engine = new Engine();
|
||||
env = new FunctionContext(null, null, null);
|
||||
var builderEnv = new FunctionContext(null, new NativeTypeRegister(), null);
|
||||
|
||||
env = new Environment(null, null, null);
|
||||
var exited = new boolean[1];
|
||||
|
||||
env.global.define("exit", ctx -> {
|
||||
exited[0] = true;
|
||||
task.interrupt();
|
||||
throw new InterruptedException();
|
||||
});
|
||||
env.global.define("go", ctx -> {
|
||||
try {
|
||||
var func = ctx.compile("do.js", new String(Files.readAllBytes(Path.of("do.js"))));
|
||||
return func.call(ctx);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new EngineException("Couldn't open do.js");
|
||||
}
|
||||
});
|
||||
engine.pushMsg(false, new Message(engine), new NativeFunction((ctx, thisArg, _a) -> {
|
||||
new Internals().apply(env);
|
||||
|
||||
engine.pushMsg(false, new Context(builderEnv, new MessageContext(engine)), "core.js", resourceToString("js/core.js"), null, env, new Internals()).toObservable().on(valuePrinter);
|
||||
env.global.define("exit", _ctx -> {
|
||||
exited[0] = true;
|
||||
task.interrupt();
|
||||
throw new InterruptedException();
|
||||
});
|
||||
env.global.define("go", _ctx -> {
|
||||
try {
|
||||
var func = _ctx.compile("do.js", new String(Files.readAllBytes(Path.of("do.js"))));
|
||||
return func.call(_ctx);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new EngineException("Couldn't open do.js");
|
||||
}
|
||||
});
|
||||
|
||||
return null;
|
||||
}), null);
|
||||
|
||||
task = engine.start();
|
||||
var reader = new Thread(() -> {
|
||||
@@ -114,7 +94,7 @@ public class Main {
|
||||
var raw = in.readLine();
|
||||
|
||||
if (raw == null) break;
|
||||
engine.pushMsg(false, new Context(env, new MessageContext(engine)), "<stdio>", raw, null).toObservable().once(valuePrinter);
|
||||
engine.pushMsg(false, env.context(new Message(engine)), "<stdio>", raw, null).toObservable().once(valuePrinter);
|
||||
}
|
||||
catch (EngineException e) {
|
||||
try {
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public abstract class AssignStatement extends Statement {
|
||||
public abstract void compile(List<Instruction> target, ScopeRecord scope, boolean retPrevValue);
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
compile(target, scope, false);
|
||||
}
|
||||
|
||||
protected AssignStatement(Location loc) {
|
||||
super(loc);
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.engine.Operation;
|
||||
|
||||
public abstract class AssignableStatement extends Statement {
|
||||
public abstract AssignStatement toAssign(Statement val, Operation operation);
|
||||
public abstract Statement toAssign(Statement val, Operation operation);
|
||||
|
||||
protected AssignableStatement(Location loc) {
|
||||
super(loc);
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
public class CompileOptions {
|
||||
public final boolean emitBpMap;
|
||||
public final boolean emitVarNames;
|
||||
|
||||
public CompileOptions(boolean emitBpMap, boolean emitVarNames) {
|
||||
this.emitBpMap = emitBpMap;
|
||||
this.emitVarNames = emitVarNames;
|
||||
}
|
||||
}
|
||||
27
src/me/topchetoeu/jscript/compilation/CompileTarget.java
Normal file
27
src/me/topchetoeu/jscript/compilation/CompileTarget.java
Normal file
@@ -0,0 +1,27 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Vector;
|
||||
|
||||
public class CompileTarget {
|
||||
public final Vector<Instruction> target = new Vector<>();
|
||||
public final Map<Long, Instruction[]> functions;
|
||||
|
||||
public Instruction add(Instruction instr) {
|
||||
target.add(instr);
|
||||
return instr;
|
||||
}
|
||||
public Instruction set(int i, Instruction instr) {
|
||||
return target.set(i, instr);
|
||||
}
|
||||
public Instruction get(int i) {
|
||||
return target.get(i);
|
||||
}
|
||||
public int size() { return target.size(); }
|
||||
|
||||
public Instruction[] array() { return target.toArray(Instruction[]::new); }
|
||||
|
||||
public CompileTarget(Map<Long, Instruction[]> functions) {
|
||||
this.functions = functions;
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Vector;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.control.ContinueStatement;
|
||||
@@ -13,16 +12,6 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
public class CompoundStatement extends Statement {
|
||||
public final Statement[] statements;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() {
|
||||
for (var stm : statements) {
|
||||
if (stm instanceof FunctionStatement) continue;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord varsScope) {
|
||||
for (var stm : statements) {
|
||||
@@ -31,7 +20,7 @@ public class CompoundStatement extends Statement {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
for (var stm : statements) {
|
||||
if (stm instanceof FunctionStatement) {
|
||||
int start = target.size();
|
||||
@@ -45,14 +34,14 @@ public class CompoundStatement extends Statement {
|
||||
var stm = statements[i];
|
||||
|
||||
if (stm instanceof FunctionStatement) continue;
|
||||
if (i != statements.length - 1) stm.compileNoPollution(target, scope, true);
|
||||
else stm.compileWithPollution(target, scope);
|
||||
if (i != statements.length - 1) stm.compileWithDebug(target, scope, false);
|
||||
else stm.compileWithDebug(target, scope, pollute);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement optimize() {
|
||||
var res = new ArrayList<Statement>();
|
||||
var res = new Vector<Statement>(statements.length);
|
||||
|
||||
for (var i = 0; i < statements.length; i++) {
|
||||
var stm = statements[i].optimize();
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.values.ConstantStatement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
@@ -10,13 +8,9 @@ public class DiscardStatement extends Statement {
|
||||
public final Statement value;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
if (value == null) return;
|
||||
value.compile(target, scope);
|
||||
if (value.pollutesStack()) target.add(Instruction.discard());
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
value.compile(target, scope, false);
|
||||
|
||||
}
|
||||
@Override
|
||||
public Statement optimize() {
|
||||
|
||||
@@ -7,7 +7,6 @@ import me.topchetoeu.jscript.exceptions.SyntaxException;
|
||||
public class Instruction {
|
||||
public static enum Type {
|
||||
RETURN,
|
||||
SIGNAL,
|
||||
THROW,
|
||||
THROW_SYNTAX,
|
||||
DELETE,
|
||||
@@ -162,12 +161,6 @@ public class Instruction {
|
||||
return new Instruction(null, Type.NOP, args);
|
||||
}
|
||||
|
||||
/**
|
||||
* ATTENTION: Usage outside of try/catch is broken af
|
||||
*/
|
||||
public static Instruction signal(String name) {
|
||||
return new Instruction(null, Type.SIGNAL, name);
|
||||
}
|
||||
public static Instruction nop(Object ...params) {
|
||||
for (var param : params) {
|
||||
if (param instanceof String) continue;
|
||||
@@ -221,9 +214,9 @@ public class Instruction {
|
||||
public static Instruction loadRegex(String pattern, String flags) {
|
||||
return new Instruction(null, Type.LOAD_REGEX, pattern, flags);
|
||||
}
|
||||
public static Instruction loadFunc(int instrN, int varN, int len, int[] captures) {
|
||||
public static Instruction loadFunc(long id, int varN, int len, int[] captures) {
|
||||
var args = new Object[3 + captures.length];
|
||||
args[0] = instrN;
|
||||
args[0] = id;
|
||||
args[1] = varN;
|
||||
args[2] = len;
|
||||
for (var i = 0; i < captures.length; i++) args[i + 3] = captures[i];
|
||||
|
||||
@@ -1,36 +1,20 @@
|
||||
package me.topchetoeu.jscript.compilation;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public abstract class Statement {
|
||||
private Location _loc;
|
||||
|
||||
public abstract boolean pollutesStack();
|
||||
public boolean pure() { return false; }
|
||||
public abstract void compile(List<Instruction> target, ScopeRecord scope);
|
||||
public abstract void compile(CompileTarget target, ScopeRecord scope, boolean pollute);
|
||||
public void declare(ScopeRecord varsScope) { }
|
||||
public Statement optimize() { return this; }
|
||||
|
||||
public void compileNoPollution(List<Instruction> target, ScopeRecord scope, boolean debug) {
|
||||
public void compileWithDebug(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
int start = target.size();
|
||||
compile(target, scope);
|
||||
if (debug && target.size() != start) target.get(start).setDebug(true);
|
||||
if (pollutesStack()) target.add(Instruction.discard().locate(loc()));
|
||||
}
|
||||
public void compileWithPollution(List<Instruction> target, ScopeRecord scope, boolean debug) {
|
||||
int start = target.size();
|
||||
compile(target, scope);
|
||||
if (debug && target.size() != start) target.get(start).setDebug(true);
|
||||
if (!pollutesStack()) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
}
|
||||
public void compileNoPollution(List<Instruction> target, ScopeRecord scope) {
|
||||
compileNoPollution(target, scope, false);
|
||||
}
|
||||
public void compileWithPollution(List<Instruction> target, ScopeRecord scope) {
|
||||
compileWithPollution(target, scope, false);
|
||||
compile(target, scope, pollute);
|
||||
if (target.size() != start) target.get(start).setDebug(true);
|
||||
}
|
||||
|
||||
public Location loc() { return _loc; }
|
||||
|
||||
@@ -19,8 +19,6 @@ public class VariableDeclareStatement extends Statement {
|
||||
|
||||
public final List<Pair> values;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
@Override
|
||||
public void declare(ScopeRecord varsScope) {
|
||||
for (var key : values) {
|
||||
@@ -28,7 +26,7 @@ public class VariableDeclareStatement extends Statement {
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
for (var entry : values) {
|
||||
if (entry.name == null) continue;
|
||||
var key = scope.getKey(entry.name);
|
||||
@@ -39,10 +37,12 @@ public class VariableDeclareStatement extends Statement {
|
||||
target.add(Instruction.storeVar(key).locate(loc()));
|
||||
}
|
||||
else if (entry.value != null) {
|
||||
entry.value.compileWithPollution(target, scope);
|
||||
entry.value.compile(target, scope, true);
|
||||
target.add(Instruction.storeVar(key).locate(loc()));
|
||||
}
|
||||
}
|
||||
|
||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
}
|
||||
|
||||
public VariableDeclareStatement(Location loc, List<Pair> values) {
|
||||
|
||||
@@ -1,37 +1,35 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public class ArrayStatement extends Statement {
|
||||
public final Statement[] statements;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
@Override
|
||||
public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
target.add(Instruction.loadArr(statements.length).locate(loc()));
|
||||
var i = 0;
|
||||
for (var el : statements) {
|
||||
if (el != null) {
|
||||
target.add(Instruction.dup().locate(loc()));
|
||||
target.add(Instruction.loadValue(i).locate(loc()));
|
||||
el.compileWithPollution(target, scope);
|
||||
target.add(Instruction.storeMember().locate(loc()));
|
||||
}
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
public ArrayStatement(Location loc, Statement[] statements) {
|
||||
super(loc);
|
||||
this.statements = statements;
|
||||
}
|
||||
}
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public class ArrayStatement extends Statement {
|
||||
public final Statement[] statements;
|
||||
|
||||
@Override
|
||||
public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
target.add(Instruction.loadArr(statements.length).locate(loc()));
|
||||
var i = 0;
|
||||
for (var el : statements) {
|
||||
if (el != null) {
|
||||
target.add(Instruction.dup().locate(loc()));
|
||||
target.add(Instruction.loadValue(i).locate(loc()));
|
||||
el.compile(target, scope, true);
|
||||
target.add(Instruction.storeMember().locate(loc()));
|
||||
}
|
||||
i++;
|
||||
}
|
||||
if (!pollute) target.add(Instruction.discard().locate(loc()));
|
||||
}
|
||||
|
||||
public ArrayStatement(Location loc, Statement[] statements) {
|
||||
super(loc);
|
||||
this.statements = statements;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
@@ -11,11 +10,9 @@ public class BreakStatement extends Statement {
|
||||
public final String label;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
target.add(Instruction.nop("break", label).locate(loc()));
|
||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
}
|
||||
|
||||
public BreakStatement(Location loc, String label) {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
@@ -11,11 +10,9 @@ public class ContinueStatement extends Statement {
|
||||
public final String label;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
target.add(Instruction.nop("cont", label).locate(loc()));
|
||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
}
|
||||
|
||||
public ContinueStatement(Location loc, String label) {
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public class DebugStatement extends Statement {
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
target.add(Instruction.debug().locate(loc()));
|
||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
}
|
||||
|
||||
public DebugStatement(Location loc) {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
@@ -12,13 +11,12 @@ public class DeleteStatement extends Statement {
|
||||
public final Statement value;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
value.compile(target, scope, true);
|
||||
key.compile(target, scope, true);
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
value.compile(target, scope);
|
||||
key.compile(target, scope);
|
||||
target.add(Instruction.delete().locate(loc()));
|
||||
if (!pollute) target.add(Instruction.discard().locate(loc()));
|
||||
}
|
||||
|
||||
public DeleteStatement(Location loc, Statement key, Statement value) {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.CompoundStatement;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
@@ -14,19 +13,16 @@ public class DoWhileStatement extends Statement {
|
||||
public final Statement condition, body;
|
||||
public final String label;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord globScope) {
|
||||
body.declare(globScope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (condition instanceof ConstantStatement) {
|
||||
int start = target.size();
|
||||
body.compileNoPollution(target, scope);
|
||||
body.compile(target, scope, false);
|
||||
int end = target.size();
|
||||
if (Values.toBoolean(((ConstantStatement)condition).value)) {
|
||||
WhileStatement.replaceBreaks(target, label, start, end, end + 1, end + 1);
|
||||
@@ -35,13 +31,14 @@ public class DoWhileStatement extends Statement {
|
||||
target.add(Instruction.jmp(start - end).locate(loc()));
|
||||
WhileStatement.replaceBreaks(target, label, start, end, start, end + 1);
|
||||
}
|
||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
return;
|
||||
}
|
||||
|
||||
int start = target.size();
|
||||
body.compileNoPollution(target, scope, true);
|
||||
body.compileWithDebug(target, scope, false);
|
||||
int mid = target.size();
|
||||
condition.compileWithPollution(target, scope);
|
||||
condition.compile(target, scope, true);
|
||||
int end = target.size();
|
||||
|
||||
WhileStatement.replaceBreaks(target, label, start, mid - 1, mid, end + 1);
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.Operation;
|
||||
@@ -14,9 +13,6 @@ public class ForInStatement extends Statement {
|
||||
public final Statement varValue, object, body;
|
||||
public final String label;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord globScope) {
|
||||
body.declare(globScope);
|
||||
@@ -24,16 +20,16 @@ public class ForInStatement extends Statement {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
var key = scope.getKey(varName);
|
||||
if (key instanceof String) target.add(Instruction.makeVar((String)key));
|
||||
|
||||
if (varValue != null) {
|
||||
varValue.compileWithPollution(target, scope);
|
||||
varValue.compile(target, scope, true);
|
||||
target.add(Instruction.storeVar(scope.getKey(varName)));
|
||||
}
|
||||
|
||||
object.compileWithPollution(target, scope);
|
||||
object.compile(target, scope, true);
|
||||
target.add(Instruction.keys());
|
||||
|
||||
int start = target.size();
|
||||
@@ -58,8 +54,7 @@ public class ForInStatement extends Statement {
|
||||
|
||||
for (var i = start; i < target.size(); i++) target.get(i).locate(loc());
|
||||
|
||||
body.compileNoPollution(target, scope, true);
|
||||
|
||||
body.compileWithDebug(target, scope, false);
|
||||
|
||||
int end = target.size();
|
||||
|
||||
@@ -68,6 +63,7 @@ public class ForInStatement extends Statement {
|
||||
target.add(Instruction.jmp(start - end).locate(loc()));
|
||||
target.add(Instruction.discard().locate(loc()));
|
||||
target.set(mid, Instruction.jmpIf(end - mid + 1).locate(loc()));
|
||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
}
|
||||
|
||||
public ForInStatement(Location loc, String label, boolean isDecl, String varName, Statement varValue, Statement object, Statement body) {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.CompoundStatement;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.values.ConstantStatement;
|
||||
@@ -14,44 +13,43 @@ public class ForStatement extends Statement {
|
||||
public final Statement declaration, assignment, condition, body;
|
||||
public final String label;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord globScope) {
|
||||
declaration.declare(globScope);
|
||||
body.declare(globScope);
|
||||
}
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
declaration.compile(target, scope);
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
declaration.compile(target, scope, false);
|
||||
|
||||
if (condition instanceof ConstantStatement) {
|
||||
if (Values.toBoolean(((ConstantStatement)condition).value)) {
|
||||
int start = target.size();
|
||||
body.compileNoPollution(target, scope);
|
||||
body.compile(target, scope, false);
|
||||
int mid = target.size();
|
||||
assignment.compileNoPollution(target, scope, true);
|
||||
assignment.compileWithDebug(target, scope, false);
|
||||
int end = target.size();
|
||||
WhileStatement.replaceBreaks(target, label, start, mid, mid, end + 1);
|
||||
target.add(Instruction.jmp(start - target.size()).locate(loc()));
|
||||
return;
|
||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
int start = target.size();
|
||||
condition.compileWithPollution(target, scope);
|
||||
condition.compile(target, scope, true);
|
||||
int mid = target.size();
|
||||
target.add(Instruction.nop());
|
||||
body.compileNoPollution(target, scope);
|
||||
body.compile(target, scope, false);
|
||||
int beforeAssign = target.size();
|
||||
assignment.compile(target, scope);
|
||||
assignment.compileWithDebug(target, scope, false);
|
||||
int end = target.size();
|
||||
|
||||
WhileStatement.replaceBreaks(target, label, mid + 1, end, beforeAssign, end + 1);
|
||||
|
||||
target.add(Instruction.jmp(start - end).locate(loc()));
|
||||
target.set(mid, Instruction.jmpIfNot(end - mid + 1).locate(loc()));
|
||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
}
|
||||
@Override
|
||||
public Statement optimize() {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.CompoundStatement;
|
||||
import me.topchetoeu.jscript.compilation.DiscardStatement;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
@@ -14,9 +13,6 @@ import me.topchetoeu.jscript.engine.values.Values;
|
||||
public class IfStatement extends Statement {
|
||||
public final Statement condition, body, elseBody;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord globScope) {
|
||||
body.declare(globScope);
|
||||
@@ -24,33 +20,34 @@ public class IfStatement extends Statement {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (condition instanceof ConstantStatement) {
|
||||
if (Values.not(((ConstantStatement)condition).value)) {
|
||||
if (elseBody != null) elseBody.compileNoPollution(target, scope, true);
|
||||
if (elseBody != null) elseBody.compileWithDebug(target, scope, pollute);
|
||||
}
|
||||
else {
|
||||
body.compileNoPollution(target, scope, true);
|
||||
body.compileWithDebug(target, scope, pollute);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
condition.compileWithPollution(target, scope);
|
||||
condition.compile(target, scope, true);
|
||||
|
||||
if (elseBody == null) {
|
||||
int i = target.size();
|
||||
target.add(Instruction.nop());
|
||||
body.compileNoPollution(target, scope, true);
|
||||
body.compileWithDebug(target, scope, pollute);
|
||||
int endI = target.size();
|
||||
target.set(i, Instruction.jmpIfNot(endI - i).locate(loc()));
|
||||
}
|
||||
else {
|
||||
int start = target.size();
|
||||
target.add(Instruction.nop());
|
||||
body.compileNoPollution(target, scope, true);
|
||||
body.compileWithDebug(target, scope, pollute);
|
||||
target.add(Instruction.nop());
|
||||
int mid = target.size();
|
||||
elseBody.compileNoPollution(target, scope, true);
|
||||
elseBody.compileWithDebug(target, scope, pollute);
|
||||
int end = target.size();
|
||||
|
||||
target.set(start, Instruction.jmpIfNot(mid - start).locate(loc()));
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
@@ -11,12 +10,9 @@ public class ReturnStatement extends Statement {
|
||||
public final Statement value;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (value == null) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
else value.compileWithPollution(target, scope);
|
||||
else value.compile(target, scope, true);
|
||||
target.add(Instruction.ret().locate(loc()));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.compilation.Instruction.Type;
|
||||
@@ -21,9 +21,6 @@ public class SwitchStatement extends Statement {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
|
||||
public final Statement value;
|
||||
public final SwitchCase[] cases;
|
||||
public final Statement[] body;
|
||||
@@ -35,15 +32,15 @@ public class SwitchStatement extends Statement {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
var caseMap = new HashMap<Integer, Integer>();
|
||||
var stmIndexMap = new HashMap<Integer, Integer>();
|
||||
|
||||
value.compile(target, scope);
|
||||
value.compile(target, scope, true);
|
||||
|
||||
for (var ccase : cases) {
|
||||
target.add(Instruction.dup().locate(loc()));
|
||||
ccase.value.compileWithPollution(target, scope);
|
||||
ccase.value.compile(target, scope, true);
|
||||
target.add(Instruction.operation(Operation.EQUALS).locate(loc()));
|
||||
caseMap.put(target.size(), ccase.statementI);
|
||||
target.add(Instruction.nop());
|
||||
@@ -55,7 +52,7 @@ public class SwitchStatement extends Statement {
|
||||
|
||||
for (var stm : body) {
|
||||
stmIndexMap.put(stmIndexMap.size(), target.size());
|
||||
stm.compileNoPollution(target, scope, true);
|
||||
stm.compileWithDebug(target, scope, false);
|
||||
}
|
||||
|
||||
if (defaultI < 0 || defaultI >= body.length) target.set(start, Instruction.jmp(target.size() - start).locate(loc()));
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
@@ -11,11 +10,8 @@ public class ThrowStatement extends Statement {
|
||||
public final Statement value;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
value.compileWithPollution(target, scope);
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
value.compile(target, scope, true);
|
||||
target.add(Instruction.throwInstr().locate(loc()));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.GlobalScope;
|
||||
@@ -15,9 +14,6 @@ public class TryStatement extends Statement {
|
||||
public final Statement finallyBody;
|
||||
public final String name;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord globScope) {
|
||||
tryBody.declare(globScope);
|
||||
@@ -26,30 +22,31 @@ public class TryStatement extends Statement {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
target.add(Instruction.nop());
|
||||
|
||||
int start = target.size(), tryN, catchN = -1, finN = -1;
|
||||
|
||||
tryBody.compileNoPollution(target, scope);
|
||||
tryBody.compile(target, scope, false);
|
||||
tryN = target.size() - start;
|
||||
|
||||
if (catchBody != null) {
|
||||
int tmp = target.size();
|
||||
var local = scope instanceof GlobalScope ? scope.child() : (LocalScopeRecord)scope;
|
||||
local.define(name, true);
|
||||
catchBody.compileNoPollution(target, scope);
|
||||
catchBody.compile(target, scope, false);
|
||||
local.undefine();
|
||||
catchN = target.size() - tmp;
|
||||
}
|
||||
|
||||
if (finallyBody != null) {
|
||||
int tmp = target.size();
|
||||
finallyBody.compileNoPollution(target, scope);
|
||||
finallyBody.compile(target, scope, false);
|
||||
finN = target.size() - tmp;
|
||||
}
|
||||
|
||||
target.set(start - 1, Instruction.tryInstr(tryN, catchN, finN).locate(loc()));
|
||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
}
|
||||
|
||||
public TryStatement(Location loc, Statement tryBody, Statement catchBody, Statement finallyBody, String name) {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package me.topchetoeu.jscript.compilation.control;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.CompoundStatement;
|
||||
import me.topchetoeu.jscript.compilation.DiscardStatement;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
@@ -16,19 +15,16 @@ public class WhileStatement extends Statement {
|
||||
public final Statement condition, body;
|
||||
public final String label;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return false; }
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord globScope) {
|
||||
body.declare(globScope);
|
||||
}
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (condition instanceof ConstantStatement) {
|
||||
if (Values.toBoolean(((ConstantStatement)condition).value)) {
|
||||
int start = target.size();
|
||||
body.compileNoPollution(target, scope);
|
||||
body.compile(target, scope, false);
|
||||
int end = target.size();
|
||||
replaceBreaks(target, label, start, end, start, end + 1);
|
||||
target.add(Instruction.jmp(start - target.size()).locate(loc()));
|
||||
@@ -37,10 +33,10 @@ public class WhileStatement extends Statement {
|
||||
}
|
||||
|
||||
int start = target.size();
|
||||
condition.compileWithPollution(target, scope);
|
||||
condition.compile(target, scope, true);
|
||||
int mid = target.size();
|
||||
target.add(Instruction.nop());
|
||||
body.compileNoPollution(target, scope);
|
||||
body.compile(target, scope, false);
|
||||
|
||||
int end = target.size();
|
||||
|
||||
@@ -48,6 +44,7 @@ public class WhileStatement extends Statement {
|
||||
|
||||
target.add(Instruction.jmp(start - end).locate(loc()));
|
||||
target.set(mid, Instruction.jmpIfNot(end - mid + 1).locate(loc()));
|
||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
}
|
||||
@Override
|
||||
public Statement optimize() {
|
||||
@@ -70,7 +67,7 @@ public class WhileStatement extends Statement {
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public static void replaceBreaks(List<Instruction> target, String label, int start, int end, int continuePoint, int breakPoint) {
|
||||
public static void replaceBreaks(CompileTarget target, String label, int start, int end, int continuePoint, int breakPoint) {
|
||||
for (int i = start; i < end; i++) {
|
||||
var instr = target.get(i);
|
||||
if (instr.type == Type.NOP && instr.is(0, "cont") && (instr.get(1) == null || instr.is(1, label))) {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
@@ -12,23 +11,19 @@ public class CallStatement extends Statement {
|
||||
public final Statement[] args;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (func instanceof IndexStatement) {
|
||||
((IndexStatement)func).compile(target, scope, true);
|
||||
((IndexStatement)func).compile(target, scope, true, true);
|
||||
}
|
||||
else {
|
||||
target.add(Instruction.loadValue(null).locate(loc()));
|
||||
func.compileWithPollution(target, scope);
|
||||
target.add(Instruction.loadValue(null).locate(loc()));
|
||||
func.compile(target, scope, true);
|
||||
}
|
||||
|
||||
for (var arg : args) {
|
||||
arg.compileWithPollution(target, scope);
|
||||
}
|
||||
for (var arg : args) arg.compile(target, scope, true);
|
||||
|
||||
target.add(Instruction.call(args.length).locate(loc()).setDebug(true));
|
||||
if (!pollute) target.add(Instruction.discard().locate(loc()));
|
||||
}
|
||||
|
||||
public CallStatement(Location loc, Statement func, Statement ...args) {
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.AssignableStatement;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.Operation;
|
||||
@@ -15,11 +14,9 @@ public class ChangeStatement extends Statement {
|
||||
public final boolean postfix;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
value.toAssign(new ConstantStatement(loc(), -addAmount), Operation.SUBTRACT).compile(target, scope, postfix);
|
||||
if (!pollute) target.add(Instruction.discard().locate(loc()));
|
||||
}
|
||||
|
||||
public ChangeStatement(Location loc, AssignableStatement value, double addAmount, boolean postfix) {
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public class CommaStatement extends Statement {
|
||||
public final Statement first;
|
||||
public final Statement second;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
@Override
|
||||
public boolean pure() { return first.pure() && second.pure(); }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
first.compileNoPollution(target, scope);
|
||||
second.compileWithPollution(target, scope);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement optimize() {
|
||||
var f = first.optimize();
|
||||
var s = second.optimize();
|
||||
if (f.pure()) return s;
|
||||
else return new CommaStatement(loc(), f, s);
|
||||
}
|
||||
|
||||
public CommaStatement(Location loc, Statement first, Statement second) {
|
||||
super(loc);
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,7 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
@@ -10,14 +9,12 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
public class ConstantStatement extends Statement {
|
||||
public final Object value;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
@Override
|
||||
public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
target.add(Instruction.loadValue(value).locate(loc()));
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (pollute) target.add(Instruction.loadValue(value).locate(loc()));
|
||||
}
|
||||
|
||||
public ConstantStatement(Location loc, Object val) {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.CompoundStatement;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
@@ -15,17 +16,17 @@ public class FunctionStatement extends Statement {
|
||||
public final String name;
|
||||
public final String[] args;
|
||||
|
||||
private static Random rand = new Random();
|
||||
|
||||
@Override
|
||||
public boolean pure() { return name == null; }
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord scope) {
|
||||
if (name != null) scope.define(name);
|
||||
}
|
||||
|
||||
public static void checkBreakAndCont(List<Instruction> target, int start) {
|
||||
public static void checkBreakAndCont(CompileTarget target, int start) {
|
||||
for (int i = start; i < target.size(); i++) {
|
||||
if (target.get(i).type == Type.NOP) {
|
||||
if (target.get(i).is(0, "break") ) {
|
||||
@@ -38,7 +39,7 @@ public class FunctionStatement extends Statement {
|
||||
}
|
||||
}
|
||||
|
||||
public void compile(List<Instruction> target, ScopeRecord scope, String name, boolean isStatement) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, String name, boolean isStatement) {
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
for (var j = 0; j < i; j++) {
|
||||
if (args[i].equals(args[j])){
|
||||
@@ -50,32 +51,33 @@ public class FunctionStatement extends Statement {
|
||||
var subscope = scope.child();
|
||||
|
||||
int start = target.size();
|
||||
var funcTarget = new CompileTarget(target.functions);
|
||||
|
||||
target.add(Instruction.nop());
|
||||
subscope.define("this");
|
||||
var argsVar = subscope.define("arguments");
|
||||
|
||||
if (args.length > 0) {
|
||||
for (var i = 0; i < args.length; i++) {
|
||||
target.add(Instruction.loadVar(argsVar).locate(loc()));
|
||||
target.add(Instruction.loadMember(i).locate(loc()));
|
||||
target.add(Instruction.storeVar(subscope.define(args[i])).locate(loc()));
|
||||
funcTarget.add(Instruction.loadVar(argsVar).locate(loc()));
|
||||
funcTarget.add(Instruction.loadMember(i).locate(loc()));
|
||||
funcTarget.add(Instruction.storeVar(subscope.define(args[i])).locate(loc()));
|
||||
}
|
||||
}
|
||||
|
||||
if (!isStatement && this.name != null) {
|
||||
target.add(Instruction.storeSelfFunc((int)subscope.define(this.name)));
|
||||
funcTarget.add(Instruction.storeSelfFunc((int)subscope.define(this.name)));
|
||||
}
|
||||
|
||||
body.declare(subscope);
|
||||
target.add(Instruction.debugVarNames(subscope.locals()));
|
||||
body.compile(target, subscope);
|
||||
funcTarget.add(Instruction.debugVarNames(subscope.locals()));
|
||||
body.compile(funcTarget, subscope, false);
|
||||
funcTarget.add(Instruction.ret().locate(loc()));
|
||||
checkBreakAndCont(funcTarget, start);
|
||||
|
||||
checkBreakAndCont(target, start);
|
||||
var id = rand.nextLong();
|
||||
|
||||
if (!(body instanceof CompoundStatement)) target.add(Instruction.ret().locate(loc()));
|
||||
|
||||
target.set(start, Instruction.loadFunc(target.size() - start, subscope.localsCount(), args.length, subscope.getCaptures()).locate(loc()));
|
||||
target.add(Instruction.loadFunc(id, subscope.localsCount(), args.length, subscope.getCaptures()).locate(loc()));
|
||||
target.functions.put(id, funcTarget.array());
|
||||
|
||||
if (name == null) name = this.name;
|
||||
|
||||
@@ -90,12 +92,14 @@ public class FunctionStatement extends Statement {
|
||||
var key = scope.getKey(this.name);
|
||||
|
||||
if (key instanceof String) target.add(Instruction.makeVar((String)key).locate(loc()));
|
||||
target.add(Instruction.storeVar(scope.getKey(this.name), true).locate(loc()));
|
||||
target.add(Instruction.storeVar(scope.getKey(this.name), false).locate(loc()));
|
||||
}
|
||||
|
||||
}
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
compile(target, scope, null, false);
|
||||
if (!pollute) target.add(Instruction.discard().locate(loc()));
|
||||
}
|
||||
|
||||
public FunctionStatement(Location loc, String name, String[] args, CompoundStatement body) {
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public class GlobalThisStatement extends Statement {
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
@Override
|
||||
public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
target.add(Instruction.loadGlob().locate(loc()));
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (pollute) target.add(Instruction.loadGlob().locate(loc()));
|
||||
}
|
||||
|
||||
public GlobalThisStatement(Location loc) {
|
||||
|
||||
@@ -1,51 +1,38 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.AssignStatement;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.Operation;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public class IndexAssignStatement extends AssignStatement {
|
||||
public class IndexAssignStatement extends Statement {
|
||||
public final Statement object;
|
||||
public final Statement index;
|
||||
public final Statement value;
|
||||
public final Operation operation;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope, boolean retPrevValue) {
|
||||
int start = 0;
|
||||
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (operation != null) {
|
||||
object.compileWithPollution(target, scope);
|
||||
index.compileWithPollution(target, scope);
|
||||
object.compile(target, scope, true);
|
||||
index.compile(target, scope, true);
|
||||
target.add(Instruction.dup(2, 0).locate(loc()));
|
||||
|
||||
target.add(Instruction.loadMember().locate(loc()));
|
||||
if (retPrevValue) {
|
||||
target.add(Instruction.dup().locate(loc()));
|
||||
target.add(Instruction.move(3, 1).locate(loc()));
|
||||
}
|
||||
value.compileWithPollution(target, scope);
|
||||
value.compile(target, scope, true);
|
||||
target.add(Instruction.operation(operation).locate(loc()));
|
||||
|
||||
target.add(Instruction.storeMember(!retPrevValue).locate(loc()).setDebug(true));
|
||||
target.add(Instruction.storeMember(pollute).locate(loc()).setDebug(true));
|
||||
}
|
||||
else {
|
||||
object.compileWithPollution(target, scope);
|
||||
if (retPrevValue) target.add(Instruction.dup().locate(loc()));
|
||||
index.compileWithPollution(target, scope);
|
||||
value.compileWithPollution(target, scope);
|
||||
object.compile(target, scope, true);
|
||||
index.compile(target, scope, true);
|
||||
value.compile(target, scope, true);
|
||||
|
||||
target.add(Instruction.storeMember(!retPrevValue).locate(loc()).setDebug(true));
|
||||
target.add(Instruction.storeMember(pollute).locate(loc()).setDebug(true));
|
||||
}
|
||||
target.get(start);
|
||||
}
|
||||
|
||||
public IndexAssignStatement(Location loc, Statement object, Statement index, Statement value, Operation operation) {
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.AssignStatement;
|
||||
import me.topchetoeu.jscript.compilation.AssignableStatement;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.Operation;
|
||||
@@ -14,31 +12,30 @@ public class IndexStatement extends AssignableStatement {
|
||||
public final Statement object;
|
||||
public final Statement index;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
@Override
|
||||
public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public AssignStatement toAssign(Statement val, Operation operation) {
|
||||
public Statement toAssign(Statement val, Operation operation) {
|
||||
return new IndexAssignStatement(loc(), object, index, val, operation);
|
||||
}
|
||||
public void compile(List<Instruction> target, ScopeRecord scope, boolean dupObj) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean dupObj, boolean pollute) {
|
||||
int start = 0;
|
||||
object.compileWithPollution(target, scope);
|
||||
object.compile(target, scope, true);
|
||||
if (dupObj) target.add(Instruction.dup().locate(loc()));
|
||||
if (index instanceof ConstantStatement) {
|
||||
target.add(Instruction.loadMember(((ConstantStatement)index).value).locate(loc()));
|
||||
return;
|
||||
}
|
||||
|
||||
index.compileWithPollution(target, scope);
|
||||
index.compile(target, scope, true);
|
||||
target.add(Instruction.loadMember().locate(loc()));
|
||||
target.get(start).setDebug(true);
|
||||
if (!pollute) target.add(Instruction.discard().locate(loc()));
|
||||
}
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
compile(target, scope, false);
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
compile(target, scope, false, pollute);
|
||||
}
|
||||
|
||||
public IndexStatement(Location loc, Statement object, Statement index) {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
@@ -11,29 +10,27 @@ import me.topchetoeu.jscript.engine.values.Values;
|
||||
public class LazyAndStatement extends Statement {
|
||||
public final Statement first, second;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
@Override
|
||||
public boolean pure() {
|
||||
return first.pure() && second.pure();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (first instanceof ConstantStatement) {
|
||||
if (Values.not(((ConstantStatement)first).value)) {
|
||||
first.compileWithPollution(target, scope);
|
||||
first.compile(target, scope, pollute);
|
||||
}
|
||||
else second.compileWithPollution(target, scope);
|
||||
else second.compile(target, scope, pollute);
|
||||
return;
|
||||
}
|
||||
|
||||
first.compileWithPollution(target, scope);
|
||||
target.add(Instruction.dup().locate(loc()));
|
||||
first.compile(target, scope, true);
|
||||
if (pollute) target.add(Instruction.dup().locate(loc()));
|
||||
int start = target.size();
|
||||
target.add(Instruction.nop());
|
||||
target.add(Instruction.discard().locate(loc()));
|
||||
second.compileWithPollution(target, scope);
|
||||
second.compile(target, scope, pollute);
|
||||
target.set(start, Instruction.jmpIfNot(target.size() - start).locate(loc()));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
@@ -11,29 +10,27 @@ import me.topchetoeu.jscript.engine.values.Values;
|
||||
public class LazyOrStatement extends Statement {
|
||||
public final Statement first, second;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
@Override
|
||||
public boolean pure() {
|
||||
return first.pure() && second.pure();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (first instanceof ConstantStatement) {
|
||||
if (Values.not(((ConstantStatement)first).value)) {
|
||||
second.compileWithPollution(target, scope);
|
||||
second.compile(target, scope, pollute);
|
||||
}
|
||||
else first.compileWithPollution(target, scope);
|
||||
else first.compile(target, scope, pollute);
|
||||
return;
|
||||
}
|
||||
|
||||
first.compileWithPollution(target, scope);
|
||||
target.add(Instruction.dup().locate(loc()));
|
||||
first.compile(target, scope, true);
|
||||
if (pollute) target.add(Instruction.dup().locate(loc()));
|
||||
int start = target.size();
|
||||
target.add(Instruction.nop());
|
||||
target.add(Instruction.discard().locate(loc()));
|
||||
second.compileWithPollution(target, scope);
|
||||
second.compile(target, scope, pollute);
|
||||
target.set(start, Instruction.jmpIf(target.size() - start).locate(loc()));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
@@ -12,14 +11,10 @@ public class NewStatement extends Statement {
|
||||
public final Statement[] args;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
func.compile(target, scope, true);
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
func.compileWithPollution(target, scope);
|
||||
for (var arg : args) {
|
||||
arg.compileWithPollution(target, scope);
|
||||
}
|
||||
for (var arg : args) arg.compile(target, scope, true);
|
||||
|
||||
target.add(Instruction.callNew(args.length).locate(loc()).setDebug(true));
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
@@ -15,10 +15,7 @@ public class ObjectStatement extends Statement {
|
||||
public final Map<Object, FunctionStatement> setters;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
target.add(Instruction.loadObj().locate(loc()));
|
||||
|
||||
for (var el : map.entrySet()) {
|
||||
@@ -26,7 +23,7 @@ public class ObjectStatement extends Statement {
|
||||
target.add(Instruction.loadValue(el.getKey()).locate(loc()));
|
||||
var val = el.getValue();
|
||||
if (val instanceof FunctionStatement) ((FunctionStatement)val).compile(target, scope, el.getKey().toString(), false);
|
||||
else val.compileWithPollution(target, scope);
|
||||
else val.compile(target, scope, true);
|
||||
target.add(Instruction.storeMember().locate(loc()));
|
||||
}
|
||||
|
||||
@@ -38,14 +35,16 @@ public class ObjectStatement extends Statement {
|
||||
if (key instanceof String) target.add(Instruction.loadValue((String)key).locate(loc()));
|
||||
else target.add(Instruction.loadValue((Double)key).locate(loc()));
|
||||
|
||||
if (getters.containsKey(key)) getters.get(key).compileWithPollution(target, scope);
|
||||
if (getters.containsKey(key)) getters.get(key).compile(target, scope, true);
|
||||
else target.add(Instruction.loadValue(null).locate(loc()));
|
||||
|
||||
if (setters.containsKey(key)) setters.get(key).compileWithPollution(target, scope);
|
||||
if (setters.containsKey(key)) setters.get(key).compile(target, scope, true);
|
||||
else target.add(Instruction.loadValue(null).locate(loc()));
|
||||
|
||||
target.add(Instruction.defProp().locate(loc()));
|
||||
}
|
||||
|
||||
if (!pollute) target.add(Instruction.discard().locate(loc()));
|
||||
}
|
||||
|
||||
public ObjectStatement(Location loc, Map<Object, Statement> map, Map<Object, FunctionStatement> getters, Map<Object, FunctionStatement> setters) {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.compilation.control.ThrowStatement;
|
||||
@@ -16,15 +15,15 @@ public class OperationStatement extends Statement {
|
||||
public final Operation operation;
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
for (var arg : args) {
|
||||
arg.compileWithPollution(target, scope);
|
||||
arg.compile(target, scope, true);
|
||||
}
|
||||
target.add(Instruction.operation(operation).locate(loc()));
|
||||
|
||||
if (pollute) target.add(Instruction.operation(operation).locate(loc()));
|
||||
else target.add(Instruction.discard().locate(loc()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
@Override
|
||||
public boolean pure() {
|
||||
for (var arg : args) {
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
@@ -10,14 +9,13 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
public class RegexStatement extends Statement {
|
||||
public final String pattern, flags;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
@Override
|
||||
public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
target.add(Instruction.loadRegex(pattern, flags).locate(loc()));
|
||||
if (!pollute) target.add(Instruction.discard().locate(loc()));
|
||||
}
|
||||
|
||||
public RegexStatement(Location loc, String pattern, String flags) {
|
||||
|
||||
@@ -1,58 +0,0 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
|
||||
public class TernaryStatement extends Statement {
|
||||
public final Statement condition;
|
||||
public final Statement first;
|
||||
public final Statement second;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
@Override
|
||||
public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
if (condition instanceof ConstantStatement) {
|
||||
if (!Values.toBoolean(((ConstantStatement)condition).value)) {
|
||||
second.compileWithPollution(target, scope);
|
||||
}
|
||||
else first.compileWithPollution(target, scope);
|
||||
return;
|
||||
}
|
||||
|
||||
condition.compileWithPollution(target, scope);
|
||||
int start = target.size();
|
||||
target.add(Instruction.nop());
|
||||
first.compileWithPollution(target, scope);
|
||||
int mid = target.size();
|
||||
target.add(Instruction.nop());
|
||||
second.compileWithPollution(target, scope);
|
||||
int end = target.size();
|
||||
|
||||
target.set(start, Instruction.jmpIfNot(mid - start + 1).locate(loc()));
|
||||
target.set(mid, Instruction.jmp(end - mid).locate(loc()));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Statement optimize() {
|
||||
var cond = condition.optimize();
|
||||
var f = first.optimize();
|
||||
var s = second.optimize();
|
||||
return new TernaryStatement(loc(), cond, f, s);
|
||||
}
|
||||
|
||||
public TernaryStatement(Location loc, Statement condition, Statement first, Statement second) {
|
||||
super(loc);
|
||||
this.condition = condition;
|
||||
this.first = first;
|
||||
this.second = second;
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,21 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.compilation.control.ArrayStatement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
|
||||
public class TypeofStatement extends Statement {
|
||||
public final Statement value;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
@Override
|
||||
public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (value instanceof VariableStatement) {
|
||||
var i = scope.getKey(((VariableStatement)value).name);
|
||||
if (i instanceof String) {
|
||||
@@ -25,7 +23,7 @@ public class TypeofStatement extends Statement {
|
||||
return;
|
||||
}
|
||||
}
|
||||
value.compileWithPollution(target, scope);
|
||||
value.compile(target, scope, pollute);
|
||||
target.add(Instruction.typeof().locate(loc()));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,39 +1,34 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.compilation.AssignStatement;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.engine.Operation;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public class VariableAssignStatement extends AssignStatement {
|
||||
public class VariableAssignStatement extends Statement {
|
||||
public final String name;
|
||||
public final Statement value;
|
||||
public final Operation operation;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope, boolean retPrevValue) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
var i = scope.getKey(name);
|
||||
if (operation != null) {
|
||||
target.add(Instruction.loadVar(i).locate(loc()));
|
||||
if (retPrevValue) target.add(Instruction.dup().locate(loc()));
|
||||
if (value instanceof FunctionStatement) ((FunctionStatement)value).compile(target, scope, name, false);
|
||||
else value.compileWithPollution(target, scope);
|
||||
else value.compile(target, scope, true);
|
||||
target.add(Instruction.operation(operation).locate(loc()));
|
||||
target.add(Instruction.storeVar(i, !retPrevValue).locate(loc()));
|
||||
target.add(Instruction.storeVar(i, false).locate(loc()));
|
||||
}
|
||||
else {
|
||||
if (retPrevValue) target.add(Instruction.loadVar(i).locate(loc()));
|
||||
if (value instanceof FunctionStatement) ((FunctionStatement)value).compile(target, scope, name, false);
|
||||
else value.compileWithPollution(target, scope);
|
||||
target.add(Instruction.storeVar(i, !retPrevValue).locate(loc()));
|
||||
else value.compile(target, scope, true);
|
||||
target.add(Instruction.storeVar(i, false).locate(loc()));
|
||||
}
|
||||
|
||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
}
|
||||
|
||||
public VariableAssignStatement(Location loc, String name, Statement val, Operation operation) {
|
||||
|
||||
@@ -1,23 +1,20 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
|
||||
public class VariableIndexStatement extends Statement {
|
||||
public final int index;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
@Override
|
||||
public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
target.add(Instruction.loadVar(index).locate(loc()));
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (pollute) target.add(Instruction.loadVar(index).locate(loc()));
|
||||
}
|
||||
|
||||
public VariableIndexStatement(Location loc, int i) {
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.AssignStatement;
|
||||
import me.topchetoeu.jscript.compilation.AssignableStatement;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.Operation;
|
||||
@@ -13,20 +11,19 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
public class VariableStatement extends AssignableStatement {
|
||||
public final String name;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
@Override
|
||||
public boolean pure() { return true; }
|
||||
|
||||
@Override
|
||||
public AssignStatement toAssign(Statement val, Operation operation) {
|
||||
public Statement toAssign(Statement val, Operation operation) {
|
||||
return new VariableAssignStatement(loc(), name, val, operation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
var i = scope.getKey(name);
|
||||
target.add(Instruction.loadVar(i).locate(loc()));
|
||||
if (!pollute) target.add(Instruction.discard().locate(loc()));
|
||||
}
|
||||
|
||||
public VariableStatement(Location loc, String name) {
|
||||
|
||||
@@ -1,22 +1,18 @@
|
||||
package me.topchetoeu.jscript.compilation.values;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Statement;
|
||||
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
|
||||
import me.topchetoeu.jscript.compilation.CompileTarget;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
|
||||
public class VoidStatement extends Statement {
|
||||
public final Statement value;
|
||||
|
||||
@Override
|
||||
public boolean pollutesStack() { return true; }
|
||||
|
||||
@Override
|
||||
public void compile(List<Instruction> target, ScopeRecord scope) {
|
||||
if (value != null) value.compileNoPollution(target, scope);
|
||||
target.add(Instruction.loadValue(null).locate(loc()));
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
if (value != null) value.compile(target, scope, false);
|
||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -5,16 +5,23 @@ import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.parsing.Parsing;
|
||||
|
||||
public class Context {
|
||||
public final FunctionContext function;
|
||||
public final MessageContext message;
|
||||
public final Environment env;
|
||||
public final Message message;
|
||||
|
||||
public FunctionValue compile(String filename, String raw) throws InterruptedException {
|
||||
var res = Values.toString(this, function.compile.call(this, null, raw, filename));
|
||||
return Parsing.compile(function, filename, res);
|
||||
var res = Values.toString(this, env.compile.call(this, null, raw, filename));
|
||||
return Parsing.compile(message.engine.functions, env, filename, res);
|
||||
}
|
||||
|
||||
public Context(FunctionContext funcCtx, MessageContext msgCtx) {
|
||||
this.function = funcCtx;
|
||||
this.message = msgCtx;
|
||||
public Context setEnv(Environment env) {
|
||||
return new Context(env, message);
|
||||
}
|
||||
public Context setMsg(Message msg) {
|
||||
return new Context(env, msg);
|
||||
}
|
||||
|
||||
public Context(Environment env, Message msg) {
|
||||
this.env = env;
|
||||
this.message = msg;
|
||||
}
|
||||
}
|
||||
|
||||
60
src/me/topchetoeu/jscript/engine/Data.java
Normal file
60
src/me/topchetoeu/jscript/engine/Data.java
Normal file
@@ -0,0 +1,60 @@
|
||||
package me.topchetoeu.jscript.engine;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public class Data implements Iterable<Entry<DataKey<?>, ?>> {
|
||||
private HashMap<DataKey<Object>, Object> data = new HashMap<>();
|
||||
|
||||
public Data copy() {
|
||||
return new Data().addAll(this);
|
||||
}
|
||||
|
||||
public Data addAll(Iterable<Entry<DataKey<?>, ?>> data) {
|
||||
for (var el : data) {
|
||||
add((DataKey<Object>)el.getKey(), (Object)el.getValue());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public <T> Data set(DataKey<T> key, T val) {
|
||||
if (val == null) data.remove(key);
|
||||
else data.put((DataKey<Object>)key, (Object)val);
|
||||
return this;
|
||||
}
|
||||
public <T> T add(DataKey<T> key, T val) {
|
||||
if (data.containsKey(key)) return (T)data.get(key);
|
||||
else {
|
||||
if (val == null) data.remove(key);
|
||||
else data.put((DataKey<Object>)key, (Object)val);
|
||||
return val;
|
||||
}
|
||||
}
|
||||
public <T> T get(DataKey<T> key) {
|
||||
return get(key, null);
|
||||
}
|
||||
public <T> T get(DataKey<T> key, T defaultVal) {
|
||||
if (!has(key)) return defaultVal;
|
||||
else return (T)data.get(key);
|
||||
}
|
||||
public boolean has(DataKey<?> key) { return data.containsKey(key); }
|
||||
|
||||
public int increase(DataKey<Integer> key, int n, int start) {
|
||||
int res;
|
||||
set(key, res = get(key, start) + n);
|
||||
return res;
|
||||
}
|
||||
public int increase(DataKey<Integer> key, int n) {
|
||||
return increase(key, n, 0);
|
||||
}
|
||||
public int increase(DataKey<Integer> key) {
|
||||
return increase(key, 1, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Entry<DataKey<?>, ?>> iterator() {
|
||||
return (Iterator<Entry<DataKey<?>, ?>>)data.entrySet();
|
||||
}
|
||||
}
|
||||
3
src/me/topchetoeu/jscript/engine/DataKey.java
Normal file
3
src/me/topchetoeu/jscript/engine/DataKey.java
Normal file
@@ -0,0 +1,3 @@
|
||||
package me.topchetoeu.jscript.engine;
|
||||
|
||||
public class DataKey<T> { }
|
||||
@@ -1,7 +1,9 @@
|
||||
package me.topchetoeu.jscript.engine;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.events.Awaitable;
|
||||
import me.topchetoeu.jscript.events.DataNotifier;
|
||||
@@ -11,19 +13,19 @@ public class Engine {
|
||||
private class UncompiledFunction extends FunctionValue {
|
||||
public final String filename;
|
||||
public final String raw;
|
||||
public final FunctionContext ctx;
|
||||
public final Environment env;
|
||||
|
||||
@Override
|
||||
public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException {
|
||||
ctx = new Context(this.ctx, ctx.message);
|
||||
ctx = ctx.setEnv(env);
|
||||
return ctx.compile(filename, raw).call(ctx, thisArg, args);
|
||||
}
|
||||
|
||||
public UncompiledFunction(FunctionContext ctx, String filename, String raw) {
|
||||
public UncompiledFunction(Environment env, String filename, String raw) {
|
||||
super(filename, 0);
|
||||
this.filename = filename;
|
||||
this.raw = raw;
|
||||
this.ctx = ctx;
|
||||
this.env = env;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,10 +34,10 @@ public class Engine {
|
||||
public final Object thisArg;
|
||||
public final Object[] args;
|
||||
public final DataNotifier<Object> notifier = new DataNotifier<>();
|
||||
public final MessageContext ctx;
|
||||
public final Message msg;
|
||||
|
||||
public Task(MessageContext ctx, FunctionValue func, Object thisArg, Object[] args) {
|
||||
this.ctx = ctx;
|
||||
public Task(Message ctx, FunctionValue func, Object thisArg, Object[] args) {
|
||||
this.msg = ctx;
|
||||
this.func = func;
|
||||
this.thisArg = thisArg;
|
||||
this.args = args;
|
||||
@@ -49,10 +51,11 @@ public class Engine {
|
||||
private LinkedBlockingDeque<Task> microTasks = new LinkedBlockingDeque<>();
|
||||
|
||||
public final int id = ++nextId;
|
||||
public final HashMap<Long, Instruction[]> functions = new HashMap<>();
|
||||
|
||||
private void runTask(Task task) throws InterruptedException {
|
||||
try {
|
||||
task.notifier.next(task.func.call(new Context(null, task.ctx), task.thisArg, task.args));
|
||||
task.notifier.next(task.func.call(task.msg.context(null), task.thisArg, task.args));
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
task.notifier.error(new RuntimeException(e));
|
||||
@@ -102,14 +105,14 @@ public class Engine {
|
||||
return this.thread != null;
|
||||
}
|
||||
|
||||
public Awaitable<Object> pushMsg(boolean micro, MessageContext ctx, FunctionValue func, Object thisArg, Object ...args) {
|
||||
public Awaitable<Object> pushMsg(boolean micro, Message ctx, FunctionValue func, Object thisArg, Object ...args) {
|
||||
var msg = new Task(ctx, func, thisArg, args);
|
||||
if (micro) microTasks.addLast(msg);
|
||||
else macroTasks.addLast(msg);
|
||||
return msg.notifier;
|
||||
}
|
||||
public Awaitable<Object> pushMsg(boolean micro, Context ctx, String filename, String raw, Object thisArg, Object ...args) {
|
||||
return pushMsg(micro, ctx.message, new UncompiledFunction(ctx.function, filename, raw), thisArg, args);
|
||||
return pushMsg(micro, ctx.message, new UncompiledFunction(ctx.env, filename, raw), thisArg, args);
|
||||
}
|
||||
|
||||
// public Engine() {
|
||||
|
||||
84
src/me/topchetoeu/jscript/engine/Environment.java
Normal file
84
src/me/topchetoeu/jscript/engine/Environment.java
Normal file
@@ -0,0 +1,84 @@
|
||||
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.engine.values.Symbol;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
import me.topchetoeu.jscript.interop.NativeSetter;
|
||||
import me.topchetoeu.jscript.interop.NativeWrapperProvider;
|
||||
|
||||
public class Environment {
|
||||
private HashMap<String, ObjectValue> prototypes = new HashMap<>();
|
||||
|
||||
public final Data data = new Data();
|
||||
public final HashMap<String, Symbol> symbols = 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.").setContext(ctx);
|
||||
});
|
||||
|
||||
public Environment addData(Data data) {
|
||||
this.data.addAll(data);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Native public ObjectValue proto(String name) {
|
||||
return prototypes.get(name);
|
||||
}
|
||||
@Native public void setProto(String name, ObjectValue val) {
|
||||
prototypes.put(name, val);
|
||||
}
|
||||
|
||||
@Native public Symbol symbol(String name) {
|
||||
if (symbols.containsKey(name))
|
||||
return symbols.get(name);
|
||||
else {
|
||||
var res = new Symbol(name);
|
||||
symbols.put(name, res);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
@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 Context context(Message msg) {
|
||||
return new Context(this, msg);
|
||||
}
|
||||
|
||||
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 NativeWrapperProvider(this);
|
||||
if (global == null) global = new GlobalScope();
|
||||
|
||||
this.wrappersProvider = nativeConverter;
|
||||
this.compile = compile;
|
||||
this.global = global;
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
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 FunctionContext {
|
||||
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 FunctionContext fork() {
|
||||
var res = new FunctionContext(compile, wrappersProvider, global);
|
||||
res.regexConstructor = regexConstructor;
|
||||
res.prototypes = new HashMap<>(prototypes);
|
||||
return res;
|
||||
}
|
||||
|
||||
@Native
|
||||
public FunctionContext child() {
|
||||
var res = fork();
|
||||
res.global = res.global.globalChild();
|
||||
return res;
|
||||
}
|
||||
|
||||
public FunctionContext(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;
|
||||
}
|
||||
}
|
||||
@@ -7,15 +7,22 @@ import java.util.List;
|
||||
import me.topchetoeu.jscript.engine.frame.CodeFrame;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
|
||||
public class MessageContext {
|
||||
public class Message {
|
||||
public final Engine engine;
|
||||
|
||||
private final ArrayList<CodeFrame> frames = new ArrayList<>();
|
||||
public int maxStackFrames = 1000;
|
||||
|
||||
public final Data data = new Data();
|
||||
|
||||
public List<CodeFrame> frames() { return Collections.unmodifiableList(frames); }
|
||||
|
||||
public MessageContext pushFrame(CodeFrame frame) {
|
||||
public Message addData(Data data) {
|
||||
this.data.addAll(data);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Message pushFrame(Context ctx, CodeFrame frame) throws InterruptedException {
|
||||
this.frames.add(frame);
|
||||
if (this.frames.size() > maxStackFrames) throw EngineException.ofRange("Stack overflow!");
|
||||
return this;
|
||||
@@ -27,7 +34,30 @@ public class MessageContext {
|
||||
return true;
|
||||
}
|
||||
|
||||
public MessageContext(Engine engine) {
|
||||
public List<String> stackTrace() {
|
||||
var res = new ArrayList<String>();
|
||||
|
||||
for (var el : frames) {
|
||||
var name = el.function.name;
|
||||
var loc = el.function.loc();
|
||||
var trace = "";
|
||||
|
||||
if (loc != null) trace += "at " + loc.toString() + " ";
|
||||
if (name != null && !name.equals("")) trace += "in " + name + " ";
|
||||
|
||||
trace = trace.trim();
|
||||
|
||||
if (!res.equals("")) res.add(trace);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public Context context(Environment env) {
|
||||
return new Context(env, this);
|
||||
}
|
||||
|
||||
public Message(Engine engine) {
|
||||
this.engine = engine;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
package me.topchetoeu.jscript.engine;
|
||||
|
||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
|
||||
public interface WrappersProvider {
|
||||
public ObjectValue getProto(Class<?> obj);
|
||||
public ObjectValue getConstr(Class<?> obj);
|
||||
public FunctionValue getConstr(Class<?> obj);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package me.topchetoeu.jscript.engine.frame;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Stack;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
@@ -9,6 +8,7 @@ import me.topchetoeu.jscript.engine.scope.LocalScope;
|
||||
import me.topchetoeu.jscript.engine.scope.ValueVariable;
|
||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.engine.values.CodeFunction;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
|
||||
@@ -24,8 +24,8 @@ public class CodeFrame {
|
||||
public final int tryStart, catchStart, finallyStart, end;
|
||||
public int state;
|
||||
public Object retVal;
|
||||
public int jumpPtr;
|
||||
public EngineException err;
|
||||
public int jumpPtr;
|
||||
|
||||
public TryCtx(int tryStart, int tryN, int catchN, int finallyN) {
|
||||
hasCatch = catchN >= 0;
|
||||
@@ -45,7 +45,7 @@ public class CodeFrame {
|
||||
public final LocalScope scope;
|
||||
public final Object thisArg;
|
||||
public final Object[] args;
|
||||
public final List<TryCtx> tryStack = new ArrayList<>();
|
||||
public final Stack<TryCtx> tryStack = new Stack<>();
|
||||
public final CodeFunction function;
|
||||
|
||||
public Object[] stack = new Object[32];
|
||||
@@ -93,6 +93,12 @@ public class CodeFrame {
|
||||
stack[stackPtr++] = Values.normalize(ctx, val);
|
||||
}
|
||||
|
||||
private void setCause(Context ctx, EngineException err, EngineException cause) throws InterruptedException {
|
||||
if (err.value instanceof ObjectValue) {
|
||||
Values.setMember(ctx, err, ctx.env.symbol("Symbol.cause"), cause);
|
||||
}
|
||||
err.cause = cause;
|
||||
}
|
||||
private Object nextNoTry(Context ctx) throws InterruptedException {
|
||||
if (Thread.currentThread().isInterrupted()) throw new InterruptedException();
|
||||
if (codePtr < 0 || codePtr >= function.body.length) return null;
|
||||
@@ -107,163 +113,125 @@ public class CodeFrame {
|
||||
return Runners.exec(ctx, instr, this);
|
||||
}
|
||||
catch (EngineException e) {
|
||||
throw e.add(function.name, prevLoc);
|
||||
throw e.add(function.name, prevLoc).setContext(ctx);
|
||||
}
|
||||
}
|
||||
|
||||
public Object next(Context ctx, Object prevReturn, Object prevError) throws InterruptedException {
|
||||
TryCtx tryCtx = null;
|
||||
if (prevError != Runners.NO_RETURN) prevReturn = Runners.NO_RETURN;
|
||||
public Object next(Context ctx, Object value, Object returnValue, EngineException error) throws InterruptedException {
|
||||
if (value != Runners.NO_RETURN) push(ctx, value);
|
||||
|
||||
while (!tryStack.isEmpty()) {
|
||||
var tmp = tryStack.get(tryStack.size() - 1);
|
||||
var remove = false;
|
||||
|
||||
if (prevError != Runners.NO_RETURN) {
|
||||
remove = true;
|
||||
if (tmp.state == TryCtx.STATE_TRY) {
|
||||
tmp.jumpPtr = tmp.end;
|
||||
|
||||
if (tmp.hasCatch) {
|
||||
tmp.state = TryCtx.STATE_CATCH;
|
||||
scope.catchVars.add(new ValueVariable(false, prevError));
|
||||
prevError = Runners.NO_RETURN;
|
||||
codePtr = tmp.catchStart;
|
||||
remove = false;
|
||||
}
|
||||
else if (tmp.hasFinally) {
|
||||
tmp.state = TryCtx.STATE_FINALLY_THREW;
|
||||
tmp.err = new EngineException(prevError);
|
||||
prevError = Runners.NO_RETURN;
|
||||
codePtr = tmp.finallyStart;
|
||||
remove = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (prevReturn != Runners.NO_RETURN) {
|
||||
remove = true;
|
||||
if (tmp.hasFinally && tmp.state <= TryCtx.STATE_CATCH) {
|
||||
tmp.state = TryCtx.STATE_FINALLY_RETURNED;
|
||||
tmp.retVal = prevReturn;
|
||||
prevReturn = Runners.NO_RETURN;
|
||||
codePtr = tmp.finallyStart;
|
||||
remove = false;
|
||||
}
|
||||
}
|
||||
else if (tmp.state == TryCtx.STATE_TRY) {
|
||||
if (codePtr < tmp.tryStart || codePtr >= tmp.catchStart) {
|
||||
if (jumpFlag) tmp.jumpPtr = codePtr;
|
||||
else tmp.jumpPtr = tmp.end;
|
||||
|
||||
if (tmp.hasFinally) {
|
||||
tmp.state = TryCtx.STATE_FINALLY_JUMPED;
|
||||
codePtr = tmp.finallyStart;
|
||||
}
|
||||
else codePtr = tmp.jumpPtr;
|
||||
remove = !tmp.hasFinally;
|
||||
}
|
||||
}
|
||||
else if (tmp.state == TryCtx.STATE_CATCH) {
|
||||
if (codePtr < tmp.catchStart || codePtr >= tmp.finallyStart) {
|
||||
if (jumpFlag) tmp.jumpPtr = codePtr;
|
||||
else tmp.jumpPtr = tmp.end;
|
||||
scope.catchVars.remove(scope.catchVars.size() - 1);
|
||||
|
||||
if (tmp.hasFinally) {
|
||||
tmp.state = TryCtx.STATE_FINALLY_JUMPED;
|
||||
codePtr = tmp.finallyStart;
|
||||
}
|
||||
else codePtr = tmp.jumpPtr;
|
||||
remove = !tmp.hasFinally;
|
||||
}
|
||||
}
|
||||
else if (codePtr < tmp.finallyStart || codePtr >= tmp.end) {
|
||||
if (!jumpFlag) {
|
||||
if (tmp.state == TryCtx.STATE_FINALLY_THREW) throw tmp.err;
|
||||
else if (tmp.state == TryCtx.STATE_FINALLY_RETURNED) return tmp.retVal;
|
||||
else if (tmp.state == TryCtx.STATE_FINALLY_JUMPED) codePtr = tmp.jumpPtr;
|
||||
}
|
||||
else codePtr = tmp.jumpPtr;
|
||||
remove = true;
|
||||
}
|
||||
|
||||
if (remove) tryStack.remove(tryStack.size() - 1);
|
||||
else {
|
||||
tryCtx = tmp;
|
||||
break;
|
||||
}
|
||||
if (returnValue == Runners.NO_RETURN && error == null) {
|
||||
try { returnValue = nextNoTry(ctx); }
|
||||
catch (EngineException e) { error = e; }
|
||||
}
|
||||
|
||||
if (prevError != Runners.NO_RETURN) throw new EngineException(prevError);
|
||||
if (prevReturn != Runners.NO_RETURN) return prevReturn;
|
||||
while (!tryStack.empty()) {
|
||||
var tryCtx = tryStack.peek();
|
||||
var newState = -1;
|
||||
|
||||
if (tryCtx == null) return nextNoTry(ctx);
|
||||
else if (tryCtx.state == TryCtx.STATE_TRY) {
|
||||
try {
|
||||
var res = nextNoTry(ctx);
|
||||
if (res != Runners.NO_RETURN && tryCtx.hasFinally) {
|
||||
tryCtx.retVal = res;
|
||||
tryCtx.state = TryCtx.STATE_FINALLY_RETURNED;
|
||||
}
|
||||
switch (tryCtx.state) {
|
||||
case TryCtx.STATE_TRY:
|
||||
if (error != null) {
|
||||
if (tryCtx.hasCatch) {
|
||||
tryCtx.err = error;
|
||||
newState = TryCtx.STATE_CATCH;
|
||||
}
|
||||
else if (tryCtx.hasFinally) {
|
||||
tryCtx.err = error;
|
||||
newState = TryCtx.STATE_FINALLY_THREW;
|
||||
}
|
||||
break;
|
||||
}
|
||||
else if (returnValue != Runners.NO_RETURN) {
|
||||
if (tryCtx.hasFinally) {
|
||||
tryCtx.retVal = error;
|
||||
newState = TryCtx.STATE_FINALLY_RETURNED;
|
||||
}
|
||||
break;
|
||||
}
|
||||
else if (codePtr >= tryCtx.tryStart && codePtr < tryCtx.catchStart) return Runners.NO_RETURN;
|
||||
|
||||
else return res;
|
||||
if (tryCtx.hasFinally) {
|
||||
if (jumpFlag) tryCtx.jumpPtr = codePtr;
|
||||
else tryCtx.jumpPtr = tryCtx.end;
|
||||
newState = TryCtx.STATE_FINALLY_JUMPED;
|
||||
}
|
||||
else codePtr = tryCtx.end;
|
||||
break;
|
||||
case TryCtx.STATE_CATCH:
|
||||
if (error != null) {
|
||||
if (tryCtx.hasFinally) {
|
||||
tryCtx.err = error;
|
||||
newState = TryCtx.STATE_FINALLY_THREW;
|
||||
}
|
||||
break;
|
||||
}
|
||||
else if (returnValue != Runners.NO_RETURN) {
|
||||
if (tryCtx.hasFinally) {
|
||||
tryCtx.retVal = returnValue;
|
||||
newState = TryCtx.STATE_FINALLY_RETURNED;
|
||||
}
|
||||
break;
|
||||
}
|
||||
else if (codePtr >= tryCtx.catchStart && codePtr < tryCtx.finallyStart) return Runners.NO_RETURN;
|
||||
|
||||
if (tryCtx.hasFinally) {
|
||||
if (jumpFlag) tryCtx.jumpPtr = codePtr;
|
||||
else tryCtx.jumpPtr = tryCtx.end;
|
||||
newState = TryCtx.STATE_FINALLY_JUMPED;
|
||||
}
|
||||
else codePtr = tryCtx.end;
|
||||
break;
|
||||
case TryCtx.STATE_FINALLY_THREW:
|
||||
if (error != null) setCause(ctx, error, tryCtx.err);
|
||||
else if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) error = tryCtx.err;
|
||||
else return Runners.NO_RETURN;
|
||||
break;
|
||||
case TryCtx.STATE_FINALLY_RETURNED:
|
||||
if (returnValue == Runners.NO_RETURN) {
|
||||
if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) returnValue = tryCtx.retVal;
|
||||
else return Runners.NO_RETURN;
|
||||
}
|
||||
break;
|
||||
case TryCtx.STATE_FINALLY_JUMPED:
|
||||
if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) {
|
||||
if (!jumpFlag) codePtr = tryCtx.jumpPtr;
|
||||
else codePtr = tryCtx.end;
|
||||
}
|
||||
else return Runners.NO_RETURN;
|
||||
break;
|
||||
}
|
||||
catch (EngineException e) {
|
||||
if (tryCtx.hasCatch) {
|
||||
tryCtx.state = TryCtx.STATE_CATCH;
|
||||
tryCtx.err = e;
|
||||
|
||||
if (tryCtx.state == TryCtx.STATE_CATCH) scope.catchVars.remove(scope.catchVars.size() - 1);
|
||||
|
||||
if (newState == -1) {
|
||||
tryStack.pop();
|
||||
continue;
|
||||
}
|
||||
|
||||
tryCtx.state = newState;
|
||||
switch (newState) {
|
||||
case TryCtx.STATE_CATCH:
|
||||
scope.catchVars.add(new ValueVariable(false, tryCtx.err.value));
|
||||
codePtr = tryCtx.catchStart;
|
||||
scope.catchVars.add(new ValueVariable(false, e.value));
|
||||
return Runners.NO_RETURN;
|
||||
}
|
||||
else if (tryCtx.hasFinally) {
|
||||
tryCtx.err = e;
|
||||
tryCtx.state = TryCtx.STATE_FINALLY_THREW;
|
||||
}
|
||||
else throw e;
|
||||
break;
|
||||
default:
|
||||
codePtr = tryCtx.finallyStart;
|
||||
}
|
||||
|
||||
codePtr = tryCtx.finallyStart;
|
||||
return Runners.NO_RETURN;
|
||||
}
|
||||
else if (tryCtx.state == TryCtx.STATE_CATCH) {
|
||||
try {
|
||||
var res = nextNoTry(ctx);
|
||||
if (res != Runners.NO_RETURN && tryCtx.hasFinally) {
|
||||
tryCtx.retVal = res;
|
||||
tryCtx.state = TryCtx.STATE_FINALLY_RETURNED;
|
||||
}
|
||||
else return res;
|
||||
}
|
||||
catch (EngineException e) {
|
||||
e.cause = tryCtx.err;
|
||||
if (tryCtx.hasFinally) {
|
||||
tryCtx.err = e;
|
||||
tryCtx.state = TryCtx.STATE_FINALLY_THREW;
|
||||
}
|
||||
else throw e;
|
||||
}
|
||||
|
||||
codePtr = tryCtx.finallyStart;
|
||||
return Runners.NO_RETURN;
|
||||
}
|
||||
else if (tryCtx.state == TryCtx.STATE_FINALLY_THREW) {
|
||||
try {
|
||||
return nextNoTry(ctx);
|
||||
}
|
||||
catch (EngineException e) {
|
||||
e.cause = tryCtx.err;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
else return nextNoTry(ctx);
|
||||
|
||||
if (error != null) throw error.setContext(ctx);
|
||||
if (returnValue != Runners.NO_RETURN) return returnValue;
|
||||
return Runners.NO_RETURN;
|
||||
}
|
||||
|
||||
public Object run(Context ctx) throws InterruptedException {
|
||||
try {
|
||||
ctx.message.pushFrame(this);
|
||||
ctx.message.pushFrame(ctx, this);
|
||||
while (true) {
|
||||
var res = next(ctx, Runners.NO_RETURN, Runners.NO_RETURN);
|
||||
var res = next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, null);
|
||||
if (res != Runners.NO_RETURN) return res;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import me.topchetoeu.jscript.engine.scope.ValueVariable;
|
||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.engine.values.CodeFunction;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.SignalValue;
|
||||
import me.topchetoeu.jscript.engine.values.Symbol;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
@@ -20,13 +19,10 @@ public class Runners {
|
||||
public static Object execReturn(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
return frame.pop();
|
||||
}
|
||||
public static Object execSignal(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
return new SignalValue(instr.get(0));
|
||||
}
|
||||
public static Object execThrow(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
throw new EngineException(frame.pop());
|
||||
}
|
||||
public static Object execThrowSyntax(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
public static Object execThrowSyntax(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
throw EngineException.ofSyntax((String)instr.get(0));
|
||||
}
|
||||
|
||||
@@ -48,16 +44,18 @@ public class Runners {
|
||||
var callArgs = frame.take(instr.get(0));
|
||||
var funcObj = frame.pop();
|
||||
|
||||
if (Values.isFunction(funcObj) && Values.function(funcObj).special) {
|
||||
frame.push(ctx, call(ctx, funcObj, null, callArgs));
|
||||
}
|
||||
else {
|
||||
var proto = Values.getMember(ctx, funcObj, "prototype");
|
||||
var obj = new ObjectValue();
|
||||
obj.setPrototype(ctx, proto);
|
||||
call(ctx, funcObj, obj, callArgs);
|
||||
frame.push(ctx, obj);
|
||||
}
|
||||
frame.push(ctx, Values.callNew(ctx, funcObj, callArgs));
|
||||
|
||||
// if (Values.isFunction(funcObj) && Values.function(funcObj).special) {
|
||||
// frame.push(ctx, call(ctx, funcObj, null, callArgs));
|
||||
// }
|
||||
// else {
|
||||
// var proto = Values.getMember(ctx, funcObj, "prototype");
|
||||
// var obj = new ObjectValue();
|
||||
// obj.setPrototype(ctx, proto);
|
||||
// call(ctx, funcObj, obj, callArgs);
|
||||
// frame.push(ctx, obj);
|
||||
// }
|
||||
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
@@ -65,7 +63,7 @@ public class Runners {
|
||||
|
||||
public static Object execMakeVar(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
var name = (String)instr.get(0);
|
||||
ctx.function.global.define(name);
|
||||
ctx.env.global.define(name);
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
@@ -160,7 +158,7 @@ public class Runners {
|
||||
public static Object execLoadVar(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
var i = instr.get(0);
|
||||
|
||||
if (i instanceof String) frame.push(ctx, ctx.function.global.get(ctx, (String)i));
|
||||
if (i instanceof String) frame.push(ctx, ctx.env.global.get(ctx, (String)i));
|
||||
else frame.push(ctx, frame.scope.get((int)i).get(ctx));
|
||||
|
||||
frame.codePtr++;
|
||||
@@ -172,7 +170,7 @@ public class Runners {
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execLoadGlob(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
frame.push(ctx, ctx.function.global.obj);
|
||||
frame.push(ctx, ctx.env.global.obj);
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
@@ -184,7 +182,7 @@ public class Runners {
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execLoadFunc(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
int n = (Integer)instr.get(0);
|
||||
long id = (Long)instr.get(0);
|
||||
int localsN = (Integer)instr.get(1);
|
||||
int len = (Integer)instr.get(2);
|
||||
var captures = new ValueVariable[instr.params.length - 3];
|
||||
@@ -193,15 +191,12 @@ public class Runners {
|
||||
captures[i - 3] = frame.scope.get(instr.get(i));
|
||||
}
|
||||
|
||||
var start = frame.codePtr + 1;
|
||||
var end = start + n - 1;
|
||||
var body = new Instruction[end - start];
|
||||
System.arraycopy(frame.function.body, start, body, 0, end - start);
|
||||
var body = ctx.message.engine.functions.get(id);
|
||||
var func = new CodeFunction(ctx.env, "", localsN, len, captures, body);
|
||||
|
||||
var func = new CodeFunction(ctx.function, "", localsN, len, captures, body);
|
||||
frame.push(ctx, func);
|
||||
|
||||
frame.codePtr += n;
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execLoadMember(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
@@ -222,7 +217,7 @@ public class Runners {
|
||||
return execLoadMember(ctx, instr, frame);
|
||||
}
|
||||
public static Object execLoadRegEx(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
frame.push(ctx, ctx.function.regexConstructor.call(ctx, null, instr.get(0), instr.get(1)));
|
||||
frame.push(ctx, ctx.env.regexConstructor.call(ctx, null, instr.get(0), instr.get(1)));
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
@@ -246,7 +241,7 @@ public class Runners {
|
||||
var val = (boolean)instr.get(1) ? frame.peek() : frame.pop();
|
||||
var i = instr.get(0);
|
||||
|
||||
if (i instanceof String) ctx.function.global.set(ctx, (String)i, val);
|
||||
if (i instanceof String) ctx.env.global.set(ctx, (String)i, val);
|
||||
else frame.scope.get((int)i).set(ctx, val);
|
||||
|
||||
frame.codePtr++;
|
||||
@@ -293,8 +288,8 @@ public class Runners {
|
||||
Object obj;
|
||||
|
||||
if (name != null) {
|
||||
if (ctx.function.global.has(ctx, name)) {
|
||||
obj = ctx.function.global.get(ctx, name);
|
||||
if (ctx.env.global.has(ctx, name)) {
|
||||
obj = ctx.env.global.get(ctx, name);
|
||||
}
|
||||
else obj = null;
|
||||
}
|
||||
@@ -305,7 +300,7 @@ public class Runners {
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
public static Object execNop(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
public static Object execNop(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
if (instr.is(0, "dbg_names")) {
|
||||
var names = new String[instr.params.length - 1];
|
||||
for (var i = 0; i < instr.params.length - 1; i++) {
|
||||
@@ -341,11 +336,9 @@ public class Runners {
|
||||
}
|
||||
|
||||
public static Object exec(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
|
||||
// System.out.println(instr + "@" + instr.location);
|
||||
switch (instr.type) {
|
||||
case NOP: return execNop(ctx, instr, frame);
|
||||
case RETURN: return execReturn(ctx, instr, frame);
|
||||
case SIGNAL: return execSignal(ctx, instr, frame);
|
||||
case THROW: return execThrow(ctx, instr, frame);
|
||||
case THROW_SYNTAX: return execThrowSyntax(ctx, instr, frame);
|
||||
case CALL: return execCall(ctx, instr, frame);
|
||||
|
||||
@@ -9,7 +9,8 @@ public class LocalScope {
|
||||
public final ArrayList<ValueVariable> catchVars = new ArrayList<>();
|
||||
|
||||
public ValueVariable get(int i) {
|
||||
if (i >= locals.length) return catchVars.get(i - locals.length);
|
||||
if (i >= locals.length)
|
||||
return catchVars.get(i - locals.length);
|
||||
if (i >= 0) return locals[i];
|
||||
else return captures[~i];
|
||||
}
|
||||
|
||||
@@ -1,90 +1,128 @@
|
||||
package me.topchetoeu.jscript.engine.values;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Comparator;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
|
||||
public class ArrayValue extends ObjectValue {
|
||||
private static final Object EMPTY = new Object();
|
||||
private final ArrayList<Object> values = new ArrayList<>();
|
||||
// TODO: Make methods generic
|
||||
public class ArrayValue extends ObjectValue implements Iterable<Object> {
|
||||
private static final Object UNDEFINED = new Object();
|
||||
private Object[] values;
|
||||
private int size;
|
||||
|
||||
public int size() { return values.size(); }
|
||||
private void alloc(int index) {
|
||||
if (index < values.length) return;
|
||||
if (index < values.length * 2) index = values.length * 2;
|
||||
|
||||
var arr = new Object[index];
|
||||
System.arraycopy(values, 0, arr, 0, values.length);
|
||||
values = arr;
|
||||
}
|
||||
|
||||
public int size() { return size; }
|
||||
public boolean setSize(int val) {
|
||||
if (val < 0) return false;
|
||||
while (size() > val) {
|
||||
values.remove(values.size() - 1);
|
||||
}
|
||||
while (size() < val) {
|
||||
values.add(EMPTY);
|
||||
if (size > val) shrink(size - val);
|
||||
else {
|
||||
alloc(val);
|
||||
size = val;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public Object get(int i) {
|
||||
if (i < 0 || i >= values.size()) return null;
|
||||
var res = values.get(i);
|
||||
if (res == EMPTY) return null;
|
||||
if (i < 0 || i >= size) return null;
|
||||
var res = values[i];
|
||||
if (res == UNDEFINED) return null;
|
||||
else return res;
|
||||
}
|
||||
public void set(Context ctx, int i, Object val) {
|
||||
if (i < 0) return;
|
||||
|
||||
while (values.size() <= i) {
|
||||
values.add(EMPTY);
|
||||
}
|
||||
alloc(i);
|
||||
|
||||
values.set(i, Values.normalize(ctx, val));
|
||||
val = Values.normalize(ctx, val);
|
||||
if (val == null) val = UNDEFINED;
|
||||
values[i] = val;
|
||||
if (i >= size) size = i + 1;
|
||||
}
|
||||
public boolean has(int i) {
|
||||
return i >= 0 && i < values.size() && values.get(i) != EMPTY;
|
||||
return i >= 0 && i < values.length && values[i] != null;
|
||||
}
|
||||
public void remove(int i) {
|
||||
if (i < 0 || i >= values.size()) return;
|
||||
values.set(i, EMPTY);
|
||||
if (i < 0 || i >= values.length) return;
|
||||
values[i] = null;
|
||||
}
|
||||
public void shrink(int n) {
|
||||
if (n > values.size()) values.clear();
|
||||
if (n >= values.length) {
|
||||
values = new Object[16];
|
||||
size = 0;
|
||||
}
|
||||
else {
|
||||
for (int i = 0; i < n && values.size() > 0; i++) {
|
||||
values.remove(values.size() - 1);
|
||||
for (int i = 0; i < n; i++) {
|
||||
values[--size] = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Object[] toArray() {
|
||||
Object[] res = new Object[size];
|
||||
copyTo(res, 0, 0, size);
|
||||
return res;
|
||||
}
|
||||
public void copyTo(Object[] arr, int sourceStart, int destStart, int count) {
|
||||
for (var i = 0; i < count; i++) {
|
||||
if (i + sourceStart < 0 || i + sourceStart >= size) arr[i + destStart] = null;
|
||||
if (values[i + sourceStart] == UNDEFINED) arr[i + destStart] = null;
|
||||
else arr[i + sourceStart] = values[i + destStart];
|
||||
}
|
||||
}
|
||||
public void copyTo(Context ctx, ArrayValue arr, int sourceStart, int destStart, int count) {
|
||||
// Iterate in reverse to reallocate at most once
|
||||
for (var i = count - 1; i >= 0; i--) {
|
||||
if (i + sourceStart < 0 || i + sourceStart >= size) arr.set(ctx, i + destStart, null);
|
||||
if (values[i + sourceStart] == UNDEFINED) arr.set(ctx, i + destStart, null);
|
||||
else arr.set(ctx, i + destStart, values[i + sourceStart]);
|
||||
}
|
||||
}
|
||||
|
||||
public void copyFrom(Context ctx, Object[] arr, int sourceStart, int destStart, int count) {
|
||||
for (var i = 0; i < count; i++) {
|
||||
set(ctx, i + destStart, arr[i + sourceStart]);
|
||||
}
|
||||
}
|
||||
|
||||
public void move(int srcI, int dstI, int n) {
|
||||
alloc(dstI + n);
|
||||
|
||||
System.arraycopy(values, srcI, values, dstI, n);
|
||||
|
||||
if (dstI + n >= size) size = dstI + n;
|
||||
}
|
||||
|
||||
public void sort(Comparator<Object> comparator) {
|
||||
values.sort((a, b) -> {
|
||||
Arrays.sort(values, 0, size, (a, b) -> {
|
||||
var _a = 0;
|
||||
var _b = 0;
|
||||
|
||||
if (a == null) _a = 1;
|
||||
if (a == EMPTY) _a = 2;
|
||||
if (a == UNDEFINED) _a = 1;
|
||||
if (a == null) _a = 2;
|
||||
|
||||
if (b == null) _b = 1;
|
||||
if (b == EMPTY) _b = 2;
|
||||
if (b == UNDEFINED) _b = 1;
|
||||
if (b == null) _b = 2;
|
||||
|
||||
if (Integer.compare(_a, _b) != 0) return Integer.compare(_a, _b);
|
||||
if (_a != 0 || _b != 0) return Integer.compare(_a, _b);
|
||||
|
||||
return comparator.compare(a, b);
|
||||
});
|
||||
}
|
||||
|
||||
public Object[] toArray() {
|
||||
Object[] res = new Object[values.size()];
|
||||
|
||||
for (var i = 0; i < values.size(); i++) {
|
||||
if (values.get(i) == EMPTY) res[i] = null;
|
||||
else res[i] = values.get(i);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Object getField(Context ctx, Object key) throws InterruptedException {
|
||||
if (key.equals("length")) return values.size();
|
||||
if (key instanceof Number) {
|
||||
var i = ((Number)key).doubleValue();
|
||||
if (i >= 0 && i - Math.floor(i) == 0) {
|
||||
@@ -96,9 +134,6 @@ public class ArrayValue extends ObjectValue {
|
||||
}
|
||||
@Override
|
||||
protected boolean setField(Context ctx, Object key, Object val) throws InterruptedException {
|
||||
if (key.equals("length")) {
|
||||
return setSize((int)Values.toNumber(ctx, val));
|
||||
}
|
||||
if (key instanceof Number) {
|
||||
var i = Values.number(key);
|
||||
if (i >= 0 && i - Math.floor(i) == 0) {
|
||||
@@ -111,7 +146,6 @@ public class ArrayValue extends ObjectValue {
|
||||
}
|
||||
@Override
|
||||
protected boolean hasField(Context ctx, Object key) throws InterruptedException {
|
||||
if (key.equals("length")) return true;
|
||||
if (key instanceof Number) {
|
||||
var i = Values.number(key);
|
||||
if (i >= 0 && i - Math.floor(i) == 0) {
|
||||
@@ -140,18 +174,42 @@ public class ArrayValue extends ObjectValue {
|
||||
for (var i = 0; i < size(); i++) {
|
||||
if (has(i)) res.add(i);
|
||||
}
|
||||
if (includeNonEnumerable) res.add("length");
|
||||
return res;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Iterator<Object> iterator() {
|
||||
return new Iterator<Object>() {
|
||||
private int i = 0;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return i < size();
|
||||
}
|
||||
@Override
|
||||
public Object next() {
|
||||
if (!hasNext()) return null;
|
||||
return get(i++);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public ArrayValue() {
|
||||
super(PlaceholderProto.ARRAY);
|
||||
nonEnumerableSet.add("length");
|
||||
nonConfigurableSet.add("length");
|
||||
values = new Object[16];
|
||||
size = 0;
|
||||
}
|
||||
public ArrayValue(int cap) {
|
||||
super(PlaceholderProto.ARRAY);
|
||||
values = new Object[cap];
|
||||
size = 0;
|
||||
}
|
||||
public ArrayValue(Context ctx, Object ...values) {
|
||||
this();
|
||||
for (var i = 0; i < values.length; i++) this.values.add(Values.normalize(ctx, values[i]));
|
||||
this.values = new Object[values.length];
|
||||
size = values.length;
|
||||
|
||||
for (var i = 0; i < size; i++) this.values[i] = Values.normalize(ctx, values[i]);
|
||||
}
|
||||
|
||||
public static ArrayValue of(Context ctx, Collection<Object> values) {
|
||||
|
||||
@@ -3,7 +3,7 @@ package me.topchetoeu.jscript.engine.values;
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.FunctionContext;
|
||||
import me.topchetoeu.jscript.engine.Environment;
|
||||
import me.topchetoeu.jscript.engine.frame.CodeFrame;
|
||||
import me.topchetoeu.jscript.engine.scope.ValueVariable;
|
||||
|
||||
@@ -12,7 +12,7 @@ public class CodeFunction extends FunctionValue {
|
||||
public final int length;
|
||||
public final Instruction[] body;
|
||||
public final ValueVariable[] captures;
|
||||
public FunctionContext environment;
|
||||
public Environment environment;
|
||||
|
||||
public Location loc() {
|
||||
for (var instr : body) {
|
||||
@@ -29,10 +29,10 @@ public class CodeFunction extends FunctionValue {
|
||||
|
||||
@Override
|
||||
public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException {
|
||||
return new CodeFrame(ctx, thisArg, args, this).run(new Context(environment, ctx.message));
|
||||
return new CodeFrame(ctx, thisArg, args, this).run(ctx.setEnv(environment));
|
||||
}
|
||||
|
||||
public CodeFunction(FunctionContext environment, String name, int localsN, int length, ValueVariable[] captures, Instruction[] body) {
|
||||
public CodeFunction(Environment environment, String name, int localsN, int length, ValueVariable[] captures, Instruction[] body) {
|
||||
super(name, length);
|
||||
this.captures = captures;
|
||||
this.environment = environment;
|
||||
|
||||
@@ -8,7 +8,8 @@ public class NativeWrapper extends ObjectValue {
|
||||
|
||||
@Override
|
||||
public ObjectValue getPrototype(Context ctx) throws InterruptedException {
|
||||
if (prototype == NATIVE_PROTO) return ctx.function.wrappersProvider.getProto(wrapped.getClass());
|
||||
if (prototype == NATIVE_PROTO)
|
||||
return ctx.env.wrappersProvider.getProto(wrapped.getClass());
|
||||
else return super.getPrototype(ctx);
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ public class ObjectValue {
|
||||
|
||||
public final boolean memberWritable(Object key) {
|
||||
if (state == State.FROZEN) return false;
|
||||
return !nonWritableSet.contains(key);
|
||||
return !values.containsKey(key) || !nonWritableSet.contains(key);
|
||||
}
|
||||
public final boolean memberConfigurable(Object key) {
|
||||
if (state == State.SEALED || state == State.FROZEN) return false;
|
||||
@@ -147,13 +147,13 @@ public class ObjectValue {
|
||||
|
||||
public ObjectValue getPrototype(Context ctx) throws InterruptedException {
|
||||
try {
|
||||
if (prototype == OBJ_PROTO) return ctx.function.proto("object");
|
||||
if (prototype == ARR_PROTO) return ctx.function.proto("array");
|
||||
if (prototype == FUNC_PROTO) return ctx.function.proto("function");
|
||||
if (prototype == ERR_PROTO) return ctx.function.proto("error");
|
||||
if (prototype == RANGE_ERR_PROTO) return ctx.function.proto("rangeErr");
|
||||
if (prototype == SYNTAX_ERR_PROTO) return ctx.function.proto("syntaxErr");
|
||||
if (prototype == TYPE_ERR_PROTO) return ctx.function.proto("typeErr");
|
||||
if (prototype == OBJ_PROTO) return ctx.env.proto("object");
|
||||
if (prototype == ARR_PROTO) return ctx.env.proto("array");
|
||||
if (prototype == FUNC_PROTO) return ctx.env.proto("function");
|
||||
if (prototype == ERR_PROTO) return ctx.env.proto("error");
|
||||
if (prototype == RANGE_ERR_PROTO) return ctx.env.proto("rangeErr");
|
||||
if (prototype == SYNTAX_ERR_PROTO) return ctx.env.proto("syntaxErr");
|
||||
if (prototype == TYPE_ERR_PROTO) return ctx.env.proto("typeErr");
|
||||
}
|
||||
catch (NullPointerException e) {
|
||||
return null;
|
||||
@@ -172,14 +172,14 @@ public class ObjectValue {
|
||||
else if (Values.isObject(val)) {
|
||||
var obj = Values.object(val);
|
||||
|
||||
if (ctx != null && ctx.function != null) {
|
||||
if (obj == ctx.function.proto("object")) prototype = OBJ_PROTO;
|
||||
else if (obj == ctx.function.proto("array")) prototype = ARR_PROTO;
|
||||
else if (obj == ctx.function.proto("function")) prototype = FUNC_PROTO;
|
||||
else if (obj == ctx.function.proto("error")) prototype = ERR_PROTO;
|
||||
else if (obj == ctx.function.proto("syntaxErr")) prototype = SYNTAX_ERR_PROTO;
|
||||
else if (obj == ctx.function.proto("typeErr")) prototype = TYPE_ERR_PROTO;
|
||||
else if (obj == ctx.function.proto("rangeErr")) prototype = RANGE_ERR_PROTO;
|
||||
if (ctx != null && ctx.env != null) {
|
||||
if (obj == ctx.env.proto("object")) prototype = OBJ_PROTO;
|
||||
else if (obj == ctx.env.proto("array")) prototype = ARR_PROTO;
|
||||
else if (obj == ctx.env.proto("function")) prototype = FUNC_PROTO;
|
||||
else if (obj == ctx.env.proto("error")) prototype = ERR_PROTO;
|
||||
else if (obj == ctx.env.proto("syntaxErr")) prototype = SYNTAX_ERR_PROTO;
|
||||
else if (obj == ctx.env.proto("typeErr")) prototype = TYPE_ERR_PROTO;
|
||||
else if (obj == ctx.env.proto("rangeErr")) prototype = RANGE_ERR_PROTO;
|
||||
else prototype = obj;
|
||||
}
|
||||
else prototype = obj;
|
||||
@@ -233,7 +233,7 @@ public class ObjectValue {
|
||||
public final Object getMember(Context ctx, Object key, Object thisArg) throws InterruptedException {
|
||||
key = Values.normalize(ctx, key);
|
||||
|
||||
if (key.equals("__proto__")) {
|
||||
if ("__proto__".equals(key)) {
|
||||
var res = getPrototype(ctx);
|
||||
return res == null ? Values.NULL : res;
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
package me.topchetoeu.jscript.engine.values;
|
||||
|
||||
public final class SignalValue {
|
||||
public final String data;
|
||||
|
||||
public SignalValue(String data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public static boolean isSignal(Object signal, String value) {
|
||||
if (!(signal instanceof SignalValue)) return false;
|
||||
var val = ((SignalValue)signal).data;
|
||||
|
||||
if (value.endsWith("*")) return val.startsWith(value.substring(0, value.length() - 1));
|
||||
else return val.equals(value);
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,6 @@ public final class Symbol {
|
||||
@Override
|
||||
public String toString() {
|
||||
if (value == null) return "Symbol";
|
||||
else return "Symbol(" + value + ")";
|
||||
else return "@@" + value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,9 @@ import java.util.Map;
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.Operation;
|
||||
import me.topchetoeu.jscript.engine.frame.ConvertHint;
|
||||
import me.topchetoeu.jscript.exceptions.ConvertException;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.exceptions.SyntaxException;
|
||||
|
||||
public class Values {
|
||||
public static final Object NULL = new Object();
|
||||
@@ -82,7 +84,6 @@ public class Values {
|
||||
obj instanceof String ||
|
||||
obj instanceof Boolean ||
|
||||
obj instanceof Symbol ||
|
||||
obj instanceof SignalValue ||
|
||||
obj == null ||
|
||||
obj == NULL;
|
||||
}
|
||||
@@ -115,8 +116,8 @@ public class Values {
|
||||
public static double toNumber(Context ctx, Object obj) throws InterruptedException {
|
||||
var val = toPrimitive(ctx, obj, ConvertHint.VALUEOF);
|
||||
|
||||
if (val instanceof Number) return number(obj);
|
||||
if (val instanceof Boolean) return ((Boolean)obj) ? 1 : 0;
|
||||
if (val instanceof Number) return number(val);
|
||||
if (val instanceof Boolean) return ((Boolean)val) ? 1 : 0;
|
||||
if (val instanceof String) {
|
||||
try {
|
||||
return Double.parseDouble((String)val);
|
||||
@@ -132,7 +133,7 @@ public class Values {
|
||||
if (val == NULL) return "null";
|
||||
|
||||
if (val instanceof Number) {
|
||||
var d = number(obj);
|
||||
var d = number(val);
|
||||
if (d == Double.NEGATIVE_INFINITY) return "-Infinity";
|
||||
if (d == Double.POSITIVE_INFINITY) return "Infinity";
|
||||
if (Double.isNaN(d)) return "NaN";
|
||||
@@ -141,7 +142,6 @@ public class Values {
|
||||
if (val instanceof Boolean) return (Boolean)val ? "true" : "false";
|
||||
if (val instanceof String) return (String)val;
|
||||
if (val instanceof Symbol) return ((Symbol)val).toString();
|
||||
if (val instanceof SignalValue) return "[signal '" + ((SignalValue)val).data + "']";
|
||||
|
||||
return "Unknown value";
|
||||
}
|
||||
@@ -321,10 +321,10 @@ public class Values {
|
||||
if (isObject(obj)) return object(obj).getPrototype(ctx);
|
||||
if (ctx == null) return null;
|
||||
|
||||
if (obj instanceof String) return ctx.function.proto("string");
|
||||
else if (obj instanceof Number) return ctx.function.proto("number");
|
||||
else if (obj instanceof Boolean) return ctx.function.proto("bool");
|
||||
else if (obj instanceof Symbol) return ctx.function.proto("symbol");
|
||||
if (obj instanceof String) return ctx.env.proto("string");
|
||||
else if (obj instanceof Number) return ctx.env.proto("number");
|
||||
else if (obj instanceof Boolean) return ctx.env.proto("bool");
|
||||
else if (obj instanceof Symbol) return ctx.env.proto("symbol");
|
||||
|
||||
return null;
|
||||
}
|
||||
@@ -352,12 +352,38 @@ public class Values {
|
||||
|
||||
return res;
|
||||
}
|
||||
public static ObjectValue getMemberDescriptor(Context ctx, Object obj, Object key) throws InterruptedException {
|
||||
if (obj instanceof ObjectValue) return ((ObjectValue)obj).getMemberDescriptor(ctx, key);
|
||||
else if (obj instanceof String && key instanceof Number) {
|
||||
var i = ((Number)key).intValue();
|
||||
var _i = ((Number)key).doubleValue();
|
||||
if (i - _i != 0) return null;
|
||||
if (i < 0 || i >= ((String)obj).length()) return null;
|
||||
|
||||
return new ObjectValue(ctx, Map.of(
|
||||
"value", ((String)obj).charAt(i) + "",
|
||||
"writable", false,
|
||||
"enumerable", true,
|
||||
"configurable", false
|
||||
));
|
||||
}
|
||||
else return null;
|
||||
}
|
||||
|
||||
public static Object call(Context ctx, Object func, Object thisArg, Object ...args) throws InterruptedException {
|
||||
if (!isFunction(func))
|
||||
throw EngineException.ofType("Tried to call a non-function value.");
|
||||
if (!isFunction(func)) throw EngineException.ofType("Tried to call a non-function value.");
|
||||
return function(func).call(ctx, thisArg, args);
|
||||
}
|
||||
public static Object callNew(Context ctx, Object func, Object ...args) throws InterruptedException {
|
||||
var res = new ObjectValue();
|
||||
var proto = Values.getMember(ctx, func, "prototype");
|
||||
res.setPrototype(ctx, proto);
|
||||
|
||||
var ret = call(ctx, func, res, args);
|
||||
|
||||
if (ret != null && func instanceof FunctionValue && ((FunctionValue)func).special) return ret;
|
||||
return res;
|
||||
}
|
||||
|
||||
public static boolean strictEquals(Context ctx, Object a, Object b) {
|
||||
a = normalize(ctx, a); b = normalize(ctx, b);
|
||||
@@ -420,7 +446,7 @@ public class Values {
|
||||
|
||||
if (val instanceof Class) {
|
||||
if (ctx == null) return null;
|
||||
else return ctx.function.wrappersProvider.getConstr((Class<?>)val);
|
||||
else return ctx.env.wrappersProvider.getConstr((Class<?>)val);
|
||||
}
|
||||
|
||||
return new NativeWrapper(val);
|
||||
@@ -429,17 +455,15 @@ public class Values {
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <T> T convert(Context ctx, Object obj, Class<T> clazz) throws InterruptedException {
|
||||
if (clazz == Void.class) return null;
|
||||
if (clazz == null || clazz == Object.class) return (T)obj;
|
||||
|
||||
var err = new IllegalArgumentException(String.format("Cannot convert '%s' to '%s'.", type(obj), clazz.getName()));
|
||||
|
||||
if (obj instanceof NativeWrapper) {
|
||||
var res = ((NativeWrapper)obj).wrapped;
|
||||
if (clazz.isInstance(res)) return (T)res;
|
||||
}
|
||||
|
||||
if (clazz == null || clazz == Object.class) return (T)obj;
|
||||
|
||||
if (obj instanceof ArrayValue) {
|
||||
|
||||
if (clazz.isAssignableFrom(ArrayList.class)) {
|
||||
var raw = array(obj).toArray();
|
||||
var res = new ArrayList<>();
|
||||
@@ -480,11 +504,9 @@ public class Values {
|
||||
|
||||
if (clazz == Character.class || clazz == char.class) {
|
||||
if (obj instanceof Number) return (T)(Character)(char)number(obj);
|
||||
else if (obj == NULL) throw new IllegalArgumentException("Cannot convert null to character.");
|
||||
else if (obj == null) throw new IllegalArgumentException("Cannot convert undefined to character.");
|
||||
else {
|
||||
var res = toString(ctx, obj);
|
||||
if (res.length() == 0) throw new IllegalArgumentException("Cannot convert empty string to character.");
|
||||
if (res.length() == 0) throw new ConvertException("\"\"", "Character");
|
||||
else return (T)(Character)res.charAt(0);
|
||||
}
|
||||
}
|
||||
@@ -492,30 +514,32 @@ public class Values {
|
||||
if (obj == null) return null;
|
||||
if (clazz.isInstance(obj)) return (T)obj;
|
||||
|
||||
throw err;
|
||||
throw new ConvertException(type(obj), clazz.getSimpleName());
|
||||
}
|
||||
|
||||
public static Iterable<Object> toJavaIterable(Context ctx, Object obj) throws InterruptedException {
|
||||
public static Iterable<Object> toJavaIterable(Context ctx, Object obj) {
|
||||
return () -> {
|
||||
try {
|
||||
var constr = getMember(ctx, ctx.function.proto("symbol"), "constructor");
|
||||
var symbol = getMember(ctx, constr, "iterator");
|
||||
var symbol = ctx.env.symbol("Symbol.iterator");
|
||||
|
||||
var iteratorFunc = getMember(ctx, obj, symbol);
|
||||
if (!isFunction(iteratorFunc)) return Collections.emptyIterator();
|
||||
var iterator = getMember(ctx, call(ctx, iteratorFunc, obj), "next");
|
||||
if (!isFunction(iterator)) return Collections.emptyIterator();
|
||||
var iterable = obj;
|
||||
var iterator = iteratorFunc instanceof FunctionValue ?
|
||||
((FunctionValue)iteratorFunc).call(ctx, obj, obj) :
|
||||
iteratorFunc;
|
||||
var nextFunc = getMember(ctx, call(ctx, iteratorFunc, obj), "next");
|
||||
|
||||
if (!isFunction(nextFunc)) return Collections.emptyIterator();
|
||||
|
||||
return new Iterator<Object>() {
|
||||
private Object value = null;
|
||||
public boolean consumed = true;
|
||||
private FunctionValue next = function(iterator);
|
||||
private FunctionValue next = (FunctionValue)nextFunc;
|
||||
|
||||
private void loadNext() throws InterruptedException {
|
||||
if (next == null) value = null;
|
||||
else if (consumed) {
|
||||
var curr = object(next.call(ctx, iterable));
|
||||
var curr = object(next.call(ctx, iterator));
|
||||
if (curr == null) { next = null; value = null; }
|
||||
if (toBoolean(curr.getMember(ctx, "done"))) { next = null; value = null; }
|
||||
else {
|
||||
@@ -562,12 +586,11 @@ public class Values {
|
||||
};
|
||||
}
|
||||
|
||||
public static ObjectValue fromJavaIterable(Context ctx, Iterable<?> iterable) throws InterruptedException {
|
||||
public static ObjectValue fromJavaIterator(Context ctx, Iterator<?> it) throws InterruptedException {
|
||||
var res = new ObjectValue();
|
||||
var it = iterable.iterator();
|
||||
|
||||
try {
|
||||
var key = getMember(ctx, getMember(ctx, ctx.function.proto("symbol"), "constructor"), "iterator");
|
||||
var key = getMember(ctx, getMember(ctx, ctx.env.proto("symbol"), "constructor"), "iterator");
|
||||
res.defineProperty(ctx, key, new NativeFunction("", (_ctx, thisArg, args) -> thisArg));
|
||||
}
|
||||
catch (IllegalArgumentException | NullPointerException e) { }
|
||||
@@ -580,6 +603,10 @@ public class Values {
|
||||
return res;
|
||||
}
|
||||
|
||||
public static ObjectValue fromJavaIterable(Context ctx, Iterable<?> it) throws InterruptedException {
|
||||
return fromJavaIterator(ctx, it.iterator());
|
||||
}
|
||||
|
||||
private static void printValue(Context ctx, Object val, HashSet<Object> passed, int tab) throws InterruptedException {
|
||||
if (passed.contains(val)) {
|
||||
System.out.print("[circular]");
|
||||
@@ -604,7 +631,7 @@ public class Values {
|
||||
if (i != 0) System.out.print(", ");
|
||||
else System.out.print(" ");
|
||||
if (obj.has(i)) printValue(ctx, obj.get(i), passed, tab);
|
||||
else System.out.print(", ");
|
||||
else System.out.print("<empty>");
|
||||
}
|
||||
System.out.print(" ] ");
|
||||
}
|
||||
@@ -655,4 +682,25 @@ public class Values {
|
||||
public static void printValue(Context ctx, Object val) throws InterruptedException {
|
||||
printValue(ctx, val, new HashSet<>(), 0);
|
||||
}
|
||||
public static void printError(RuntimeException err, String prefix) throws InterruptedException {
|
||||
prefix = prefix == null ? "Uncaught" : "Uncaught " + prefix;
|
||||
try {
|
||||
if (err instanceof EngineException) {
|
||||
System.out.println(prefix + " " + ((EngineException)err).toString(((EngineException)err).ctx));
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
catch (EngineException ex) {
|
||||
System.out.println("Uncaught ");
|
||||
Values.printValue(null, ((EngineException)err).value);
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
11
src/me/topchetoeu/jscript/exceptions/ConvertException.java
Normal file
11
src/me/topchetoeu/jscript/exceptions/ConvertException.java
Normal file
@@ -0,0 +1,11 @@
|
||||
package me.topchetoeu.jscript.exceptions;
|
||||
|
||||
public class ConvertException extends RuntimeException {
|
||||
public final String source, target;
|
||||
|
||||
public ConvertException(String source, String target) {
|
||||
super(String.format("Cannot convert '%s' to '%s'.", source, target));
|
||||
this.source = source;
|
||||
this.target = target;
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto;
|
||||
public class EngineException extends RuntimeException {
|
||||
public final Object value;
|
||||
public EngineException cause;
|
||||
public Context ctx = null;
|
||||
public final List<String> stackTrace = new ArrayList<>();
|
||||
|
||||
public EngineException add(String name, Location location) {
|
||||
@@ -27,6 +28,10 @@ public class EngineException extends RuntimeException {
|
||||
this.cause = cause;
|
||||
return this;
|
||||
}
|
||||
public EngineException setContext(Context ctx) {
|
||||
this.ctx = ctx;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String toString(Context ctx) throws InterruptedException {
|
||||
var ss = new StringBuilder();
|
||||
@@ -36,16 +41,17 @@ public class EngineException extends RuntimeException {
|
||||
catch (EngineException e) {
|
||||
ss.append("[Error while stringifying]\n");
|
||||
}
|
||||
for (var line : stackTrace) {
|
||||
ss.append(" ").append(line).append('\n');
|
||||
}
|
||||
if (cause != null) ss.append("Caused by ").append(cause.toString(ctx)).append('\n');
|
||||
// for (var line : stackTrace) {
|
||||
// ss.append(" ").append(line).append('\n');
|
||||
// }
|
||||
// if (cause != null) ss.append("Caused by ").append(cause.toString(ctx)).append('\n');
|
||||
ss.deleteCharAt(ss.length() - 1);
|
||||
return ss.toString();
|
||||
}
|
||||
|
||||
private static Object err(String msg, PlaceholderProto proto) {
|
||||
private static Object err(String name, String msg, PlaceholderProto proto) {
|
||||
var res = new ObjectValue(proto);
|
||||
if (name != null) res.defineProperty(null, "name", name);
|
||||
res.defineProperty(null, "message", msg);
|
||||
return res;
|
||||
}
|
||||
@@ -57,19 +63,22 @@ public class EngineException extends RuntimeException {
|
||||
this.cause = null;
|
||||
}
|
||||
|
||||
public static EngineException ofError(String name, String msg) {
|
||||
return new EngineException(err(name, msg, PlaceholderProto.ERROR));
|
||||
}
|
||||
public static EngineException ofError(String msg) {
|
||||
return new EngineException(err(msg, PlaceholderProto.ERROR));
|
||||
return new EngineException(err(null, msg, PlaceholderProto.ERROR));
|
||||
}
|
||||
public static EngineException ofSyntax(SyntaxException e) {
|
||||
return new EngineException(err(e.msg, PlaceholderProto.SYNTAX_ERROR)).add(null, e.loc);
|
||||
return new EngineException(err(null, e.msg, PlaceholderProto.SYNTAX_ERROR)).add(null, e.loc);
|
||||
}
|
||||
public static EngineException ofSyntax(String msg) {
|
||||
return new EngineException(err(msg, PlaceholderProto.SYNTAX_ERROR));
|
||||
return new EngineException(err(null, msg, PlaceholderProto.SYNTAX_ERROR));
|
||||
}
|
||||
public static EngineException ofType(String msg) {
|
||||
return new EngineException(err(msg, PlaceholderProto.TYPE_ERROR));
|
||||
return new EngineException(err(null, msg, PlaceholderProto.TYPE_ERROR));
|
||||
}
|
||||
public static EngineException ofRange(String msg) {
|
||||
return new EngineException(err(msg, PlaceholderProto.RANGE_ERROR));
|
||||
return new EngineException(err(null, msg, PlaceholderProto.RANGE_ERROR));
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user