Compare commits

..

62 Commits

Author SHA1 Message Date
df8465cb49 Merge pull request #8 from TopchetoEU/TopchetoEU/tests
Integrate typescript
2023-11-05 20:32:42 +02:00
e3104c223c fix: remove some unnececeary logs 2023-11-05 20:29:21 +02:00
1d0bae3de8 feat: include typescript code in source code 2023-11-05 20:27:23 +02:00
b66acd3089 fix: several more fixes 2023-11-05 19:44:44 +02:00
e326847287 feat: send value stack to debug client 2023-11-05 19:44:35 +02:00
26591d6631 fix: lazy operators incorrectly pop values from stack 2023-11-05 19:44:08 +02:00
af31b1ab79 fix: several small fixes 2023-11-05 19:43:53 +02:00
f885d4349f refactor: fix some bad code >:( 2023-11-05 19:43:28 +02:00
d57044acb7 fix: several bug fixes to help with typescript support 2023-11-05 12:44:29 +02:00
7df4e3b03f fix: oops 2023-11-04 11:43:57 +02:00
ed1009ab69 refactor: some code restructuring in the debugging 2023-11-04 11:42:06 +02:00
f856cdf37e fix: messages larger than 64KB are now fragmented properly 2023-11-04 11:41:31 +02:00
4f82574b8c fix: various small behavioural issues
fix: pesky try-catch logic
2023-11-04 11:40:50 +02:00
0ae24148d8 feat: write some tests 2023-11-04 11:38:48 +02:00
ac128d17f4 feat: implement Array.reduce
fix: native functions are now named
2023-11-04 11:38:29 +02:00
6508f15bb0 refactor: remove typescript source code from repo (for now) 2023-11-04 11:37:13 +02:00
69f93b4f87 refactor: make filenames more consistent 2023-11-04 11:36:36 +02:00
b675411925 fix: a lot of minor bugs 2023-10-29 23:47:48 +02:00
d1e93c2088 Merge branch 'master' of https://github.com/TopchetoEU/java-jscript 2023-10-29 12:33:18 +02:00
942db54546 feat: improve vscode debugging compatibility 2023-10-29 12:30:43 +02:00
d20df66982 feat: improve vscode debugging compatibility 2023-10-29 12:25:33 +02:00
16a9e5d761 Merge pull request #7 from TopchetoEU/TopcehtoEU/debugging
Debugging support
2023-10-28 17:11:55 +03:00
dc9d84a370 chore: nothing of use 2023-10-28 17:10:27 +03:00
edb71daef4 feat: fully implement local and capture scope object wrappers
feat: implement object sending and receiving
2023-10-28 16:58:33 +03:00
4b84309df6 feat: complete steptrough and breakpoints in debugger 2023-10-27 15:12:14 +03:00
d2d9fa9738 fix: exit now works 2023-10-07 14:08:47 +03:00
a4e5f7f471 refactor: clean up useless catch blocks 2023-10-07 13:55:44 +03:00
cc044374ba refactor: replace InterruptedException with unchecked alternative 2023-10-07 13:42:55 +03:00
517e3e6657 refactor: clean up context and stack data 2023-10-07 13:07:07 +03:00
926b9c17d8 feat: readd JSON to lib 2023-10-06 18:42:35 +03:00
fc705e7383 feat: readd date internals 2023-10-06 12:03:33 +03:00
a17ec737b7 refactor: rename polyfills to libs 2023-10-06 11:57:29 +03:00
952a4d631d fix: keep order of object fields 2023-10-04 22:06:49 +03:00
005610ca40 Merge pull request #6 from TopchetoEU/TopchetoEU/function-optimizations
Cache function bodies
2023-10-04 21:38:29 +03:00
9743a6c078 fix: cache function bodies 2023-10-04 21:34:33 +03:00
21a6d20ac5 refactor: some code cleanup, emit better bytecode 2023-10-04 12:16:06 +03:00
4d1846f082 fix: reconfigure workflow and build.js 2023-10-04 09:00:44 +03:00
9547c86b32 Merge pull request #5 from TopchetoEU/TopchetoEU/corelib-reprogramming
Core library reprogramming
2023-10-04 08:50:26 +03:00
1cccfa90a8 refactor: remove pesky node files 2023-10-04 08:49:49 +03:00
604b752be6 feat: add global functions 2023-10-04 08:49:20 +03:00
6c7fe6deaf feat: add old data to both subcontexts 2023-10-04 08:30:33 +03:00
68f3b6d926 feat: fully remove typescript init code 2023-10-04 08:10:26 +03:00
63b04019cf feat: implement timers in java 2023-09-28 10:25:32 +03:00
9c65bacbac fix: type name can f itself 2023-09-28 09:38:51 +03:00
0dacaaeb4c a lot of fixes 2023-09-27 15:08:23 +03:00
c1b84689c4 fix: rewrite try-catch-finally logic 2023-09-27 10:27:19 +03:00
bd08902196 fix: revert back to old error protocol 2023-09-27 10:27:04 +03:00
22ec95a7b5 feat: implement errors 2023-09-26 22:54:12 +03:00
6c71911575 feat: implement symbols in java 2023-09-26 11:29:49 +03:00
e16c0fedb1 feat: implement map and set polyfills in java 2023-09-26 08:31:27 +03:00
cf36b7adc5 fix: some string polyfill fixes 2023-09-25 18:33:07 +03:00
ff9b57aeb9 fix: remove string from core.ts 2023-09-25 18:32:59 +03:00
47c62128ab feat: implement string polyfill in java 2023-09-25 18:18:36 +03:00
4aaf2f26db feat: add some standard functions to Number 2023-09-24 20:50:53 +03:00
f21cdc831c fix: primitive values will be native classes now 2023-09-24 14:15:12 +03:00
86b206051d feat: implement bool polyfills in java 2023-09-24 13:49:37 +03:00
f2cd50726d feat: implement array polyfills in java 2023-09-24 12:56:53 +03:00
d8071af480 refactor: remove dead .ts code 2023-09-21 10:50:48 +03:00
0b7442a3d8 fix: remove raw native funcs, use this arg passing instead 2023-09-21 10:50:03 +03:00
356a5a5b78 feat: a lot of typescript corelibs translated to java 2023-09-20 23:02:23 +03:00
da4b35f506 feat: Create filesystem interface and a physical filesystem 2023-09-18 10:31:50 +03:00
8c049ac08f fix: forgor to delete this 2023-09-09 19:02:55 +03:00
191 changed files with 9287 additions and 5360 deletions

View File

@@ -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"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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,
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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
View File

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

View File

@@ -1 +0,0 @@
{}

BIN
src/assets/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

30
src/assets/index.html Normal file
View File

@@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JScript Debugger</title>
</head>
<body>
<p>
This is the debugger of JScript. It implement the <a href="https://chromedevtools.github.io/devtools-protocol/1-2/">V8 Debugging protocol</a>,
so you can use the devtools in chrome. <br>
The debugger is still in early development, so please report any issues to
<a href="https://github.com/TopchetoEU/java-jscript/issues">the github repo</a>.
</p>
<p>
Here are the available entrypoints:
<ul>
<li><a href="json/version">/json/version</a> - version and other stuff about the JScript engine</li>
<li><a href="json/list">/json/list</a> - a list of all entrypoints</li>
<li><a href="json/protocol">/json/protocol</a> - documentation of the implemented V8 protocol</li>
<li>/(any target) - websocket entrypoints for debugging</li>
</ul>
</p>
<p>
Running ${NAME} v${VERSION} by ${AUTHOR}
</p>
</body>
</html>

2007
src/assets/protocol.json Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,54 @@
package me.topchetoeu.jscript;
import java.io.File;
public class Filename {
public final String protocol;
public final String path;
public String toString() {
return protocol + "://" + path;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + protocol.hashCode();
result = prime * result + path.hashCode();
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null) return false;
if (getClass() != obj.getClass()) return false;
var other = (Filename) obj;
if (protocol == null) {
if (other.protocol != null) return false;
}
else if (!protocol.equals(other.protocol)) return false;
if (path == null) {
if (other.path != null) return false;
}
else if (!path.equals(other.path)) return false;
return true;
}
public static Filename fromFile(File file) {
return new Filename("file", file.getAbsolutePath());
}
public Filename(String protocol, String path) {
path = path.trim();
protocol = protocol.trim();
this.protocol = protocol;
this.path = path;
}
}

View File

