Compare commits

...

127 Commits

Author SHA1 Message Date
8c6379eb24 Merge pull request #10 from TopchetoEU/TopchetoEU/mapping
Add support for source mappings
2023-12-18 22:42:38 +02:00
380a5c720a feat: make environment hidable from stack trace 2023-12-18 22:38:26 +02:00
76c3d377af feat: use mappings in stack traces 2023-12-18 22:30:14 +02:00
42f443572a fix: how the hell did i fix this (it was the cache all along) 2023-12-18 22:11:08 +02:00
773bc72f3e refactor: rename compileWithDebug to compile 2023-12-14 21:07:16 +02:00
0b5178e9fd fix: return minified typescript 2023-12-14 21:06:23 +02:00
8cffcff7db refactor: remove unused instructions 2023-12-14 17:02:24 +02:00
60bbaaccd4 fix: some issues with try-catch 2023-12-14 16:49:51 +02:00
60b1762462 refactor: remove printf 2023-12-14 16:49:35 +02:00
34434965d2 cant be fucked to split this one up 2023-12-14 12:39:01 +02:00
fe86123f0f refactor: improve function statement 2023-11-29 13:04:41 +02:00
d5e6edfa8b refactor: replace .locate with argument 2023-11-29 13:03:53 +02:00
73345062ca feat: implement new API with source maps 2023-11-28 21:40:37 +02:00
124341969c fix: simplify source map API 2023-11-28 21:39:25 +02:00
8defd93855 fix: use proper name for native constructors 2023-11-28 21:37:46 +02:00
6c57e0e9f2 fix: properly handle wrapper function 2023-11-28 21:37:13 +02:00
f1932914ee fix: move debugger assets to correct location 2023-11-27 20:28:02 +02:00
977701e601 feat: implement source maps 2023-11-26 16:50:07 +02:00
e8a7ac8da8 refactor: reorganize assets 2023-11-26 16:49:47 +02:00
6b1cb852c2 fix: arrays wrongly stringified in JSONLib 2023-11-26 13:51:56 +02:00
b59a003086 feat: implement VLQ parsing 2023-11-26 13:51:43 +02:00
1902e41f61 feat: make typescript output mappings 2023-11-26 11:52:47 +02:00
27162ef8ac feat: improve Bufffer API 2023-11-26 11:51:32 +02:00
4f22e76d2b fix: make code java 11 compatible 2023-11-25 20:22:16 +02:00
8924e7aadc build: fix build script to exit with code when error occurs 2023-11-25 20:16:06 +02:00
1d0e31a423 Merge pull request #9 from TopchetoEU/TopchetoEU/perms-and-fs
Permissions and filesystems
2023-11-25 20:10:59 +02:00
ab56908171 fix: faulty insufficient permissions error 2023-11-25 20:09:59 +02:00
eb14bb080c fix: debugger breakpoints not registered 2023-11-25 20:09:47 +02:00
f52f47cdb4 feat: properly implement filesystems 2023-11-25 19:36:18 +02:00
567eaa8514 fix: incorrect declarations 2023-11-25 19:13:20 +02:00
2cfdd8e335 feat: add utf8 encoding and decoding 2023-11-25 19:12:56 +02:00
4b1ec671e2 fix: micro tasks not handled properly 2023-11-25 18:58:35 +02:00
b127aadcf6 fix: promise was sending macro tasks instead of micro 2023-11-25 18:58:24 +02:00
b6eaff65ca style: remove unnececeary import 2023-11-25 18:48:46 +02:00
443dc0ffa1 feat: add await function to promise 2023-11-25 18:47:51 +02:00
e107dd3507 fix: promise doesn't use microtasks in some cases 2023-11-25 18:47:36 +02:00
6af3c70fce feat: add filesystem libs 2023-11-25 18:47:01 +02:00
8b743f49d1 feat: add toString, equals and hashCode overrides to wrappers and objects 2023-11-25 18:46:13 +02:00
e1ce384815 feat: add parse function to Filename 2023-11-25 18:44:27 +02:00
86d205e521 fix: parsing files won't modify last instruction to RET, instead will just append it 2023-11-25 18:44:11 +02:00
f0ad936e5b refactor: use new iterable names 2023-11-25 18:43:43 +02:00
4a1473c5be fix: sorting with no comparator now works 2023-11-25 18:42:54 +02:00
4111dbf5c4 fix: functions now print their name when stringified 2023-11-25 18:41:47 +02:00
1666682dc2 refactor: get rid of data parent hierarchy 2023-11-25 18:41:18 +02:00
f2b33d0233 feat: add support for async iterators
fix: comparasions had small behavioural issue
2023-11-25 18:40:49 +02:00
f5a0b6eaf7 fix: some improvements of compiled bytecode 2023-11-25 18:38:43 +02:00
829bea755d fix: CodeFrame didnt't handle out of bounds jumps well 2023-11-25 18:38:16 +02:00
4b0dcffd13 feat: add fs declarations in lib.d.ts 2023-11-25 18:37:40 +02:00
987f8b8f00 fix: handle native errors properly-ish 2023-11-25 14:18:46 +02:00
55e3d46bc2 feat: implement memory and root fs 2023-11-25 14:18:04 +02:00
3e25068219 feat: implement bulk of fs 2023-11-25 14:17:37 +02:00
7ecb8bfabb feat: implement permissions 2023-11-25 14:16:02 +02:00
488deea164 fix: improve performance of typescript by caching separate declarations 2023-11-14 09:24:39 +02:00
ed08041335 fix: internal error when trying to use key of "undefined" 2023-11-14 09:24:00 +02:00
0a4149ba81 fix: remove double space in "Uncaught ..." 2023-11-14 09:23:15 +02:00
30f5d619c3 fix: errors now have the right prototype and name 2023-11-14 09:22:56 +02:00
e7dbe91374 refactor: clean up protocol.json 2023-11-13 20:06:07 +02:00
455f5a613e feat: implement simple permission matching 2023-11-13 19:09:33 +02:00
1eeac3ae97 fix: replace templates in Metadata class with placeholder data 2023-11-13 19:08:56 +02:00
1acd78e119 refactor: clean up Main class 2023-11-13 18:56:59 +02:00
df9932874d feat: remove unnececeary @NativeInit directives 2023-11-06 14:03:15 +02:00
b47d1a7576 refactor: remove StackData and Data usage 2023-11-06 13:53:36 +02:00
fdfa8d7713 refactor: some minor fixes, rewrite README example 2023-11-06 10:55:38 +02:00
f5d1287948 fix: some annoying bugs, as well as splice 2023-11-06 00:55:30 +02:00
15f4278cb1 fix: build with java 17 2023-11-05 21:00:39 +02:00
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
210 changed files with 10733 additions and 6635 deletions

View File

@@ -11,9 +11,15 @@ jobs:
runs-on: "ubuntu-latest"
steps:
- name: Setup Java
uses: actions/setup-java@v3
with:
distribution: 'adopt'
java-version: '11'
- 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

@@ -4,31 +4,42 @@
**WARNING: Currently, this code is mostly undocumented. Proceed with caution and a psychiatrist.**
JScript is an engine, capable of running EcmaScript 5, written entirely in Java. This engine has been developed with the goal of being easy to integrate with your preexisting codebase, **THE GOAL OF THIS ENGINE IS NOT PERFORMANCE**. My crude experiments show that this engine is 50x-100x slower than V8, which, although bad, is acceptable for most simple scripting purposes.
JScript is an engine, capable of running EcmaScript 5, written entirely in Java. This engine has been developed with the goal of being easy to integrate with your preexisting codebase, **THE GOAL OF THIS ENGINE IS NOT PERFORMANCE**. My crude experiments show that this engine is 50x-100x slower than V8, which, although bad, is acceptable for most simple scripting purposes. Note that although the codebase has a Main class, this isn't meant to be a standalone program, but instead a library for running JavaScript code.
## Example
The following will create a REPL using the engine as a backend. Not that this won't properly log errors. I recommend checking out the implementation in `Main.main`:
```java
var engine = new PolyfillEngine(new File("."));
var in = new BufferedReader(new InputStreamReader(System.in));
var engine = new Engine(true /* false if you dont want debugging */);
var env = new Environment(null, null, null);
var debugger = new DebugServer();
// Create one target for the engine and start debugging server
debugger.targets.put("target", (socket, req) -> new SimpleDebugger(socket, engine));
debugger.start(new InetSocketAddress("127.0.0.1", 9229), true);
// Queue code to load internal libraries and start engine
engine.pushMsg(false, null, new Internals().getApplier(env));
engine.start();
while (true) {
try {
var raw = in.readLine();
var raw = Reading.read();
if (raw == null) break;
var res = engine.pushMsg(false, engine.global(), Map.of(), "<stdio>", raw, null).await();
Values.printValue(engine.context(), res);
System.out.println();
// Push a message to the engine with the raw REPL code
var res = engine.pushMsg(
false, new Context(engine).pushEnv(env),
new Filename("jscript", "repl.js"), raw, null
).await();
Values.printValue(null, res);
}
catch (EngineException e) {
try {
System.out.println("Uncaught " + e.toString(engine.context()));
}
catch (InterruptedException _e) { return; }
catch (EngineException e) { Values.printError(e, ""); }
catch (SyntaxException ex) {
System.out.println("Syntax error:" + ex.msg);
}
catch (IOException | InterruptedException e) { return; }
catch (IOException e) { }
}
```

View File

@@ -1,7 +1,7 @@
const { spawn } = require('child_process');
const fs = require('fs/promises');
const pt = require('path');
const { argv } = require('process');
const { argv, exit } = require('process');
const conf = {
name: "java-jscript",
@@ -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,12 +69,12 @@ 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', '.');
}
catch (e) {
if (argv[2] === 'debug') throw e;
else console.log(e.toString());
console.log(e.toString());
exit(-1);
}
})();

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

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>

File diff suppressed because it is too large Load Diff

113
src/assets/js/bootstrap.js vendored Normal file
View File