@@ -1,18 +1,18 @@
package me.topchetoeu.jscript;
public class Location {
public static final Location INTERNAL = new Location(0, 0, "<internal>");
public class Location implements Comparable<Location> {
public static final Location INTERNAL = new Location(0, 0, new Filename("jscript", "native"));
private int line;
private int start;
private String filename;
private Filename filename;
public int line() { return line; }
public int start() { return start; }
public String filename() { return filename; }
public Filename filename() { return filename; }
@Override
public String toString() {
return filename + ":" + line + ":" + start;
return filename.toString() + ":" + line + ":" + start;
}
public Location add(int n, boolean clone) {
@@ -55,7 +55,18 @@ public class Location {
return true;
}
public Location(int line, int start, String filename) {
@Override
public int compareTo(Location other) {
int a = filename.toString().compareTo(other.filename.toString());
int b = Integer.compare(line, other.line);
int c = Integer.compare(start, other.start);
if (a != 0) return a;
if (b != 0) return b;
return c;
}
public Location(int line, int start, Filename filename) {
this.line = line;
this.start = start;
this.filename = filename;

View File

@@ -1,149 +1,153 @@
package me.topchetoeu.jscript;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.nio.file.Path;
import me.topchetoeu.jscript.engine.MessageContext;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Engine;
import me.topchetoeu.jscript.engine.FunctionContext;
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;
public static String streamToString(InputStream in) {
try {
StringBuilder out = new StringBuilder();
BufferedReader br = new BufferedReader(new InputStreamReader(in));
for(var line = br.readLine(); line != null; line = br.readLine()) {
out.append(line).append('\n');
}
br.close();
return out.toString();
}
catch (IOException e) {
return null;
}
}
public static String resourceToString(String name) {
var str = Main.class.getResourceAsStream("/me/topchetoeu/jscript/" + name);
if (str == null) return null;
return streamToString(str);
}
private static Observer<Object> valuePrinter = new Observer<Object>() {
public void next(Object 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;
}
}
};
public static void main(String args[]) {
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);
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 Context(builderEnv, new MessageContext(engine)), "core.js", resourceToString("js/core.js"), null, env, new Internals()).toObservable().on(valuePrinter);
task = engine.start();
var reader = new Thread(() -> {
try {
while (true) {
try {
var raw = in.readLine();
if (raw == null) break;
engine.pushMsg(false, new Context(env, new MessageContext(engine)), "<stdio>", raw, null).toObservable().once(valuePrinter);
}
catch (EngineException e) {
try {
System.out.println("Uncaught " + e.toString(null));
}
catch (EngineException ex) {
System.out.println("Uncaught [error while converting to string]");
}
}
}
}
catch (IOException e) {
e.printStackTrace();
return;
}
catch (SyntaxException ex) {
if (exited[0]) return;
System.out.println("Syntax error:" + ex.msg);
}
catch (RuntimeException ex) {
if (exited[0]) return;
System.out.println("Internal error ocurred:");
ex.printStackTrace();
}
catch (InterruptedException e) { return; }
if (exited[0]) return;
});
reader.setDaemon(true);
reader.setName("STD Reader");
reader.start();
}
}
package me.topchetoeu.jscript;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.file.Files;
import java.nio.file.Path;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Engine;
import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.StackData;
import me.topchetoeu.jscript.engine.debug.DebugServer;
import me.topchetoeu.jscript.engine.debug.SimpleDebugger;
import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.NativeFunction;
import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.events.Observer;
import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.InterruptException;
import me.topchetoeu.jscript.exceptions.SyntaxException;
import me.topchetoeu.jscript.lib.Internals;
public class Main {
static Thread engineTask, debugTask;
static Engine engine;
static Environment env;
static int j = 0;
private static Observer<Object> valuePrinter = new Observer<Object>() {
public void next(Object data) {
Values.printValue(null, data);
System.out.println();
}
public void error(RuntimeException err) {
Values.printError(err, null);
}
@Override
public void finish() {
engineTask.interrupt();
}
};
public static void main(String args[]) {
System.out.println(String.format("Running %s v%s by %s", Metadata.NAME, Metadata.VERSION, Metadata.AUTHOR));
engine = new Engine(true);
env = new Environment(null, null, null);
var exited = new boolean[1];
var server = new DebugServer();
server.targets.put("target", (ws, req) -> new SimpleDebugger(ws, engine));
engineTask = engine.start();
debugTask = server.start(new InetSocketAddress("127.0.0.1", 9229), true);
engine.pushMsg(false, null, new NativeFunction((ctx, thisArg, _a) -> {
new Internals().apply(env);
env.global.define("exit", _ctx -> {
exited[0] = true;
throw new InterruptException();
});
env.global.define("go", _ctx -> {
try {
var f = Path.of("do.js");
var func = _ctx.compile(new Filename("do", "do/" + j++ + ".js"), new String(Files.readAllBytes(f)));
return func.call(_ctx);
}
catch (IOException e) {
throw new EngineException("Couldn't open do.js");
}
});
// TODO: make better API
env.global.define(true, new NativeFunction("include", (_ctx, th, __args) -> {
try {
var currFilename = StackData.peekFrame(_ctx).function.loc().filename();
var loc = Path.of("").toAbsolutePath();
if (currFilename.protocol.equals("file")) loc = Path.of(currFilename.path).getParent();
var path = loc.resolve(Path.of(__args.length >= 1 ? Values.toString(_ctx, __args[0]) : ""));
var src = Files.readString(path);
var func = _ctx.compile(Filename.fromFile(path.toFile()), src);
var callArgs = new ArrayValue();
if (__args.length >= 2 && __args[1] instanceof ArrayValue) callArgs = (ArrayValue)__args[1];
return func.call(_ctx, null, callArgs);
}
catch (IOException e) { throw EngineException.ofError("IOError", "Couldn't open file."); }
}));
return null;
}), null).await();
try {
var tsEnv = env.child();
tsEnv.global.define(null, "module", false, new ObjectValue());
engine.pushMsg(
false, new Context(engine).pushEnv(tsEnv),
new Filename("jscript", "ts.js"),
Reading.resourceToString("js/ts.js"), null
).await();
System.out.println("Loaded typescript!");
var ctx = new Context(engine).pushEnv(env.child());
engine.pushMsg(
false, ctx,
new Filename("jscript", "internals/bootstrap.js"), Reading.resourceToString("js/bootstrap.js"), null,
tsEnv.global.get(ctx, "ts"), env, new ArrayValue(null, Reading.resourceToString("js/lib.d.ts"))
).await();
}
catch (EngineException e) {
Values.printError(e, "(while initializing TS)");
}
var reader = new Thread(() -> {
try {
for (var arg : args) {
try {
var file = Path.of(arg);
var raw = Files.readString(file);
valuePrinter.next(engine.pushMsg(false, new Context(engine).pushEnv(env), Filename.fromFile(file.toFile()), raw, null).await());
}
catch (EngineException e) { Values.printError(e, ""); }
}
for (var i = 0; ; i++) {
try {
var raw = Reading.read();
if (raw == null) break;
valuePrinter.next(engine.pushMsg(false, new Context(engine).pushEnv(env), new Filename("jscript", "repl/" + i + ".js"), raw, null).await());
}
catch (EngineException e) { Values.printError(e, ""); }
}
}
catch (IOException e) { exited[0] = true; }
catch (SyntaxException ex) {
if (exited[0]) return;
System.out.println("Syntax error:" + ex.msg);
}
catch (RuntimeException ex) {
if (!exited[0]) {
System.out.println("Internal error ocurred:");
ex.printStackTrace();
}
}
if (exited[0]) debugTask.interrupt();
});
reader.setDaemon(true);
reader.setName("STD Reader");
reader.start();
}
}

View File

@@ -0,0 +1,36 @@
package me.topchetoeu.jscript;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import me.topchetoeu.jscript.exceptions.UncheckedException;
public class Reading {
private static final BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
public static synchronized String read() throws IOException {
return reader.readLine();
}
public static String streamToString(InputStream in) {
try {
StringBuilder out = new StringBuilder();
BufferedReader br = new BufferedReader(new InputStreamReader(in));
for(var line = br.readLine(); line != null; line = br.readLine()) {
out.append(line).append('\n');
}
br.close();
return out.toString();
}
catch (Throwable e) { throw new UncheckedException(e); }
}
public static String resourceToString(String name) {
var str = Main.class.getResourceAsStream("/me/topchetoeu/jscript/" + name);
if (str == null) return null;
return streamToString(str);
}
}

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,38 @@
package me.topchetoeu.jscript.compilation;
import java.util.Map;
import java.util.TreeSet;
import java.util.Vector;
import me.topchetoeu.jscript.Location;
public class CompileTarget {
public final Vector<Instruction> target = new Vector<>();
public final Map<Long, FunctionBody> functions;
public final TreeSet<Location> breakpoints;
public Instruction add(Instruction instr) {
target.add(instr);
return instr;
}
public Instruction set(int i, Instruction instr) {
return target.set(i, instr);
}
public void setDebug(int i) {
breakpoints.add(target.get(i).location);
}
public void setDebug() {
setDebug(target.size() - 1);
}
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, FunctionBody> functions, TreeSet<Location> breakpoints) {
this.functions = functions;
this.breakpoints = breakpoints;
}
}

View File