@@ -0,0 +1,113 @@
(function (ts, env, libs) {
var src = '', version = 0;
var lib = libs.concat([
'declare function exit(): never;',
'declare function go(): any;',
'declare function getTsDeclarations(): string[];'
]).join('');
var libSnapshot = ts.ScriptSnapshot.fromString(lib);
var environments = {};
var declSnapshots = [];
var settings = {
outDir: "/out",
declarationDir: "/out",
target: ts.ScriptTarget.ES5,
lib: [ ],
module: ts.ModuleKind.None,
declaration: true,
stripInternal: true,
downlevelIteration: true,
forceConsistentCasingInFileNames: true,
experimentalDecorators: true,
strict: true,
sourceMap: true,
};
var reg = ts.createDocumentRegistry();
var service = ts.createLanguageService({
getCurrentDirectory: function() { return "/"; },
getDefaultLibFileName: function() { return "/lib.d.ts"; },
getScriptFileNames: function() {
var res = [ "/src.ts", "/lib.d.ts" ];
for (var i = 0; i < declSnapshots.length; i++) res.push("/glob." + (i + 1) + ".d.ts");
return res;
},
getCompilationSettings: function () { return settings; },
fileExists: function(filename) { return filename === "/lib.d.ts" || filename === "/src.ts" || filename === "/glob.d.ts"; },
getScriptSnapshot: function(filename) {
if (filename === "/lib.d.ts") return libSnapshot;
if (filename === "/src.ts") return ts.ScriptSnapshot.fromString(src);
var index = /\/glob\.(\d+)\.d\.ts/g.exec(filename);
if (index && index[1] && (index = Number(index[1])) && index > 0 && index <= declSnapshots.length) {
return declSnapshots[index - 1];
}
throw new Error("File '" + filename + "' doesn't exist.");
},
getScriptVersion: function (filename) {
if (filename === "/lib.d.ts" || filename.startsWith("/glob.")) return 0;
else return version;
},
}, reg);
service.getEmitOutput("/lib.d.ts");
log("Loaded libraries!");
var oldCompile = env.compile;
function compile(code, filename, env) {
src = code;
version++;
if (!environments[env.id]) environments[env.id] = []
declSnapshots = environments[env.id];
var emit = service.getEmitOutput("/src.ts");
var diagnostics = []
.concat(service.getCompilerOptionsDiagnostics())
.concat(service.getSyntacticDiagnostics("/src.ts"))
.concat(service.getSemanticDiagnostics("/src.ts"))
.map(function (diagnostic) {
var message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n");
if (diagnostic.file) {
var pos = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
var file = diagnostic.file.fileName.substring(1);
if (file === "src.ts") file = filename;
return file + ":" + (pos.line + 1) + ":" + (pos.character + 1) + ": " + message;
}
else return message;
});
if (diagnostics.length > 0) {
throw new SyntaxError(diagnostics.join("\n"));
}
var map = JSON.parse(emit.outputFiles[0].text);
var result = emit.outputFiles[1].text;
var declaration = emit.outputFiles[2].text;
var compiled = oldCompile(result, filename, env);
return {
function: function () {
var val = compiled.function.apply(this, arguments);
if (declaration !== '') declSnapshots.push(ts.ScriptSnapshot.fromString(declaration));
return val;
},
breakpoints: compiled.breakpoints,
mapChain: compiled.mapChain.concat(map.mappings),
};
}
function apply(env) {
env.compile = compile;
env.global.getTsDeclarations = function() {
return environments[env.id];
}
}
apply(env);
})(arguments[0], arguments[1], arguments[2]);

File diff suppressed because it is too large Load Diff

18
src/assets/js/ts.js Normal file

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,53 @@
package me.topchetoeu.jscript;
public class Buffer {
private byte[] data;
private int length;
public void write(int i, byte[] val) {
if (i + val.length > data.length) {
var newCap = i + val.length + 1;
if (newCap < data.length * 2) newCap = data.length * 2;
var tmp = new byte[newCap];
System.arraycopy(this.data, 0, tmp, 0, length);
this.data = tmp;
}
System.arraycopy(val, 0, data, i, val.length);
if (i + val.length > length) length = i + val.length;
}
public int read(int i, byte[] buff) {
int n = buff.length;
if (i + n > length) n = length - i;
System.arraycopy(data, i, buff, 0, n);
return n;
}
public void append(byte b) {
write(length, new byte[] { b });
}
public byte[] data() {
var res = new byte[length];
System.arraycopy(this.data, 0, res, 0, length);
return res;
}
public int length() {
return length;
}
public Buffer(byte[] data) {
this.data = new byte[data.length];
this.length = data.length;
System.arraycopy(data, 0, this.data, 0, data.length);
}
public Buffer(int capacity) {
this.data = new byte[capacity];
this.length = 0;
}
public Buffer() {
this.data = new byte[128];
this.length = 0;
}
}

View File

@@ -0,0 +1,60 @@
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;
}
public static Filename parse(String val) {
var i = val.indexOf("://");
if (i >= 0) return new Filename(val.substring(0, i).trim(), val.substring(i + 3).trim());
else return new Filename("file", val.trim());
}
}

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,9 +55,39 @@ 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;
}
public static Location parse(String raw) {
int i0 = -1, i1 = -1;
for (var i = raw.length() - 1; i >= 0; i--) {
if (raw.charAt(i) == ':') {
if (i1 == -1) i1 = i;
else if (i0 == -1) {
i0 = i;
break;
}
}
}
return new Location(
Integer.parseInt(raw.substring(i0 + 1, i1)),
Integer.parseInt(raw.substring(i1 + 1)),
Filename.parse(raw.substring(0, i0))
);
}
}

View File