@@ -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;
@@ -12,16 +11,7 @@ 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;
}
public Location end;
@Override
public void declare(ScopeRecord varsScope) {
@@ -31,28 +21,33 @@ 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();
((FunctionStatement)stm).compile(target, scope, null, true);
target.get(start).setDebug(true);
target.setDebug(start);
target.add(Instruction.discard());
}
}
for (var i = 0; i < statements.length; i++) {
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);
}
if (end != null) {
target.add(Instruction.nop().locate(end));
target.setDebug();
}
}
@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();
@@ -70,6 +65,11 @@ public class CompoundStatement extends Statement {
else return new CompoundStatement(loc(), res.toArray(Statement[]::new));
}
public CompoundStatement setEnd(Location loc) {
this.end = loc;
return this;
}
public CompoundStatement(Location loc, Statement ...statements) {
super(loc);
this.statements = statements;

View File

@@ -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() {

View File

@@ -0,0 +1,17 @@
package me.topchetoeu.jscript.compilation;
public class FunctionBody {
public final Instruction[] instructions;
public final String[] captureNames, localNames;
public FunctionBody(Instruction[] instructions, String[] captureNames, String[] localNames) {
this.instructions = instructions;
this.captureNames = captureNames;
this.localNames = localNames;
}
public FunctionBody(Instruction[] instructions) {
this.instructions = instructions;
this.captureNames = new String[0];
this.localNames = new String[0];
}
}

View File

@@ -7,7 +7,6 @@ import me.topchetoeu.jscript.exceptions.SyntaxException;
public class Instruction {
public static enum Type {
RETURN,
SIGNAL,
THROW,
THROW_SYNTAX,
DELETE,
@@ -91,16 +90,11 @@ public class Instruction {
public final Type type;
public final Object[] params;
public Location location;
public boolean debugged;
public Instruction locate(Location loc) {
this.location = loc;
return this;
}
public Instruction setDebug(boolean debug) {
debugged = debug;
return this;
}
@SuppressWarnings("unchecked")
public <T> T get(int i) {
@@ -153,21 +147,7 @@ public class Instruction {
public static Instruction debug() {
return new Instruction(null, Type.NOP, "debug");
}
public static Instruction debugVarNames(String[] names) {
var args = new Object[names.length + 1];
args[0] = "dbg_vars";
System.arraycopy(names, 0, args, 1, names.length);
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 +201,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];
@@ -271,8 +251,8 @@ public class Instruction {
return new Instruction(null, Type.TYPEOF, varName);
}
public static Instruction keys() {
return new Instruction(null, Type.KEYS);
public static Instruction keys(boolean forInFormat) {
return new Instruction(null, Type.KEYS, forInFormat);
}
public static Instruction defProp() {

View File

@@ -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.setDebug(start);
}
public Location loc() { return _loc; }

View File

@@ -10,17 +10,17 @@ public class VariableDeclareStatement extends Statement {
public static class Pair {
public final String name;
public final Statement value;
public final Location location;
public Pair(String name, Statement value) {
public Pair(String name, Statement value, Location location) {
this.name = name;
this.value = value;
this.location = location;
}
}
public final List<Pair> values;
@Override
public boolean pollutesStack() { return false; }
@Override
public void declare(ScopeRecord varsScope) {
for (var key : values) {
@@ -28,21 +28,27 @@ 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);
if (key instanceof String) target.add(Instruction.makeVar((String)key).locate(loc()));
int start = target.size();
if (key instanceof String) target.add(Instruction.makeVar((String)key).locate(entry.location));
if (entry.value instanceof FunctionStatement) {
((FunctionStatement)entry.value).compile(target, scope, entry.name, false);
target.add(Instruction.storeVar(key).locate(loc()));
target.add(Instruction.storeVar(key).locate(entry.location));
}
else if (entry.value != null) {
entry.value.compileWithPollution(target, scope);
target.add(Instruction.storeVar(key).locate(loc()));
entry.value.compile(target, scope, true);
target.add(Instruction.storeVar(key).locate(entry.location));
}
if (target.size() != start) target.setDebug(start);
}
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
}
public VariableDeclareStatement(Location loc, List<Pair> values) {

View File

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

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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) {

View File

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

View File

@@ -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;
@@ -13,9 +12,7 @@ public class ForInStatement extends Statement {
public final boolean isDeclaration;
public final Statement varValue, object, body;
public final String label;
@Override
public boolean pollutesStack() { return false; }
public final Location varLocation;
@Override
public void declare(ScopeRecord globScope) {
@@ -24,54 +21,48 @@ 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);
int first = target.size();
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);
target.add(Instruction.keys());
object.compileWithDebug(target, scope, true);
target.add(Instruction.keys(true));
int start = target.size();
target.add(Instruction.dup());
target.add(Instruction.loadMember("length"));
target.add(Instruction.loadValue(0));
target.add(Instruction.operation(Operation.LESS_EQUALS));
target.add(Instruction.loadValue(null));
target.add(Instruction.operation(Operation.EQUALS));
int mid = target.size();
target.add(Instruction.nop());
target.add(Instruction.dup());
target.add(Instruction.dup());
target.add(Instruction.loadMember("length"));
target.add(Instruction.loadValue(1));
target.add(Instruction.operation(Operation.SUBTRACT));
target.add(Instruction.dup(1, 2));
target.add(Instruction.loadValue("length"));
target.add(Instruction.dup(1, 2));
target.add(Instruction.storeMember());
target.add(Instruction.loadMember());
target.add(Instruction.loadMember("value").locate(varLocation));
target.setDebug();
target.add(Instruction.storeVar(key));
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();
WhileStatement.replaceBreaks(target, label, mid + 1, end, start, end + 1);
target.add(Instruction.jmp(start - end).locate(loc()));
target.add(Instruction.discard().locate(loc()));
target.set(mid, Instruction.jmpIf(end - mid + 1).locate(loc()));
target.add(Instruction.jmp(start - end));
target.add(Instruction.discard());
target.set(mid, Instruction.jmpIf(end - mid + 1));
if (pollute) target.add(Instruction.loadValue(null));
target.get(first).locate(loc());
target.setDebug(first);
}
public ForInStatement(Location loc, String label, boolean isDecl, String varName, Statement varValue, Statement object, Statement body) {
public ForInStatement(Location loc, Location varLocation, String label, boolean isDecl, String varName, Statement varValue, Statement object, Statement body) {
super(loc);
this.varLocation = varLocation;
this.label = label;
this.isDeclaration = isDecl;
this.varName = varName;

View File

@@ -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() {

View File

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

View File

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

View File

@@ -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,18 +32,18 @@ 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());
target.add(Instruction.nop().locate(ccase.value.loc()));
}
int start = target.size();
@@ -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()));
@@ -71,7 +68,8 @@ public class SwitchStatement extends Statement {
var loc = target.get(el.getKey()).location;
var i = stmIndexMap.get(el.getValue());
if (i == null) i = target.size();
target.set(el.getKey(), Instruction.jmpIf(i - el.getKey()).locate(loc).setDebug(true));
target.set(el.getKey(), Instruction.jmpIf(i - el.getKey()).locate(loc));
target.setDebug(el.getKey());
}
target.add(Instruction.discard().locate(loc()));

View File

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

View File

@@ -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) {

View File

@@ -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))) {

View File

@@ -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,20 @@ 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);
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));
target.add(Instruction.call(args.length).locate(loc()));
target.setDebug();
if (!pollute) target.add(Instruction.discard().locate(loc()));
}
public CallStatement(Location loc, Statement func, Statement ...args) {

View File

@@ -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,13 @@ public class ChangeStatement extends Statement {
public final boolean postfix;
@Override
public boolean pollutesStack() { return true; }
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
value.toAssign(new ConstantStatement(loc(), -addAmount), Operation.SUBTRACT).compile(target, scope, postfix);
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
value.toAssign(new ConstantStatement(loc(), -addAmount), Operation.SUBTRACT).compile(target, scope, true);
if (!pollute) target.add(Instruction.discard().locate(loc()));
else if (postfix) {
target.add(Instruction.loadValue(addAmount));
target.add(Instruction.operation(Operation.SUBTRACT));
}
}
public ChangeStatement(Location loc, AssignableStatement value, double addAmount, boolean postfix) {

View File

@@ -1,38 +1,38 @@
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;
}
}
package me.topchetoeu.jscript.compilation.values;
import java.util.Vector;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class CommaStatement extends Statement {
public final Statement[] values;
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
for (var i = 0; i < values.length; i++) {
values[i].compile(target, scope, i == values.length - 1 && pollute);
}
}
@Override
public Statement optimize() {
var res = new Vector<Statement>(values.length);
for (var i = 0; i < values.length; i++) {
var stm = values[i].optimize();
if (i < values.length - 1 && stm.pure()) continue;
res.add(stm);
}
if (res.size() == 1) return res.get(0);
else return new CommaStatement(loc(), res.toArray(Statement[]::new));
}
public CommaStatement(Location loc, Statement ...args) {
super(loc);
this.values = args;
}
}

View File

@@ -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) {

View File

@@ -1,9 +1,11 @@
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.FunctionBody;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.Instruction.Type;
@@ -15,17 +17,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 +40,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 +52,32 @@ public class FunctionStatement extends Statement {
var subscope = scope.child();
int start = target.size();
var funcTarget = new CompileTarget(target.functions, target.breakpoints);
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);
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, new FunctionBody(funcTarget.array(), subscope.captures(), subscope.locals()));
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) {

View File

@@ -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) {

View File

@@ -1,51 +1,40 @@
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()));
target.setDebug();
}
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()));
target.setDebug();
}
target.get(start);
}
public IndexAssignStatement(Location loc, Statement object, Statement index, Statement value, Operation operation) {

View File

@@ -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) {
int start = 0;
object.compileWithPollution(target, scope);
public void compile(CompileTarget target, ScopeRecord scope, boolean dupObj, boolean pollute) {
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()));
target.setDebug();
return;
}
index.compileWithPollution(target, scope);
index.compile(target, scope, true);
target.add(Instruction.loadMember().locate(loc()));
target.get(start).setDebug(true);
target.setDebug();
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) {

View File

@@ -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);
if (pollute) target.add(Instruction.discard().locate(loc()));
second.compile(target, scope, pollute);
target.set(start, Instruction.jmpIfNot(target.size() - start).locate(loc()));
}

View File

@@ -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);
if (pollute) target.add(Instruction.discard().locate(loc()));
second.compile(target, scope, pollute);
target.set(start, Instruction.jmpIf(target.size() - start).locate(loc()));
}

View File

@@ -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,16 +11,13 @@ 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));
target.add(Instruction.callNew(args.length).locate(loc()));
target.setDebug();
}
public NewStatement(Location loc, Statement func, Statement ...args) {

View File

@@ -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) {

View File

@@ -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) {
@@ -50,13 +49,8 @@ public class OperationStatement extends Statement {
vals[i] = ((ConstantStatement)args[i]).value;
}
try {
return new ConstantStatement(loc(), Values.operation(null, operation, vals));
}
catch (EngineException e) {
return new ThrowStatement(loc(), new ConstantStatement(loc(), e.value));
}
catch (InterruptedException e) { return null; }
try { return new ConstantStatement(loc(), Values.operation(null, operation, vals)); }
catch (EngineException e) { return new ThrowStatement(loc(), new ConstantStatement(loc(), e.value)); }
}
return new OperationStatement(loc(), operation, args);

View File

@@ -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) {

View File

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

View File

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

View File

@@ -1,38 +1,31 @@
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, pollute).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, pollute).locate(loc()));
}
}

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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

View File

@@ -1,20 +1,59 @@
package me.topchetoeu.jscript.engine;
import java.util.Stack;
import java.util.TreeSet;
import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.parsing.Parsing;
public class Context {
public final FunctionContext function;
public final MessageContext message;
private final Stack<Environment> env = new Stack<>();
public final Data data;
public final Engine engine;
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);
public Environment environment() {
return env.empty() ? null : env.peek();
}
public Context(FunctionContext funcCtx, MessageContext msgCtx) {
this.function = funcCtx;
this.message = msgCtx;
public Context pushEnv(Environment env) {
this.env.push(env);
return this;
}
public void popEnv() {
if (!env.empty()) this.env.pop();
}
public FunctionValue compile(Filename filename, String raw) {
var transpiled = environment().compile.call(this, null, raw, filename.toString());
String source = null;
FunctionValue runner = null;
if (transpiled instanceof ObjectValue) {
source = Values.toString(this, Values.getMember(this, transpiled, "source"));
var _runner = Values.getMember(this, transpiled, "runner");
if (_runner instanceof FunctionValue) runner = (FunctionValue)_runner;
}
else source = Values.toString(this, transpiled);
var breakpoints = new TreeSet<Location>();
FunctionValue res = Parsing.compile(Engine.functions, breakpoints, environment(), filename, source);
engine.onSource(filename, source, breakpoints);
if (runner != null) res = (FunctionValue)runner.call(this, null, res);
return res;
}
public Context(Engine engine, Data data) {
this.data = new Data(engine.data);
if (data != null) this.data.addAll(data);
this.engine = engine;
}
public Context(Engine engine) {
this(engine, null);
}
}