@@ -1,147 +1,168 @@
package me.topchetoeu.jscript;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
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.Environment;
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.interop.NativeTypeRegister;
import me.topchetoeu.jscript.polyfills.Internals;
import me.topchetoeu.jscript.filesystem.MemoryFilesystem;
import me.topchetoeu.jscript.filesystem.Mode;
import me.topchetoeu.jscript.filesystem.PhysicalFilesystem;
import me.topchetoeu.jscript.lib.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 class Main {
public static class Printer implements Observer<Object> {
public void next(Object data) {
try {
Values.printValue(null, data);
}
catch (InterruptedException e) { }
Values.printValue(null, data);
System.out.println();
}
public void error(RuntimeException err) {
try {
Values.printError(err, null);
}
public void finish() {
engineTask.interrupt();
}
}
static Thread engineTask, debugTask;
static Engine engine = new Engine(true);
static DebugServer debugServer = new DebugServer();
static Environment environment = new Environment(null, null, null);
static int j = 0;
static boolean exited = false;
static String[] args;
private static void reader() {
try {
for (var arg : args) {
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;
if (arg.equals("--ts")) initTypescript();
else {
System.out.println("Internal error ocurred:");
err.printStackTrace();
var file = Path.of(arg);
var raw = Files.readString(file);
var res = engine.pushMsg(
false, new Context(engine, environment),
Filename.fromFile(file.toFile()),
raw, null
).await();
Values.printValue(null, res);
System.out.println();
}
}
catch (EngineException ex) {
System.out.println("Uncaught ");
Values.printValue(null, ((EngineException)err).value);
catch (EngineException e) { Values.printError(e, null); }
}
for (var i = 0; ; i++) {
try {
var raw = Reading.read();
if (raw == null) break;
var res = engine.pushMsg(
false, new Context(engine, environment),
new Filename("jscript", "repl/" + i + ".js"),
raw, null
).await();
Values.printValue(null, res);
System.out.println();
}
}
catch (InterruptedException ex) {
return;
catch (EngineException e) { Values.printError(e, null); }
catch (SyntaxException e) { Values.printError(e, null); }
}
}
};
catch (IOException e) {
System.out.println(e.toString());
exited = true;
}
catch (RuntimeException ex) {
if (!exited) {
System.out.println("Internal error ocurred:");
ex.printStackTrace();
}
}
if (exited) {
debugTask.interrupt();
engineTask.interrupt();
}
}
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];
private static void initEnv() {
environment = Internals.apply(environment);
env.global.define("exit", ctx -> {
exited[0] = true;
task.interrupt();
throw new InterruptedException();
});
env.global.define("go", ctx -> {
environment.global.define(false, new NativeFunction("exit", (_ctx, th, args) -> {
exited = true;
throw new InterruptException();
}));
environment.global.define(false, new NativeFunction("go", (_ctx, th, args) -> {
try {
var func = ctx.compile("do.js", new String(Files.readAllBytes(Path.of("do.js"))));
return func.call(ctx);
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");
}
});
}));
engine.pushMsg(false, new Context(builderEnv, new MessageContext(engine)), "core.js", resourceToString("js/core.js"), null, env, new Internals()).toObservable().on(valuePrinter);
environment.filesystem.protocols.put("temp", new MemoryFilesystem(Mode.READ_WRITE));
environment.filesystem.protocols.put("file", new PhysicalFilesystem(Path.of(".").toAbsolutePath()));
}
private static void initEngine() {
debugServer.targets.put("target", (ws, req) -> new SimpleDebugger(ws, engine));
engineTask = engine.start();
debugTask = debugServer.start(new InetSocketAddress("127.0.0.1", 9229), true);
}
private static void initTypescript() {
try {
var tsEnv = Internals.apply(new Environment(null, null, null));
tsEnv.stackVisible = false;
tsEnv.global.define(null, "module", false, new ObjectValue());
var bsEnv = Internals.apply(new Environment(null, null, null));
bsEnv.stackVisible = false;
task = engine.start();
var reader = new Thread(() -> {
try {
while (true) {
try {
var raw = in.readLine();
engine.pushMsg(
false, new Context(engine, tsEnv),
new Filename("jscript", "ts.js"),
Reading.resourceToString("js/ts.js"), null
).await();
System.out.println("Loaded typescript!");
var ctx = new Context(engine, bsEnv);
engine.pushMsg(
false, ctx,
new Filename("jscript", "bootstrap.js"), Reading.resourceToString("js/bootstrap.js"), null,
tsEnv.global.get(ctx, "ts"), environment, new ArrayValue(null, Reading.resourceToString("js/lib.d.ts"))
).await();
}
catch (EngineException e) {
Values.printError(e, "(while initializing TS)");
}
}
public static void main(String args[]) {
System.out.println(String.format("Running %s v%s by %s", Metadata.name(), Metadata.version(), Metadata.author()));
Main.args = args;
var reader = new Thread(Main::reader);
initEnv();
initEngine();
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();

View File

@@ -1,7 +1,20 @@
package me.topchetoeu.jscript;
public class Metadata {
public static final String VERSION = "${VERSION}";
public static final String AUTHOR = "${AUTHOR}";
public static final String NAME = "${NAME}";
private static final String VERSION = "${VERSION}";
private static final String AUTHOR = "${AUTHOR}";
private static final String NAME = "${NAME}";
public static String version() {
if (VERSION.equals("$" + "{VERSION}")) return "1337-devel";
else return VERSION;
}
public static String author() {
if (AUTHOR.equals("$" + "{AUTHOR}")) return "anonymous";
else return AUTHOR;
}
public static String name() {
if (NAME.equals("$" + "{NAME}")) return "some-product";
else return NAME;
}
}

View File

@@ -0,0 +1,27 @@
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 { return new String(in.readAllBytes()); }
catch (Throwable e) { throw new UncheckedException(e); }
}
public static InputStream resourceToStream(String name) {
return Reading.class.getResourceAsStream("/assets/" + name);
}
public static String resourceToString(String name) {
return streamToString(resourceToStream(name));
}
}

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

@@ -0,0 +1,21 @@
package me.topchetoeu.jscript.compilation;
import me.topchetoeu.jscript.engine.values.Values;
public final class CalculateResult {
public final boolean exists;
public final Object value;
public final boolean isTruthy() {
return exists && Values.toBoolean(value);
}
public CalculateResult(Object value) {
this.exists = true;
this.value = value;
}
public CalculateResult() {
this.exists = false;
this.value = null;
}
}

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,66 @@
package me.topchetoeu.jscript.compilation;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeSet;
import java.util.Vector;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.values.CodeFunction;
public class CompileTarget {
public final Vector<Instruction> target = new Vector<>();
public final Map<Long, FunctionBody> functions;
public final TreeSet<Location> breakpoints;
private final HashMap<Location, Instruction> bpToInstr = new HashMap<>();
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, BreakpointType type) {
var instr = target.get(i);
instr.breakpoint = type;
if (type == BreakpointType.NONE) {
breakpoints.remove(target.get(i).location);
bpToInstr.remove(instr.location, instr);
}
else {
breakpoints.add(target.get(i).location);
var old = bpToInstr.put(instr.location, instr);
if (old != null) old.breakpoint = BreakpointType.NONE;
}
}
public void setDebug(BreakpointType type) {
setDebug(target.size() - 1, type);
}
public Instruction get(int i) {
return target.get(i);
}
public int size() { return target.size(); }
public Location lastLoc(Location fallback) {
if (target.size() == 0) return fallback;
else return target.get(target.size() - 1).location;
}
public Instruction[] array() { return target.toArray(Instruction[]::new); }
public FunctionBody body() {
return functions.get(0l);
}
public CodeFunction func(Environment env) {
return new CodeFunction(env, "", body());
}
public CompileTarget(Map<Long, FunctionBody> functions, TreeSet<Location> breakpoints) {
this.functions = functions;
this.breakpoints = breakpoints;
}
}

View File

@@ -1,77 +1,64 @@
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;
import me.topchetoeu.jscript.compilation.control.ReturnStatement;
import me.topchetoeu.jscript.compilation.control.ThrowStatement;
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.values.FunctionStatement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class CompoundStatement extends Statement {
public final Statement[] statements;
public final boolean separateFuncs;
public Location end;
@Override
public boolean pollutesStack() {
@Override public boolean pure() {
for (var stm : statements) {
if (stm instanceof FunctionStatement) continue;
return true;
if (!stm.pure()) return false;
}
return false;
return true;
}
@Override
public void declare(ScopeRecord varsScope) {
for (var stm : statements) {
stm.declare(varsScope);
}
for (var stm : statements) stm.declare(varsScope);
}
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
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.add(Instruction.discard());
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType type) {
List<Statement> statements = new Vector<Statement>();
if (separateFuncs) for (var stm : this.statements) {
if (stm instanceof FunctionStatement && ((FunctionStatement)stm).statement) {
stm.compile(target, scope, false);
}
else statements.add(stm);
}
else statements = List.of(this.statements);
var polluted = false;
for (var i = 0; i < statements.size(); i++) {
var stm = statements.get(i);
if (i != statements.size() - 1) stm.compile(target, scope, false, BreakpointType.STEP_OVER);
else stm.compile(target, scope, polluted = pollute, BreakpointType.STEP_OVER);
}
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 (!polluted && pollute) {
target.add(Instruction.loadValue(loc(), null));
}
}
@Override
public Statement optimize() {
var res = new ArrayList<Statement>();
for (var i = 0; i < statements.length; i++) {
var stm = statements[i].optimize();
if (i < statements.length - 1 && stm.pure()) continue;
res.add(stm);
if (
stm instanceof ContinueStatement ||
stm instanceof ReturnStatement ||
stm instanceof ThrowStatement ||
stm instanceof ContinueStatement
) break;
}
if (res.size() == 1) return res.get(0);
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) {
public CompoundStatement(Location loc, boolean separateFuncs, Statement ...statements) {
super(loc);
this.separateFuncs = separateFuncs;
this.statements = statements;
}
}

View File

@@ -1,33 +0,0 @@
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;
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());
}
@Override
public Statement optimize() {
if (value == null) return this;
var val = value.optimize();
if (val.pure()) return new ConstantStatement(loc(), null);
else return new DiscardStatement(loc(), val);
}
public DiscardStatement(Location loc, Statement val) {
super(loc);
this.value = val;
}
}

View File

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

View File

@@ -7,11 +7,11 @@ import me.topchetoeu.jscript.exceptions.SyntaxException;
public class Instruction {
public static enum Type {
RETURN,
SIGNAL,
THROW,
THROW_SYNTAX,
DELETE,
TRY,
TRY_START,
TRY_END,
NOP,
CALL,
@@ -34,7 +34,6 @@ public class Instruction {
LOAD_REGEX,
DUP,
MOVE,
STORE_VAR,
STORE_MEMBER,
@@ -46,61 +45,35 @@ public class Instruction {
TYPEOF,
OPERATION;
// TYPEOF,
// INSTANCEOF(true),
// IN(true),
}
public static enum BreakpointType {
NONE,
STEP_OVER,
STEP_IN;
// MULTIPLY(true),
// DIVIDE(true),
// MODULO(true),
// ADD(true),
// SUBTRACT(true),
// USHIFT_RIGHT(true),
// SHIFT_RIGHT(true),
// SHIFT_LEFT(true),
// GREATER(true),
// LESS(true),
// GREATER_EQUALS(true),
// LESS_EQUALS(true),
// LOOSE_EQUALS(true),
// LOOSE_NOT_EQUALS(true),
// EQUALS(true),
// NOT_EQUALS(true),
// AND(true),
// OR(true),
// XOR(true),
// NEG(true),
// POS(true),
// NOT(true),
// INVERSE(true);
// final boolean isOperation;
// private Type(boolean isOperation) {
// this.isOperation = isOperation;
// }
// private Type() {
// this(false);
// }
public boolean shouldStepIn() {
return this != NONE;
}
public boolean shouldStepOver() {
return this == STEP_OVER;
}
}
public final Type type;
public final Object[] params;
public Location location;
public boolean debugged;
public BreakpointType breakpoint = BreakpointType.NONE;
public Instruction setDbgData(Instruction other) {
this.location = other.location;
this.breakpoint = other.breakpoint;
return this;
}
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) {
@@ -135,40 +108,32 @@ public class Instruction {
this.params = params;
}
public static Instruction tryInstr(int n, int catchN, int finallyN) {
return new Instruction(null, Type.TRY, n, catchN, finallyN);
public static Instruction tryStart(Location loc, int catchStart, int finallyStart, int end) {
return new Instruction(loc, Type.TRY_START, catchStart, finallyStart, end);
}
public static Instruction throwInstr() {
return new Instruction(null, Type.THROW);
public static Instruction tryEnd(Location loc) {
return new Instruction(loc, Type.TRY_END);
}
public static Instruction throwSyntax(SyntaxException err) {
return new Instruction(null, Type.THROW_SYNTAX, err.getMessage());
public static Instruction throwInstr(Location loc) {
return new Instruction(loc, Type.THROW);
}
public static Instruction delete() {
return new Instruction(null, Type.DELETE);
public static Instruction throwSyntax(Location loc, SyntaxException err) {
return new Instruction(loc, Type.THROW_SYNTAX, err.getMessage());
}
public static Instruction ret() {
return new Instruction(null, Type.RETURN);
public static Instruction throwSyntax(Location loc, String err) {
return new Instruction(loc, Type.THROW_SYNTAX, err);
}
public static Instruction debug() {
return new Instruction(null, Type.NOP, "debug");
public static Instruction delete(Location loc) {
return new Instruction(loc, Type.DELETE);
}
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);
public static Instruction ret(Location loc) {
return new Instruction(loc, Type.RETURN);
}
public static Instruction debug(Location loc) {
return new Instruction(loc, Type.NOP, "debug");
}
/**
* 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) {
public static Instruction nop(Location loc, Object ...params) {
for (var param : params) {
if (param instanceof String) continue;
if (param instanceof Boolean) continue;
@@ -178,109 +143,104 @@ public class Instruction {
throw new RuntimeException("NOP params may contain only strings, booleans, doubles, integers and nulls.");
}
return new Instruction(null, Type.NOP, params);
return new Instruction(loc, Type.NOP, params);
}
public static Instruction call(int argn) {
return new Instruction(null, Type.CALL, argn);
public static Instruction call(Location loc, int argn) {
return new Instruction(loc, Type.CALL, argn);
}
public static Instruction callNew(int argn) {
return new Instruction(null, Type.CALL_NEW, argn);
public static Instruction callNew(Location loc, int argn) {
return new Instruction(loc, Type.CALL_NEW, argn);
}
public static Instruction jmp(int offset) {
return new Instruction(null, Type.JMP, offset);
public static Instruction jmp(Location loc, int offset) {
return new Instruction(loc, Type.JMP, offset);
}
public static Instruction jmpIf(int offset) {
return new Instruction(null, Type.JMP_IF, offset);
public static Instruction jmpIf(Location loc, int offset) {
return new Instruction(loc, Type.JMP_IF, offset);
}
public static Instruction jmpIfNot(int offset) {
return new Instruction(null, Type.JMP_IFN, offset);
public static Instruction jmpIfNot(Location loc, int offset) {
return new Instruction(loc, Type.JMP_IFN, offset);
}
public static Instruction loadValue(Object val) {
return new Instruction(null, Type.LOAD_VALUE, val);
public static Instruction loadValue(Location loc, Object val) {
return new Instruction(loc, Type.LOAD_VALUE, val);
}
public static Instruction makeVar(String name) {
return new Instruction(null, Type.MAKE_VAR, name);
public static Instruction makeVar(Location loc, String name) {
return new Instruction(loc, Type.MAKE_VAR, name);
}
public static Instruction loadVar(Object i) {
return new Instruction(null, Type.LOAD_VAR, i);
public static Instruction loadVar(Location loc, Object i) {
return new Instruction(loc, Type.LOAD_VAR, i);
}
public static Instruction loadGlob() {
return new Instruction(null, Type.LOAD_GLOB);
public static Instruction loadGlob(Location loc) {
return new Instruction(loc, Type.LOAD_GLOB);
}
public static Instruction loadMember() {
return new Instruction(null, Type.LOAD_MEMBER);
public static Instruction loadMember(Location loc) {
return new Instruction(loc, Type.LOAD_MEMBER);
}
public static Instruction loadMember(Object key) {
public static Instruction loadMember(Location loc, Object key) {
if (key instanceof Number) key = ((Number)key).doubleValue();
return new Instruction(null, Type.LOAD_VAL_MEMBER, key);
return new Instruction(loc, Type.LOAD_VAL_MEMBER, key);
}
public static Instruction loadRegex(String pattern, String flags) {
return new Instruction(null, Type.LOAD_REGEX, pattern, flags);
public static Instruction loadRegex(Location loc, String pattern, String flags) {
return new Instruction(loc, Type.LOAD_REGEX, pattern, flags);
}
public static Instruction loadFunc(int instrN, int varN, int len, int[] captures) {
var args = new Object[3 + captures.length];
args[0] = instrN;
args[1] = varN;
args[2] = len;
for (var i = 0; i < captures.length; i++) args[i + 3] = captures[i];
return new Instruction(null, Type.LOAD_FUNC, args);
public static Instruction loadFunc(Location loc, long id, int[] captures) {
var args = new Object[1 + captures.length];
args[0] = id;
for (var i = 0; i < captures.length; i++) args[i + 1] = captures[i];
return new Instruction(loc, Type.LOAD_FUNC, args);
}
public static Instruction loadObj() {
return new Instruction(null, Type.LOAD_OBJ);
public static Instruction loadObj(Location loc) {
return new Instruction(loc, Type.LOAD_OBJ);
}
public static Instruction loadArr(int count) {
return new Instruction(null, Type.LOAD_ARR, count);
public static Instruction loadArr(Location loc, int count) {
return new Instruction(loc, Type.LOAD_ARR, count);
}
public static Instruction dup() {
return new Instruction(null, Type.DUP, 0, 1);
public static Instruction dup(Location loc) {
return new Instruction(loc, Type.DUP, 1);
}
public static Instruction dup(int count, int offset) {
return new Instruction(null, Type.DUP, offset, count);
}
public static Instruction move(int count, int offset) {
return new Instruction(null, Type.MOVE, offset, count);
public static Instruction dup(Location loc, int count) {
return new Instruction(loc, Type.DUP, count);
}
public static Instruction storeSelfFunc(int i) {
return new Instruction(null, Type.STORE_SELF_FUNC, i);
public static Instruction storeSelfFunc(Location loc, int i) {
return new Instruction(loc, Type.STORE_SELF_FUNC, i);
}
public static Instruction storeVar(Object i) {
return new Instruction(null, Type.STORE_VAR, i, false);
public static Instruction storeVar(Location loc, Object i) {
return new Instruction(loc, Type.STORE_VAR, i, false);
}
public static Instruction storeVar(Object i, boolean keep) {
return new Instruction(null, Type.STORE_VAR, i, keep);
public static Instruction storeVar(Location loc, Object i, boolean keep) {
return new Instruction(loc, Type.STORE_VAR, i, keep);
}
public static Instruction storeMember() {
return new Instruction(null, Type.STORE_MEMBER, false);
public static Instruction storeMember(Location loc) {
return new Instruction(loc, Type.STORE_MEMBER, false);
}
public static Instruction storeMember(boolean keep) {
return new Instruction(null, Type.STORE_MEMBER, keep);
public static Instruction storeMember(Location loc, boolean keep) {
return new Instruction(loc, Type.STORE_MEMBER, keep);
}
public static Instruction discard() {
return new Instruction(null, Type.DISCARD);
public static Instruction discard(Location loc) {
return new Instruction(loc, Type.DISCARD);
}
public static Instruction typeof() {
return new Instruction(null, Type.TYPEOF);
public static Instruction typeof(Location loc) {
return new Instruction(loc, Type.TYPEOF);
}
public static Instruction typeof(Object varName) {
return new Instruction(null, Type.TYPEOF, varName);
public static Instruction typeof(Location loc, Object varName) {
return new Instruction(loc, Type.TYPEOF, varName);
}
public static Instruction keys() {
return new Instruction(null, Type.KEYS);
public static Instruction keys(Location loc, boolean forInFormat) {
return new Instruction(loc, Type.KEYS, forInFormat);
}
public static Instruction defProp() {
return new Instruction(null, Type.DEF_PROP);
public static Instruction defProp(Location loc) {
return new Instruction(loc, Type.DEF_PROP);
}
public static Instruction operation(Operation op) {
return new Instruction(null, Type.OPERATION, op);
public static Instruction operation(Location loc, Operation op) {
return new Instruction(loc, Type.OPERATION, op);
}
@Override

View File

@@ -1,36 +1,26 @@
package me.topchetoeu.jscript.compilation;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
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 void declare(ScopeRecord varsScope) { }
public Statement optimize() { return this; }
public void compileNoPollution(List<Instruction> target, ScopeRecord scope, boolean debug) {
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType type) {
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()));
compile(target, scope, pollute);
if (target.size() != start) {
target.get(start).locate(loc());
target.setDebug(start, type);
}
}
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);
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
compile(target, scope, pollute, BreakpointType.NONE);
}
public Location loc() { return _loc; }

View File

@@ -0,0 +1,18 @@
package me.topchetoeu.jscript.compilation;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
import me.topchetoeu.jscript.exceptions.SyntaxException;
public class ThrowSyntaxStatement extends Statement {
public final String name;
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
target.add(Instruction.throwSyntax(loc(), name));
}
public ThrowSyntaxStatement(SyntaxException e) {
super(e.loc);
this.name = e.msg;
}
}

View File

@@ -3,6 +3,7 @@ package me.topchetoeu.jscript.compilation;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.values.FunctionStatement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
@@ -10,17 +11,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 +29,20 @@ 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()));
if (entry.value instanceof FunctionStatement) {
((FunctionStatement)entry.value).compile(target, scope, entry.name, false);
target.add(Instruction.storeVar(key).locate(loc()));
}
else if (entry.value != null) {
entry.value.compileWithPollution(target, scope);
target.add(Instruction.storeVar(key).locate(loc()));
if (key instanceof String) target.add(Instruction.makeVar(entry.location, (String)key));
if (entry.value != null) {
FunctionStatement.compileWithName(entry.value, target, scope, true, entry.name, BreakpointType.STEP_OVER);
target.add(Instruction.storeVar(entry.location, key));
}
}
if (pollute) target.add(Instruction.loadValue(loc(), null));
}
public VariableDeclareStatement(Location loc, List<Pair> values) {

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) {
target.add(Instruction.nop("break", label).locate(loc()));
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
target.add(Instruction.nop(loc(), "break", label));
if (pollute) target.add(Instruction.loadValue(loc(), null));
}
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) {
target.add(Instruction.nop("cont", label).locate(loc()));
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
target.add(Instruction.nop(loc(), "cont", label));
if (pollute) target.add(Instruction.loadValue(loc(), null));
}
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) {
target.add(Instruction.debug().locate(loc()));
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
target.add(Instruction.debug(loc()));
if (pollute) target.add(Instruction.loadValue(loc(), null));
}
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()));
target.add(Instruction.delete(loc()));
if (pollute) target.add(Instruction.loadValue(loc(), true));
}
public DeleteStatement(Location loc, Statement key, Statement value) {

View File

@@ -1,76 +1,31 @@
package me.topchetoeu.jscript.compilation.control;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.CompoundStatement;
import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.values.ConstantStatement;
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
import me.topchetoeu.jscript.engine.values.Values;
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) {
if (condition instanceof ConstantStatement) {
int start = target.size();
body.compileNoPollution(target, scope);
int end = target.size();
if (Values.toBoolean(((ConstantStatement)condition).value)) {
WhileStatement.replaceBreaks(target, label, start, end, end + 1, end + 1);
}
else {
target.add(Instruction.jmp(start - end).locate(loc()));
WhileStatement.replaceBreaks(target, label, start, end, start, end + 1);
}
return;
}
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
int start = target.size();
body.compileNoPollution(target, scope, true);
body.compile(target, scope, false, BreakpointType.STEP_OVER);
int mid = target.size();
condition.compileWithPollution(target, scope);
condition.compile(target, scope, true, BreakpointType.STEP_OVER);
int end = target.size();
WhileStatement.replaceBreaks(target, label, start, mid - 1, mid, end + 1);
target.add(Instruction.jmpIf(start - end).locate(loc()));
}
@Override
public Statement optimize() {
var cond = condition.optimize();
var b = body.optimize();
if (b instanceof CompoundStatement) {
var comp = (CompoundStatement)b;
if (comp.statements.length > 0) {
var last = comp.statements[comp.statements.length - 1];
if (last instanceof ContinueStatement) comp.statements[comp.statements.length - 1] = new CompoundStatement(loc());
if (last instanceof BreakStatement) {
comp.statements[comp.statements.length - 1] = new CompoundStatement(loc());
return new CompoundStatement(loc());
}
}
}
else if (b instanceof ContinueStatement) {
b = new CompoundStatement(loc());
}
else if (b instanceof BreakStatement) return new CompoundStatement(loc());
if (b.pure()) return new DoWhileStatement(loc(), label, cond, new CompoundStatement(loc()));
else return new DoWhileStatement(loc(), label, cond, b);
target.add(Instruction.jmpIf(loc(), start - end));
}
public DoWhileStatement(Location loc, String label, Statement condition, Statement body) {

View File

@@ -1,10 +1,10 @@
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.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
@@ -13,9 +13,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 +22,47 @@ public class ForInStatement extends Statement {
}
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
var key = scope.getKey(varName);
if (key instanceof String) target.add(Instruction.makeVar((String)key));
int first = target.size();
if (key instanceof String) target.add(Instruction.makeVar(loc(), (String)key));
if (varValue != null) {
varValue.compileWithPollution(target, scope);
target.add(Instruction.storeVar(scope.getKey(varName)));
varValue.compile(target, scope, true);
target.add(Instruction.storeVar(loc(), scope.getKey(varName)));
}
object.compileWithPollution(target, scope);
target.add(Instruction.keys());
object.compile(target, scope, true, BreakpointType.STEP_OVER);
target.add(Instruction.keys(loc(), 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.dup(loc()));
target.add(Instruction.loadValue(loc(), null));
target.add(Instruction.operation(loc(), Operation.EQUALS));
int mid = target.size();
target.add(Instruction.nop());
target.add(Instruction.nop(loc()));
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.storeVar(key));
for (var i = start; i < target.size(); i++) target.get(i).locate(loc());
body.compileNoPollution(target, scope, true);
target.add(Instruction.loadMember(varLocation, "value"));
target.add(Instruction.storeVar(object.loc(), key));
target.setDebug(BreakpointType.STEP_OVER);
body.compile(target, scope, false, BreakpointType.STEP_OVER);
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(loc(), start - end));
target.add(Instruction.discard(loc()));
target.set(mid, Instruction.jmpIf(loc(), end - mid + 1));
if (pollute) target.add(Instruction.loadValue(loc(), null));
target.get(first).locate(loc());
}
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,79 +1,39 @@
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.CompoundStatement;
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.values.ConstantStatement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
import me.topchetoeu.jscript.engine.values.Values;
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);
if (condition instanceof ConstantStatement) {
if (Values.toBoolean(((ConstantStatement)condition).value)) {
int start = target.size();
body.compileNoPollution(target, scope);
int mid = target.size();
assignment.compileNoPollution(target, scope, true);
int end = target.size();
WhileStatement.replaceBreaks(target, label, start, mid, mid, end + 1);
target.add(Instruction.jmp(start - target.size()).locate(loc()));
return;
}
}
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
declaration.compile(target, scope, false, BreakpointType.STEP_OVER);
int start = target.size();
condition.compileWithPollution(target, scope);
condition.compile(target, scope, true, BreakpointType.STEP_OVER);
int mid = target.size();
target.add(Instruction.nop());
body.compileNoPollution(target, scope);
target.add(Instruction.nop(null));
body.compile(target, scope, false, BreakpointType.STEP_OVER);
int beforeAssign = target.size();
assignment.compile(target, scope);
assignment.compile(target, scope, false, BreakpointType.STEP_OVER);
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()));
}
@Override
public Statement optimize() {
var decl = declaration.optimize();
var asgn = assignment.optimize();
var cond = condition.optimize();
var b = body.optimize();
if (asgn.pure()) {
if (decl.pure()) return new WhileStatement(loc(), label, cond, b).optimize();
else return new CompoundStatement(loc(),
decl, new WhileStatement(loc(), label, cond, b)
).optimize();
}
else if (b instanceof ContinueStatement) return new CompoundStatement(loc(),
decl, new WhileStatement(loc(), label, cond, new CompoundStatement(loc(), b, asgn))
);
else if (b instanceof BreakStatement) return decl;
if (b.pure()) return new ForStatement(loc(), label, decl, cond, asgn, new CompoundStatement(null));
else return new ForStatement(loc(), label, decl, cond, asgn, b);
target.add(Instruction.jmp(loc(), start - end));
target.set(mid, Instruction.jmpIfNot(loc(), end - mid + 1));
if (pollute) target.add(Instruction.loadValue(loc(), null));
}
public ForStatement(Location loc, String label, Statement declaration, Statement condition, Statement assignment, Statement body) {
@@ -84,14 +44,4 @@ public class ForStatement extends Statement {
this.assignment = assignment;
this.body = body;
}
public static CompoundStatement ofFor(Location loc, String label, Statement declaration, Statement condition, Statement increment, Statement body) {
return new CompoundStatement(loc,
declaration,
new WhileStatement(loc, label, condition, new CompoundStatement(loc,
body,
increment
))
);
}
}

View File

@@ -1,74 +1,46 @@
package me.topchetoeu.jscript.compilation.control;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.CompoundStatement;
import me.topchetoeu.jscript.compilation.DiscardStatement;
import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.values.ConstantStatement;
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
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);
if (elseBody != null) elseBody.declare(globScope);
}
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
if (condition instanceof ConstantStatement) {
if (Values.not(((ConstantStatement)condition).value)) {
if (elseBody != null) elseBody.compileNoPollution(target, scope, true);
}
else {
body.compileNoPollution(target, scope, true);
}
@Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType breakpoint) {
condition.compile(target, scope, true, breakpoint);
return;
}
condition.compileWithPollution(target, scope);
if (elseBody == null) {
int i = target.size();
target.add(Instruction.nop());
body.compileNoPollution(target, scope, true);
target.add(Instruction.nop(null));
body.compile(target, scope, pollute, breakpoint);
int endI = target.size();
target.set(i, Instruction.jmpIfNot(endI - i).locate(loc()));
target.set(i, Instruction.jmpIfNot(loc(), endI - i));
}
else {
int start = target.size();
target.add(Instruction.nop());
body.compileNoPollution(target, scope, true);
target.add(Instruction.nop());
target.add(Instruction.nop(null));
body.compile(target, scope, pollute, breakpoint);
target.add(Instruction.nop(null));
int mid = target.size();
elseBody.compileNoPollution(target, scope, true);
elseBody.compile(target, scope, pollute, breakpoint);
int end = target.size();
target.set(start, Instruction.jmpIfNot(mid - start).locate(loc()));
target.set(mid - 1, Instruction.jmp(end - mid + 1).locate(loc()));
target.set(start, Instruction.jmpIfNot(loc(), mid - start));
target.set(mid - 1, Instruction.jmp(loc(), end - mid + 1));
}
}
@Override
public Statement optimize() {
var cond = condition.optimize();
var b = body.optimize();
var e = elseBody == null ? null : elseBody.optimize();
if (b.pure()) b = new CompoundStatement(null);
if (e != null && e.pure()) e = null;
if (b.pure() && e == null) return new DiscardStatement(loc(), cond).optimize();
else return new IfStatement(loc(), cond, b, e);
@Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
compile(target, scope, pollute, BreakpointType.STEP_IN);
}
public IfStatement(Location loc, Statement condition, Statement body, Statement elseBody) {

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,13 +10,10 @@ public class ReturnStatement extends Statement {
public final Statement value;
@Override
public boolean pollutesStack() { return false; }
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
if (value == null) target.add(Instruction.loadValue(null).locate(loc()));
else value.compileWithPollution(target, scope);
target.add(Instruction.ret().locate(loc()));
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
if (value == null) target.add(Instruction.loadValue(loc(), null));
else value.compile(target, scope, true);
target.add(Instruction.ret(loc()));
}
public ReturnStatement(Location loc, Statement value) {

View File

@@ -1,11 +1,12 @@
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.BreakpointType;
import me.topchetoeu.jscript.compilation.Instruction.Type;
import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
@@ -21,9 +22,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,46 +33,48 @@ public class SwitchStatement extends Statement {
}
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
var caseMap = new HashMap<Integer, Integer>();
var stmIndexMap = new HashMap<Integer, Integer>();
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
var caseToStatement = new HashMap<Integer, Integer>();
var statementToIndex = new HashMap<Integer, Integer>();
value.compile(target, scope);
value.compile(target, scope, true, BreakpointType.STEP_OVER);
for (var ccase : cases) {
target.add(Instruction.dup().locate(loc()));
ccase.value.compileWithPollution(target, scope);
target.add(Instruction.operation(Operation.EQUALS).locate(loc()));
caseMap.put(target.size(), ccase.statementI);
target.add(Instruction.nop());
target.add(Instruction.dup(loc()));
ccase.value.compile(target, scope, true);
target.add(Instruction.operation(loc(), Operation.EQUALS));
caseToStatement.put(target.size(), ccase.statementI);
target.add(Instruction.nop(null));
}
int start = target.size();
target.add(Instruction.nop());
target.add(Instruction.nop(null));
for (var stm : body) {
stmIndexMap.put(stmIndexMap.size(), target.size());
stm.compileNoPollution(target, scope, true);
statementToIndex.put(statementToIndex.size(), target.size());
stm.compile(target, scope, false, BreakpointType.STEP_OVER);
}
if (defaultI < 0 || defaultI >= body.length) target.set(start, Instruction.jmp(target.size() - start).locate(loc()));
else target.set(start, Instruction.jmp(stmIndexMap.get(defaultI) - start)).locate(loc());
int end = target.size();
target.add(Instruction.discard(loc()));
if (pollute) target.add(Instruction.loadValue(loc(), null));
for (int i = start; i < target.size(); i++) {
if (defaultI < 0 || defaultI >= body.length) target.set(start, Instruction.jmp(loc(), end - start));
else target.set(start, Instruction.jmp(loc(), statementToIndex.get(defaultI) - start));
for (int i = start; i < end; i++) {
var instr = target.get(i);
if (instr.type == Type.NOP && instr.is(0, "break") && instr.get(1) == null) {
target.set(i, Instruction.jmp(target.size() - i).locate(instr.location));
target.set(i, Instruction.jmp(loc(), end - i).locate(instr.location));
}
}
for (var el : caseMap.entrySet()) {
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));
for (var el : caseToStatement.entrySet()) {
var i = statementToIndex.get(el.getValue());
if (i == null) i = end;
target.set(el.getKey(), Instruction.jmpIf(loc(), i - el.getKey()));
}
target.add(Instruction.discard().locate(loc()));
}
public SwitchStatement(Location loc, Statement value, int defaultI, SwitchCase[] cases, Statement[] body) {

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 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);
target.add(Instruction.throwInstr().locate(loc()));
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
value.compile(target, scope, true);
target.add(Instruction.throwInstr(loc()));
}
public ThrowStatement(Location loc, Statement value) {

View File

@@ -1,10 +1,10 @@
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.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.engine.scope.GlobalScope;
import me.topchetoeu.jscript.engine.scope.LocalScopeRecord;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
@@ -15,9 +15,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 +23,32 @@ public class TryStatement extends Statement {
}
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
target.add(Instruction.nop());
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType bpt) {
target.add(Instruction.nop(null));
int start = target.size(), tryN, catchN = -1, finN = -1;
int start = target.size(), catchStart = -1, finallyStart = -1;
tryBody.compileNoPollution(target, scope);
tryN = target.size() - start;
tryBody.compile(target, scope, false);
target.add(Instruction.tryEnd(loc()));
if (catchBody != null) {
int tmp = target.size();
catchStart = target.size() - start;
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;
target.add(Instruction.tryEnd(loc()));
}
if (finallyBody != null) {
int tmp = target.size();
finallyBody.compileNoPollution(target, scope);
finN = target.size() - tmp;
finallyStart = target.size() - start;
finallyBody.compile(target, scope, false);
target.add(Instruction.tryEnd(loc()));
}
target.set(start - 1, Instruction.tryInstr(tryN, catchN, finN).locate(loc()));
target.set(start - 1, Instruction.tryStart(loc(), catchStart, finallyStart, target.size() - start));
target.setDebug(start - 1, BreakpointType.STEP_OVER);
if (pollute) target.add(Instruction.loadValue(loc(), null));
}
public TryStatement(Location loc, Statement tryBody, Statement catchBody, Statement finallyBody, String name) {

View File

@@ -1,66 +1,36 @@
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.CompoundStatement;
import me.topchetoeu.jscript.compilation.DiscardStatement;
import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.compilation.Instruction.Type;
import me.topchetoeu.jscript.compilation.values.ConstantStatement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
import me.topchetoeu.jscript.engine.values.Values;
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) {
if (condition instanceof ConstantStatement) {
if (Values.toBoolean(((ConstantStatement)condition).value)) {
int start = target.size();
body.compileNoPollution(target, scope);
int end = target.size();
replaceBreaks(target, label, start, end, start, end + 1);
target.add(Instruction.jmp(start - target.size()).locate(loc()));
return;
}
}
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
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);
target.add(Instruction.nop(null));
body.compile(target, scope, false, BreakpointType.STEP_OVER);
int end = target.size();
replaceBreaks(target, label, mid + 1, end, start, end + 1);
target.add(Instruction.jmp(start - end).locate(loc()));
target.set(mid, Instruction.jmpIfNot(end - mid + 1).locate(loc()));
}
@Override
public Statement optimize() {
var cond = condition.optimize();
var b = body.optimize();
if (b instanceof ContinueStatement) {
b = new CompoundStatement(loc());
}
else if (b instanceof BreakStatement) return new DiscardStatement(loc(), cond).optimize();
if (b.pure()) return new WhileStatement(loc(), label, cond, new CompoundStatement(null));
else return new WhileStatement(loc(), label, cond, b);
target.add(Instruction.jmp(loc(), start - end));
target.set(mid, Instruction.jmpIfNot(loc(), end - mid + 1));
if (pollute) target.add(Instruction.loadValue(loc(), null));
}
public WhileStatement(Location loc, String label, Statement condition, Statement body) {
@@ -70,27 +40,15 @@ 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))) {
target.set(i, Instruction.jmp(continuePoint - i));
target.get(i).location = instr.location;
target.set(i, Instruction.jmp(instr.location, continuePoint - i).setDbgData(target.get(i)));
}
if (instr.type == Type.NOP && instr.is(0, "break") && (instr.get(1) == null || instr.is(1, label))) {
target.set(i, Instruction.jmp(breakPoint - i));
target.get(i).location = instr.location;
target.set(i, Instruction.jmp(instr.location, breakPoint - i).setDbgData(target.get(i)));
}
}
}
// public static CompoundStatement ofFor(Location loc, String label, Statement declaration, Statement condition, Statement increment, Statement body) {
// return new CompoundStatement(loc,
// declaration,
// new WhileStatement(loc, label, condition, new CompoundStatement(loc,
// body,
// increment
// ))
// );
// }
}

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,24 +9,29 @@ 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 boolean pure() {
for (var stm : statements) {
if (!stm.pure()) return false;
}
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) {
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
target.add(Instruction.loadArr(loc(), statements.length));
for (var i = 0; i < statements.length; i++) {
var el = statements[i];
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()));
target.add(Instruction.dup(loc()));
target.add(Instruction.loadValue(loc(), i));
el.compile(target, scope, true);
target.add(Instruction.storeMember(loc()));
}
i++;
}
if (!pollute) target.add(Instruction.discard(loc()));
}
public ArrayStatement(Location loc, Statement[] statements) {

View File

@@ -1,43 +1,50 @@
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.Instruction.BreakpointType;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class CallStatement extends Statement {
public final Statement func;
public final Statement[] args;
public final boolean isNew;
@Override
public boolean pollutesStack() { return true; }
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
if (func instanceof IndexStatement) {
((IndexStatement)func).compile(target, scope, true);
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType type) {
if (isNew) func.compile(target, scope, true);
else if (func instanceof IndexStatement) {
((IndexStatement)func).compile(target, scope, true, true);
}
else {
target.add(Instruction.loadValue(null).locate(loc()));
func.compileWithPollution(target, scope);
target.add(Instruction.loadValue(loc(), null));
func.compile(target, scope, true);
}
for (var arg : args) {
arg.compileWithPollution(target, scope);
}
for (var arg : args) arg.compile(target, scope, true);
target.add(Instruction.call(args.length).locate(loc()).setDebug(true));
if (isNew) target.add(Instruction.callNew(loc(), args.length));
else target.add(Instruction.call(loc(), args.length));
target.setDebug(type);
if (!pollute) target.add(Instruction.discard(loc()));
}
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
compile(target, scope, pollute, BreakpointType.STEP_IN);
}
public CallStatement(Location loc, Statement func, Statement ...args) {
public CallStatement(Location loc, boolean isNew, Statement func, Statement ...args) {
super(loc);
this.isNew = isNew;
this.func = func;
this.args = args;
}
public CallStatement(Location loc, Statement obj, Object key, Statement ...args) {
public CallStatement(Location loc, boolean isNew, Statement obj, Object key, Statement ...args) {
super(loc);
this.isNew = isNew;
this.func = new IndexStatement(loc, obj, new ConstantStatement(loc, key));
this.args = 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(loc()));
else if (postfix) {
target.add(Instruction.loadValue(loc(), addAmount));
target.add(Instruction.operation(loc(), Operation.SUBTRACT));
}
}
public ChangeStatement(Location loc, AssignableStatement value, double addAmount, boolean postfix) {

View File

@@ -1,38 +0,0 @@
package me.topchetoeu.jscript.compilation.values;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class CommaStatement extends Statement {
public final Statement first;
public final Statement second;
@Override
public boolean pollutesStack() { return true; }
@Override
public boolean pure() { return first.pure() && second.pure(); }
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
first.compileNoPollution(target, scope);
second.compileWithPollution(target, scope);
}
@Override
public Statement optimize() {
var f = first.optimize();
var s = second.optimize();
if (f.pure()) return s;
else return new CommaStatement(loc(), f, s);
}
public CommaStatement(Location loc, Statement first, Statement second) {
super(loc);
this.first = first;
this.second = second;
}
}

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,11 @@ 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 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(loc(), value));
}
public ConstantStatement(Location loc, Object val) {

View File

@@ -0,0 +1,24 @@
package me.topchetoeu.jscript.compilation.values;
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 DiscardStatement extends Statement {
public final Statement value;
@Override public boolean pure() { return value.pure(); }
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
value.compile(target, scope, false);
if (pollute) target.add(Instruction.loadValue(loc(), null));
}
public DiscardStatement(Location loc, Statement val) {
super(loc);
this.value = val;
}
}

View File

@@ -1,31 +1,35 @@
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.BreakpointType;
import me.topchetoeu.jscript.compilation.Instruction.Type;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
import me.topchetoeu.jscript.exceptions.SyntaxException;
public class FunctionStatement extends Statement {
public final CompoundStatement body;
public final String name;
public final String varName;
public final String[] args;
public final boolean statement;
public final Location end;
@Override
public boolean pure() { return name == null; }
@Override
public boolean pollutesStack() { return true; }
private static Random rand = new Random();
@Override public boolean pure() { return varName == null && statement; }
@Override
public void declare(ScopeRecord scope) {
if (name != null) scope.define(name);
if (varName != null && statement) scope.define(varName);
}
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,71 +42,98 @@ public class FunctionStatement extends Statement {
}
}
public void compile(List<Instruction> target, ScopeRecord scope, String name, boolean isStatement) {
private long compileBody(CompileTarget target, ScopeRecord scope, boolean polute, BreakpointType bp) {
for (var i = 0; i < args.length; i++) {
for (var j = 0; j < i; j++) {
if (args[i].equals(args[j])){
target.add(Instruction.throwSyntax(new SyntaxException(loc(), "Duplicate parameter '" + args[i] + "'.")));
return;
if (args[i].equals(args[j])) {
throw new SyntaxException(loc(), "Duplicate parameter '" + args[i] + "'.");
}
}
}
var id = rand.nextLong();
var subscope = scope.child();
var subtarget = new CompileTarget(target.functions, target.breakpoints);
int start = target.size();
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()));
subtarget.add(Instruction.loadVar(loc(), argsVar));
subtarget.add(Instruction.loadMember(loc(), i));
subtarget.add(Instruction.storeVar(loc(), subscope.define(args[i])));
}
}
if (!isStatement && this.name != null) {
target.add(Instruction.storeSelfFunc((int)subscope.define(this.name)));
if (!statement && this.varName != null) {
subtarget.add(Instruction.storeSelfFunc(loc(), (int)subscope.define(this.varName)));
subtarget.setDebug(bp);
}
body.declare(subscope);
target.add(Instruction.debugVarNames(subscope.locals()));
body.compile(target, subscope);
body.compile(subtarget, subscope, false);
subtarget.add(Instruction.ret(end));
checkBreakAndCont(subtarget, 0);
checkBreakAndCont(target, start);
if (polute) target.add(Instruction.loadFunc(loc(), id, subscope.getCaptures()));
target.functions.put(id, new FunctionBody(
subscope.localsCount(), args.length,
subtarget.array(), subscope.captures(), subscope.locals()
));
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()));
if (name == null) name = this.name;
if (name != null) {
target.add(Instruction.dup().locate(loc()));
target.add(Instruction.loadValue("name").locate(loc()));
target.add(Instruction.loadValue(name).locate(loc()));
target.add(Instruction.storeMember().locate(loc()));
}
if (this.name != null && isStatement) {
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()));
}
}
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
compile(target, scope, null, false);
return id;
}
public FunctionStatement(Location loc, String name, String[] args, CompoundStatement body) {
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, String name, BreakpointType bp) {
if (this.varName != null) name = this.varName;
var hasVar = this.varName != null && statement;
var hasName = name != null;
compileBody(target, scope, pollute || hasVar || hasName, bp);
if (hasName) {
if (pollute || hasVar) target.add(Instruction.dup(loc()));
target.add(Instruction.loadValue(loc(), "name"));
target.add(Instruction.loadValue(loc(), name));
target.add(Instruction.storeMember(loc()));
}
if (hasVar) {
var key = scope.getKey(this.varName);
if (key instanceof String) target.add(Instruction.makeVar(loc(), (String)key));
target.add(Instruction.storeVar(loc(), scope.getKey(this.varName), false));
}
}
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, String name) {
compile(target, scope, pollute, name, BreakpointType.NONE);
}
@Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute, BreakpointType bp) {
compile(target, scope, pollute, (String)null, bp);
}
@Override public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
compile(target, scope, pollute, (String)null, BreakpointType.NONE);
}
public FunctionStatement(Location loc, Location end, String varName, String[] args, boolean statement, CompoundStatement body) {
super(loc);
this.name = name;
this.end = end;
this.varName = varName;
this.statement = statement;
this.args = args;
this.body = body;
}
public static void compileWithName(Statement stm, CompileTarget target, ScopeRecord scope, boolean pollute, String name) {
if (stm instanceof FunctionStatement) ((FunctionStatement)stm).compile(target, scope, pollute, name);
else stm.compile(target, scope, pollute);
}
public static void compileWithName(Statement stm, CompileTarget target, ScopeRecord scope, boolean pollute, String name, BreakpointType bp) {
if (stm instanceof FunctionStatement) ((FunctionStatement)stm).compile(target, scope, pollute, name, bp);
else stm.compile(target, scope, pollute, bp);
}
}

View File

@@ -1,21 +1,17 @@
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 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(loc()));
}
public GlobalThisStatement(Location loc) {

View File

@@ -1,51 +1,41 @@
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.compilation.Instruction.BreakpointType;
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);
target.add(Instruction.dup(2, 0).locate(loc()));
object.compile(target, scope, true);
index.compile(target, scope, true);
target.add(Instruction.dup(loc(), 2));
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);
target.add(Instruction.operation(operation).locate(loc()));
target.add(Instruction.loadMember(loc()));
value.compile(target, scope, true);
target.add(Instruction.operation(loc(), operation));
target.add(Instruction.storeMember(!retPrevValue).locate(loc()).setDebug(true));
target.add(Instruction.storeMember(loc(), pollute));
target.setDebug(BreakpointType.STEP_IN);
}
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(loc(), pollute));
target.setDebug(BreakpointType.STEP_IN);
}
target.get(start);
}
public IndexAssignStatement(Location loc, Statement object, Statement index, Statement value, Operation operation) {

View File

@@ -1,12 +1,11 @@
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.compilation.Instruction.BreakpointType;
import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
@@ -15,30 +14,26 @@ public class IndexStatement extends AssignableStatement {
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);
if (dupObj) target.add(Instruction.dup().locate(loc()));
public void compile(CompileTarget target, ScopeRecord scope, boolean dupObj, boolean pollute) {
object.compile(target, scope, true);
if (dupObj) target.add(Instruction.dup(loc()));
if (index instanceof ConstantStatement) {
target.add(Instruction.loadMember(((ConstantStatement)index).value).locate(loc()));
target.add(Instruction.loadMember(loc(), ((ConstantStatement)index).value));
target.setDebug(BreakpointType.STEP_IN);
return;
}
index.compileWithPollution(target, scope);
target.add(Instruction.loadMember().locate(loc()));
target.get(start).setDebug(true);
index.compile(target, scope, true);
target.add(Instruction.loadMember(loc()));
target.setDebug(BreakpointType.STEP_IN);
if (!pollute) target.add(Instruction.discard(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,30 +10,25 @@ 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 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(loc()));
int start = target.size();
target.add(Instruction.nop());
target.add(Instruction.discard().locate(loc()));
second.compileWithPollution(target, scope);
target.set(start, Instruction.jmpIfNot(target.size() - start).locate(loc()));
target.add(Instruction.nop(null));
if (pollute) target.add(Instruction.discard(loc()));
second.compile(target, scope, pollute);
target.set(start, Instruction.jmpIfNot(loc(), target.size() - start));
}
public LazyAndStatement(Location loc, Statement first, Statement second) {

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,30 +10,25 @@ 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 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(loc()));
int start = target.size();
target.add(Instruction.nop());
target.add(Instruction.discard().locate(loc()));
second.compileWithPollution(target, scope);
target.set(start, Instruction.jmpIf(target.size() - start).locate(loc()));
target.add(Instruction.nop(null));
if (pollute) target.add(Instruction.discard(loc()));
second.compile(target, scope, pollute);
target.set(start, Instruction.jmpIf(loc(), target.size() - start));
}
public LazyOrStatement(Location loc, Statement first, Statement second) {

View File

@@ -1,32 +0,0 @@
package me.topchetoeu.jscript.compilation.values;
import java.util.List;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class NewStatement extends Statement {
public final Statement func;
public final Statement[] args;
@Override
public boolean pollutesStack() { return true; }
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
func.compileWithPollution(target, scope);
for (var arg : args) {
arg.compileWithPollution(target, scope);
}
target.add(Instruction.callNew(args.length).locate(loc()).setDebug(true));
}
public NewStatement(Location loc, Statement func, Statement ...args) {
super(loc);
this.func = func;
this.args = 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;
@@ -14,20 +14,24 @@ public class ObjectStatement extends Statement {
public final Map<Object, FunctionStatement> getters;
public final Map<Object, FunctionStatement> setters;
@Override
public boolean pollutesStack() { return true; }
@Override public boolean pure() {
for (var el : map.values()) {
if (!el.pure()) return false;
}
return true;
}
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
target.add(Instruction.loadObj().locate(loc()));
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
target.add(Instruction.loadObj(loc()));
for (var el : map.entrySet()) {
target.add(Instruction.dup().locate(loc()));
target.add(Instruction.loadValue(el.getKey()).locate(loc()));
target.add(Instruction.dup(loc()));
target.add(Instruction.loadValue(loc(), el.getKey()));
var val = el.getValue();
if (val instanceof FunctionStatement) ((FunctionStatement)val).compile(target, scope, el.getKey().toString(), false);
else val.compileWithPollution(target, scope);
target.add(Instruction.storeMember().locate(loc()));
FunctionStatement.compileWithName(val, target, scope, true, el.getKey().toString());
target.add(Instruction.storeMember(loc()));
}
var keys = new ArrayList<Object>();
@@ -35,17 +39,19 @@ public class ObjectStatement extends Statement {
keys.addAll(setters.keySet());
for (var key : keys) {
if (key instanceof String) target.add(Instruction.loadValue((String)key).locate(loc()));
else target.add(Instruction.loadValue((Double)key).locate(loc()));
if (key instanceof String) target.add(Instruction.loadValue(loc(), (String)key));
else target.add(Instruction.loadValue(loc(), (Double)key));
if (getters.containsKey(key)) getters.get(key).compileWithPollution(target, scope);
else target.add(Instruction.loadValue(null).locate(loc()));
if (getters.containsKey(key)) getters.get(key).compile(target, scope, true);
else target.add(Instruction.loadValue(loc(), null));
if (setters.containsKey(key)) setters.get(key).compileWithPollution(target, scope);
else target.add(Instruction.loadValue(null).locate(loc()));
if (setters.containsKey(key)) setters.get(key).compile(target, scope, true);
else target.add(Instruction.loadValue(loc(), null));
target.add(Instruction.defProp().locate(loc()));
target.add(Instruction.defProp(loc()));
}
if (!pollute) target.add(Instruction.discard(loc()));
}
public ObjectStatement(Location loc, Map<Object, Statement> map, Map<Object, FunctionStatement> getters, Map<Object, FunctionStatement> setters) {

View File

@@ -1,66 +1,32 @@
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;
import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ScopeRecord;
import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException;
public class OperationStatement extends Statement {
public final Statement[] args;
public final Operation operation;
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
for (var arg : args) {
arg.compileWithPollution(target, scope);
@Override public boolean pure() {
for (var el : args) {
if (!el.pure()) return false;
}
target.add(Instruction.operation(operation).locate(loc()));
}
@Override
public boolean pollutesStack() { return true; }
@Override
public boolean pure() {
for (var arg : args) {
if (!arg.pure()) return false;
}
return true;
}
@Override
public Statement optimize() {
var args = new Statement[this.args.length];
var allConst = true;
for (var i = 0; i < this.args.length; i++) {
args[i] = this.args[i].optimize();
if (!(args[i] instanceof ConstantStatement)) allConst = false;
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
for (var arg : args) {
arg.compile(target, scope, true);
}
if (allConst) {
var vals = new Object[this.args.length];
for (var i = 0; i < args.length; i++) {
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; }
}
return new OperationStatement(loc(), operation, args);
if (pollute) target.add(Instruction.operation(loc(), operation));
else target.add(Instruction.discard(loc()));
}
public OperationStatement(Location loc, Operation operation, Statement ...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; }
// Not really pure, since a function is called, but can be ignored.
@Override public boolean pure() { return true; }
@Override
public void compile(List<Instruction> target, ScopeRecord scope) {
target.add(Instruction.loadRegex(pattern, flags).locate(loc()));
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
target.add(Instruction.loadRegex(loc(), pattern, flags));
if (!pollute) target.add(Instruction.discard(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,49 +1,29 @@
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;
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; }
// Not really pure, since a variable from the global scope could be accessed,
// which could lead to code execution, that would get omitted
@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) {
target.add(Instruction.typeof((String)i).locate(loc()));
target.add(Instruction.typeof(loc(), (String)i));
return;
}
}
value.compileWithPollution(target, scope);
target.add(Instruction.typeof().locate(loc()));
}
@Override
public Statement optimize() {
var val = value.optimize();
if (val instanceof ConstantStatement) {
return new ConstantStatement(loc(), Values.type(((ConstantStatement)val).value));
}
else if (
val instanceof ObjectStatement ||
val instanceof ArrayStatement ||
val instanceof GlobalThisStatement
) return new ConstantStatement(loc(), "object");
else if(val instanceof FunctionStatement) return new ConstantStatement(loc(), "function");
return new TypeofStatement(loc(), val);
value.compile(target, scope, pollute);
target.add(Instruction.typeof(loc()));
}
public TypeofStatement(Location loc, Statement value) {

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 boolean pure() { return false; }
@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);
target.add(Instruction.operation(operation).locate(loc()));
target.add(Instruction.storeVar(i, !retPrevValue).locate(loc()));
target.add(Instruction.loadVar(loc(), i));
FunctionStatement.compileWithName(value, target, scope, true, name);
target.add(Instruction.operation(loc(), operation));
target.add(Instruction.storeVar(loc(), i, pollute));
}
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()));
FunctionStatement.compileWithName(value, target, scope, true, name);
target.add(Instruction.storeVar(loc(), i, pollute));
}
}

View File

@@ -1,23 +1,19 @@
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 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(loc(), index));
}
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,18 @@ 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 boolean pure() { return false; }
@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()));
target.add(Instruction.loadVar(loc(), i));
if (!pollute) target.add(Instruction.discard(loc()));
}
public VariableStatement(Location loc, String name) {

View File

@@ -1,34 +0,0 @@
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.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()));
}
@Override
public Statement optimize() {
if (value == null) return this;
var val = value.optimize();
if (val.pure()) return new ConstantStatement(loc(), null);
else return new VoidStatement(loc(), val);
}
public VoidStatement(Location loc, Statement value) {
super(loc);
this.value = value;
}
}

View File

@@ -1,20 +1,125 @@
package me.topchetoeu.jscript.engine;
import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.parsing.Parsing;
public class Context {
public final FunctionContext function;
public final MessageContext message;
public 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 Context(FunctionContext funcCtx, MessageContext msgCtx) {
this.function = funcCtx;
this.message = msgCtx;
}
}
package me.topchetoeu.jscript.engine;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Stack;
import java.util.TreeSet;
import java.util.stream.Collectors;
import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.mapping.SourceMap;
public class Context {
private final Stack<Environment> env = new Stack<>();
private final ArrayList<CodeFrame> frames = new ArrayList<>();
public final Engine engine;
public Environment environment() {
return env.empty() ? null : env.peek();
}
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 env = environment();
var result = env.compile.call(this, null, raw, filename.toString(), env);
var function = (FunctionValue)Values.getMember(this, result, "function");
if (!engine.debugging) return function;
var rawMapChain = ((ArrayValue)Values.getMember(this, result, "mapChain")).toArray();
var breakpoints = new TreeSet<>(
Arrays.stream(((ArrayValue)Values.getMember(this, result, "breakpoints")).toArray())
.map(v -> Location.parse(Values.toString(this, v)))
.collect(Collectors.toList())
);
var maps = new SourceMap[rawMapChain.length];
for (var i = 0; i < maps.length; i++) maps[i] = SourceMap.parse(Values.toString(this, (String)rawMapChain[i]));
var map = SourceMap.chain(maps);
if (map != null) {
var newBreakpoints = new TreeSet<Location>();
for (var bp : breakpoints) {
bp = map.toCompiled(bp);
if (bp != null) newBreakpoints.add(bp);
}
breakpoints = newBreakpoints;
}
engine.onSource(filename, raw, breakpoints, map);
return function;
}
public void pushFrame(CodeFrame frame) {
frames.add(frame);
if (frames.size() > engine.maxStackFrames) throw EngineException.ofRange("Stack overflow!");
pushEnv(frame.function.environment);
engine.onFramePush(this, frame);
}
public boolean popFrame(CodeFrame frame) {
if (frames.size() == 0) return false;
if (frames.get(frames.size() - 1) != frame) return false;
frames.remove(frames.size() - 1);
popEnv();
engine.onFramePop(this, frame);
return true;
}
public CodeFrame peekFrame() {
if (frames.size() == 0) return null;
return frames.get(frames.size() - 1);
}
public List<CodeFrame> frames() {
return Collections.unmodifiableList(frames);
}
public List<String> stackTrace() {
var res = new ArrayList<String>();
for (var i = frames.size() - 1; i >= 0; i--) {
var el = frames.get(i);
var name = el.function.name;
Location loc = null;
for (var j = el.codePtr; j >= 0 && loc == null; j--) loc = el.function.body[j].location;
if (loc == null) loc = el.function.loc();
var trace = "";
if (loc != null) trace += "at " + loc.toString() + " ";
if (name != null && !name.equals("")) trace += "in " + name + " ";
trace = trace.trim();
if (!trace.equals("")) res.add(trace);
}
return res;
}
public Context(Engine engine) {
this.engine = engine;
}
public Context(Engine engine, Environment env) {
this(engine);
this.pushEnv(env);
}
}

View File

@@ -0,0 +1,56 @@
package me.topchetoeu.jscript.engine;
import java.util.HashMap;
import java.util.Map;
@SuppressWarnings("unchecked")
public class Data {
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) {
if (data.containsKey(key)) return (T)data.get((DataKey<Object>)key);
set(key, val);
return val;
}
public <T> T get(DataKey<T> key) {
if (data.containsKey(key)) return (T)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);
}
}

View File

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

View File

@@ -1,84 +1,123 @@
package me.topchetoeu.jscript.engine;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.HashMap;
import java.util.TreeSet;
import java.util.concurrent.PriorityBlockingQueue;
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;
import me.topchetoeu.jscript.mapping.SourceMap;
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;
}
}
private static class Task {
private static class Task implements Comparable<Task> {
public final FunctionValue func;
public final Object thisArg;
public final Object[] args;
public final DataNotifier<Object> notifier = new DataNotifier<>();
public final MessageContext ctx;
public final Context ctx;
public final boolean micro;
public Task(MessageContext ctx, FunctionValue func, Object thisArg, Object[] args) {
public Task(Context ctx, FunctionValue func, Object thisArg, Object[] args, boolean micro) {
this.ctx = ctx;
this.func = func;
this.thisArg = thisArg;
this.args = args;
this.micro = micro;
}
@Override
public int compareTo(Task other) {
return Integer.compare(this.micro ? 0 : 1, other.micro ? 0 : 1);
}
}
private static int nextId = 0;
private Thread thread;
private LinkedBlockingDeque<Task> macroTasks = new LinkedBlockingDeque<>();
private LinkedBlockingDeque<Task> microTasks = new LinkedBlockingDeque<>();
public static final HashMap<Long, FunctionBody> functions = new HashMap<>();
public final int id = ++nextId;
public final boolean debugging;
public int maxStackFrames = 10000;
private void runTask(Task task) throws InterruptedException {
private final HashMap<Filename, String> sources = new HashMap<>();
private final HashMap<Filename, TreeSet<Location>> bpts = new HashMap<>();
private final HashMap<Filename, SourceMap> maps = new HashMap<>();
public Location mapToCompiled(Location location) {
var map = maps.get(location.filename());
if (map == null) return location;
return map.toCompiled(location);
}
public Location mapToOriginal(Location location) {
var map = maps.get(location.filename());
if (map == null) return location;
return map.toOriginal(location);
}
private DebugController debugger;
private Thread thread;
private PriorityBlockingQueue<Task> tasks = new PriorityBlockingQueue<>();
public synchronized 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()),
maps.get(source.getKey())
);
this.debugger = debugger;
return true;
}
public synchronized boolean detachDebugger() {
if (!debugging || this.debugger == null) return false;
this.debugger = null;
return true;
}
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 || !tasks.isEmpty()) {
try {
runTask(macroTasks.take());
while (!microTasks.isEmpty()) {
runTask(microTasks.take());
}
runTask(tasks.take());
}
catch (InterruptedException e) {
for (var msg : macroTasks) {
msg.notifier.error(new RuntimeException(e));
}
catch (InterruptedException | InterruptException e) {
for (var msg : tasks) msg.notifier.error(new InterruptException(e));
break;
}
}
@@ -86,7 +125,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;
@@ -98,21 +137,39 @@ public class Engine {
public boolean inExecThread() {
return Thread.currentThread() == thread;
}
public boolean isRunning() {
public synchronized boolean isRunning() {
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);
if (micro) microTasks.addLast(msg);
else macroTasks.addLast(msg);
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, micro);
tasks.add(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();
// }
@Override
public void onFramePush(Context ctx, CodeFrame frame) {
if (debugging && debugger != null) debugger.onFramePush(ctx, frame);
}
@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, SourceMap map) {
if (!debugging) return;
if (debugger != null) debugger.onSource(filename, source, breakpoints, map);
sources.put(filename, source);
bpts.put(filename, breakpoints);
maps.put(filename, map);
}
public Engine(boolean debugging) {
this.debugging = debugging;
}
}

View File

@@ -0,0 +1,131 @@
package me.topchetoeu.jscript.engine;
import java.util.HashMap;
import java.util.stream.Collectors;
import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.engine.scope.GlobalScope;
import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.NativeFunction;
import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Symbol;
import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.filesystem.RootFilesystem;
import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.NativeGetter;
import me.topchetoeu.jscript.interop.NativeSetter;
import me.topchetoeu.jscript.interop.NativeWrapperProvider;
import me.topchetoeu.jscript.parsing.Parsing;
import me.topchetoeu.jscript.permissions.Permission;
import me.topchetoeu.jscript.permissions.PermissionsProvider;
public class Environment implements PermissionsProvider {
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;
public PermissionsProvider permissions = null;
public final RootFilesystem filesystem = new RootFilesystem(this);
private static int nextId = 0;
@Native public boolean stackVisible = true;
@Native public int id = ++nextId;
@Native public FunctionValue compile = new NativeFunction("compile", (ctx, thisArg, args) -> {
var source = Values.toString(ctx, args[0]);
var filename = Values.toString(ctx, args[1]);
var isDebug = Values.toBoolean(args[2]);
var env = Values.wrapper(args[2], Environment.class);
var res = new ObjectValue();
var target = Parsing.compile(env, Filename.parse(filename), source);
Engine.functions.putAll(target.functions);
Engine.functions.remove(0l);
res.defineProperty(ctx, "function", target.func(env));
res.defineProperty(ctx, "mapChain", new ArrayValue());
if (isDebug) {
res.defineProperty(ctx, "breakpoints", ArrayValue.of(ctx, target.breakpoints.stream().map(Location::toString).collect(Collectors.toList())));
}
return res;
});
@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) {
return getSymbol(name);
}
@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;
}
@Override public boolean hasPermission(Permission perm, char delim) {
return permissions == null || permissions.hasPermission(perm, delim);
}
@Override public boolean hasPermission(Permission perm) {
return permissions == null || permissions.hasPermission(perm);
}
public Context context(Engine engine) {
return new Context(engine).pushEnv(this);
}
public static Symbol getSymbol(String name) {
if (symbols.containsKey(name)) return symbols.get(name);
else {
var res = new Symbol(name);
symbols.put(name, res);
return res;
}
}
public Environment(FunctionValue compile, WrappersProvider nativeConverter, GlobalScope global) {
if (compile != null) this.compile = compile;
if (nativeConverter == null) nativeConverter = new NativeWrapperProvider(this);
if (global == null) global = new GlobalScope();
this.wrappers = nativeConverter;
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

@@ -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,51 @@
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;
import me.topchetoeu.jscript.mapping.SourceMap;
public interface DebugController {
/**
* Called when a script has been loaded
* @param filename The name of the source
* @param source The name of the source
* @param breakpoints A set of all the breakpointable locations in this source
* @param map The source map associated with this file. null if this source map isn't mapped
*/
void onSource(Filename filename, String source, TreeSet<Location> breakpoints, SourceMap map);
/**
* 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 before a frame has been pushed on 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 pushed
*/
void onFramePush(Context ctx, CodeFrame frame);
/**
* 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);
}

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