View File

@@ -0,0 +1,71 @@
package me.topchetoeu.jscript.engine;
import java.util.HashMap;
import java.util.Map;
@SuppressWarnings("unchecked")
public class Data {
public final Data parent;
private HashMap<DataKey<Object>, Object> data = new HashMap<>();
public Data copy() {
return new Data().addAll(this);
}
public Data addAll(Map<DataKey<?>, ?> data) {
for (var el : data.entrySet()) {
get((DataKey<Object>)el.getKey(), (Object)el.getValue());
}
return this;
}
public Data addAll(Data data) {
for (var el : data.data.entrySet()) {
get((DataKey<Object>)el.getKey(), (Object)el.getValue());
}
return this;
}
public <T> T remove(DataKey<T> key) {
return (T)data.remove(key);
}
public <T> Data set(DataKey<T> key, T val) {
data.put((DataKey<Object>)key, (Object)val);
return this;
}
public <T> T get(DataKey<T> key, T val) {
for (var it = this; it != null; it = it.parent) {
if (it.data.containsKey(key)) {
return (T)it.data.get((DataKey<Object>)key);
}
}
set(key, val);
return val;
}
public <T> T get(DataKey<T> key) {
for (var it = this; it != null; it = it.parent) {
if (it.data.containsKey(key)) return (T)it.data.get((DataKey<Object>)key);
}
return null;
}
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);
}
public Data() {
this.parent = null;
}
public Data(Data parent) {
this.parent = parent;
}
}

View File

@@ -0,0 +1,3 @@
package me.topchetoeu.jscript.engine;
public class DataKey<T> { }

View File

@@ -1,29 +1,37 @@
package me.topchetoeu.jscript.engine;
import java.util.HashMap;
import java.util.TreeSet;
import java.util.concurrent.LinkedBlockingDeque;
import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.FunctionBody;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.engine.debug.DebugController;
import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.events.Awaitable;
import me.topchetoeu.jscript.events.DataNotifier;
import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.InterruptException;
public class Engine {
public class Engine implements DebugController {
private class UncompiledFunction extends FunctionValue {
public final String filename;
public final Filename filename;
public final String raw;
public final FunctionContext ctx;
private FunctionValue compiled = null;
@Override
public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException {
ctx = new Context(this.ctx, ctx.message);
return ctx.compile(filename, raw).call(ctx, thisArg, args);
public Object call(Context ctx, Object thisArg, Object ...args) {
if (compiled == null) compiled = ctx.compile(filename, raw);
return compiled.call(ctx, thisArg, args);
}
public UncompiledFunction(FunctionContext ctx, String filename, String raw) {
super(filename, 0);
public UncompiledFunction(Filename filename, String raw) {
super(filename + "", 0);
this.filename = filename;
this.raw = raw;
this.ctx = ctx;
}
}
@@ -32,9 +40,9 @@ public class Engine {
public final Object thisArg;
public final Object[] args;
public final DataNotifier<Object> notifier = new DataNotifier<>();
public final MessageContext ctx;
public final Context ctx;
public Task(MessageContext ctx, FunctionValue func, Object thisArg, Object[] args) {
public Task(Context ctx, FunctionValue func, Object thisArg, Object[] args) {
this.ctx = ctx;
this.func = func;
this.thisArg = thisArg;
@@ -43,31 +51,60 @@ public class Engine {
}
private static int nextId = 0;
public static final HashMap<Long, FunctionBody> functions = new HashMap<>();
private Thread thread;
private LinkedBlockingDeque<Task> macroTasks = new LinkedBlockingDeque<>();
private LinkedBlockingDeque<Task> microTasks = new LinkedBlockingDeque<>();
public final int id = ++nextId;
public final Data data = new Data().set(StackData.MAX_FRAMES, 200);
public final boolean debugging;
private final HashMap<Filename, String> sources = new HashMap<>();
private final HashMap<Filename, TreeSet<Location>> bpts = new HashMap<>();
private DebugController debugger;
private void runTask(Task task) throws InterruptedException {
public boolean attachDebugger(DebugController debugger) {
if (!debugging || this.debugger != null) return false;
for (var source : sources.entrySet()) {
debugger.onSource(source.getKey(), source.getValue(), bpts.get(source.getKey()));
}
this.debugger = debugger;
return true;
}
public boolean detachDebugger() {
if (!debugging || this.debugger == null) return false;
this.debugger = null;
return true;
}
@Override public void onFramePop(Context ctx, CodeFrame frame) {
if (debugging && debugger != null) debugger.onFramePop(ctx, frame);
}
@Override public boolean onInstruction(Context ctx, CodeFrame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught) {
if (debugging && debugger != null) return debugger.onInstruction(ctx, frame, instruction, returnVal, error, caught);
else return false;
}
@Override public void onSource(Filename filename, String source, TreeSet<Location> breakpoints) {
if (!debugging) return;
if (debugger != null) debugger.onSource(filename, source, breakpoints);
sources.put(filename, source);
bpts.put(filename, breakpoints);
}
private void runTask(Task task) {
try {
task.notifier.next(task.func.call(new Context(null, task.ctx), task.thisArg, task.args));
}
catch (InterruptedException e) {
task.notifier.error(new RuntimeException(e));
throw e;
}
catch (EngineException e) {
task.notifier.error(e);
task.notifier.next(task.func.call(task.ctx, task.thisArg, task.args));
}
catch (RuntimeException e) {
if (e instanceof InterruptException) throw e;
task.notifier.error(e);
e.printStackTrace();
}
}
private void run() {
while (true) {
public void run(boolean untilEmpty) {
while (!untilEmpty || !macroTasks.isEmpty()) {
try {
runTask(macroTasks.take());
@@ -75,9 +112,9 @@ public class Engine {
runTask(microTasks.take());
}
}
catch (InterruptedException e) {
catch (InterruptedException | InterruptException e) {
for (var msg : macroTasks) {
msg.notifier.error(new RuntimeException(e));
msg.notifier.error(new InterruptException(e));
}
break;
}
@@ -86,7 +123,7 @@ public class Engine {
public Thread start() {
if (this.thread == null) {
this.thread = new Thread(this::run, "JavaScript Runner #" + id);
this.thread = new Thread(() -> run(false), "JavaScript Runner #" + id);
this.thread.start();
}
return this.thread;
@@ -102,17 +139,17 @@ public class Engine {
return this.thread != null;
}
public Awaitable<Object> pushMsg(boolean micro, MessageContext ctx, FunctionValue func, Object thisArg, Object ...args) {
var msg = new Task(ctx, func, thisArg, args);
public Awaitable<Object> pushMsg(boolean micro, Context ctx, FunctionValue func, Object thisArg, Object ...args) {
var msg = new Task(ctx == null ? new Context(this) : ctx, func, thisArg, args);
if (micro) microTasks.addLast(msg);
else macroTasks.addLast(msg);
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);
public Awaitable<Object> pushMsg(boolean micro, Context ctx, Filename filename, String raw, Object thisArg, Object ...args) {
return pushMsg(micro, ctx, new UncompiledFunction(filename, raw), thisArg, args);
}
// public Engine() {
// this.typeRegister = new NativeTypeRegister();
// }
public Engine(boolean debugging) {
this.debugging = debugging;
}
}

View File

@@ -0,0 +1,87 @@
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 static final HashMap<String, Symbol> symbols = new HashMap<>();
public GlobalScope global;
public WrappersProvider wrappers;
@Native public FunctionValue compile;
@Native public FunctionValue regexConstructor = new NativeFunction("RegExp", (ctx, thisArg, args) -> {
throw EngineException.ofError("Regular expressions not supported.").setCtx(ctx.environment(), ctx.engine);
});
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, null, global);
res.wrappers = wrappers.fork(res);
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(Engine engine, Data data) {
return new Context(engine, data).pushEnv(this);
}
public Context context(Engine engine) {
return new Context(engine).pushEnv(this);
}
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.wrappers = nativeConverter;
this.compile = compile;
this.global = global;
}
}

View File

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

View File

@@ -1,33 +0,0 @@
package me.topchetoeu.jscript.engine;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.exceptions.EngineException;
public class MessageContext {
public final Engine engine;
private final ArrayList<CodeFrame> frames = new ArrayList<>();
public int maxStackFrames = 1000;
public List<CodeFrame> frames() { return Collections.unmodifiableList(frames); }
public MessageContext pushFrame(CodeFrame frame) {
this.frames.add(frame);
if (this.frames.size() > maxStackFrames) throw EngineException.ofRange("Stack overflow!");
return this;
}
public boolean popFrame(CodeFrame frame) {
if (this.frames.size() == 0) return false;
if (this.frames.get(this.frames.size() - 1) != frame) return false;
this.frames.remove(this.frames.size() - 1);
return true;
}
public MessageContext(Engine engine) {
this.engine = engine;
}
}

View File

@@ -0,0 +1,65 @@
package me.topchetoeu.jscript.engine;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.engine.debug.Debugger;
import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.exceptions.EngineException;
public class StackData {
public static final DataKey<ArrayList<CodeFrame>> FRAMES = new DataKey<>();
public static final DataKey<Integer> MAX_FRAMES = new DataKey<>();
public static final DataKey<Debugger> DEBUGGER = new DataKey<>();
public static void pushFrame(Context ctx, CodeFrame frame) {
var frames = ctx.data.get(FRAMES, new ArrayList<>());
frames.add(frame);
if (frames.size() > ctx.data.get(MAX_FRAMES, 10000)) throw EngineException.ofRange("Stack overflow!");
ctx.pushEnv(frame.function.environment);
}
public static boolean popFrame(Context ctx, CodeFrame frame) {
var frames = ctx.data.get(FRAMES, new ArrayList<>());
if (frames.size() == 0) return false;
if (frames.get(frames.size() - 1) != frame) return false;
frames.remove(frames.size() - 1);
ctx.popEnv();
ctx.engine.onFramePop(ctx, frame);
return true;
}
public static CodeFrame peekFrame(Context ctx) {
var frames = ctx.data.get(FRAMES, new ArrayList<>());
if (frames.size() == 0) return null;
return frames.get(frames.size() - 1);
}
public static List<CodeFrame> frames(Context ctx) {
return Collections.unmodifiableList(ctx.data.get(FRAMES, new ArrayList<>()));
}
public static List<String> stackTrace(Context ctx) {
var res = new ArrayList<String>();
var frames = frames(ctx);
for (var i = frames.size() - 1; i >= 0; i--) {
var el = frames.get(i);
var name = el.function.name;
Location loc = null;
for (var j = el.codePtr; j >= 0 && loc == null; j--) loc = el.function.body[j].location;
if (loc == null) loc = el.function.loc();
var trace = "";
if (loc != null) trace += "at " + loc.toString() + " ";
if (name != null && !name.equals("")) trace += "in " + name + " ";
trace = trace.trim();
if (!trace.equals("")) res.add(trace);
}
return res;
}
}

View File

@@ -1,8 +1,12 @@
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 ObjectValue getNamespace(Class<?> obj);
public FunctionValue getConstr(Class<?> obj);
public WrappersProvider fork(Environment env);
}

View File

@@ -0,0 +1,40 @@
package me.topchetoeu.jscript.engine.debug;
import java.util.TreeSet;
import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.exceptions.EngineException;
public interface DebugController {
/**
* Called when a script has been loaded
* @param breakpoints
*/
void onSource(Filename filename, String source, TreeSet<Location> breakpoints);
/**
* Called immediatly before an instruction is executed, as well as after an instruction, if it has threw or returned.
* This function might pause in order to await debugging commands.
* @param ctx The context of execution
* @param frame The frame in which execution is occuring
* @param instruction The instruction which was or will be executed
* @param loc The most recent location the code frame has been at
* @param returnVal The return value of the instruction, Runners.NO_RETURN if none
* @param error The error that the instruction threw, null if none
* @param caught Whether or not the error has been caught
* @return Whether or not the frame should restart
*/
boolean onInstruction(Context ctx, CodeFrame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught);
/**
* Called immediatly after a frame has been popped out of the frame stack.
* This function might pause in order to await debugging commands.
* @param ctx The context of execution
* @param frame The code frame which was popped out
*/
void onFramePop(Context ctx, CodeFrame frame);
}

View File

@@ -0,0 +1,35 @@
package me.topchetoeu.jscript.engine.debug;
public interface DebugHandler {
void enable(V8Message msg);
void disable(V8Message msg);
void setBreakpoint(V8Message msg);
void setBreakpointByUrl(V8Message msg);
void removeBreakpoint(V8Message msg);
void continueToLocation(V8Message msg);
void getScriptSource(V8Message msg);
void getPossibleBreakpoints(V8Message msg);
void resume(V8Message msg);
void pause(V8Message msg);
void stepInto(V8Message msg);
void stepOut(V8Message msg);
void stepOver(V8Message msg);
void setPauseOnExceptions(V8Message msg);
void evaluateOnCallFrame(V8Message msg);
void getProperties(V8Message msg);
void releaseObjectGroup(V8Message msg);
void releaseObject(V8Message msg);
/**
* This method might not execute the actual code for well-known requests
*/
void callFunctionOn(V8Message msg);
void runtimeEnable(V8Message msg);
}

View File

@@ -0,0 +1,246 @@
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 java.util.HashMap;
import me.topchetoeu.jscript.Metadata;
import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type;
import me.topchetoeu.jscript.events.Notifier;
import me.topchetoeu.jscript.exceptions.SyntaxException;
import me.topchetoeu.jscript.exceptions.UncheckedException;
import me.topchetoeu.jscript.exceptions.UncheckedIOException;
import me.topchetoeu.jscript.json.JSON;
import me.topchetoeu.jscript.json.JSONList;
import me.topchetoeu.jscript.json.JSONMap;
public class DebugServer {
public static String browserDisplayName = Metadata.NAME + "/" + Metadata.VERSION;
public final HashMap<String, DebuggerProvider> targets = new HashMap<>();
private final byte[] favicon, index, protocol;
private final Notifier connNotifier = new Notifier();
private static void send(HttpRequest req, String val) throws IOException {
req.writeResponse(200, "OK", "application/json", val.getBytes());
}
// SILENCE JAVA
private MessageDigest getDigestInstance() {
try {
return MessageDigest.getInstance("sha1");
}
catch (Throwable e) { throw new UncheckedException(e); }
}
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, Debugger debugger) {
WebSocketMessage raw;
debugger.connect();
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;
}
try {
switch (msg.name) {
case "Debugger.enable":
connNotifier.next();
debugger.enable(msg); continue;
case "Debugger.disable": debugger.disable(msg); continue;
case "Debugger.setBreakpoint": debugger.setBreakpoint(msg); continue;
case "Debugger.setBreakpointByUrl": debugger.setBreakpointByUrl(msg); continue;
case "Debugger.removeBreakpoint": debugger.removeBreakpoint(msg); continue;
case "Debugger.continueToLocation": debugger.continueToLocation(msg); continue;
case "Debugger.getScriptSource": debugger.getScriptSource(msg); continue;
case "Debugger.getPossibleBreakpoints": debugger.getPossibleBreakpoints(msg); continue;
case "Debugger.resume": debugger.resume(msg); continue;
case "Debugger.pause": debugger.pause(msg); continue;
case "Debugger.stepInto": debugger.stepInto(msg); continue;
case "Debugger.stepOut": debugger.stepOut(msg); continue;
case "Debugger.stepOver": debugger.stepOver(msg); continue;
case "Debugger.setPauseOnExceptions": debugger.setPauseOnExceptions(msg); continue;
case "Debugger.evaluateOnCallFrame": debugger.evaluateOnCallFrame(msg); continue;
case "Runtime.releaseObjectGroup": debugger.releaseObjectGroup(msg); continue;
case "Runtime.releaseObject": debugger.releaseObject(msg); continue;
case "Runtime.getProperties": debugger.getProperties(msg); continue;
case "Runtime.callFunctionOn": debugger.callFunctionOn(msg); continue;
// case "NodeWorker.enable": debugger.nodeWorkerEnable(msg); continue;
case "Runtime.enable": debugger.runtimeEnable(msg); continue;
}
if (
msg.name.startsWith("DOM.") ||
msg.name.startsWith("DOMDebugger.") ||
msg.name.startsWith("Emulation.") ||
msg.name.startsWith("Input.") ||
msg.name.startsWith("Network.") ||
msg.name.startsWith("Page.")
) ws.send(new V8Error("This isn't a browser..."));
else ws.send(new V8Error("This API is not supported yet."));
}
catch (Throwable e) {
e.printStackTrace();
throw new UncheckedException(e);
}
}
debugger.disconnect();
}
private void onWsConnect(HttpRequest req, Socket socket, DebuggerProvider debuggerProvider) {
var key = req.headers.get("sec-websocket-key");
if (key == null) {
req.writeResponse(
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()
));
req.writeCode(101, "Switching Protocols");
req.writeHeader("Connection", "Upgrade");
req.writeHeader("Sec-WebSocket-Accept", resKey);
req.writeLastHeader("Upgrade", "WebSocket");
var ws = new WebSocket(socket);
var debugger = debuggerProvider.getDebugger(ws, req);
if (debugger == null) {
ws.close();
return;
}
runAsync(() -> {
try { handle(ws, debugger); }
catch (RuntimeException e) {
ws.send(new V8Error(e.getMessage()));
}
finally { ws.close(); debugger.disconnect(); }
}, "Debug Handler");
}
public void awaitConnection() {
connNotifier.await();
}
public void run(InetSocketAddress address) {
try {
ServerSocket server = new ServerSocket();
server.bind(address);
try {
while (true) {
var socket = server.accept();
var req = HttpRequest.read(socket);
if (req == null) continue;
switch (req.path) {
case "/json/version":
send(req, "{\"Browser\":\"" + browserDisplayName + "\",\"Protocol-Version\":\"1.1\"}");
break;
case "/json/list":
case "/json": {
var res = new JSONList();
for (var el : targets.entrySet()) {
res.add(new JSONMap()
.set("description", "JScript debugger")
.set("favicon", "/favicon.ico")
.set("id", el.getKey())
.set("type", "node")
.set("webSocketDebuggerUrl", "ws://" + address.getHostString() + ":" + address.getPort() + "/" + el.getKey())
);
}
send(req, JSON.stringify(res));
break;
}
case "/json/protocol":
req.writeResponse(200, "OK", "application/json", protocol);
break;
case "/json/new":
case "/json/activate":
case "/json/close":
case "/devtools/inspector.html":
req.writeResponse(
501, "Not Implemented", "text/txt",
"This feature isn't (and probably won't be) implemented.".getBytes()
);
break;
case "/":
case "/index.html":
req.writeResponse(200, "OK", "text/html", index);
break;
case "/favicon.ico":
req.writeResponse(200, "OK", "image/png", favicon);
break;
default:
if (req.path.length() > 1 && targets.containsKey(req.path.substring(1))) {
onWsConnect(req, socket, targets.get(req.path.substring(1)));
}
break;
}
}
}
finally { server.close(); }
}
catch (IOException e) { throw new UncheckedIOException(e); }
}
public Thread start(InetSocketAddress address, boolean daemon) {
var res = new Thread(() -> run(address), "Debug Server");
res.setDaemon(daemon);
res.start();
return res;
}
public DebugServer() {
try {
this.favicon = getClass().getClassLoader().getResourceAsStream("assets/favicon.png").readAllBytes();
this.protocol = getClass().getClassLoader().getResourceAsStream("assets/protocol.json").readAllBytes();
var index = new String(getClass().getClassLoader().getResourceAsStream("assets/index.html").readAllBytes());
this.index = index
.replace("${NAME}", Metadata.NAME)
.replace("${VERSION}", Metadata.VERSION)
.replace("${AUTHOR}", Metadata.AUTHOR)
.getBytes();
}
catch (IOException e) { throw new UncheckedIOException(e); }
}
}

View File

@@ -0,0 +1,6 @@
package me.topchetoeu.jscript.engine.debug;
public interface Debugger extends DebugHandler, DebugController {
void connect();
void disconnect();
}

View File

@@ -0,0 +1,5 @@
package me.topchetoeu.jscript.engine.debug;
public interface DebuggerProvider {
Debugger getDebugger(WebSocket socket, HttpRequest req);
}

View File

@@ -0,0 +1,102 @@
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.net.Socket;
import java.util.HashMap;
import java.util.IllegalFormatException;
import java.util.Map;
public class HttpRequest {
public final String method;
public final String path;
public final Map<String, String> headers;
public final OutputStream out;
public void writeCode(int code, String name) {
try { out.write(("HTTP/1.1 " + code + " " + name + "\r\n").getBytes()); }
catch (IOException e) { }
}
public void writeHeader(String name, String value) {
try { out.write((name + ": " + value + "\r\n").getBytes()); }
catch (IOException e) { }
}
public void writeLastHeader(String name, String value) {
try { out.write((name + ": " + value + "\r\n\r\n").getBytes()); }
catch (IOException e) { }
}
public void writeHeadersEnd() {
try { out.write("\n".getBytes()); }
catch (IOException e) { }
}
public void writeResponse(int code, String name, String type, byte[] data) {
writeCode(code, name);
writeHeader("Content-Type", type);
writeLastHeader("Content-Length", data.length + "");
try {
out.write(data);
out.close();
}
catch (IOException e) { }
}
public void writeResponse(int code, String name, String type, InputStream data) {
try {
writeResponse(code, name, type, data.readAllBytes());
}
catch (IOException e) { }
}
public HttpRequest(String method, String path, Map<String, String> headers, OutputStream out) {
this.method = method;
this.path = path;
this.headers = headers;
this.out = out;
}
// We dont need no http library
public static HttpRequest read(Socket socket) {
try {
var str = socket.getInputStream();
var lines = new BufferedReader(new InputStreamReader(str));
var line = lines.readLine();
var i1 = line.indexOf(" ");
var i2 = line.indexOf(" ", i1 + 1);
if (i1 < 0 || i2 < 0) {
socket.close();
return null;
}
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, socket.getOutputStream());
}
catch (IOException | NullPointerException e) { return null; }
}
}

View File

@@ -0,0 +1,813 @@
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.Random;
import java.util.TreeSet;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Instruction.Type;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Engine;
import me.topchetoeu.jscript.engine.StackData;
import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.engine.frame.Runners;
import me.topchetoeu.jscript.engine.scope.GlobalScope;
import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.CodeFunction;
import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Symbol;
import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.events.Notifier;
import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.json.JSON;
import me.topchetoeu.jscript.json.JSONElement;
import me.topchetoeu.jscript.json.JSONList;
import me.topchetoeu.jscript.json.JSONMap;
public class SimpleDebugger implements Debugger {
public static final String CHROME_GET_PROP_FUNC = "function s(e){let t=this;const n=JSON.parse(e);for(let e=0,i=n.length;e<i;++e)t=t[n[e]];return t}";
public static final String VSCODE_STRINGIFY_VAL = "function(...runtimeArgs){\n let t = 1024; let e = null;\n if(e)try{let r=\"<<default preview>>\",i=e.call(this,r);if(i!==r)return String(i)}catch(r){return`<<indescribable>>${JSON.stringify([String(r),\"object\"])}`}if(typeof this==\"object\"&&this){let r;for(let i of[Symbol.for(\"debug.description\"),Symbol.for(\"nodejs.util.inspect.custom\")])try{r=this[i]();break}catch{}if(!r&&!String(this.toString).includes(\"[native code]\")&&(r=String(this)),r&&!r.startsWith(\"[object \"))return r.length>=t?r.slice(0,t)+\"\\u2026\":r}\n ;\n\n}";
public static final String VSCODE_STRINGIFY_PROPS = "function(...runtimeArgs){\n let t = 1024; let e = null;\n let r={},i=\"<<default preview>>\";if(typeof this!=\"object\"||!this)return r;for(let[n,s]of Object.entries(this)){if(e)try{let o=e.call(s,i);if(o!==i){r[n]=String(o);continue}}catch(o){r[n]=`<<indescribable>>${JSON.stringify([String(o),n])}`;continue}if(typeof s==\"object\"&&s){let o;for(let a of runtimeArgs[0])try{o=s[a]();break}catch{}!o&&!String(s.toString).includes(\"[native code]\")&&(o=String(s)),o&&!o.startsWith(\"[object \")&&(r[n]=o.length>=t?o.slice(0,t)+\"\\u2026\":o)}}return r\n ;\n\n}";
public static final String VSCODE_SYMBOL_REQUEST = "function(){return[Symbol.for(\"debug.description\"),Symbol.for(\"nodejs.util.inspect.custom\")]\n}";
public static final String VSCODE_SHALLOW_COPY = "function(){let t={__proto__:this.__proto__\n},e=Object.getOwnPropertyNames(this);for(let r=0;r<e.length;++r){let i=e[r],n=i>>>0;if(String(n>>>0)===i&&n>>>0!==4294967295)continue;let s=Object.getOwnPropertyDescriptor(this,i);s&&Object.defineProperty(t,i,s)}return t}";
public static final String VSCODE_FLATTEN_ARRAY = "function(t,e){let r={\n},i=t===-1?0:t,n=e===-1?this.length:t+e;for(let s=i;s<n&&s<this.length;++s){let o=Object.getOwnPropertyDescriptor(this,s);o&&Object.defineProperty(r,s,o)}return r}";
public static final String VSCODE_CALL = "function(t){return t.call(this)\n}";
private static enum State {
RESUMED,
STEPPING_IN,
STEPPING_OUT,
STEPPING_OVER,
PAUSED_NORMAL,
PAUSED_EXCEPTION,
}
private static enum CatchType {
NONE,
UNCAUGHT,
ALL,
}
private static class Source {
public final int id;
public final Filename filename;
public final String source;
public final TreeSet<Location> breakpoints;
public Source(int id, Filename filename, String source, TreeSet<Location> breakpoints) {
this.id = id;
this.filename = filename;
this.source = source;
this.breakpoints = breakpoints;
}
}
private static class Breakpoint {
public final int id;
public final Location location;
public final String condition;
public Breakpoint(int id, Location location, String condition) {
this.id = id;
this.location = location;
this.condition = condition;
}
}
private static class BreakpointCandidate {
public final int id;
public final String condition;
public final Pattern pattern;
public final int line, start;
public final HashSet<Breakpoint> resolvedBreakpoints = new HashSet<>();
public BreakpointCandidate(int id, Pattern pattern, int line, int start, String condition) {
this.id = id;
this.pattern = pattern;
this.line = line;
this.start = start;
if (condition != null && condition.trim().equals("")) condition = null;
this.condition = condition;
}
}
private class Frame {
public CodeFrame frame;
public CodeFunction func;
public int id;
public ObjectValue local, capture, global, valstack;
public JSONMap serialized;
public Location location;
public boolean debugData = false;
public void updateLoc(Location loc) {
serialized.set("location", serializeLocation(loc));
this.location = loc;
}
public Frame(Context ctx, CodeFrame frame, int id) {
this.frame = frame;
this.func = frame.function;
this.id = id;
this.local = new ObjectValue();
this.location = frame.function.loc();
this.global = frame.function.environment.global.obj;
this.local = frame.getLocalScope(ctx, true);
this.capture = frame.getCaptureScope(ctx, true);
this.local.setPrototype(ctx, capture);
this.capture.setPrototype(ctx, global);
this.valstack = frame.getValStackScope(ctx);
if (location != null) {
debugData = true;
this.serialized = new JSONMap()
.set("callFrameId", id + "")
.set("functionName", func.name)
.set("location", serializeLocation(location))
.set("scopeChain", new JSONList()
.add(new JSONMap().set("type", "local").set("name", "Local Scope").set("object", serializeObj(ctx, local)))
.add(new JSONMap().set("type", "closure").set("name", "Closure").set("object", serializeObj(ctx, capture)))
.add(new JSONMap().set("type", "global").set("name", "Global Scope").set("object", serializeObj(ctx, global)))
.add(new JSONMap().set("type", "other").set("name", "Value Stack").set("object", serializeObj(ctx, valstack)))
);
}
}
}
private class RunResult {
public final Context ctx;
public final Object result;
public final EngineException error;
public RunResult(Context ctx, Object result, EngineException error) {
this.ctx = ctx;
this.result = result;
this.error = error;
}
}
public boolean enabled = true;
public CatchType execptionType = CatchType.ALL;
public State state = State.RESUMED;
public final WebSocket ws;
public final Engine target;
private ObjectValue emptyObject = new ObjectValue();
private HashMap<Integer, BreakpointCandidate> idToBptCand = new HashMap<>();
private HashMap<Integer, Breakpoint> idToBreakpoint = new HashMap<>();
private HashMap<Location, Breakpoint> locToBreakpoint = new HashMap<>();
private HashSet<Location> tmpBreakpts = new HashSet<>();
private HashMap<Filename, Integer> filenameToId = new HashMap<>();
private HashMap<Integer, Source> idToSource = new HashMap<>();
private ArrayList<Source> pendingSources = new ArrayList<>();
private HashMap<Integer, Frame> idToFrame = new HashMap<>();
private HashMap<CodeFrame, Frame> codeFrameToFrame = new HashMap<>();
private HashMap<Integer, ObjectValue> idToObject = new HashMap<>();
private HashMap<ObjectValue, Integer> objectToId = new HashMap<>();
private HashMap<ObjectValue, Context> objectToCtx = new HashMap<>();
private HashMap<String, ArrayList<ObjectValue>> objectGroups = new HashMap<>();
private Notifier updateNotifier = new Notifier();
private int nextId = new Random().nextInt() & 0x7FFFFFFF;
private Location prevLocation = null;
private Frame stepOutFrame = null, currFrame = null;
private int nextId() {
return nextId++ ^ 1630022591 /* big prime */;
}
private void updateFrames(Context ctx) {
var frame = StackData.peekFrame(ctx);
if (frame == null) return;
if (!codeFrameToFrame.containsKey(frame)) {
var id = nextId();
var fr = new Frame(ctx, frame, id);
idToFrame.put(id, fr);
codeFrameToFrame.put(frame, fr);
}
currFrame = codeFrameToFrame.get(frame);
}
private JSONList serializeFrames(Context ctx) {
var res = new JSONList();
var frames = StackData.frames(ctx);
for (var i = frames.size() - 1; i >= 0; i--) {
res.add(codeFrameToFrame.get(frames.get(i)).serialized);
}
return res;
}
private Location correctLocation(Source source, Location loc) {
var set = source.breakpoints;
if (set.contains(loc)) return loc;
var tail = set.tailSet(loc);
if (tail.isEmpty()) return null;
return tail.first();
}
private Location deserializeLocation(JSONElement el, boolean correct) {
if (!el.isMap()) throw new RuntimeException("Expected location to be a map.");
var id = Integer.parseInt(el.map().string("scriptId"));
var line = (int)el.map().number("lineNumber") + 1;
var column = (int)el.map().number("columnNumber") + 1;
if (!idToSource.containsKey(id)) throw new RuntimeException(String.format("The specified source %s doesn't exist.", id));
var res = new Location(line, column, idToSource.get(id).filename);
if (correct) res = correctLocation(idToSource.get(id), res);
return res;
}
private JSONMap serializeLocation(Location loc) {
var source = filenameToId.get(loc.filename());
return new JSONMap()
.set("scriptId", source + "")
.set("lineNumber", loc.line() - 1)
.set("columnNumber", loc.start() - 1);
}
private Integer objectId(Context ctx, ObjectValue obj) {
if (objectToId.containsKey(obj)) return objectToId.get(obj);
else {
int id = nextId();
objectToId.put(obj, id);
if (ctx != null) objectToCtx.put(obj, ctx);
idToObject.put(id, obj);
return id;
}
}
private JSONMap serializeObj(Context ctx, Object val) {
val = Values.normalize(null, val);
if (val == Values.NULL) {
return new JSONMap()
.set("type", "object")
.set("subtype", "null")
.setNull("value")
.set("description", "null");
}
if (val instanceof ObjectValue) {
var obj = (ObjectValue)val;
var id = objectId(ctx, obj);
var type = "object";
String subtype = null;
String className = null;
if (obj instanceof FunctionValue) type = "function";
if (obj instanceof ArrayValue) subtype = "array";
try { className = Values.toString(ctx, Values.getMember(ctx, obj.getMember(ctx, "constructor"), "name")); }
catch (Exception e) { }
var res = new JSONMap()
.set("type", type)
.set("objectId", id + "");
if (subtype != null) res.set("subtype", subtype);
if (className != null) {
res.set("className", className);
res.set("description", "{ " + className + " }");
}
return res;
}
if (val == null) return new JSONMap().set("type", "undefined");
if (val instanceof String) return new JSONMap().set("type", "string").set("value", (String)val);
if (val instanceof Boolean) return new JSONMap().set("type", "boolean").set("value", (Boolean)val);
if (val instanceof Symbol) return new JSONMap().set("type", "symbol").set("description", val.toString());
if (val instanceof Number) {
var num = (double)(Number)val;
var res = new JSONMap().set("type", "number");
if (Double.POSITIVE_INFINITY == num) res.set("unserializableValue", "Infinity");
else if (Double.NEGATIVE_INFINITY == num) res.set("unserializableValue", "-Infinity");
else if (Double.doubleToRawLongBits(num) == Double.doubleToRawLongBits(-0d)) res.set("unserializableValue", "-0");
else if (Double.doubleToRawLongBits(num) == Double.doubleToRawLongBits(0d)) res.set("unserializableValue", "0");
else if (Double.isNaN(num)) res.set("unserializableValue", "NaN");
else res.set("value", num);
return res;
}
throw new IllegalArgumentException("Unexpected JS object.");
}
private void setObjectGroup(String name, Object val) {
if (val instanceof ObjectValue) {
var obj = (ObjectValue)val;
if (objectGroups.containsKey(name)) objectGroups.get(name).add(obj);
else objectGroups.put(name, new ArrayList<>(List.of(obj)));
}
}
private void releaseGroup(String name) {
var objs = objectGroups.remove(name);
if (objs != null) {
for (var obj : objs) {
var id = objectToId.remove(obj);
if (id != null) idToObject.remove(id);
}
}
}
private Object deserializeArgument(JSONMap val) {
if (val.isString("objectId")) return idToObject.get(Integer.parseInt(val.string("objectId")));
else if (val.isString("unserializableValue")) switch (val.string("unserializableValue")) {
case "NaN": return Double.NaN;
case "-Infinity": return Double.NEGATIVE_INFINITY;
case "Infinity": return Double.POSITIVE_INFINITY;
case "-0": return -0.;
}
return JSON.toJs(val.get("value"));
}
private JSONMap serializeException(Context ctx, EngineException err) {
String text = null;
try {
text = Values.toString(ctx, err.value);
}
catch (EngineException e) {
text = "[error while stringifying]";
}
var res = new JSONMap()
.set("exceptionId", nextId())
.set("exception", serializeObj(ctx, err.value))
.set("exception", serializeObj(ctx, err.value))
.set("text", text);
return res;
}
private void resume(State state) {
this.state = state;
ws.send(new V8Event("Debugger.resumed", new JSONMap()));
updateNotifier.next();
}
private void pauseDebug(Context ctx, Breakpoint bp) {
state = State.PAUSED_NORMAL;
var map = new JSONMap()
.set("callFrames", serializeFrames(ctx))
.set("reason", "debugCommand");
if (bp != null) map.set("hitBreakpoints", new JSONList().add(bp.id + ""));
ws.send(new V8Event("Debugger.paused", map));
}
private void pauseException(Context ctx) {
state = State.PAUSED_EXCEPTION;
var map = new JSONMap()
.set("callFrames", serializeFrames(ctx))
.set("reason", "exception");
ws.send(new V8Event("Debugger.paused", map));
}
private void sendSource(Source src) {
ws.send(new V8Event("Debugger.scriptParsed", new JSONMap()
.set("scriptId", src.id + "")
.set("hash", src.source.hashCode())
.set("url", src.filename + "")
));
}
private void addBreakpoint(Breakpoint bpt) {
idToBreakpoint.put(bpt.id, bpt);
locToBreakpoint.put(bpt.location, bpt);
ws.send(new V8Event("Debugger.breakpointResolved", new JSONMap()
.set("breakpointId", bpt.id)
.set("location", serializeLocation(bpt.location))
));
}
private RunResult run(Frame codeFrame, String code) {
var engine = new Engine(false);
var env = codeFrame.func.environment.fork();
ObjectValue global = env.global.obj,
capture = codeFrame.frame.getCaptureScope(null, enabled),
local = codeFrame.frame.getLocalScope(null, enabled);
capture.setPrototype(null, global);
local.setPrototype(null, capture);
env.global = new GlobalScope(local);
var ctx = new Context(engine).pushEnv(env);
var awaiter = engine.pushMsg(false, ctx, new Filename("jscript", "eval"), code, codeFrame.frame.thisArg, codeFrame.frame.args);
engine.run(true);
try { return new RunResult(ctx, awaiter.await(), null); }
catch (EngineException e) { return new RunResult(ctx, null, e); }
}
@Override public void enable(V8Message msg) {
enabled = true;
ws.send(msg.respond());
for (var el : pendingSources) sendSource(el);
pendingSources.clear();
updateNotifier.next();
}
@Override public void disable(V8Message msg) {
enabled = false;
ws.send(msg.respond());
updateNotifier.next();
}
@Override public void getScriptSource(V8Message msg) {
int id = Integer.parseInt(msg.params.string("scriptId"));
ws.send(msg.respond(new JSONMap().set("scriptSource", idToSource.get(id).source)));
}
@Override public void getPossibleBreakpoints(V8Message msg) {
var src = idToSource.get(Integer.parseInt(msg.params.map("start").string("scriptId")));
var start = deserializeLocation(msg.params.get("start"), false);
var end = msg.params.isMap("end") ? deserializeLocation(msg.params.get("end"), false) : null;
var res = new JSONList();
for (var loc : src.breakpoints.tailSet(start, true)) {
if (end != null && loc.compareTo(end) > 0) break;
res.add(serializeLocation(loc));
}
ws.send(msg.respond(new JSONMap().set("locations", res)));
}
@Override public void pause(V8Message msg) {
}
@Override public void resume(V8Message msg) {
resume(State.RESUMED);
ws.send(msg.respond(new JSONMap()));
}
@Override public void setBreakpoint(V8Message msg) {
// int id = nextId();
// var loc = deserializeLocation(msg.params.get("location"), true);
// var bpt = new Breakpoint(id, loc, null);
// breakpoints.put(loc, bpt);
// idToBrpt.put(id, bpt);
// ws.send(msg.respond(new JSONMap()
// .set("breakpointId", id)
// .set("actualLocation", serializeLocation(loc))
// ));
}
@Override public void setBreakpointByUrl(V8Message msg) {
var line = (int)msg.params.number("lineNumber") + 1;
var col = (int)msg.params.number("columnNumber", 0) + 1;
var cond = msg.params.string("condition", null);
if (cond != null) cond = "(" + cond + ")";
Pattern regex;
if (msg.params.isString("url")) regex = Pattern.compile(Pattern.quote(msg.params.string("url")));
else regex = Pattern.compile(msg.params.string("urlRegex"));
var bpcd = new BreakpointCandidate(nextId(), regex, line, col, cond);
idToBptCand.put(bpcd.id, bpcd);
var locs = new JSONList();
for (var src : idToSource.values()) {
if (regex.matcher(src.filename.toString()).matches()) {
var loc = correctLocation(src, new Location(line, col, src.filename));
if (loc == null) continue;
var bp = new Breakpoint(nextId(), loc, bpcd.condition);
bpcd.resolvedBreakpoints.add(bp);
locs.add(serializeLocation(loc));
addBreakpoint(bp);
}
}
ws.send(msg.respond(new JSONMap()
.set("breakpointId", bpcd.id + "")
.set("locations", locs)
));
}
@Override public void removeBreakpoint(V8Message msg) {
var id = Integer.parseInt(msg.params.string("breakpointId"));
if (idToBptCand.containsKey(id)) {
var bpcd = idToBptCand.get(id);
for (var bp : bpcd.resolvedBreakpoints) {
idToBreakpoint.remove(bp.id);
locToBreakpoint.remove(bp.location);
}
idToBptCand.remove(id);
}
else if (idToBreakpoint.containsKey(id)) {
var bp = idToBreakpoint.remove(id);
locToBreakpoint.remove(bp.location);
}
ws.send(msg.respond());
}
@Override public void continueToLocation(V8Message msg) {
var loc = deserializeLocation(msg.params.get("location"), true);
tmpBreakpts.add(loc);
resume(State.RESUMED);
ws.send(msg.respond());
}
@Override public void setPauseOnExceptions(V8Message msg) {
ws.send(new V8Error("i dont wanna to"));
}
@Override public void stepInto(V8Message msg) {
if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed."));
else {
prevLocation = currFrame.location;
stepOutFrame = currFrame;
resume(State.STEPPING_IN);
ws.send(msg.respond());
}
}
@Override public void stepOut(V8Message msg) {
if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed."));
else {
prevLocation = currFrame.location;
stepOutFrame = currFrame;
resume(State.STEPPING_OUT);
ws.send(msg.respond());
}
}
@Override public void stepOver(V8Message msg) {
if (state == State.RESUMED) ws.send(new V8Error("Debugger is resumed."));
else {
prevLocation = currFrame.location;
stepOutFrame = currFrame;
resume(State.STEPPING_OVER);
ws.send(msg.respond());
}
}
@Override public void evaluateOnCallFrame(V8Message msg) {
var cfId = Integer.parseInt(msg.params.string("callFrameId"));
var expr = msg.params.string("expression");
var group = msg.params.string("objectGroup", null);
var cf = idToFrame.get(cfId);
var res = run(cf, expr);
if (group != null) setObjectGroup(group, res.result);
if (res.error != null) ws.send(msg.respond(new JSONMap().set("exceptionDetails", serializeObj(res.ctx, res.result))));
ws.send(msg.respond(new JSONMap().set("result", serializeObj(res.ctx, res.result))));
}
@Override public void releaseObjectGroup(V8Message msg) {
var group = msg.params.string("objectGroup");
releaseGroup(group);
ws.send(msg.respond());
}
@Override
public void releaseObject(V8Message msg) {
var id = Integer.parseInt(msg.params.string("objectId"));
var obj = idToObject.remove(id);
if (obj != null) objectToId.remove(obj);
ws.send(msg.respond());
}
@Override public void getProperties(V8Message msg) {
var obj = idToObject.get(Integer.parseInt(msg.params.string("objectId")));
var res = new JSONList();
var ctx = objectToCtx.get(obj);
if (obj != emptyObject) {
for (var key : obj.keys(true)) {
var propDesc = new JSONMap();
if (obj.properties.containsKey(key)) {
var prop = obj.properties.get(key);
propDesc.set("name", Values.toString(ctx, key));
if (prop.getter != null) propDesc.set("get", serializeObj(ctx, prop.getter));
if (prop.setter != null) propDesc.set("set", serializeObj(ctx, prop.setter));
propDesc.set("enumerable", obj.memberEnumerable(key));
propDesc.set("configurable", obj.memberConfigurable(key));
propDesc.set("isOwn", true);
res.add(propDesc);
}
else {
propDesc.set("name", Values.toString(ctx, key));
propDesc.set("value", serializeObj(ctx, obj.getMember(ctx, key)));
propDesc.set("writable", obj.memberWritable(key));
propDesc.set("enumerable", obj.memberEnumerable(key));
propDesc.set("configurable", obj.memberConfigurable(key));
propDesc.set("isOwn", true);
res.add(propDesc);
}
}
var proto = obj.getPrototype(ctx);
var protoDesc = new JSONMap();
protoDesc.set("name", "__proto__");
protoDesc.set("value", serializeObj(ctx, proto == null ? Values.NULL : proto));
protoDesc.set("writable", true);
protoDesc.set("enumerable", false);
protoDesc.set("configurable", false);
protoDesc.set("isOwn", true);
res.add(protoDesc);
}
ws.send(msg.respond(new JSONMap().set("result", res)));
}
@Override public void callFunctionOn(V8Message msg) {
var src = msg.params.string("functionDeclaration");
var args = msg.params
.list("arguments", new JSONList())
.stream()
.map(v -> v.map())
.map(this::deserializeArgument)
.collect(Collectors.toList());
var thisArg = idToObject.get(Integer.parseInt(msg.params.string("objectId")));
var ctx = objectToCtx.get(thisArg);
while (true) {
var start = src.lastIndexOf("//# sourceURL=");
if (start < 0) break;
var end = src.indexOf("\n", start);
if (end < 0) src = src.substring(0, start);
else src = src.substring(0, start) + src.substring(end + 1);
}
try {
switch (src) {
case CHROME_GET_PROP_FUNC: {
var path = JSON.parse(null, (String)args.get(0)).list();
Object res = thisArg;
for (var el : path) res = Values.getMember(ctx, res, JSON.toJs(el));
ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, res))));
return;
}
case VSCODE_STRINGIFY_VAL:
case VSCODE_STRINGIFY_PROPS:
case VSCODE_SHALLOW_COPY:
case VSCODE_SYMBOL_REQUEST:
ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, emptyObject))));
break;
case VSCODE_FLATTEN_ARRAY:
ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, thisArg))));
break;
case VSCODE_CALL: {
var func = (FunctionValue)(args.size() < 1 ? null : args.get(0));
ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, func.call(ctx, thisArg)))));
break;
}
default:
ws.send(new V8Error("Please use well-known functions with callFunctionOn"));
break;
// default: {
// var res = ctx.compile(new Filename("jscript", "eval"), src).call(ctx);
// if (res instanceof FunctionValue) ((FunctionValue)res).call(ctx, thisArg, args);
// ws.send(msg.respond(new JSONMap().set("result", serializeObj(ctx, res))));
// }
}
}
catch (EngineException e) { ws.send(msg.respond(new JSONMap().set("exceptionDetails", serializeException(ctx, e)))); }
}
@Override
public void runtimeEnable(V8Message msg) {
ws.send(msg.respond());
}
@Override public void onSource(Filename filename, String source, TreeSet<Location> locations) {
int id = nextId();
var src = new Source(id, filename, source, locations);
idToSource.put(id, src);
filenameToId.put(filename, id);
for (var bpcd : idToBptCand.values()) {
if (!bpcd.pattern.matcher(filename.toString()).matches()) continue;
var loc = correctLocation(src, new Location(bpcd.line, bpcd.start, filename));
var bp = new Breakpoint(nextId(), loc, bpcd.condition);
if (loc == null) continue;
bpcd.resolvedBreakpoints.add(bp);
addBreakpoint(bp);
}
if (!enabled) pendingSources.add(src);
else sendSource(src);
}
@Override public boolean onInstruction(Context ctx, CodeFrame cf, Instruction instruction, Object returnVal, EngineException error, boolean caught) {
if (!enabled) return false;
updateFrames(ctx);
var frame = codeFrameToFrame.get(cf);
if (!frame.debugData) return false;
if (instruction.location != null) frame.updateLoc(instruction.location);
var loc = frame.location;
var isBreakpointable = loc != null && (
idToSource.get(filenameToId.get(loc.filename())).breakpoints.contains(loc) ||
returnVal != Runners.NO_RETURN
);
// TODO: FIXXXX
// if (error != null && !caught && StackData.frames(ctx).size() > 1) error = null;
if (error != null && (execptionType == CatchType.ALL || execptionType == CatchType.UNCAUGHT && !caught)) {
pauseException(ctx);
}
else if (isBreakpointable && locToBreakpoint.containsKey(loc)) {
var bp = locToBreakpoint.get(loc);
var ok = bp.condition == null ? true : Values.toBoolean(run(currFrame, bp.condition).result);
if (ok) pauseDebug(ctx, locToBreakpoint.get(loc));
}
else if (isBreakpointable && tmpBreakpts.remove(loc)) pauseDebug(ctx, null);
else if (instruction.type == Type.NOP && instruction.match("debug")) pauseDebug(ctx, null);
while (enabled) {
switch (state) {
case PAUSED_EXCEPTION:
case PAUSED_NORMAL: break;
case STEPPING_OUT:
case RESUMED: return false;
case STEPPING_IN:
if (!prevLocation.equals(loc)) {
if (isBreakpointable) pauseDebug(ctx, null);
else if (returnVal != Runners.NO_RETURN) pauseDebug(ctx, null);
else return false;
}
else return false;
break;
case STEPPING_OVER:
if (stepOutFrame.frame == frame.frame) {
if (isBreakpointable && (
!loc.filename().equals(prevLocation.filename()) ||
loc.line() != prevLocation.line()
)) pauseDebug(ctx, null);
else return false;
}
else return false;
break;
}
updateNotifier.await();
}
return false;
}
@Override public void onFramePop(Context ctx, CodeFrame frame) {
updateFrames(ctx);
try { idToFrame.remove(codeFrameToFrame.remove(frame).id); }
catch (NullPointerException e) { }
if (StackData.frames(ctx).size() == 0) resume(State.RESUMED);
else if (stepOutFrame != null && stepOutFrame.frame == frame &&
(state == State.STEPPING_OUT || state == State.STEPPING_IN || state == State.STEPPING_OVER)
) {
// if (state == State.STEPPING_OVER) state = State.STEPPING_IN;
// else {
pauseDebug(ctx, null);
updateNotifier.await();
// }
}
}
@Override public void connect() {
if (!target.attachDebugger(this)) {
ws.send(new V8Error("A debugger is already attached to this engine."));
}
}
@Override public void disconnect() {
target.detachDebugger();
enabled = false;
updateNotifier.next();
}
public SimpleDebugger(WebSocket ws, Engine target) {
this.ws = ws;
this.target = target;
}
}

Some files were not shown because too many files have changed in this diff Show More