Compare commits

...

74 Commits

Author SHA1 Message Date
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
139 changed files with 6626 additions and 2500 deletions

View File

@@ -11,6 +11,11 @@ jobs:
runs-on: "ubuntu-latest" runs-on: "ubuntu-latest"
steps: steps:
- name: Setup Java
uses: actions/setup-java@v3
with:
distribution: 'adopt'
java-version: '11'
- name: Clone repository - name: Clone repository
uses: GuillaumeFalourd/clone-github-repo-action@main uses: GuillaumeFalourd/clone-github-repo-action@main
with: with:

View File

@@ -4,31 +4,42 @@
**WARNING: Currently, this code is mostly undocumented. Proceed with caution and a psychiatrist.** **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 ## 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`: 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 ```java
var engine = new PolyfillEngine(new File(".")); var engine = new Engine(true /* false if you dont want debugging */);
var in = new BufferedReader(new InputStreamReader(System.in)); 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(); engine.start();
while (true) { while (true) {
try { 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(); // Push a message to the engine with the raw REPL code
Values.printValue(engine.context(), res); var res = engine.pushMsg(
System.out.println(); false, new Context(engine).pushEnv(env),
new Filename("jscript", "repl.js"), raw, null
).await();
Values.printValue(null, res);
} }
catch (EngineException e) { catch (EngineException e) { Values.printError(e, ""); }
try { catch (SyntaxException ex) {
System.out.println("Uncaught " + e.toString(engine.context())); System.out.println("Syntax error:" + ex.msg);
}
catch (InterruptedException _e) { return; }
} }
catch (IOException | InterruptedException e) { return; } catch (IOException e) { }
} }
``` ```

View File

@@ -1,7 +1,7 @@
const { spawn } = require('child_process'); const { spawn } = require('child_process');
const fs = require('fs/promises'); const fs = require('fs/promises');
const pt = require('path'); const pt = require('path');
const { argv } = require('process'); const { argv, exit } = require('process');
const conf = { const conf = {
name: "java-jscript", name: "java-jscript",
@@ -74,6 +74,7 @@ async function compileJava() {
} }
catch (e) { catch (e) {
if (argv[2] === 'debug') throw e; if (argv[2] === 'debug') throw e;
else console.log(e.toString()); console.log(e.toString());
exit(-1);
} }
})(); })();

BIN
src/assets/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

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

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

1004
src/assets/protocol.json Normal file

File diff suppressed because it is too large Load Diff

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; package me.topchetoeu.jscript;
public class Location { public class Location implements Comparable<Location> {
public static final Location INTERNAL = new Location(0, 0, "<internal>"); public static final Location INTERNAL = new Location(0, 0, new Filename("jscript", "native"));
private int line; private int line;
private int start; private int start;
private String filename; private Filename filename;
public int line() { return line; } public int line() { return line; }
public int start() { return start; } public int start() { return start; }
public String filename() { return filename; } public Filename filename() { return filename; }
@Override @Override
public String toString() { public String toString() {
return filename + ":" + line + ":" + start; return filename.toString() + ":" + line + ":" + start;
} }
public Location add(int n, boolean clone) { public Location add(int n, boolean clone) {
@@ -55,7 +55,18 @@ public class Location {
return true; 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.line = line;
this.start = start; this.start = start;
this.filename = filename; this.filename = filename;

View File

@@ -1,129 +1,165 @@
package me.topchetoeu.jscript; package me.topchetoeu.jscript;
import java.io.BufferedReader; import java.io.IOException;
import java.io.IOException; import java.net.InetSocketAddress;
import java.io.InputStream; import java.nio.file.Files;
import java.io.InputStreamReader; import java.nio.file.Path;
import java.nio.file.Files;
import java.nio.file.Path; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Engine;
import me.topchetoeu.jscript.engine.Message; import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.Engine; import me.topchetoeu.jscript.engine.debug.DebugServer;
import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.debug.SimpleDebugger;
import me.topchetoeu.jscript.engine.values.NativeFunction; import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.events.Observer; import me.topchetoeu.jscript.events.Observer;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.SyntaxException; import me.topchetoeu.jscript.exceptions.InterruptException;
import me.topchetoeu.jscript.polyfills.Internals; import me.topchetoeu.jscript.exceptions.SyntaxException;
import me.topchetoeu.jscript.filesystem.MemoryFilesystem;
public class Main { import me.topchetoeu.jscript.filesystem.Mode;
static Thread task; import me.topchetoeu.jscript.filesystem.PhysicalFilesystem;
static Engine engine; import me.topchetoeu.jscript.lib.Internals;
static Environment env;
public class Main {
public static String streamToString(InputStream in) { public static class Printer implements Observer<Object> {
try { public void next(Object data) {
StringBuilder out = new StringBuilder(); Values.printValue(null, data);
BufferedReader br = new BufferedReader(new InputStreamReader(in)); System.out.println();
}
for(var line = br.readLine(); line != null; line = br.readLine()) {
out.append(line).append('\n'); public void error(RuntimeException err) {
} Values.printError(err, null);
}
br.close();
return out.toString(); public void finish() {
} engineTask.interrupt();
catch (IOException e) { }
return null; }
}
} static Thread engineTask, debugTask;
public static String resourceToString(String name) { static Engine engine = new Engine(true);
var str = Main.class.getResourceAsStream("/me/topchetoeu/jscript/" + name); static DebugServer debugServer = new DebugServer();
if (str == null) return null; static Environment environment = new Environment(null, null, null);
return streamToString(str);
} static int j = 0;
static boolean exited = false;
private static Observer<Object> valuePrinter = new Observer<Object>() { static String[] args;
public void next(Object data) {
try { Values.printValue(null, data); } private static void reader() {
catch (InterruptedException e) { } try {
System.out.println(); for (var arg : args) {
} try {
if (arg.equals("--ts")) initTypescript();
public void error(RuntimeException err) { else {
try { Values.printError(err, null); } var file = Path.of(arg);
catch (InterruptedException ex) { return; } var raw = Files.readString(file);
} var res = engine.pushMsg(
}; false, new Context(engine, environment),
Filename.fromFile(file.toFile()),
public static void main(String args[]) { raw, null
System.out.println(String.format("Running %s v%s by %s", Metadata.NAME, Metadata.VERSION, Metadata.AUTHOR)); ).await();
var in = new BufferedReader(new InputStreamReader(System.in)); Values.printValue(null, res);
engine = new Engine(); System.out.println();
}
env = new Environment(null, null, null); }
var exited = new boolean[1]; catch (EngineException e) { Values.printError(e, null); }
}
engine.pushMsg(false, new Message(engine), new NativeFunction((ctx, thisArg, _a) -> { for (var i = 0; ; i++) {
new Internals().apply(env); try {
var raw = Reading.read();
env.global.define("exit", _ctx -> {
exited[0] = true; if (raw == null) break;
task.interrupt(); var res = engine.pushMsg(
throw new InterruptedException(); false, new Context(engine, environment),
}); new Filename("jscript", "repl/" + i + ".js"),
env.global.define("go", _ctx -> { raw, null
try { ).await();
var func = _ctx.compile("do.js", new String(Files.readAllBytes(Path.of("do.js")))); Values.printValue(null, res);
return func.call(_ctx); System.out.println();
} }
catch (IOException e) { catch (EngineException e) { Values.printError(e, null); }
throw new EngineException("Couldn't open do.js"); catch (SyntaxException e) { Values.printError(e, null); }
} }
}); }
catch (IOException e) {
return null; System.out.println(e.toString());
}), null); exited = true;
}
task = engine.start(); catch (RuntimeException ex) {
var reader = new Thread(() -> { if (!exited) {
try { System.out.println("Internal error ocurred:");
while (true) { ex.printStackTrace();
try { }
var raw = in.readLine(); }
if (exited) {
if (raw == null) break; debugTask.interrupt();
engine.pushMsg(false, env.context(new Message(engine)), "<stdio>", raw, null).toObservable().once(valuePrinter); engineTask.interrupt();
} }
catch (EngineException e) { }
try {
System.out.println("Uncaught " + e.toString(null)); private static void initEnv() {
} environment = Internals.apply(environment);
catch (EngineException ex) {
System.out.println("Uncaught [error while converting to string]"); environment.global.define("exit", _ctx -> {
} exited = true;
} throw new InterruptException();
} });
} environment.global.define("go", _ctx -> {
catch (IOException e) { try {
e.printStackTrace(); var f = Path.of("do.js");
return; var func = _ctx.compile(new Filename("do", "do/" + j++ + ".js"), new String(Files.readAllBytes(f)));
} return func.call(_ctx);
catch (SyntaxException ex) { }
if (exited[0]) return; catch (IOException e) {
System.out.println("Syntax error:" + ex.msg); throw new EngineException("Couldn't open do.js");
} }
catch (RuntimeException ex) { });
if (exited[0]) return;
System.out.println("Internal error ocurred:"); environment.filesystem.protocols.put("temp", new MemoryFilesystem(Mode.READ_WRITE));
ex.printStackTrace(); environment.filesystem.protocols.put("file", new PhysicalFilesystem(Path.of(".").toAbsolutePath()));
} }
catch (InterruptedException e) { return; } private static void initEngine() {
if (exited[0]) return; debugServer.targets.put("target", (ws, req) -> new SimpleDebugger(ws, engine));
}); engineTask = engine.start();
reader.setDaemon(true); debugTask = debugServer.start(new InetSocketAddress("127.0.0.1", 9229), true);
reader.setName("STD Reader"); }
reader.start(); private static void initTypescript() {
} try {
} var tsEnv = Internals.apply(new Environment(null, null, null));
var bsEnv = Internals.apply(new Environment(null, null, null));
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", "internals/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();
reader.setDaemon(true);
reader.setName("STD Reader");
reader.start();
}
}

View File

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

View File

@@ -1,11 +1,15 @@
package me.topchetoeu.jscript.compilation; package me.topchetoeu.jscript.compilation;
import java.util.Map; import java.util.Map;
import java.util.TreeSet;
import java.util.Vector; import java.util.Vector;
import me.topchetoeu.jscript.Location;
public class CompileTarget { public class CompileTarget {
public final Vector<Instruction> target = new Vector<>(); public final Vector<Instruction> target = new Vector<>();
public final Map<Long, Instruction[]> functions; public final Map<Long, FunctionBody> functions;
public final TreeSet<Location> breakpoints;
public Instruction add(Instruction instr) { public Instruction add(Instruction instr) {
target.add(instr); target.add(instr);
@@ -14,6 +18,12 @@ public class CompileTarget {
public Instruction set(int i, Instruction instr) { public Instruction set(int i, Instruction instr) {
return target.set(i, instr); return target.set(i, instr);
} }
public void setDebug(int i) {
breakpoints.add(target.get(i).location);
}
public void setDebug() {
setDebug(target.size() - 1);
}
public Instruction get(int i) { public Instruction get(int i) {
return target.get(i); return target.get(i);
} }
@@ -21,7 +31,8 @@ public class CompileTarget {
public Instruction[] array() { return target.toArray(Instruction[]::new); } public Instruction[] array() { return target.toArray(Instruction[]::new); }
public CompileTarget(Map<Long, Instruction[]> functions) { public CompileTarget(Map<Long, FunctionBody> functions, TreeSet<Location> breakpoints) {
this.functions = functions; this.functions = functions;
this.breakpoints = breakpoints;
} }
} }

View File

@@ -11,6 +11,7 @@ import me.topchetoeu.jscript.engine.scope.ScopeRecord;
public class CompoundStatement extends Statement { public class CompoundStatement extends Statement {
public final Statement[] statements; public final Statement[] statements;
public Location end;
@Override @Override
public void declare(ScopeRecord varsScope) { public void declare(ScopeRecord varsScope) {
@@ -23,20 +24,23 @@ public class CompoundStatement extends Statement {
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
for (var stm : statements) { for (var stm : statements) {
if (stm instanceof FunctionStatement) { if (stm instanceof FunctionStatement) {
int start = target.size();
((FunctionStatement)stm).compile(target, scope, null, true); ((FunctionStatement)stm).compile(target, scope, null, true);
target.get(start).setDebug(true);
target.add(Instruction.discard()); target.add(Instruction.discard());
} }
} }
for (var i = 0; i < statements.length; i++) { for (var i = 0; i < statements.length; i++) {
var stm = statements[i]; var stm = statements[i];
if (stm instanceof FunctionStatement) continue; if (stm instanceof FunctionStatement) continue;
if (i != statements.length - 1) stm.compileWithDebug(target, scope, false); if (i != statements.length - 1) stm.compileWithDebug(target, scope, false);
else stm.compileWithDebug(target, scope, pollute); else stm.compileWithDebug(target, scope, pollute);
} }
if (end != null) {
target.add(Instruction.nop().locate(end));
target.setDebug();
}
} }
@Override @Override
@@ -59,6 +63,11 @@ public class CompoundStatement extends Statement {
else return new CompoundStatement(loc(), res.toArray(Statement[]::new)); 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, Statement ...statements) {
super(loc); super(loc);
this.statements = statements; this.statements = statements;

View File

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

View File

@@ -90,16 +90,11 @@ public class Instruction {
public final Type type; public final Type type;
public final Object[] params; public final Object[] params;
public Location location; public Location location;
public boolean debugged;
public Instruction locate(Location loc) { public Instruction locate(Location loc) {
this.location = loc; this.location = loc;
return this; return this;
} }
public Instruction setDebug(boolean debug) {
debugged = debug;
return this;
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <T> T get(int i) { public <T> T get(int i) {
@@ -152,14 +147,6 @@ public class Instruction {
public static Instruction debug() { public static Instruction debug() {
return new Instruction(null, Type.NOP, "debug"); return new Instruction(null, Type.NOP, "debug");
} }
public static Instruction debugVarNames(String[] names) {
var args = new Object[names.length + 1];
args[0] = "dbg_vars";
System.arraycopy(names, 0, args, 1, names.length);
return new Instruction(null, Type.NOP, args);
}
public static Instruction nop(Object ...params) { public static Instruction nop(Object ...params) {
for (var param : params) { for (var param : params) {
@@ -264,8 +251,8 @@ public class Instruction {
return new Instruction(null, Type.TYPEOF, varName); return new Instruction(null, Type.TYPEOF, varName);
} }
public static Instruction keys() { public static Instruction keys(boolean forInFormat) {
return new Instruction(null, Type.KEYS); return new Instruction(null, Type.KEYS, forInFormat);
} }
public static Instruction defProp() { public static Instruction defProp() {

View File

@@ -14,7 +14,7 @@ public abstract class Statement {
public void compileWithDebug(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compileWithDebug(CompileTarget target, ScopeRecord scope, boolean pollute) {
int start = target.size(); int start = target.size();
compile(target, scope, pollute); compile(target, scope, pollute);
if (target.size() != start) target.get(start).setDebug(true); if (target.size() != start) target.setDebug(start);
} }
public Location loc() { return _loc; } public Location loc() { return _loc; }

View File

@@ -10,10 +10,12 @@ public class VariableDeclareStatement extends Statement {
public static class Pair { public static class Pair {
public final String name; public final String name;
public final Statement value; 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.name = name;
this.value = value; this.value = value;
this.location = location;
} }
} }
@@ -30,16 +32,20 @@ public class VariableDeclareStatement extends Statement {
for (var entry : values) { for (var entry : values) {
if (entry.name == null) continue; if (entry.name == null) continue;
var key = scope.getKey(entry.name); var key = scope.getKey(entry.name);
if (key instanceof String) target.add(Instruction.makeVar((String)key).locate(loc())); int start = target.size();
if (key instanceof String) target.add(Instruction.makeVar((String)key).locate(entry.location));
if (entry.value instanceof FunctionStatement) { if (entry.value instanceof FunctionStatement) {
((FunctionStatement)entry.value).compile(target, scope, entry.name, false); ((FunctionStatement)entry.value).compile(target, scope, entry.name, false);
target.add(Instruction.storeVar(key).locate(loc())); target.add(Instruction.storeVar(key).locate(entry.location));
} }
else if (entry.value != null) { else if (entry.value != null) {
entry.value.compile(target, scope, true); entry.value.compile(target, scope, true);
target.add(Instruction.storeVar(key).locate(loc())); target.add(Instruction.storeVar(key).locate(entry.location));
} }
if (target.size() != start) target.setDebug(start);
} }
if (pollute) target.add(Instruction.loadValue(null).locate(loc())); if (pollute) target.add(Instruction.loadValue(null).locate(loc()));

View File

@@ -12,6 +12,7 @@ public class ForInStatement extends Statement {
public final boolean isDeclaration; public final boolean isDeclaration;
public final Statement varValue, object, body; public final Statement varValue, object, body;
public final String label; public final String label;
public final Location varLocation;
@Override @Override
public void declare(ScopeRecord globScope) { public void declare(ScopeRecord globScope) {
@@ -22,6 +23,8 @@ public class ForInStatement extends Statement {
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
var key = scope.getKey(varName); var key = scope.getKey(varName);
int first = target.size();
if (key instanceof String) target.add(Instruction.makeVar((String)key)); if (key instanceof String) target.add(Instruction.makeVar((String)key));
if (varValue != null) { if (varValue != null) {
@@ -29,45 +32,37 @@ public class ForInStatement extends Statement {
target.add(Instruction.storeVar(scope.getKey(varName))); target.add(Instruction.storeVar(scope.getKey(varName)));
} }
object.compile(target, scope, true); object.compileWithDebug(target, scope, true);
target.add(Instruction.keys()); target.add(Instruction.keys(true));
int start = target.size(); int start = target.size();
target.add(Instruction.dup()); target.add(Instruction.dup());
target.add(Instruction.loadMember("length")); target.add(Instruction.loadValue(null));
target.add(Instruction.loadValue(0)); target.add(Instruction.operation(Operation.EQUALS));
target.add(Instruction.operation(Operation.LESS_EQUALS));
int mid = target.size(); int mid = target.size();
target.add(Instruction.nop()); target.add(Instruction.nop());
target.add(Instruction.dup()); target.add(Instruction.loadMember("value").locate(varLocation));
target.add(Instruction.dup()); target.setDebug();
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)); target.add(Instruction.storeVar(key));
for (var i = start; i < target.size(); i++) target.get(i).locate(loc());
body.compileWithDebug(target, scope, false); body.compileWithDebug(target, scope, false);
int end = target.size(); int end = target.size();
WhileStatement.replaceBreaks(target, label, mid + 1, end, start, end + 1); WhileStatement.replaceBreaks(target, label, mid + 1, end, start, end + 1);
target.add(Instruction.jmp(start - end).locate(loc())); target.add(Instruction.jmp(start - end));
target.add(Instruction.discard().locate(loc())); target.add(Instruction.discard());
target.set(mid, Instruction.jmpIf(end - mid + 1).locate(loc())); target.set(mid, Instruction.jmpIf(end - mid + 1));
if (pollute) target.add(Instruction.loadValue(null).locate(loc())); if (pollute) target.add(Instruction.loadValue(null));
target.get(first).locate(loc());
target.setDebug(first);
} }
public ForInStatement(Location loc, String label, boolean isDecl, String varName, Statement varValue, Statement object, Statement body) { public ForInStatement(Location loc, Location varLocation, String label, boolean isDecl, String varName, Statement varValue, Statement object, Statement body) {
super(loc); super(loc);
this.varLocation = varLocation;
this.label = label; this.label = label;
this.isDeclaration = isDecl; this.isDeclaration = isDecl;
this.varName = varName; this.varName = varName;

View File

@@ -33,8 +33,8 @@ public class SwitchStatement extends Statement {
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
var caseMap = new HashMap<Integer, Integer>(); var caseToStatement = new HashMap<Integer, Integer>();
var stmIndexMap = new HashMap<Integer, Integer>(); var statementToIndex = new HashMap<Integer, Integer>();
value.compile(target, scope, true); value.compile(target, scope, true);
@@ -42,8 +42,8 @@ public class SwitchStatement extends Statement {
target.add(Instruction.dup().locate(loc())); target.add(Instruction.dup().locate(loc()));
ccase.value.compile(target, scope, true); ccase.value.compile(target, scope, true);
target.add(Instruction.operation(Operation.EQUALS).locate(loc())); target.add(Instruction.operation(Operation.EQUALS).locate(loc()));
caseMap.put(target.size(), ccase.statementI); caseToStatement.put(target.size(), ccase.statementI);
target.add(Instruction.nop()); target.add(Instruction.nop().locate(ccase.value.loc()));
} }
int start = target.size(); int start = target.size();
@@ -51,27 +51,31 @@ public class SwitchStatement extends Statement {
target.add(Instruction.nop()); target.add(Instruction.nop());
for (var stm : body) { for (var stm : body) {
stmIndexMap.put(stmIndexMap.size(), target.size()); statementToIndex.put(statementToIndex.size(), target.size());
stm.compileWithDebug(target, scope, false); stm.compileWithDebug(target, scope, false);
} }
if (defaultI < 0 || defaultI >= body.length) target.set(start, Instruction.jmp(target.size() - start).locate(loc())); int end = target.size();
else target.set(start, Instruction.jmp(stmIndexMap.get(defaultI) - start)).locate(loc()); target.add(Instruction.discard().locate(loc()));
if (pollute) target.add(Instruction.loadValue(null));
for (int i = start; i < target.size(); i++) { if (defaultI < 0 || defaultI >= body.length) target.set(start, Instruction.jmp(end - start).locate(loc()));
else target.set(start, Instruction.jmp(statementToIndex.get(defaultI) - start)).locate(loc());
for (int i = start; i < end; i++) {
var instr = target.get(i); var instr = target.get(i);
if (instr.type == Type.NOP && instr.is(0, "break") && instr.get(1) == null) { 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(end - i).locate(instr.location));
} }
} }
for (var el : caseMap.entrySet()) { for (var el : caseToStatement.entrySet()) {
var loc = target.get(el.getKey()).location; var loc = target.get(el.getKey()).location;
var i = stmIndexMap.get(el.getValue()); var i = statementToIndex.get(el.getValue());
if (i == null) i = target.size(); if (i == null) i = end;
target.set(el.getKey(), Instruction.jmpIf(i - el.getKey()).locate(loc).setDebug(true)); target.set(el.getKey(), Instruction.jmpIf(i - el.getKey()).locate(loc));
target.setDebug(el.getKey());
} }
target.add(Instruction.discard().locate(loc()));
} }
public SwitchStatement(Location loc, Statement value, int defaultI, SwitchCase[] cases, Statement[] body) { public SwitchStatement(Location loc, Statement value, int defaultI, SwitchCase[] cases, Statement[] body) {

View File

@@ -16,13 +16,14 @@ public class CallStatement extends Statement {
((IndexStatement)func).compile(target, scope, true, true); ((IndexStatement)func).compile(target, scope, true, true);
} }
else { else {
target.add(Instruction.loadValue(null).locate(loc())); target.add(Instruction.loadValue(null).locate(loc()));
func.compile(target, scope, true); func.compile(target, scope, true);
} }
for (var arg : args) arg.compile(target, scope, true); for (var arg : args) arg.compile(target, scope, true);
target.add(Instruction.call(args.length).locate(loc()).setDebug(true)); target.add(Instruction.call(args.length).locate(loc()));
target.setDebug();
if (!pollute) target.add(Instruction.discard().locate(loc())); if (!pollute) target.add(Instruction.discard().locate(loc()));
} }

View File

@@ -15,8 +15,12 @@ public class ChangeStatement extends Statement {
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
value.toAssign(new ConstantStatement(loc(), -addAmount), Operation.SUBTRACT).compile(target, scope, postfix); value.toAssign(new ConstantStatement(loc(), -addAmount), Operation.SUBTRACT).compile(target, scope, true);
if (!pollute) target.add(Instruction.discard().locate(loc())); if (!pollute) target.add(Instruction.discard().locate(loc()));
else if (postfix) {
target.add(Instruction.loadValue(addAmount));
target.add(Instruction.operation(Operation.SUBTRACT));
}
} }
public ChangeStatement(Location loc, AssignableStatement value, double addAmount, boolean postfix) { public ChangeStatement(Location loc, AssignableStatement value, double addAmount, boolean postfix) {

View File

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

View File

@@ -5,6 +5,7 @@ import java.util.Random;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.CompileTarget; import me.topchetoeu.jscript.compilation.CompileTarget;
import me.topchetoeu.jscript.compilation.CompoundStatement; import me.topchetoeu.jscript.compilation.CompoundStatement;
import me.topchetoeu.jscript.compilation.FunctionBody;
import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.compilation.Statement; import me.topchetoeu.jscript.compilation.Statement;
import me.topchetoeu.jscript.compilation.Instruction.Type; import me.topchetoeu.jscript.compilation.Instruction.Type;
@@ -51,7 +52,7 @@ public class FunctionStatement extends Statement {
var subscope = scope.child(); var subscope = scope.child();
int start = target.size(); int start = target.size();
var funcTarget = new CompileTarget(target.functions); var funcTarget = new CompileTarget(target.functions, target.breakpoints);
subscope.define("this"); subscope.define("this");
var argsVar = subscope.define("arguments"); var argsVar = subscope.define("arguments");
@@ -69,7 +70,6 @@ public class FunctionStatement extends Statement {
} }
body.declare(subscope); body.declare(subscope);
funcTarget.add(Instruction.debugVarNames(subscope.locals()));
body.compile(funcTarget, subscope, false); body.compile(funcTarget, subscope, false);
funcTarget.add(Instruction.ret().locate(loc())); funcTarget.add(Instruction.ret().locate(loc()));
checkBreakAndCont(funcTarget, start); checkBreakAndCont(funcTarget, start);
@@ -77,7 +77,7 @@ public class FunctionStatement extends Statement {
var id = rand.nextLong(); var id = rand.nextLong();
target.add(Instruction.loadFunc(id, subscope.localsCount(), args.length, subscope.getCaptures()).locate(loc())); target.add(Instruction.loadFunc(id, subscope.localsCount(), args.length, subscope.getCaptures()).locate(loc()));
target.functions.put(id, funcTarget.array()); target.functions.put(id, new FunctionBody(funcTarget.array(), subscope.captures(), subscope.locals()));
if (name == null) name = this.name; if (name == null) name = this.name;

View File

@@ -24,14 +24,16 @@ public class IndexAssignStatement extends Statement {
value.compile(target, scope, true); value.compile(target, scope, true);
target.add(Instruction.operation(operation).locate(loc())); target.add(Instruction.operation(operation).locate(loc()));
target.add(Instruction.storeMember(pollute).locate(loc()).setDebug(true)); target.add(Instruction.storeMember(pollute).locate(loc()));
target.setDebug();
} }
else { else {
object.compile(target, scope, true); object.compile(target, scope, true);
index.compile(target, scope, true); index.compile(target, scope, true);
value.compile(target, scope, true); value.compile(target, scope, true);
target.add(Instruction.storeMember(pollute).locate(loc()).setDebug(true)); target.add(Instruction.storeMember(pollute).locate(loc()));
target.setDebug();
} }
} }

View File

@@ -20,17 +20,17 @@ public class IndexStatement extends AssignableStatement {
return new IndexAssignStatement(loc(), object, index, val, operation); return new IndexAssignStatement(loc(), object, index, val, operation);
} }
public void compile(CompileTarget target, ScopeRecord scope, boolean dupObj, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean dupObj, boolean pollute) {
int start = 0;
object.compile(target, scope, true); object.compile(target, scope, true);
if (dupObj) target.add(Instruction.dup().locate(loc())); if (dupObj) target.add(Instruction.dup().locate(loc()));
if (index instanceof ConstantStatement) { if (index instanceof ConstantStatement) {
target.add(Instruction.loadMember(((ConstantStatement)index).value).locate(loc())); target.add(Instruction.loadMember(((ConstantStatement)index).value).locate(loc()));
target.setDebug();
return; return;
} }
index.compile(target, scope, true); index.compile(target, scope, true);
target.add(Instruction.loadMember().locate(loc())); target.add(Instruction.loadMember().locate(loc()));
target.get(start).setDebug(true); target.setDebug();
if (!pollute) target.add(Instruction.discard().locate(loc())); if (!pollute) target.add(Instruction.discard().locate(loc()));
} }
@Override @Override

View File

@@ -29,7 +29,7 @@ public class LazyAndStatement extends Statement {
if (pollute) target.add(Instruction.dup().locate(loc())); if (pollute) target.add(Instruction.dup().locate(loc()));
int start = target.size(); int start = target.size();
target.add(Instruction.nop()); target.add(Instruction.nop());
target.add(Instruction.discard().locate(loc())); if (pollute) target.add(Instruction.discard().locate(loc()));
second.compile(target, scope, pollute); second.compile(target, scope, pollute);
target.set(start, Instruction.jmpIfNot(target.size() - start).locate(loc())); target.set(start, Instruction.jmpIfNot(target.size() - start).locate(loc()));
} }

View File

@@ -29,7 +29,7 @@ public class LazyOrStatement extends Statement {
if (pollute) target.add(Instruction.dup().locate(loc())); if (pollute) target.add(Instruction.dup().locate(loc()));
int start = target.size(); int start = target.size();
target.add(Instruction.nop()); target.add(Instruction.nop());
target.add(Instruction.discard().locate(loc())); if (pollute) target.add(Instruction.discard().locate(loc()));
second.compile(target, scope, pollute); second.compile(target, scope, pollute);
target.set(start, Instruction.jmpIf(target.size() - start).locate(loc())); target.set(start, Instruction.jmpIf(target.size() - start).locate(loc()));
} }

View File

@@ -16,7 +16,8 @@ public class NewStatement extends Statement {
for (var arg : args) arg.compile(target, scope, true); for (var arg : args) arg.compile(target, scope, true);
target.add(Instruction.callNew(args.length).locate(loc()).setDebug(true)); target.add(Instruction.callNew(args.length).locate(loc()));
target.setDebug();
} }
public NewStatement(Location loc, Statement func, Statement ...args) { public NewStatement(Location loc, Statement func, Statement ...args) {

View File

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

View File

@@ -20,15 +20,13 @@ public class VariableAssignStatement extends Statement {
if (value instanceof FunctionStatement) ((FunctionStatement)value).compile(target, scope, name, false); if (value instanceof FunctionStatement) ((FunctionStatement)value).compile(target, scope, name, false);
else value.compile(target, scope, true); else value.compile(target, scope, true);
target.add(Instruction.operation(operation).locate(loc())); target.add(Instruction.operation(operation).locate(loc()));
target.add(Instruction.storeVar(i, false).locate(loc())); target.add(Instruction.storeVar(i, pollute).locate(loc()));
} }
else { else {
if (value instanceof FunctionStatement) ((FunctionStatement)value).compile(target, scope, name, false); if (value instanceof FunctionStatement) ((FunctionStatement)value).compile(target, scope, name, false);
else value.compile(target, scope, true); else value.compile(target, scope, true);
target.add(Instruction.storeVar(i, false).locate(loc())); target.add(Instruction.storeVar(i, pollute).locate(loc()));
} }
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
} }
public VariableAssignStatement(Location loc, String name, Statement val, Operation operation) { public VariableAssignStatement(Location loc, String name, Statement val, Operation operation) {

View File

@@ -1,27 +1,111 @@
package me.topchetoeu.jscript.engine; package me.topchetoeu.jscript.engine;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Stack;
import java.util.TreeSet;
import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.parsing.Parsing; import me.topchetoeu.jscript.parsing.Parsing;
public class Context { public class Context {
public final Environment env; private final Stack<Environment> env = new Stack<>();
public final Message message; private final ArrayList<CodeFrame> frames = new ArrayList<>();
public final Engine engine;
public FunctionValue compile(String filename, String raw) throws InterruptedException { public Environment environment() {
var res = Values.toString(this, env.compile.call(this, null, raw, filename)); return env.empty() ? null : env.peek();
return Parsing.compile(message.engine.functions, env, filename, res);
} }
public Context setEnv(Environment env) { public Context pushEnv(Environment env) {
return new Context(env, message); this.env.push(env);
return this;
} }
public Context setMsg(Message msg) { public void popEnv() {
return new Context(env, msg); if (!env.empty()) this.env.pop();
} }
public Context(Environment env, Message msg) { public FunctionValue compile(Filename filename, String raw) {
this.env = env; var env = environment();
this.message = msg; var transpiled = env.compile.call(this, null, raw, filename.toString(), env);
String source = null;
FunctionValue runner = null;
if (transpiled instanceof ObjectValue) {
source = Values.toString(this, Values.getMember(this, transpiled, "source"));
var _runner = Values.getMember(this, transpiled, "runner");
if (_runner instanceof FunctionValue) runner = (FunctionValue)_runner;
}
else source = Values.toString(this, transpiled);
var breakpoints = new TreeSet<Location>();
FunctionValue res = Parsing.compile(Engine.functions, breakpoints, env, filename, source);
engine.onSource(filename, source, breakpoints);
if (runner != null) res = (FunctionValue)runner.call(this, null, res);
return res;
}
public void pushFrame(CodeFrame frame) {
frames.add(frame);
if (frames.size() > engine.maxStackFrames) throw EngineException.ofRange("Stack overflow!");
pushEnv(frame.function.environment);
}
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

@@ -1,43 +1,44 @@
package me.topchetoeu.jscript.engine; package me.topchetoeu.jscript.engine;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Map;
import java.util.Map.Entry;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public class Data implements Iterable<Entry<DataKey<?>, ?>> { public class Data {
private HashMap<DataKey<Object>, Object> data = new HashMap<>(); private HashMap<DataKey<Object>, Object> data = new HashMap<>();
public Data copy() { public Data copy() {
return new Data().addAll(this); return new Data().addAll(this);
} }
public Data addAll(Iterable<Entry<DataKey<?>, ?>> data) { public Data addAll(Map<DataKey<?>, ?> data) {
for (var el : data) { for (var el : data.entrySet()) {
add((DataKey<Object>)el.getKey(), (Object)el.getValue()); 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; return this;
} }
public <T> T remove(DataKey<T> key) {
return (T)data.remove(key);
}
public <T> Data set(DataKey<T> key, T val) { public <T> Data set(DataKey<T> key, T val) {
if (val == null) data.remove(key); data.put((DataKey<Object>)key, (Object)val);
else data.put((DataKey<Object>)key, (Object)val);
return this; return this;
} }
public <T> T add(DataKey<T> key, T val) { public <T> T get(DataKey<T> key, T val) {
if (data.containsKey(key)) return (T)data.get(key); if (data.containsKey(key)) return (T)data.get((DataKey<Object>)key);
else { set(key, val);
if (val == null) data.remove(key); return val;
else data.put((DataKey<Object>)key, (Object)val);
return val;
}
} }
public <T> T get(DataKey<T> key) { public <T> T get(DataKey<T> key) {
return get(key, null); if (data.containsKey(key)) return (T)data.get((DataKey<Object>)key);
} return null;
public <T> T get(DataKey<T> key, T defaultVal) {
if (!has(key)) return defaultVal;
else return (T)data.get(key);
} }
public boolean has(DataKey<?> key) { return data.containsKey(key); } public boolean has(DataKey<?> key) { return data.containsKey(key); }
@@ -52,9 +53,4 @@ public class Data implements Iterable<Entry<DataKey<?>, ?>> {
public int increase(DataKey<Integer> key) { public int increase(DataKey<Integer> key) {
return increase(key, 1, 0); return increase(key, 1, 0);
} }
@Override
public Iterator<Entry<DataKey<?>, ?>> iterator() {
return (Iterator<Entry<DataKey<?>, ?>>)data.entrySet();
}
} }

View File

@@ -1,87 +1,108 @@
package me.topchetoeu.jscript.engine; package me.topchetoeu.jscript.engine;
import java.util.HashMap; import java.util.HashMap;
import java.util.concurrent.LinkedBlockingDeque; 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.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.engine.values.FunctionValue;
import me.topchetoeu.jscript.events.Awaitable; import me.topchetoeu.jscript.events.Awaitable;
import me.topchetoeu.jscript.events.DataNotifier; import me.topchetoeu.jscript.events.DataNotifier;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.InterruptException;
public class Engine { public class Engine implements DebugController {
private class UncompiledFunction extends FunctionValue { private class UncompiledFunction extends FunctionValue {
public final String filename; public final Filename filename;
public final String raw; public final String raw;
public final Environment env; private FunctionValue compiled = null;
@Override @Override
public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException { public Object call(Context ctx, Object thisArg, Object ...args) {
ctx = ctx.setEnv(env); if (compiled == null) compiled = ctx.compile(filename, raw);
return ctx.compile(filename, raw).call(ctx, thisArg, args); return compiled.call(ctx, thisArg, args);
} }
public UncompiledFunction(Environment env, String filename, String raw) { public UncompiledFunction(Filename filename, String raw) {
super(filename, 0); super(filename + "", 0);
this.filename = filename; this.filename = filename;
this.raw = raw; this.raw = raw;
this.env = env;
} }
} }
private static class Task { private static class Task implements Comparable<Task> {
public final FunctionValue func; public final FunctionValue func;
public final Object thisArg; public final Object thisArg;
public final Object[] args; public final Object[] args;
public final DataNotifier<Object> notifier = new DataNotifier<>(); public final DataNotifier<Object> notifier = new DataNotifier<>();
public final Message msg; public final Context ctx;
public final boolean micro;
public Task(Message ctx, FunctionValue func, Object thisArg, Object[] args) { public Task(Context ctx, FunctionValue func, Object thisArg, Object[] args, boolean micro) {
this.msg = ctx; this.ctx = ctx;
this.func = func; this.func = func;
this.thisArg = thisArg; this.thisArg = thisArg;
this.args = args; 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 static int nextId = 0;
public static final HashMap<Long, FunctionBody> functions = new HashMap<>();
private Thread thread;
private LinkedBlockingDeque<Task> macroTasks = new LinkedBlockingDeque<>();
private LinkedBlockingDeque<Task> microTasks = new LinkedBlockingDeque<>();
public final int id = ++nextId; public final int id = ++nextId;
public final HashMap<Long, Instruction[]> functions = new HashMap<>(); 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 DebugController debugger;
private Thread thread;
private PriorityBlockingQueue<Task> tasks = new PriorityBlockingQueue<>();
public boolean attachDebugger(DebugController debugger) {
if (!debugging || this.debugger != null) return false;
for (var source : sources.entrySet()) {
debugger.onSource(source.getKey(), source.getValue(), bpts.get(source.getKey()));
}
this.debugger = debugger;
return true;
}
public boolean detachDebugger() {
if (!debugging || this.debugger == null) return false;
this.debugger = null;
return true;
}
private void runTask(Task task) {
try { try {
task.notifier.next(task.func.call(task.msg.context(null), task.thisArg, task.args)); task.notifier.next(task.func.call(task.ctx, task.thisArg, task.args));
}
catch (InterruptedException e) {
task.notifier.error(new RuntimeException(e));
throw e;
}
catch (EngineException e) {
task.notifier.error(e);
} }
catch (RuntimeException e) { catch (RuntimeException e) {
if (e instanceof InterruptException) throw e;
task.notifier.error(e); task.notifier.error(e);
e.printStackTrace();
} }
} }
private void run() { public void run(boolean untilEmpty) {
while (true) { while (!untilEmpty || !tasks.isEmpty()) {
try { try {
runTask(macroTasks.take()); runTask(tasks.take());
while (!microTasks.isEmpty()) {
runTask(microTasks.take());
}
} }
catch (InterruptedException e) { catch (InterruptedException | InterruptException e) {
for (var msg : macroTasks) { for (var msg : tasks) msg.notifier.error(new InterruptException(e));
msg.notifier.error(new RuntimeException(e));
}
break; break;
} }
} }
@@ -89,7 +110,7 @@ public class Engine {
public Thread start() { public Thread start() {
if (this.thread == null) { 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(); this.thread.start();
} }
return this.thread; return this.thread;
@@ -105,17 +126,30 @@ public class Engine {
return this.thread != null; return this.thread != null;
} }
public Awaitable<Object> pushMsg(boolean micro, Message ctx, FunctionValue func, Object thisArg, Object ...args) { public Awaitable<Object> pushMsg(boolean micro, Context ctx, FunctionValue func, Object thisArg, Object ...args) {
var msg = new Task(ctx, func, thisArg, args); var msg = new Task(ctx == null ? new Context(this) : ctx, func, thisArg, args, micro);
if (micro) microTasks.addLast(msg); tasks.add(msg);
else macroTasks.addLast(msg);
return msg.notifier; return msg.notifier;
} }
public Awaitable<Object> pushMsg(boolean micro, Context ctx, String filename, String raw, Object thisArg, Object ...args) { public Awaitable<Object> pushMsg(boolean micro, Context ctx, Filename filename, String raw, Object thisArg, Object ...args) {
return pushMsg(micro, ctx.message, new UncompiledFunction(ctx.env, filename, raw), thisArg, args); return pushMsg(micro, ctx, new UncompiledFunction(filename, raw), thisArg, args);
} }
// public Engine() { @Override public void onFramePop(Context ctx, CodeFrame frame) {
// this.typeRegister = new NativeTypeRegister(); if (debugging && debugger != null) debugger.onFramePop(ctx, frame);
// } }
@Override public boolean onInstruction(Context ctx, CodeFrame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught) {
if (debugging && debugger != null) return debugger.onInstruction(ctx, frame, instruction, returnVal, error, caught);
else return false;
}
@Override public void onSource(Filename filename, String source, TreeSet<Location> breakpoints) {
if (!debugging) return;
if (debugger != null) debugger.onSource(filename, source, breakpoints);
sources.put(filename, source);
bpts.put(filename, breakpoints);
}
public Engine(boolean debugging) {
this.debugging = debugging;
}
} }

View File

@@ -8,23 +8,32 @@ import me.topchetoeu.jscript.engine.values.NativeFunction;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Symbol; import me.topchetoeu.jscript.engine.values.Symbol;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.filesystem.RootFilesystem;
import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.NativeGetter; import me.topchetoeu.jscript.interop.NativeGetter;
import me.topchetoeu.jscript.interop.NativeSetter; import me.topchetoeu.jscript.interop.NativeSetter;
import me.topchetoeu.jscript.interop.NativeWrapperProvider; import me.topchetoeu.jscript.interop.NativeWrapperProvider;
import me.topchetoeu.jscript.permissions.Permission;
import me.topchetoeu.jscript.permissions.PermissionsProvider;
public class Environment { public class Environment implements PermissionsProvider {
private HashMap<String, ObjectValue> prototypes = new HashMap<>(); private HashMap<String, ObjectValue> prototypes = new HashMap<>();
public final Data data = new Data(); public final Data data = new Data();
public final HashMap<String, Symbol> symbols = new HashMap<>(); public static final HashMap<String, Symbol> symbols = new HashMap<>();
public GlobalScope global; public GlobalScope global;
public WrappersProvider wrappersProvider; public WrappersProvider wrappers;
public PermissionsProvider permissions = null;
public final RootFilesystem filesystem = new RootFilesystem(this);
private static int nextId = 0;
@Native public int id = ++nextId;
@Native public FunctionValue compile; @Native public FunctionValue compile;
@Native public FunctionValue regexConstructor = new NativeFunction("RegExp", (ctx, thisArg, args) -> { @Native public FunctionValue regexConstructor = new NativeFunction("RegExp", (ctx, thisArg, args) -> {
throw EngineException.ofError("Regular expressions not supported.").setContext(ctx); throw EngineException.ofError("Regular expressions not supported.").setCtx(ctx.environment(), ctx.engine);
}); });
public Environment addData(Data data) { public Environment addData(Data data) {
@@ -40,8 +49,7 @@ public class Environment {
} }
@Native public Symbol symbol(String name) { @Native public Symbol symbol(String name) {
if (symbols.containsKey(name)) if (symbols.containsKey(name)) return symbols.get(name);
return symbols.get(name);
else { else {
var res = new Symbol(name); var res = new Symbol(name);
symbols.put(name, res); symbols.put(name, res);
@@ -57,7 +65,8 @@ public class Environment {
} }
@Native public Environment fork() { @Native public Environment fork() {
var res = new Environment(compile, wrappersProvider, global); var res = new Environment(compile, null, global);
res.wrappers = wrappers.fork(res);
res.regexConstructor = regexConstructor; res.regexConstructor = regexConstructor;
res.prototypes = new HashMap<>(prototypes); res.prototypes = new HashMap<>(prototypes);
return res; return res;
@@ -68,8 +77,15 @@ public class Environment {
return res; return res;
} }
public Context context(Message msg) { @Override public boolean hasPermission(Permission perm, char delim) {
return new Context(this, msg); 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 Environment(FunctionValue compile, WrappersProvider nativeConverter, GlobalScope global) { public Environment(FunctionValue compile, WrappersProvider nativeConverter, GlobalScope global) {
@@ -77,7 +93,7 @@ public class Environment {
if (nativeConverter == null) nativeConverter = new NativeWrapperProvider(this); if (nativeConverter == null) nativeConverter = new NativeWrapperProvider(this);
if (global == null) global = new GlobalScope(); if (global == null) global = new GlobalScope();
this.wrappersProvider = nativeConverter; this.wrappers = nativeConverter;
this.compile = compile; this.compile = compile;
this.global = global; this.global = global;
} }

View File

@@ -1,63 +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 Message {
public final Engine engine;
private final ArrayList<CodeFrame> frames = new ArrayList<>();
public int maxStackFrames = 1000;
public final Data data = new Data();
public List<CodeFrame> frames() { return Collections.unmodifiableList(frames); }
public Message addData(Data data) {
this.data.addAll(data);
return this;
}
public Message pushFrame(Context ctx, CodeFrame frame) throws InterruptedException {
this.frames.add(frame);
if (this.frames.size() > maxStackFrames) throw EngineException.ofRange("Stack overflow!");
return this;
}
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 List<String> stackTrace() {
var res = new ArrayList<String>();
for (var el : frames) {
var name = el.function.name;
var loc = el.function.loc();
var trace = "";
if (loc != null) trace += "at " + loc.toString() + " ";
if (name != null && !name.equals("")) trace += "in " + name + " ";
trace = trace.trim();
if (!res.equals("")) res.add(trace);
}
return res;
}
public Context context(Environment env) {
return new Context(env, this);
}
public Message(Engine engine) {
this.engine = engine;
}
}

View File

@@ -5,5 +5,8 @@ import me.topchetoeu.jscript.engine.values.ObjectValue;
public interface WrappersProvider { public interface WrappersProvider {
public ObjectValue getProto(Class<?> obj); public ObjectValue getProto(Class<?> obj);
public ObjectValue getNamespace(Class<?> obj);
public FunctionValue getConstr(Class<?> obj); public FunctionValue getConstr(Class<?> obj);
public WrappersProvider fork(Environment env);
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,216 @@
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;
import me.topchetoeu.jscript.exceptions.UncheckedIOException;
public class WebSocket implements AutoCloseable {
public long maxLength = 1 << 20;
private Socket socket;
private boolean closed = false;
private OutputStream out() {
try { return socket.getOutputStream(); }
catch (IOException e) { throw new UncheckedIOException(e); }
}
private InputStream in() {
try { return socket.getInputStream(); }
catch (IOException e) { throw new UncheckedIOException(e); }
}
private long readLen(int byteLen) {
long res = 0;
try {
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;
}
catch (IOException e) { throw new UncheckedIOException(e); }
}
private byte[] readMask(boolean has) {
if (has) {
try { return new byte[] {
(byte)in().read(),
(byte)in().read(),
(byte)in().read(),
(byte)in().read()
}; }
catch (IOException e) { throw new UncheckedIOException(e); }
}
else return new byte[4];
}
private void writeLength(int len) {
try {
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((len >> 56) & 0xFF);
out().write((len >> 48) & 0xFF);
out().write((len >> 40) & 0xFF);
out().write((len >> 32) & 0xFF);
out().write((len >> 24) & 0xFF);
out().write((len >> 16) & 0xFF);
out().write((len >> 8) & 0xFF);
out().write(len & 0xFF);
}
}
catch (IOException e) { throw new UncheckedIOException(e); }
}
private synchronized void write(int type, byte[] data) {
try {
int i;
for (i = 0; i < data.length / 0xFFFF; i++) {
out().write(type);
writeLength(0xFFFF);
out().write(data, i * 0xFFFF, 0xFFFF);
type = 0;
}
out().write(type | 0x80);
writeLength(data.length % 0xFFFF);
out().write(data, i * 0xFFFF, data.length % 0xFFFF);
}
catch (IOException e) {
throw new UncheckedIOException(e);
}
}
public void send(String data) {
if (closed) throw new IllegalStateException("Object is closed.");
write(1, data.getBytes());
}
public void send(byte[] data) {
if (closed) throw new IllegalStateException("Object is closed.");
write(2, data);
}
public void send(WebSocketMessage msg) {
if (msg.type == Type.Binary) send(msg.binaryData());
else send(msg.textData());
}
public void send(Object data) {
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());
socket.close();
}
catch (Throwable e) { }
}
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() {
try {
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;
}
catch (IOException e) { throw new UncheckedIOException(e); }
}
public WebSocketMessage receive() {
try {
var data = new ByteArrayOutputStream();
var type = 0;
while (socket != null && !closed) {
var finId = in().read();
if (finId < 0) break;
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

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

View File

@@ -1,19 +1,23 @@
package me.topchetoeu.jscript.engine.frame; package me.topchetoeu.jscript.engine.frame;
import java.util.List;
import java.util.Stack; import java.util.Stack;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.scope.LocalScope; import me.topchetoeu.jscript.engine.scope.LocalScope;
import me.topchetoeu.jscript.engine.scope.ValueVariable; import me.topchetoeu.jscript.engine.scope.ValueVariable;
import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.CodeFunction; import me.topchetoeu.jscript.engine.values.CodeFunction;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.ScopeValue;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.InterruptException;
public class CodeFrame { public class CodeFrame {
private class TryCtx { public static class TryCtx {
public static final int STATE_TRY = 0; public static final int STATE_TRY = 0;
public static final int STATE_CATCH = 1; public static final int STATE_CATCH = 1;
public static final int STATE_FINALLY_THREW = 2; public static final int STATE_FINALLY_THREW = 2;
@@ -54,8 +58,56 @@ public class CodeFrame {
public boolean jumpFlag = false; public boolean jumpFlag = false;
private Location prevLoc = null; private Location prevLoc = null;
public ObjectValue getLocalScope(Context ctx, boolean props) {
var names = new String[scope.locals.length];
for (int i = 0; i < scope.locals.length; i++) {
var name = "local_" + (i - 2);
if (i == 0) name = "this";
else if (i == 1) name = "arguments";
else if (i < function.localNames.length) name = function.localNames[i];
names[i] = name;
}
return new ScopeValue(scope.locals, names);
}
public ObjectValue getCaptureScope(Context ctx, boolean props) {
var names = new String[scope.captures.length];
for (int i = 0; i < scope.captures.length; i++) {
var name = "capture_" + (i - 2);
if (i < function.captureNames.length) name = function.captureNames[i];
names[i] = name;
}
return new ScopeValue(scope.captures, names);
}
public ObjectValue getValStackScope(Context ctx) {
return new ObjectValue() {
@Override
protected Object getField(Context ctx, Object key) {
var i = (int)Values.toNumber(ctx, key);
if (i < 0 || i >= stackPtr) return null;
else return stack[i];
}
@Override
protected boolean hasField(Context ctx, Object key) {
return true;
}
@Override
public List<Object> keys(boolean includeNonEnumerable) {
var res = super.keys(includeNonEnumerable);
for (var i = 0; i < stackPtr; i++) res.add(i);
return res;
}
};
}
public void addTry(int n, int catchN, int finallyN) { public void addTry(int n, int catchN, int finallyN) {
var res = new TryCtx(codePtr + 1, n, catchN, finallyN); var res = new TryCtx(codePtr + 1, n, catchN, finallyN);
if (!tryStack.empty()) res.err = tryStack.peek().err;
tryStack.add(res); tryStack.add(res);
} }
@@ -93,35 +145,35 @@ public class CodeFrame {
stack[stackPtr++] = Values.normalize(ctx, val); stack[stackPtr++] = Values.normalize(ctx, val);
} }
private void setCause(Context ctx, EngineException err, EngineException cause) throws InterruptedException { private void setCause(Context ctx, EngineException err, EngineException cause) {
if (err.value instanceof ObjectValue) { err.setCause(cause);
Values.setMember(ctx, err, ctx.env.symbol("Symbol.cause"), cause);
}
err.cause = cause;
}
private Object nextNoTry(Context ctx) throws InterruptedException {
if (Thread.currentThread().isInterrupted()) throw new InterruptedException();
if (codePtr < 0 || codePtr >= function.body.length) return null;
var instr = function.body[codePtr];
var loc = instr.location;
if (loc != null) prevLoc = loc;
try {
this.jumpFlag = false;
return Runners.exec(ctx, instr, this);
}
catch (EngineException e) {
throw e.add(function.name, prevLoc).setContext(ctx);
}
} }
public Object next(Context ctx, Object value, Object returnValue, EngineException error) throws InterruptedException { public Object next(Context ctx, Object value, Object returnValue, EngineException error) {
if (value != Runners.NO_RETURN) push(ctx, value); if (value != Runners.NO_RETURN) push(ctx, value);
Instruction instr = null;
if (codePtr >= 0 && codePtr < function.body.length) instr = function.body[codePtr];
if (returnValue == Runners.NO_RETURN && error == null) { if (returnValue == Runners.NO_RETURN && error == null) {
try { returnValue = nextNoTry(ctx); } try {
if (Thread.currentThread().isInterrupted()) throw new InterruptException();
if (instr == null) returnValue = null;
else {
ctx.engine.onInstruction(ctx, this, instr, Runners.NO_RETURN, null, false);
if (instr.location != null) prevLoc = instr.location;
try {
this.jumpFlag = false;
returnValue = Runners.exec(ctx, instr, this);
}
catch (EngineException e) {
error = e.add(function.name, prevLoc).setCtx(function.environment, ctx.engine);
}
}
}
catch (EngineException e) { error = e; } catch (EngineException e) { error = e; }
} }
@@ -144,7 +196,7 @@ public class CodeFrame {
} }
else if (returnValue != Runners.NO_RETURN) { else if (returnValue != Runners.NO_RETURN) {
if (tryCtx.hasFinally) { if (tryCtx.hasFinally) {
tryCtx.retVal = error; tryCtx.retVal = returnValue;
newState = TryCtx.STATE_FINALLY_RETURNED; newState = TryCtx.STATE_FINALLY_RETURNED;
} }
break; break;
@@ -160,6 +212,7 @@ public class CodeFrame {
break; break;
case TryCtx.STATE_CATCH: case TryCtx.STATE_CATCH:
if (error != null) { if (error != null) {
setCause(ctx, error, tryCtx.err);
if (tryCtx.hasFinally) { if (tryCtx.hasFinally) {
tryCtx.err = error; tryCtx.err = error;
newState = TryCtx.STATE_FINALLY_THREW; newState = TryCtx.STATE_FINALLY_THREW;
@@ -185,27 +238,31 @@ public class CodeFrame {
case TryCtx.STATE_FINALLY_THREW: case TryCtx.STATE_FINALLY_THREW:
if (error != null) setCause(ctx, error, tryCtx.err); if (error != null) setCause(ctx, error, tryCtx.err);
else if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) error = tryCtx.err; else if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) error = tryCtx.err;
else return Runners.NO_RETURN; else if (returnValue == Runners.NO_RETURN) return Runners.NO_RETURN;
break; break;
case TryCtx.STATE_FINALLY_RETURNED: case TryCtx.STATE_FINALLY_RETURNED:
if (error != null) setCause(ctx, error, tryCtx.err);
if (returnValue == Runners.NO_RETURN) { if (returnValue == Runners.NO_RETURN) {
if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) returnValue = tryCtx.retVal; if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) returnValue = tryCtx.retVal;
else return Runners.NO_RETURN; else return Runners.NO_RETURN;
} }
break; break;
case TryCtx.STATE_FINALLY_JUMPED: case TryCtx.STATE_FINALLY_JUMPED:
if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) { if (error != null) setCause(ctx, error, tryCtx.err);
else if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) {
if (!jumpFlag) codePtr = tryCtx.jumpPtr; if (!jumpFlag) codePtr = tryCtx.jumpPtr;
else codePtr = tryCtx.end; else codePtr = tryCtx.end;
} }
else return Runners.NO_RETURN; else if (returnValue == Runners.NO_RETURN) return Runners.NO_RETURN;
break; break;
} }
if (tryCtx.state == TryCtx.STATE_CATCH) scope.catchVars.remove(scope.catchVars.size() - 1); if (tryCtx.state == TryCtx.STATE_CATCH) scope.catchVars.remove(scope.catchVars.size() - 1);
if (newState == -1) { if (newState == -1) {
var err = tryCtx.err;
tryStack.pop(); tryStack.pop();
if (!tryStack.isEmpty()) tryStack.peek().err = err;
continue; continue;
} }
@@ -214,6 +271,7 @@ public class CodeFrame {
case TryCtx.STATE_CATCH: case TryCtx.STATE_CATCH:
scope.catchVars.add(new ValueVariable(false, tryCtx.err.value)); scope.catchVars.add(new ValueVariable(false, tryCtx.err.value));
codePtr = tryCtx.catchStart; codePtr = tryCtx.catchStart;
ctx.engine.onInstruction(ctx, this, function.body[codePtr], null, error, true);
break; break;
default: default:
codePtr = tryCtx.finallyStart; codePtr = tryCtx.finallyStart;
@@ -221,23 +279,17 @@ public class CodeFrame {
return Runners.NO_RETURN; return Runners.NO_RETURN;
} }
if (error != null) throw error.setContext(ctx);
if (returnValue != Runners.NO_RETURN) return returnValue;
return Runners.NO_RETURN;
}
public Object run(Context ctx) throws InterruptedException { if (error != null) {
try { ctx.engine.onInstruction(ctx, this, instr, null, error, false);
ctx.message.pushFrame(ctx, this); throw error;
while (true) {
var res = next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, null);
if (res != Runners.NO_RETURN) return res;
}
} }
finally { if (returnValue != Runners.NO_RETURN) {
ctx.message.popFrame(this); ctx.engine.onInstruction(ctx, this, instr, returnValue, null, false);
return returnValue;
} }
return Runners.NO_RETURN;
} }
public CodeFrame(Context ctx, Object thisArg, Object[] args, CodeFunction func) { public CodeFrame(Context ctx, Object thisArg, Object[] args, CodeFunction func) {

View File

@@ -4,6 +4,7 @@ import java.util.Collections;
import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Engine;
import me.topchetoeu.jscript.engine.Operation; import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ValueVariable; import me.topchetoeu.jscript.engine.scope.ValueVariable;
import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.ArrayValue;
@@ -22,52 +23,37 @@ public class Runners {
public static Object execThrow(Context ctx, Instruction instr, CodeFrame frame) { public static Object execThrow(Context ctx, Instruction instr, CodeFrame frame) {
throw new EngineException(frame.pop()); throw new EngineException(frame.pop());
} }
public static Object execThrowSyntax(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execThrowSyntax(Context ctx, Instruction instr, CodeFrame frame) {
throw EngineException.ofSyntax((String)instr.get(0)); throw EngineException.ofSyntax((String)instr.get(0));
} }
private static Object call(Context ctx, Object func, Object thisArg, Object ...args) throws InterruptedException { public static Object execCall(Context ctx, Instruction instr, CodeFrame frame) {
return Values.call(ctx, func, thisArg, args);
}
public static Object execCall(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException {
var callArgs = frame.take(instr.get(0)); var callArgs = frame.take(instr.get(0));
var func = frame.pop(); var func = frame.pop();
var thisArg = frame.pop(); var thisArg = frame.pop();
frame.push(ctx, call(ctx, func, thisArg, callArgs)); frame.push(ctx, Values.call(ctx, func, thisArg, callArgs));
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execCallNew(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execCallNew(Context ctx, Instruction instr, CodeFrame frame) {
var callArgs = frame.take(instr.get(0)); var callArgs = frame.take(instr.get(0));
var funcObj = frame.pop(); var funcObj = frame.pop();
frame.push(ctx, Values.callNew(ctx, funcObj, callArgs)); frame.push(ctx, Values.callNew(ctx, funcObj, callArgs));
// if (Values.isFunction(funcObj) && Values.function(funcObj).special) {
// frame.push(ctx, call(ctx, funcObj, null, callArgs));
// }
// else {
// var proto = Values.getMember(ctx, funcObj, "prototype");
// var obj = new ObjectValue();
// obj.setPrototype(ctx, proto);
// call(ctx, funcObj, obj, callArgs);
// frame.push(ctx, obj);
// }
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execMakeVar(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execMakeVar(Context ctx, Instruction instr, CodeFrame frame) {
var name = (String)instr.get(0); var name = (String)instr.get(0);
ctx.env.global.define(name); ctx.environment().global.define(name);
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execDefProp(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execDefProp(Context ctx, Instruction instr, CodeFrame frame) {
var setter = frame.pop(); var setter = frame.pop();
var getter = frame.pop(); var getter = frame.pop();
var name = frame.pop(); var name = frame.pop();
@@ -82,7 +68,7 @@ public class Runners {
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execInstanceof(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execInstanceof(Context ctx, Instruction instr, CodeFrame frame) {
var type = frame.pop(); var type = frame.pop();
var obj = frame.pop(); var obj = frame.pop();
@@ -97,27 +83,37 @@ public class Runners {
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execKeys(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execKeys(Context ctx, Instruction instr, CodeFrame frame) {
var val = frame.pop(); var val = frame.pop();
var arr = new ObjectValue();
var i = 0;
var members = Values.getMembers(ctx, val, false, false); var members = Values.getMembers(ctx, val, false, false);
Collections.reverse(members); Collections.reverse(members);
frame.push(ctx, null);
for (var el : members) { for (var el : members) {
if (el instanceof Symbol) continue; if (el instanceof Symbol) continue;
arr.defineProperty(ctx, i++, el); var obj = new ObjectValue();
obj.defineProperty(ctx, "value", el);
frame.push(ctx, obj);
} }
// var arr = new ObjectValue();
arr.defineProperty(ctx, "length", i); // var members = Values.getMembers(ctx, val, false, false);
// Collections.reverse(members);
// for (var el : members) {
// if (el instanceof Symbol) continue;
// arr.defineProperty(ctx, i++, el);
// }
frame.push(ctx, arr); // arr.defineProperty(ctx, "length", i);
// frame.push(ctx, arr);
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execTry(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execTry(Context ctx, Instruction instr, CodeFrame frame) {
frame.addTry(instr.get(0), instr.get(1), instr.get(2)); frame.addTry(instr.get(0), instr.get(1), instr.get(2));
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
@@ -155,10 +151,10 @@ public class Runners {
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execLoadVar(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execLoadVar(Context ctx, Instruction instr, CodeFrame frame) {
var i = instr.get(0); var i = instr.get(0);
if (i instanceof String) frame.push(ctx, ctx.env.global.get(ctx, (String)i)); if (i instanceof String) frame.push(ctx, ctx.environment().global.get(ctx, (String)i));
else frame.push(ctx, frame.scope.get((int)i).get(ctx)); else frame.push(ctx, frame.scope.get((int)i).get(ctx));
frame.codePtr++; frame.codePtr++;
@@ -170,7 +166,7 @@ public class Runners {
return NO_RETURN; return NO_RETURN;
} }
public static Object execLoadGlob(Context ctx, Instruction instr, CodeFrame frame) { public static Object execLoadGlob(Context ctx, Instruction instr, CodeFrame frame) {
frame.push(ctx, ctx.env.global.obj); frame.push(ctx, ctx.environment().global.obj);
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
@@ -191,15 +187,15 @@ public class Runners {
captures[i - 3] = frame.scope.get(instr.get(i)); captures[i - 3] = frame.scope.get(instr.get(i));
} }
var body = ctx.message.engine.functions.get(id); var body = Engine.functions.get(id);
var func = new CodeFunction(ctx.env, "", localsN, len, captures, body); var func = new CodeFunction(ctx.environment(), "", localsN, len, captures, body);
frame.push(ctx, func); frame.push(ctx, func);
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execLoadMember(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execLoadMember(Context ctx, Instruction instr, CodeFrame frame) {
var key = frame.pop(); var key = frame.pop();
var obj = frame.pop(); var obj = frame.pop();
@@ -212,12 +208,12 @@ public class Runners {
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execLoadKeyMember(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execLoadKeyMember(Context ctx, Instruction instr, CodeFrame frame) {
frame.push(ctx, instr.get(0)); frame.push(ctx, instr.get(0));
return execLoadMember(ctx, instr, frame); return execLoadMember(ctx, instr, frame);
} }
public static Object execLoadRegEx(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execLoadRegEx(Context ctx, Instruction instr, CodeFrame frame) {
frame.push(ctx, ctx.env.regexConstructor.call(ctx, null, instr.get(0), instr.get(1))); frame.push(ctx, ctx.environment().regexConstructor.call(ctx, null, instr.get(0), instr.get(1)));
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
@@ -227,7 +223,7 @@ public class Runners {
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execStoreMember(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execStoreMember(Context ctx, Instruction instr, CodeFrame frame) {
var val = frame.pop(); var val = frame.pop();
var key = frame.pop(); var key = frame.pop();
var obj = frame.pop(); var obj = frame.pop();
@@ -237,11 +233,11 @@ public class Runners {
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execStoreVar(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execStoreVar(Context ctx, Instruction instr, CodeFrame frame) {
var val = (boolean)instr.get(1) ? frame.peek() : frame.pop(); var val = (boolean)instr.get(1) ? frame.peek() : frame.pop();
var i = instr.get(0); var i = instr.get(0);
if (i instanceof String) ctx.env.global.set(ctx, (String)i, val); if (i instanceof String) ctx.environment().global.set(ctx, (String)i, val);
else frame.scope.get((int)i).set(ctx, val); else frame.scope.get((int)i).set(ctx, val);
frame.codePtr++; frame.codePtr++;
@@ -258,7 +254,7 @@ public class Runners {
frame.jumpFlag = true; frame.jumpFlag = true;
return NO_RETURN; return NO_RETURN;
} }
public static Object execJmpIf(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execJmpIf(Context ctx, Instruction instr, CodeFrame frame) {
if (Values.toBoolean(frame.pop())) { if (Values.toBoolean(frame.pop())) {
frame.codePtr += (int)instr.get(0); frame.codePtr += (int)instr.get(0);
frame.jumpFlag = true; frame.jumpFlag = true;
@@ -266,7 +262,7 @@ public class Runners {
else frame.codePtr ++; else frame.codePtr ++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execJmpIfNot(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execJmpIfNot(Context ctx, Instruction instr, CodeFrame frame) {
if (Values.not(frame.pop())) { if (Values.not(frame.pop())) {
frame.codePtr += (int)instr.get(0); frame.codePtr += (int)instr.get(0);
frame.jumpFlag = true; frame.jumpFlag = true;
@@ -275,7 +271,7 @@ public class Runners {
return NO_RETURN; return NO_RETURN;
} }
public static Object execIn(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execIn(Context ctx, Instruction instr, CodeFrame frame) {
var obj = frame.pop(); var obj = frame.pop();
var index = frame.pop(); var index = frame.pop();
@@ -283,13 +279,13 @@ public class Runners {
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execTypeof(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execTypeof(Context ctx, Instruction instr, CodeFrame frame) {
String name = instr.get(0); String name = instr.get(0);
Object obj; Object obj;
if (name != null) { if (name != null) {
if (ctx.env.global.has(ctx, name)) { if (ctx.environment().global.has(ctx, name)) {
obj = ctx.env.global.get(ctx, name); obj = ctx.environment().global.get(ctx, name);
} }
else obj = null; else obj = null;
} }
@@ -300,21 +296,12 @@ public class Runners {
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execNop(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execNop(Context ctx, Instruction instr, CodeFrame frame) {
if (instr.is(0, "dbg_names")) {
var names = new String[instr.params.length - 1];
for (var i = 0; i < instr.params.length - 1; i++) {
if (!(instr.params[i + 1] instanceof String)) throw EngineException.ofSyntax("NOP dbg_names instruction must specify only string parameters.");
names[i] = (String)instr.params[i + 1];
}
frame.scope.setNames(names);
}
frame.codePtr++; frame.codePtr++;
return NO_RETURN; return NO_RETURN;
} }
public static Object execDelete(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execDelete(Context ctx, Instruction instr, CodeFrame frame) {
var key = frame.pop(); var key = frame.pop();
var val = frame.pop(); var val = frame.pop();
@@ -324,7 +311,7 @@ public class Runners {
return NO_RETURN; return NO_RETURN;
} }
public static Object execOperation(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object execOperation(Context ctx, Instruction instr, CodeFrame frame) {
Operation op = instr.get(0); Operation op = instr.get(0);
var args = new Object[op.operands]; var args = new Object[op.operands];
@@ -335,7 +322,7 @@ public class Runners {
return NO_RETURN; return NO_RETURN;
} }
public static Object exec(Context ctx, Instruction instr, CodeFrame frame) throws InterruptedException { public static Object exec(Context ctx, Instruction instr, CodeFrame frame) {
switch (instr.type) { switch (instr.type) {
case NOP: return execNop(ctx, instr, frame); case NOP: return execNop(ctx, instr, frame);
case RETURN: return execReturn(ctx, instr, frame); case RETURN: return execReturn(ctx, instr, frame);

View File

@@ -15,7 +15,7 @@ public class GlobalScope implements ScopeRecord {
@Override @Override
public GlobalScope parent() { return null; } public GlobalScope parent() { return null; }
public boolean has(Context ctx, String name) throws InterruptedException { public boolean has(Context ctx, String name) {
return obj.hasMember(ctx, name, false); return obj.hasMember(ctx, name, false);
} }
public Object getKey(String name) { public Object getKey(String name) {
@@ -32,13 +32,7 @@ public class GlobalScope implements ScopeRecord {
} }
public Object define(String name) { public Object define(String name) {
try { if (obj.hasMember(null, name, true)) return name;
if (obj.hasMember(null, name, true)) return name;
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
return name;
}
obj.defineProperty(null, name, null); obj.defineProperty(null, name, null);
return name; return name;
} }
@@ -59,11 +53,11 @@ public class GlobalScope implements ScopeRecord {
define(null, val.name, readonly, val); define(null, val.name, readonly, val);
} }
public Object get(Context ctx, String name) throws InterruptedException { public Object get(Context ctx, String name) {
if (!obj.hasMember(ctx, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist."); if (!obj.hasMember(ctx, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist.");
else return obj.getMember(ctx, name); else return obj.getMember(ctx, name);
} }
public void set(Context ctx, String name, Object val) throws InterruptedException { public void set(Context ctx, String name, Object val) {
if (!obj.hasMember(ctx, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist."); if (!obj.hasMember(ctx, name, false)) throw EngineException.ofSyntax("The variable '" + name + "' doesn't exist.");
if (!obj.setMember(ctx, name, val, false)) throw EngineException.ofSyntax("The global '" + name + "' is readonly."); if (!obj.setMember(ctx, name, val, false)) throw EngineException.ofSyntax("The global '" + name + "' is readonly.");
} }

View File

@@ -3,43 +3,21 @@ package me.topchetoeu.jscript.engine.scope;
import java.util.ArrayList; import java.util.ArrayList;
public class LocalScope { public class LocalScope {
private String[] names;
public final ValueVariable[] captures; public final ValueVariable[] captures;
public final ValueVariable[] locals; public final ValueVariable[] locals;
public final ArrayList<ValueVariable> catchVars = new ArrayList<>(); public final ArrayList<ValueVariable> catchVars = new ArrayList<>();
public ValueVariable get(int i) { public ValueVariable get(int i) {
if (i >= locals.length) if (i >= locals.length) return catchVars.get(i - locals.length);
return catchVars.get(i - locals.length);
if (i >= 0) return locals[i]; if (i >= 0) return locals[i];
else return captures[~i]; else return captures[~i];
} }
public String[] getNames() {
var res = new String[locals.length];
for (var i = 0; i < locals.length; i++) {
if (names == null || i >= names.length) res[i] = "local_" + i;
else res[i] = names[i];
}
return res;
}
public void setNames(String[] names) {
this.names = names;
}
public int size() { public int size() {
return captures.length + locals.length; return captures.length + locals.length;
} }
public void toGlobal(GlobalScope global) {
var names = getNames();
for (var i = 0; i < names.length; i++) {
global.define(names[i], locals[i]);
}
}
public LocalScope(int n, ValueVariable[] captures) { public LocalScope(int n, ValueVariable[] captures) {
locals = new ValueVariable[n]; locals = new ValueVariable[n];
this.captures = captures; this.captures = captures;

View File

@@ -11,6 +11,9 @@ public class LocalScopeRecord implements ScopeRecord {
private final ArrayList<String> captures = new ArrayList<>(); private final ArrayList<String> captures = new ArrayList<>();
private final ArrayList<String> locals = new ArrayList<>(); private final ArrayList<String> locals = new ArrayList<>();
public String[] captures() {
return captures.toArray(String[]::new);
}
public String[] locals() { public String[] locals() {
return locals.toArray(String[]::new); return locals.toArray(String[]::new);
} }
@@ -59,7 +62,7 @@ public class LocalScopeRecord implements ScopeRecord {
return name; return name;
} }
public boolean has(Context ctx, String name) throws InterruptedException { public boolean has(Context ctx, String name) {
return return
global.has(ctx, name) || global.has(ctx, name) ||
locals.contains(name) || locals.contains(name) ||

View File

@@ -3,7 +3,7 @@ package me.topchetoeu.jscript.engine.scope;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
public interface Variable { public interface Variable {
Object get(Context ctx) throws InterruptedException; Object get(Context ctx);
default boolean readonly() { return true; } default boolean readonly() { return true; }
default void set(Context ctx, Object val) throws InterruptedException { } default void set(Context ctx, Object val) { }
} }

View File

@@ -14,13 +14,14 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
private Object[] values; private Object[] values;
private int size; private int size;
private void alloc(int index) { private Object[] alloc(int index) {
if (index < values.length) return; index++;
if (index < values.length) return values;
if (index < values.length * 2) index = values.length * 2; if (index < values.length * 2) index = values.length * 2;
var arr = new Object[index]; var arr = new Object[index];
System.arraycopy(values, 0, arr, 0, values.length); System.arraycopy(values, 0, arr, 0, values.length);
values = arr; return arr;
} }
public int size() { return size; } public int size() { return size; }
@@ -28,7 +29,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
if (val < 0) return false; if (val < 0) return false;
if (size > val) shrink(size - val); if (size > val) shrink(size - val);
else { else {
alloc(val); values = alloc(val);
size = val; size = val;
} }
return true; return true;
@@ -43,7 +44,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
public void set(Context ctx, int i, Object val) { public void set(Context ctx, int i, Object val) {
if (i < 0) return; if (i < 0) return;
alloc(i); values = alloc(i);
val = Values.normalize(ctx, val); val = Values.normalize(ctx, val);
if (val == null) val = UNDEFINED; if (val == null) val = UNDEFINED;
@@ -51,7 +52,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
if (i >= size) size = i + 1; if (i >= size) size = i + 1;
} }
public boolean has(int i) { public boolean has(int i) {
return i >= 0 && i < values.length && values[i] != null; return i >= 0 && i < size && values[i] != null;
} }
public void remove(int i) { public void remove(int i) {
if (i < 0 || i >= values.length) return; if (i < 0 || i >= values.length) return;
@@ -84,8 +85,9 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
public void copyTo(Context ctx, ArrayValue arr, int sourceStart, int destStart, int count) { public void copyTo(Context ctx, ArrayValue arr, int sourceStart, int destStart, int count) {
// Iterate in reverse to reallocate at most once // Iterate in reverse to reallocate at most once
for (var i = count - 1; i >= 0; i--) { for (var i = count - 1; i >= 0; i--) {
if (i + sourceStart < 0 || i + sourceStart >= size) arr.set(ctx, i + destStart, null); if (i + sourceStart < 0 || i + sourceStart >= size) arr.remove(i + destStart);
if (values[i + sourceStart] == UNDEFINED) arr.set(ctx, i + destStart, null); if (values[i + sourceStart] == UNDEFINED) arr.set(ctx, i + destStart, null);
else if (values[i + sourceStart] == null) arr.remove(i + destStart);
else arr.set(ctx, i + destStart, values[i + sourceStart]); else arr.set(ctx, i + destStart, values[i + sourceStart]);
} }
} }
@@ -97,7 +99,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
} }
public void move(int srcI, int dstI, int n) { public void move(int srcI, int dstI, int n) {
alloc(dstI + n); values = alloc(dstI + n);
System.arraycopy(values, srcI, values, dstI, n); System.arraycopy(values, srcI, values, dstI, n);
@@ -122,7 +124,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
} }
@Override @Override
protected Object getField(Context ctx, Object key) throws InterruptedException { protected Object getField(Context ctx, Object key) {
if (key instanceof Number) { if (key instanceof Number) {
var i = ((Number)key).doubleValue(); var i = ((Number)key).doubleValue();
if (i >= 0 && i - Math.floor(i) == 0) { if (i >= 0 && i - Math.floor(i) == 0) {
@@ -133,7 +135,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
return super.getField(ctx, key); return super.getField(ctx, key);
} }
@Override @Override
protected boolean setField(Context ctx, Object key, Object val) throws InterruptedException { protected boolean setField(Context ctx, Object key, Object val) {
if (key instanceof Number) { if (key instanceof Number) {
var i = Values.number(key); var i = Values.number(key);
if (i >= 0 && i - Math.floor(i) == 0) { if (i >= 0 && i - Math.floor(i) == 0) {
@@ -145,7 +147,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
return super.setField(ctx, key, val); return super.setField(ctx, key, val);
} }
@Override @Override
protected boolean hasField(Context ctx, Object key) throws InterruptedException { protected boolean hasField(Context ctx, Object key) {
if (key instanceof Number) { if (key instanceof Number) {
var i = Values.number(key); var i = Values.number(key);
if (i >= 0 && i - Math.floor(i) == 0) { if (i >= 0 && i - Math.floor(i) == 0) {
@@ -156,7 +158,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
return super.hasField(ctx, key); return super.hasField(ctx, key);
} }
@Override @Override
protected void deleteField(Context ctx, Object key) throws InterruptedException { protected void deleteField(Context ctx, Object key) {
if (key instanceof Number) { if (key instanceof Number) {
var i = Values.number(key); var i = Values.number(key);
if (i >= 0 && i - Math.floor(i) == 0) { if (i >= 0 && i - Math.floor(i) == 0) {
@@ -212,7 +214,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
for (var i = 0; i < size; i++) this.values[i] = Values.normalize(ctx, values[i]); for (var i = 0; i < size; i++) this.values[i] = Values.normalize(ctx, values[i]);
} }
public static ArrayValue of(Context ctx, Collection<Object> values) { public static ArrayValue of(Context ctx, Collection<?> values) {
return new ArrayValue(ctx, values.toArray(Object[]::new)); return new ArrayValue(ctx, values.toArray(Object[]::new));
} }
} }

View File

@@ -1,16 +1,19 @@
package me.topchetoeu.jscript.engine.values; package me.topchetoeu.jscript.engine.values;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.FunctionBody;
import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.engine.frame.Runners;
import me.topchetoeu.jscript.engine.scope.ValueVariable; import me.topchetoeu.jscript.engine.scope.ValueVariable;
public class CodeFunction extends FunctionValue { public class CodeFunction extends FunctionValue {
public final int localsN; public final int localsN;
public final int length; public final int length;
public final Instruction[] body; public final Instruction[] body;
public final String[] captureNames, localNames;
public final ValueVariable[] captures; public final ValueVariable[] captures;
public Environment environment; public Environment environment;
@@ -28,16 +31,29 @@ public class CodeFunction extends FunctionValue {
} }
@Override @Override
public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException { public Object call(Context ctx, Object thisArg, Object ...args) {
return new CodeFrame(ctx, thisArg, args, this).run(ctx.setEnv(environment)); var frame = new CodeFrame(ctx, thisArg, args, this);
try {
ctx.pushFrame(frame);
while (true) {
var res = frame.next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, null);
if (res != Runners.NO_RETURN) return res;
}
}
finally {
ctx.popFrame(frame);
}
} }
public CodeFunction(Environment environment, String name, int localsN, int length, ValueVariable[] captures, Instruction[] body) { public CodeFunction(Environment environment, String name, int localsN, int length, ValueVariable[] captures, FunctionBody body) {
super(name, length); super(name, length);
this.captures = captures; this.captures = captures;
this.captureNames = body.captureNames;
this.localNames = body.localNames;
this.environment = environment; this.environment = environment;
this.localsN = localsN; this.localsN = localsN;
this.length = length; this.length = length;
this.body = body; this.body = body.instructions;
} }
} }

View File

@@ -11,31 +11,31 @@ public abstract class FunctionValue extends ObjectValue {
@Override @Override
public String toString() { public String toString() {
return "function(...) { ...}"; return String.format("function %s(...)", name);
} }
public abstract Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException; public abstract Object call(Context ctx, Object thisArg, Object ...args);
public Object call(Context ctx) throws InterruptedException { public Object call(Context ctx) {
return call(ctx, null); return call(ctx, null);
} }
@Override @Override
protected Object getField(Context ctx, Object key) throws InterruptedException { protected Object getField(Context ctx, Object key) {
if (key.equals("name")) return name; if ("name".equals(key)) return name;
if (key.equals("length")) return length; if ("length".equals(key)) return length;
return super.getField(ctx, key); return super.getField(ctx, key);
} }
@Override @Override
protected boolean setField(Context ctx, Object key, Object val) throws InterruptedException { protected boolean setField(Context ctx, Object key, Object val) {
if (key.equals("name")) name = Values.toString(ctx, val); if ("name".equals(key)) name = Values.toString(ctx, val);
else if (key.equals("length")) length = (int)Values.toNumber(ctx, val); else if ("length".equals(key)) length = (int)Values.toNumber(ctx, val);
else return super.setField(ctx, key, val); else return super.setField(ctx, key, val);
return true; return true;
} }
@Override @Override
protected boolean hasField(Context ctx, Object key) throws InterruptedException { protected boolean hasField(Context ctx, Object key) {
if (key.equals("name")) return true; if ("name".equals(key)) return true;
if (key.equals("length")) return true; if ("length".equals(key)) return true;
return super.hasField(ctx, key); return super.hasField(ctx, key);
} }

View File

@@ -4,13 +4,13 @@ import me.topchetoeu.jscript.engine.Context;
public class NativeFunction extends FunctionValue { public class NativeFunction extends FunctionValue {
public static interface NativeFunctionRunner { public static interface NativeFunctionRunner {
Object run(Context ctx, Object thisArg, Object[] args) throws InterruptedException; Object run(Context ctx, Object thisArg, Object[] args);
} }
public final NativeFunctionRunner action; public final NativeFunctionRunner action;
@Override @Override
public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException { public Object call(Context ctx, Object thisArg, Object ...args) {
return action.run(ctx, thisArg, args); return action.run(ctx, thisArg, args);
} }

View File

@@ -7,12 +7,24 @@ public class NativeWrapper extends ObjectValue {
public final Object wrapped; public final Object wrapped;
@Override @Override
public ObjectValue getPrototype(Context ctx) throws InterruptedException { public ObjectValue getPrototype(Context ctx) {
if (prototype == NATIVE_PROTO) if (prototype == NATIVE_PROTO) return ctx.environment().wrappers.getProto(wrapped.getClass());
return ctx.env.wrappersProvider.getProto(wrapped.getClass());
else return super.getPrototype(ctx); else return super.getPrototype(ctx);
} }
@Override
public String toString() {
return wrapped.toString();
}
@Override
public boolean equals(Object obj) {
return wrapped.equals(obj);
}
@Override
public int hashCode() {
return wrapped.hashCode();
}
public NativeWrapper(Object wrapped) { public NativeWrapper(Object wrapped) {
this.wrapped = wrapped; this.wrapped = wrapped;
prototype = NATIVE_PROTO; prototype = NATIVE_PROTO;

View File

@@ -103,8 +103,7 @@ public class ObjectValue {
) return true; ) return true;
if (!extensible() && !values.containsKey(key) && !properties.containsKey(key)) return false; if (!extensible() && !values.containsKey(key) && !properties.containsKey(key)) return false;
if (!memberConfigurable(key)) if (!memberConfigurable(key)) return false;
return false;
nonWritableSet.remove(key); nonWritableSet.remove(key);
nonEnumerableSet.remove(key); nonEnumerableSet.remove(key);
@@ -145,19 +144,17 @@ public class ObjectValue {
return true; return true;
} }
public ObjectValue getPrototype(Context ctx) throws InterruptedException { public ObjectValue getPrototype(Context ctx) {
try { try {
if (prototype == OBJ_PROTO) return ctx.env.proto("object"); if (prototype == OBJ_PROTO) return ctx.environment().proto("object");
if (prototype == ARR_PROTO) return ctx.env.proto("array"); if (prototype == ARR_PROTO) return ctx.environment().proto("array");
if (prototype == FUNC_PROTO) return ctx.env.proto("function"); if (prototype == FUNC_PROTO) return ctx.environment().proto("function");
if (prototype == ERR_PROTO) return ctx.env.proto("error"); if (prototype == ERR_PROTO) return ctx.environment().proto("error");
if (prototype == RANGE_ERR_PROTO) return ctx.env.proto("rangeErr"); if (prototype == RANGE_ERR_PROTO) return ctx.environment().proto("rangeErr");
if (prototype == SYNTAX_ERR_PROTO) return ctx.env.proto("syntaxErr"); if (prototype == SYNTAX_ERR_PROTO) return ctx.environment().proto("syntaxErr");
if (prototype == TYPE_ERR_PROTO) return ctx.env.proto("typeErr"); if (prototype == TYPE_ERR_PROTO) return ctx.environment().proto("typeErr");
}
catch (NullPointerException e) {
return null;
} }
catch (NullPointerException e) { return null; }
return (ObjectValue)prototype; return (ObjectValue)prototype;
} }
@@ -172,14 +169,14 @@ public class ObjectValue {
else if (Values.isObject(val)) { else if (Values.isObject(val)) {
var obj = Values.object(val); var obj = Values.object(val);
if (ctx != null && ctx.env != null) { if (ctx != null && ctx.environment() != null) {
if (obj == ctx.env.proto("object")) prototype = OBJ_PROTO; if (obj == ctx.environment().proto("object")) prototype = OBJ_PROTO;
else if (obj == ctx.env.proto("array")) prototype = ARR_PROTO; else if (obj == ctx.environment().proto("array")) prototype = ARR_PROTO;
else if (obj == ctx.env.proto("function")) prototype = FUNC_PROTO; else if (obj == ctx.environment().proto("function")) prototype = FUNC_PROTO;
else if (obj == ctx.env.proto("error")) prototype = ERR_PROTO; else if (obj == ctx.environment().proto("error")) prototype = ERR_PROTO;
else if (obj == ctx.env.proto("syntaxErr")) prototype = SYNTAX_ERR_PROTO; else if (obj == ctx.environment().proto("syntaxErr")) prototype = SYNTAX_ERR_PROTO;
else if (obj == ctx.env.proto("typeErr")) prototype = TYPE_ERR_PROTO; else if (obj == ctx.environment().proto("typeErr")) prototype = TYPE_ERR_PROTO;
else if (obj == ctx.env.proto("rangeErr")) prototype = RANGE_ERR_PROTO; else if (obj == ctx.environment().proto("rangeErr")) prototype = RANGE_ERR_PROTO;
else prototype = obj; else prototype = obj;
} }
else prototype = obj; else prototype = obj;
@@ -203,19 +200,19 @@ public class ObjectValue {
return true; return true;
} }
protected Property getProperty(Context ctx, Object key) throws InterruptedException { protected Property getProperty(Context ctx, Object key) {
if (properties.containsKey(key)) return properties.get(key); if (properties.containsKey(key)) return properties.get(key);
var proto = getPrototype(ctx); var proto = getPrototype(ctx);
if (proto != null) return proto.getProperty(ctx, key); if (proto != null) return proto.getProperty(ctx, key);
else return null; else return null;
} }
protected Object getField(Context ctx, Object key) throws InterruptedException { protected Object getField(Context ctx, Object key) {
if (values.containsKey(key)) return values.get(key); if (values.containsKey(key)) return values.get(key);
var proto = getPrototype(ctx); var proto = getPrototype(ctx);
if (proto != null) return proto.getField(ctx, key); if (proto != null) return proto.getField(ctx, key);
else return null; else return null;
} }
protected boolean setField(Context ctx, Object key, Object val) throws InterruptedException { protected boolean setField(Context ctx, Object key, Object val) {
if (val instanceof FunctionValue && ((FunctionValue)val).name.equals("")) { if (val instanceof FunctionValue && ((FunctionValue)val).name.equals("")) {
((FunctionValue)val).name = Values.toString(ctx, key); ((FunctionValue)val).name = Values.toString(ctx, key);
} }
@@ -223,14 +220,14 @@ public class ObjectValue {
values.put(key, val); values.put(key, val);
return true; return true;
} }
protected void deleteField(Context ctx, Object key) throws InterruptedException { protected void deleteField(Context ctx, Object key) {
values.remove(key); values.remove(key);
} }
protected boolean hasField(Context ctx, Object key) throws InterruptedException { protected boolean hasField(Context ctx, Object key) {
return values.containsKey(key); return values.containsKey(key);
} }
public final Object getMember(Context ctx, Object key, Object thisArg) throws InterruptedException { public final Object getMember(Context ctx, Object key, Object thisArg) {
key = Values.normalize(ctx, key); key = Values.normalize(ctx, key);
if ("__proto__".equals(key)) { if ("__proto__".equals(key)) {
@@ -246,11 +243,11 @@ public class ObjectValue {
} }
else return getField(ctx, key); else return getField(ctx, key);
} }
public final Object getMember(Context ctx, Object key) throws InterruptedException { public final Object getMember(Context ctx, Object key) {
return getMember(ctx, key, this); return getMember(ctx, key, this);
} }
public final boolean setMember(Context ctx, Object key, Object val, Object thisArg, boolean onlyProps) throws InterruptedException { public final boolean setMember(Context ctx, Object key, Object val, Object thisArg, boolean onlyProps) {
key = Values.normalize(ctx, key); val = Values.normalize(ctx, val); key = Values.normalize(ctx, key); val = Values.normalize(ctx, val);
var prop = getProperty(ctx, key); var prop = getProperty(ctx, key);
@@ -265,25 +262,25 @@ public class ObjectValue {
values.put(key, val); values.put(key, val);
return true; return true;
} }
else if (key.equals("__proto__")) return setPrototype(ctx, val); else if ("__proto__".equals(key)) return setPrototype(ctx, val);
else if (nonWritableSet.contains(key)) return false; else if (nonWritableSet.contains(key)) return false;
else return setField(ctx, key, val); else return setField(ctx, key, val);
} }
public final boolean setMember(Context ctx, Object key, Object val, boolean onlyProps) throws InterruptedException { public final boolean setMember(Context ctx, Object key, Object val, boolean onlyProps) {
return setMember(ctx, Values.normalize(ctx, key), Values.normalize(ctx, val), this, onlyProps); return setMember(ctx, Values.normalize(ctx, key), Values.normalize(ctx, val), this, onlyProps);
} }
public final boolean hasMember(Context ctx, Object key, boolean own) throws InterruptedException { public final boolean hasMember(Context ctx, Object key, boolean own) {
key = Values.normalize(ctx, key); key = Values.normalize(ctx, key);
if (key != null && key.equals("__proto__")) return true; if (key != null && "__proto__".equals(key)) return true;
if (hasField(ctx, key)) return true; if (hasField(ctx, key)) return true;
if (properties.containsKey(key)) return true; if (properties.containsKey(key)) return true;
if (own) return false; if (own) return false;
var proto = getPrototype(ctx); var proto = getPrototype(ctx);
return proto != null && proto.hasMember(ctx, key, own); return proto != null && proto.hasMember(ctx, key, own);
} }
public final boolean deleteMember(Context ctx, Object key) throws InterruptedException { public final boolean deleteMember(Context ctx, Object key) {
key = Values.normalize(ctx, key); key = Values.normalize(ctx, key);
if (!memberConfigurable(key)) return false; if (!memberConfigurable(key)) return false;
@@ -294,7 +291,7 @@ public class ObjectValue {
return true; return true;
} }
public final ObjectValue getMemberDescriptor(Context ctx, Object key) throws InterruptedException { public final ObjectValue getMemberDescriptor(Context ctx, Object key) {
key = Values.normalize(ctx, key); key = Values.normalize(ctx, key);
var prop = properties.get(key); var prop = properties.get(key);

View File

@@ -0,0 +1,51 @@
package me.topchetoeu.jscript.engine.values;
import java.util.HashMap;
import java.util.List;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.scope.ValueVariable;
public class ScopeValue extends ObjectValue {
public final ValueVariable[] variables;
public final HashMap<String, Integer> names = new HashMap<>();
@Override
protected Object getField(Context ctx, Object key) {
if (names.containsKey(key)) return variables[names.get(key)].get(ctx);
return super.getField(ctx, key);
}
@Override
protected boolean setField(Context ctx, Object key, Object val) {
if (names.containsKey(key)) {
variables[names.get(key)].set(ctx, val);
return true;
}
return super.setField(ctx, key, val);
}
@Override
protected void deleteField(Context ctx, Object key) {
if (names.containsKey(key)) return;
super.deleteField(ctx, key);
}
@Override
protected boolean hasField(Context ctx, Object key) {
if (names.containsKey(key)) return true;
return super.hasField(ctx, key);
}
@Override
public List<Object> keys(boolean includeNonEnumerable) {
var res = super.keys(includeNonEnumerable);
res.addAll(names.keySet());
return res;
}
public ScopeValue(ValueVariable[] variables, String[] names) {
this.variables = variables;
for (var i = 0; i < names.length && i < variables.length; i++) {
this.names.put(names[i], i);
this.nonConfigurableSet.add(names[i]);
}
}
}

View File

@@ -16,8 +16,28 @@ import me.topchetoeu.jscript.engine.frame.ConvertHint;
import me.topchetoeu.jscript.exceptions.ConvertException; import me.topchetoeu.jscript.exceptions.ConvertException;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.SyntaxException; import me.topchetoeu.jscript.exceptions.SyntaxException;
import me.topchetoeu.jscript.exceptions.UncheckedException;
import me.topchetoeu.jscript.lib.PromiseLib;
public class Values { public class Values {
public static enum CompareResult {
NOT_EQUAL,
EQUAL,
LESS,
GREATER;
public boolean less() { return this == LESS; }
public boolean greater() { return this == GREATER; }
public boolean lessOrEqual() { return this == LESS || this == EQUAL; }
public boolean greaterOrEqual() { return this == GREATER || this == EQUAL; }
public static CompareResult from(int cmp) {
if (cmp < 0) return LESS;
if (cmp > 0) return GREATER;
return EQUAL;
}
}
public static final Object NULL = new Object(); public static final Object NULL = new Object();
public static boolean isObject(Object val) { return val instanceof ObjectValue; } public static boolean isObject(Object val) { return val instanceof ObjectValue; }
@@ -67,7 +87,7 @@ public class Values {
return "object"; return "object";
} }
private static Object tryCallConvertFunc(Context ctx, Object obj, String name) throws InterruptedException { private static Object tryCallConvertFunc(Context ctx, Object obj, String name) {
var func = getMember(ctx, obj, name); var func = getMember(ctx, obj, name);
if (func != null) { if (func != null) {
@@ -88,7 +108,7 @@ public class Values {
obj == NULL; obj == NULL;
} }
public static Object toPrimitive(Context ctx, Object obj, ConvertHint hint) throws InterruptedException { public static Object toPrimitive(Context ctx, Object obj, ConvertHint hint) {
obj = normalize(ctx, obj); obj = normalize(ctx, obj);
if (isPrimitive(obj)) return obj; if (isPrimitive(obj)) return obj;
@@ -96,37 +116,32 @@ public class Values {
var second = hint == ConvertHint.VALUEOF ? "toString" : "valueOf"; var second = hint == ConvertHint.VALUEOF ? "toString" : "valueOf";
if (ctx != null) { if (ctx != null) {
try { try { return tryCallConvertFunc(ctx, obj, first); }
return tryCallConvertFunc(ctx, obj, first); catch (EngineException unused) { return tryCallConvertFunc(ctx, obj, second); }
}
catch (EngineException unused) {
return tryCallConvertFunc(ctx, obj, second);
}
} }
throw EngineException.ofType("Value couldn't be converted to a primitive."); throw EngineException.ofType("Value couldn't be converted to a primitive.");
} }
public static boolean toBoolean(Object obj) { public static boolean toBoolean(Object obj) {
if (obj == NULL || obj == null) return false; if (obj == NULL || obj == null) return false;
if (obj instanceof Number && number(obj) == 0) return false; if (obj instanceof Number && (number(obj) == 0 || Double.isNaN(number(obj)))) return false;
if (obj instanceof String && ((String)obj).equals("")) return false; if (obj instanceof String && ((String)obj).equals("")) return false;
if (obj instanceof Boolean) return (Boolean)obj; if (obj instanceof Boolean) return (Boolean)obj;
return true; return true;
} }
public static double toNumber(Context ctx, Object obj) throws InterruptedException { public static double toNumber(Context ctx, Object obj) {
var val = toPrimitive(ctx, obj, ConvertHint.VALUEOF); var val = toPrimitive(ctx, obj, ConvertHint.VALUEOF);
if (val instanceof Number) return number(val); if (val instanceof Number) return number(val);
if (val instanceof Boolean) return ((Boolean)val) ? 1 : 0; if (val instanceof Boolean) return ((Boolean)val) ? 1 : 0;
if (val instanceof String) { if (val instanceof String) {
try { try { return Double.parseDouble((String)val); }
return Double.parseDouble((String)val); catch (NumberFormatException e) { return Double.NaN; }
} catch (Throwable e) { throw new UncheckedException(e); }
catch (NumberFormatException e) { }
} }
return Double.NaN; return Double.NaN;
} }
public static String toString(Context ctx, Object obj) throws InterruptedException { public static String toString(Context ctx, Object obj) {
var val = toPrimitive(ctx, obj, ConvertHint.VALUEOF); var val = toPrimitive(ctx, obj, ConvertHint.VALUEOF);
if (val == null) return "undefined"; if (val == null) return "undefined";
@@ -141,52 +156,52 @@ public class Values {
} }
if (val instanceof Boolean) return (Boolean)val ? "true" : "false"; if (val instanceof Boolean) return (Boolean)val ? "true" : "false";
if (val instanceof String) return (String)val; if (val instanceof String) return (String)val;
if (val instanceof Symbol) return ((Symbol)val).toString(); if (val instanceof Symbol) return val.toString();
return "Unknown value"; return "Unknown value";
} }
public static Object add(Context ctx, Object a, Object b) throws InterruptedException { public static Object add(Context ctx, Object a, Object b) {
if (a instanceof String || b instanceof String) return toString(ctx, a) + toString(ctx, b); if (a instanceof String || b instanceof String) return toString(ctx, a) + toString(ctx, b);
else return toNumber(ctx, a) + toNumber(ctx, b); else return toNumber(ctx, a) + toNumber(ctx, b);
} }
public static double subtract(Context ctx, Object a, Object b) throws InterruptedException { public static double subtract(Context ctx, Object a, Object b) {
return toNumber(ctx, a) - toNumber(ctx, b); return toNumber(ctx, a) - toNumber(ctx, b);
} }
public static double multiply(Context ctx, Object a, Object b) throws InterruptedException { public static double multiply(Context ctx, Object a, Object b) {
return toNumber(ctx, a) * toNumber(ctx, b); return toNumber(ctx, a) * toNumber(ctx, b);
} }
public static double divide(Context ctx, Object a, Object b) throws InterruptedException { public static double divide(Context ctx, Object a, Object b) {
return toNumber(ctx, a) / toNumber(ctx, b); return toNumber(ctx, a) / toNumber(ctx, b);
} }
public static double modulo(Context ctx, Object a, Object b) throws InterruptedException { public static double modulo(Context ctx, Object a, Object b) {
return toNumber(ctx, a) % toNumber(ctx, b); return toNumber(ctx, a) % toNumber(ctx, b);
} }
public static double negative(Context ctx, Object obj) throws InterruptedException { public static double negative(Context ctx, Object obj) {
return -toNumber(ctx, obj); return -toNumber(ctx, obj);
} }
public static int and(Context ctx, Object a, Object b) throws InterruptedException { public static int and(Context ctx, Object a, Object b) {
return (int)toNumber(ctx, a) & (int)toNumber(ctx, b); return (int)toNumber(ctx, a) & (int)toNumber(ctx, b);
} }
public static int or(Context ctx, Object a, Object b) throws InterruptedException { public static int or(Context ctx, Object a, Object b) {
return (int)toNumber(ctx, a) | (int)toNumber(ctx, b); return (int)toNumber(ctx, a) | (int)toNumber(ctx, b);
} }
public static int xor(Context ctx, Object a, Object b) throws InterruptedException { public static int xor(Context ctx, Object a, Object b) {
return (int)toNumber(ctx, a) ^ (int)toNumber(ctx, b); return (int)toNumber(ctx, a) ^ (int)toNumber(ctx, b);
} }
public static int bitwiseNot(Context ctx, Object obj) throws InterruptedException { public static int bitwiseNot(Context ctx, Object obj) {
return ~(int)toNumber(ctx, obj); return ~(int)toNumber(ctx, obj);
} }
public static int shiftLeft(Context ctx, Object a, Object b) throws InterruptedException { public static int shiftLeft(Context ctx, Object a, Object b) {
return (int)toNumber(ctx, a) << (int)toNumber(ctx, b); return (int)toNumber(ctx, a) << (int)toNumber(ctx, b);
} }
public static int shiftRight(Context ctx, Object a, Object b) throws InterruptedException { public static int shiftRight(Context ctx, Object a, Object b) {
return (int)toNumber(ctx, a) >> (int)toNumber(ctx, b); return (int)toNumber(ctx, a) >> (int)toNumber(ctx, b);
} }
public static long unsignedShiftRight(Context ctx, Object a, Object b) throws InterruptedException { public static long unsignedShiftRight(Context ctx, Object a, Object b) {
long _a = (long)toNumber(ctx, a); long _a = (long)toNumber(ctx, a);
long _b = (long)toNumber(ctx, b); long _b = (long)toNumber(ctx, b);
@@ -195,19 +210,25 @@ public class Values {
return _a >>> _b; return _a >>> _b;
} }
public static int compare(Context ctx, Object a, Object b) throws InterruptedException { public static CompareResult compare(Context ctx, Object a, Object b) {
a = toPrimitive(ctx, a, ConvertHint.VALUEOF); a = toPrimitive(ctx, a, ConvertHint.VALUEOF);
b = toPrimitive(ctx, b, ConvertHint.VALUEOF); b = toPrimitive(ctx, b, ConvertHint.VALUEOF);
if (a instanceof String && b instanceof String) return ((String)a).compareTo((String)b); if (a instanceof String && b instanceof String) CompareResult.from(((String)a).compareTo((String)b));
else return Double.compare(toNumber(ctx, a), toNumber(ctx, b));
var _a = toNumber(ctx, a);
var _b = toNumber(ctx, b);
if (Double.isNaN(_a) || Double.isNaN(_b)) return CompareResult.NOT_EQUAL;
return CompareResult.from(Double.compare(_a, _b));
} }
public static boolean not(Object obj) { public static boolean not(Object obj) {
return !toBoolean(obj); return !toBoolean(obj);
} }
public static boolean isInstanceOf(Context ctx, Object obj, Object proto) throws InterruptedException { public static boolean isInstanceOf(Context ctx, Object obj, Object proto) {
if (obj == null || obj == NULL || proto == null || proto == NULL) return false; if (obj == null || obj == NULL || proto == null || proto == NULL) return false;
var val = getPrototype(ctx, obj); var val = getPrototype(ctx, obj);
@@ -219,7 +240,7 @@ public class Values {
return false; return false;
} }
public static Object operation(Context ctx, Operation op, Object ...args) throws InterruptedException { public static Object operation(Context ctx, Operation op, Object ...args) {
switch (op) { switch (op) {
case ADD: return add(ctx, args[0], args[1]); case ADD: return add(ctx, args[0], args[1]);
case SUBTRACT: return subtract(ctx, args[0], args[1]); case SUBTRACT: return subtract(ctx, args[0], args[1]);
@@ -236,10 +257,10 @@ public class Values {
case LOOSE_EQUALS: return looseEqual(ctx, args[0], args[1]); case LOOSE_EQUALS: return looseEqual(ctx, args[0], args[1]);
case LOOSE_NOT_EQUALS: return !looseEqual(ctx, args[0], args[1]); case LOOSE_NOT_EQUALS: return !looseEqual(ctx, args[0], args[1]);
case GREATER: return compare(ctx, args[0], args[1]) > 0; case GREATER: return compare(ctx, args[0], args[1]).greater();
case GREATER_EQUALS: return compare(ctx, args[0], args[1]) >= 0; case GREATER_EQUALS: return compare(ctx, args[0], args[1]).greaterOrEqual();
case LESS: return compare(ctx, args[0], args[1]) < 0; case LESS: return compare(ctx, args[0], args[1]).less();
case LESS_EQUALS: return compare(ctx, args[0], args[1]) <= 0; case LESS_EQUALS: return compare(ctx, args[0], args[1]).lessOrEqual();
case INVERSE: return bitwiseNot(ctx, args[0]); case INVERSE: return bitwiseNot(ctx, args[0]);
case NOT: return not(args[0]); case NOT: return not(args[0]);
@@ -260,7 +281,7 @@ public class Values {
} }
} }
public static Object getMember(Context ctx, Object obj, Object key) throws InterruptedException { public static Object getMember(Context ctx, Object obj, Object key) {
obj = normalize(ctx, obj); key = normalize(ctx, key); obj = normalize(ctx, obj); key = normalize(ctx, key);
if (obj == null) throw new IllegalArgumentException("Tried to access member of undefined."); if (obj == null) throw new IllegalArgumentException("Tried to access member of undefined.");
if (obj == NULL) throw new IllegalArgumentException("Tried to access member of null."); if (obj == NULL) throw new IllegalArgumentException("Tried to access member of null.");
@@ -276,25 +297,30 @@ public class Values {
var proto = getPrototype(ctx, obj); var proto = getPrototype(ctx, obj);
if (proto == null) return key.equals("__proto__") ? NULL : null; if (proto == null) return "__proto__".equals(key) ? NULL : null;
else if (key != null && key.equals("__proto__")) return proto; else if (key != null && "__proto__".equals(key)) return proto;
else return proto.getMember(ctx, key, obj); else return proto.getMember(ctx, key, obj);
} }
public static boolean setMember(Context ctx, Object obj, Object key, Object val) throws InterruptedException { public static Object getMemberPath(Context ctx, Object obj, Object ...path) {
var res = obj;
for (var key : path) res = getMember(ctx, res, key);
return res;
}
public static boolean setMember(Context ctx, Object obj, Object key, Object val) {
obj = normalize(ctx, obj); key = normalize(ctx, key); val = normalize(ctx, val); obj = normalize(ctx, obj); key = normalize(ctx, key); val = normalize(ctx, val);
if (obj == null) throw EngineException.ofType("Tried to access member of undefined."); if (obj == null) throw EngineException.ofType("Tried to access member of undefined.");
if (obj == NULL) throw EngineException.ofType("Tried to access member of null."); if (obj == NULL) throw EngineException.ofType("Tried to access member of null.");
if (key.equals("__proto__")) return setPrototype(ctx, obj, val); if (key != null && "__proto__".equals(key)) return setPrototype(ctx, obj, val);
if (isObject(obj)) return object(obj).setMember(ctx, key, val, false); if (isObject(obj)) return object(obj).setMember(ctx, key, val, false);
var proto = getPrototype(ctx, obj); var proto = getPrototype(ctx, obj);
return proto.setMember(ctx, key, val, obj, true); return proto.setMember(ctx, key, val, obj, true);
} }
public static boolean hasMember(Context ctx, Object obj, Object key, boolean own) throws InterruptedException { public static boolean hasMember(Context ctx, Object obj, Object key, boolean own) {
if (obj == null || obj == NULL) return false; if (obj == null || obj == NULL) return false;
obj = normalize(ctx, obj); key = normalize(ctx, key); obj = normalize(ctx, obj); key = normalize(ctx, key);
if (key.equals("__proto__")) return true; if ("__proto__".equals(key)) return true;
if (isObject(obj)) return object(obj).hasMember(ctx, key, own); if (isObject(obj)) return object(obj).hasMember(ctx, key, own);
if (obj instanceof String && key instanceof Number) { if (obj instanceof String && key instanceof Number) {
@@ -308,31 +334,31 @@ public class Values {
var proto = getPrototype(ctx, obj); var proto = getPrototype(ctx, obj);
return proto != null && proto.hasMember(ctx, key, own); return proto != null && proto.hasMember(ctx, key, own);
} }
public static boolean deleteMember(Context ctx, Object obj, Object key) throws InterruptedException { public static boolean deleteMember(Context ctx, Object obj, Object key) {
if (obj == null || obj == NULL) return false; if (obj == null || obj == NULL) return false;
obj = normalize(ctx, obj); key = normalize(ctx, key); obj = normalize(ctx, obj); key = normalize(ctx, key);
if (isObject(obj)) return object(obj).deleteMember(ctx, key); if (isObject(obj)) return object(obj).deleteMember(ctx, key);
else return false; else return false;
} }
public static ObjectValue getPrototype(Context ctx, Object obj) throws InterruptedException { public static ObjectValue getPrototype(Context ctx, Object obj) {
if (obj == null || obj == NULL) return null; if (obj == null || obj == NULL) return null;
obj = normalize(ctx, obj); obj = normalize(ctx, obj);
if (isObject(obj)) return object(obj).getPrototype(ctx); if (isObject(obj)) return object(obj).getPrototype(ctx);
if (ctx == null) return null; if (ctx == null) return null;
if (obj instanceof String) return ctx.env.proto("string"); if (obj instanceof String) return ctx.environment().proto("string");
else if (obj instanceof Number) return ctx.env.proto("number"); else if (obj instanceof Number) return ctx.environment().proto("number");
else if (obj instanceof Boolean) return ctx.env.proto("bool"); else if (obj instanceof Boolean) return ctx.environment().proto("bool");
else if (obj instanceof Symbol) return ctx.env.proto("symbol"); else if (obj instanceof Symbol) return ctx.environment().proto("symbol");
return null; return null;
} }
public static boolean setPrototype(Context ctx, Object obj, Object proto) throws InterruptedException { public static boolean setPrototype(Context ctx, Object obj, Object proto) {
obj = normalize(ctx, obj); obj = normalize(ctx, obj);
return isObject(obj) && object(obj).setPrototype(ctx, proto); return isObject(obj) && object(obj).setPrototype(ctx, proto);
} }
public static List<Object> getMembers(Context ctx, Object obj, boolean own, boolean includeNonEnumerable) throws InterruptedException { public static List<Object> getMembers(Context ctx, Object obj, boolean own, boolean includeNonEnumerable) {
List<Object> res = new ArrayList<>(); List<Object> res = new ArrayList<>();
if (isObject(obj)) res = object(obj).keys(includeNonEnumerable); if (isObject(obj)) res = object(obj).keys(includeNonEnumerable);
@@ -352,7 +378,7 @@ public class Values {
return res; return res;
} }
public static ObjectValue getMemberDescriptor(Context ctx, Object obj, Object key) throws InterruptedException { public static ObjectValue getMemberDescriptor(Context ctx, Object obj, Object key) {
if (obj instanceof ObjectValue) return ((ObjectValue)obj).getMemberDescriptor(ctx, key); if (obj instanceof ObjectValue) return ((ObjectValue)obj).getMemberDescriptor(ctx, key);
else if (obj instanceof String && key instanceof Number) { else if (obj instanceof String && key instanceof Number) {
var i = ((Number)key).intValue(); var i = ((Number)key).intValue();
@@ -370,19 +396,24 @@ public class Values {
else return null; else return null;
} }
public static Object call(Context ctx, Object func, Object thisArg, Object ...args) throws InterruptedException { public static Object call(Context ctx, Object func, Object thisArg, Object ...args) {
if (!isFunction(func)) throw EngineException.ofType("Tried to call a non-function value."); if (!isFunction(func)) throw EngineException.ofType("Tried to call a non-function value.");
return function(func).call(ctx, thisArg, args); return function(func).call(ctx, thisArg, args);
} }
public static Object callNew(Context ctx, Object func, Object ...args) throws InterruptedException { public static Object callNew(Context ctx, Object func, Object ...args) {
var res = new ObjectValue(); var res = new ObjectValue();
var proto = Values.getMember(ctx, func, "prototype"); try {
res.setPrototype(ctx, proto); var proto = Values.getMember(ctx, func, "prototype");
res.setPrototype(ctx, proto);
var ret = call(ctx, func, res, args);
var ret = call(ctx, func, res, args);
if (ret != null && func instanceof FunctionValue && ((FunctionValue)func).special) return ret;
return res; if (ret != null && func instanceof FunctionValue && ((FunctionValue)func).special) return ret;
return res;
}
catch (IllegalArgumentException e) {
throw EngineException.ofType("Tried to call new on an invalid constructor.");
}
} }
public static boolean strictEquals(Context ctx, Object a, Object b) { public static boolean strictEquals(Context ctx, Object a, Object b) {
@@ -395,7 +426,7 @@ public class Values {
return a == b || a.equals(b); return a == b || a.equals(b);
} }
public static boolean looseEqual(Context ctx, Object a, Object b) throws InterruptedException { public static boolean looseEqual(Context ctx, Object a, Object b) {
a = normalize(ctx, a); b = normalize(ctx, b); a = normalize(ctx, a); b = normalize(ctx, b);
// In loose equality, null is equivalent to undefined // In loose equality, null is equivalent to undefined
@@ -446,14 +477,14 @@ public class Values {
if (val instanceof Class) { if (val instanceof Class) {
if (ctx == null) return null; if (ctx == null) return null;
else return ctx.env.wrappersProvider.getConstr((Class<?>)val); else return ctx.environment().wrappers.getConstr((Class<?>)val);
} }
return new NativeWrapper(val); return new NativeWrapper(val);
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public static <T> T convert(Context ctx, Object obj, Class<T> clazz) throws InterruptedException { public static <T> T convert(Context ctx, Object obj, Class<T> clazz) {
if (clazz == Void.class) return null; if (clazz == Void.class) return null;
if (obj instanceof NativeWrapper) { if (obj instanceof NativeWrapper) {
@@ -517,10 +548,10 @@ public class Values {
throw new ConvertException(type(obj), clazz.getSimpleName()); throw new ConvertException(type(obj), clazz.getSimpleName());
} }
public static Iterable<Object> toJavaIterable(Context ctx, Object obj) { public static Iterable<Object> fromJSIterator(Context ctx, Object obj) {
return () -> { return () -> {
try { try {
var symbol = ctx.env.symbol("Symbol.iterator"); var symbol = ctx.environment().symbol("Symbol.iterator");
var iteratorFunc = getMember(ctx, obj, symbol); var iteratorFunc = getMember(ctx, obj, symbol);
if (!isFunction(iteratorFunc)) return Collections.emptyIterator(); if (!isFunction(iteratorFunc)) return Collections.emptyIterator();
@@ -536,7 +567,7 @@ public class Values {
public boolean consumed = true; public boolean consumed = true;
private FunctionValue next = (FunctionValue)nextFunc; private FunctionValue next = (FunctionValue)nextFunc;
private void loadNext() throws InterruptedException { private void loadNext() {
if (next == null) value = null; if (next == null) value = null;
else if (consumed) { else if (consumed) {
var curr = object(next.call(ctx, iterator)); var curr = object(next.call(ctx, iterator));
@@ -551,63 +582,89 @@ public class Values {
@Override @Override
public boolean hasNext() { public boolean hasNext() {
try { loadNext();
loadNext(); return next != null;
return next != null;
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
} }
@Override @Override
public Object next() { public Object next() {
try { loadNext();
loadNext(); var res = value;
var res = value; value = null;
value = null; consumed = true;
consumed = true; return res;
return res;
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
}
} }
}; };
} }
catch (InterruptedException e) {
Thread.currentThread().interrupt();
return null;
}
catch (IllegalArgumentException | NullPointerException e) { catch (IllegalArgumentException | NullPointerException e) {
return Collections.emptyIterator(); return Collections.emptyIterator();
} }
}; };
} }
public static ObjectValue fromJavaIterator(Context ctx, Iterator<?> it) throws InterruptedException { public static ObjectValue toJSIterator(Context ctx, Iterator<?> it) {
var res = new ObjectValue(); var res = new ObjectValue();
try { try {
var key = getMember(ctx, getMember(ctx, ctx.env.proto("symbol"), "constructor"), "iterator"); var key = getMember(ctx, getMember(ctx, ctx.environment().proto("symbol"), "constructor"), "iterator");
res.defineProperty(ctx, key, new NativeFunction("", (_ctx, thisArg, args) -> thisArg)); res.defineProperty(ctx, key, new NativeFunction("", (_ctx, thisArg, args) -> thisArg));
} }
catch (IllegalArgumentException | NullPointerException e) { } catch (IllegalArgumentException | NullPointerException e) { }
res.defineProperty(ctx, "next", new NativeFunction("", (_ctx, _th, _args) -> { res.defineProperty(ctx, "next", new NativeFunction("", (_ctx, _th, _args) -> {
if (!it.hasNext()) return new ObjectValue(ctx, Map.of("done", true)); if (!it.hasNext()) return new ObjectValue(ctx, Map.of("done", true));
else return new ObjectValue(ctx, Map.of("value", it.next())); else {
var obj = new ObjectValue();
obj.defineProperty(_ctx, "value", it.next());
return obj;
}
})); }));
return res; return res;
} }
public static ObjectValue fromJavaIterable(Context ctx, Iterable<?> it) throws InterruptedException { public static ObjectValue toJSIterator(Context ctx, Iterable<?> it) {
return fromJavaIterator(ctx, it.iterator()); return toJSIterator(ctx, it.iterator());
} }
private static void printValue(Context ctx, Object val, HashSet<Object> passed, int tab) throws InterruptedException { public static ObjectValue toJSAsyncIterator(Context ctx, Iterator<?> it) {
var res = new ObjectValue();
try {
var key = getMemberPath(ctx, ctx.environment().proto("symbol"), "constructor", "asyncIterator");
res.defineProperty(ctx, key, new NativeFunction("", (_ctx, thisArg, args) -> thisArg));
}
catch (IllegalArgumentException | NullPointerException e) { }
res.defineProperty(ctx, "next", new NativeFunction("", (_ctx, _th, _args) -> {
return PromiseLib.await(ctx, () -> {
if (!it.hasNext()) return new ObjectValue(ctx, Map.of("done", true));
else {
var obj = new ObjectValue();
obj.defineProperty(_ctx, "value", it.next());
return obj;
}
});
}));
return res;
}
private static boolean isEmptyFunc(ObjectValue val) {
if (!(val instanceof FunctionValue)) return false;
if (!val.values.containsKey("prototype") || val.values.size() + val.properties.size() > 1) return false;
var proto = val.values.get("prototype");
if (!(proto instanceof ObjectValue)) return false;
var protoObj = (ObjectValue)proto;
if (protoObj.values.get("constructor") != val) return false;
if (protoObj.values.size() + protoObj.properties.size() != 1) return false;
return true;
}
private static void printValue(Context ctx, Object val, HashSet<Object> passed, int tab) {
if (tab == 0 && val instanceof String) {
System.out.print(val);
return;
}
if (passed.contains(val)) { if (passed.contains(val)) {
System.out.print("[circular]"); System.out.print("[circular]");
return; return;
@@ -616,10 +673,7 @@ public class Values {
var printed = true; var printed = true;
if (val instanceof FunctionValue) { if (val instanceof FunctionValue) {
System.out.print("function "); System.out.print(val.toString());
var name = Values.getMember(ctx, val, "name");
if (name != null) System.out.print(Values.toString(ctx, name));
System.out.print("(...)");
var loc = val instanceof CodeFunction ? ((CodeFunction)val).loc() : null; var loc = val instanceof CodeFunction ? ((CodeFunction)val).loc() : null;
if (loc != null) System.out.print(" @ " + loc); if (loc != null) System.out.print(" @ " + loc);
@@ -649,7 +703,7 @@ public class Values {
passed.add(val); passed.add(val);
var obj = (ObjectValue)val; var obj = (ObjectValue)val;
if (obj.values.size() + obj.properties.size() == 0) { if (obj.values.size() + obj.properties.size() == 0 || isEmptyFunc(obj)) {
if (!printed) System.out.println("{}"); if (!printed) System.out.println("{}");
} }
else { else {
@@ -667,26 +721,28 @@ public class Values {
printValue(ctx, el.getKey(), passed, tab + 1); printValue(ctx, el.getKey(), passed, tab + 1);
System.out.println(": [prop],"); System.out.println(": [prop],");
} }
for (int i = 0; i < tab; i++) System.out.print(" "); for (int i = 0; i < tab; i++) System.out.print(" ");
System.out.print("}"); System.out.print("}");
passed.remove(val);
} }
passed.remove(val);
} }
else if (val == null) System.out.print("undefined"); else if (val == null) System.out.print("undefined");
else if (val == Values.NULL) System.out.print("null"); else if (val == Values.NULL) System.out.print("null");
else if (val instanceof String) System.out.print("'" + val + "'"); else if (val instanceof String) System.out.print("'" + val + "'");
else System.out.print(Values.toString(ctx, val)); else System.out.print(Values.toString(ctx, val));
} }
public static void printValue(Context ctx, Object val) throws InterruptedException { public static void printValue(Context ctx, Object val) {
printValue(ctx, val, new HashSet<>(), 0); printValue(ctx, val, new HashSet<>(), 0);
} }
public static void printError(RuntimeException err, String prefix) throws InterruptedException { public static void printError(RuntimeException err, String prefix) {
prefix = prefix == null ? "Uncaught" : "Uncaught " + prefix; prefix = prefix == null ? "Uncaught" : "Uncaught " + prefix;
try { try {
if (err instanceof EngineException) { if (err instanceof EngineException) {
System.out.println(prefix + " " + ((EngineException)err).toString(((EngineException)err).ctx)); var ee = ((EngineException)err);
System.out.println(prefix + " " + ee.toString(new Context(ee.engine).pushEnv(ee.env)));
} }
else if (err instanceof SyntaxException) { else if (err instanceof SyntaxException) {
System.out.println("Syntax error:" + ((SyntaxException)err).msg); System.out.println("Syntax error:" + ((SyntaxException)err).msg);

View File

@@ -1,7 +1,9 @@
package me.topchetoeu.jscript.events; package me.topchetoeu.jscript.events;
import me.topchetoeu.jscript.exceptions.InterruptException;
public interface Awaitable<T> { public interface Awaitable<T> {
T await() throws FinishedException, InterruptedException; T await() throws FinishedException;
default Observable<T> toObservable() { default Observable<T> toObservable() {
return sub -> { return sub -> {
@@ -10,9 +12,7 @@ public interface Awaitable<T> {
sub.next(await()); sub.next(await());
sub.finish(); sub.finish();
} }
catch (InterruptedException | FinishedException e) { catch (InterruptException | FinishedException e) { sub.finish(); }
sub.finish();
}
catch (RuntimeException e) { catch (RuntimeException e) {
sub.error(e); sub.error(e);
} }

View File

@@ -19,7 +19,7 @@ public class DataNotifier<T> implements Awaitable<T> {
isErr = false; isErr = false;
notifier.next(); notifier.next();
} }
public T await() throws InterruptedException { public T await() {
notifier.await(); notifier.await();
try { try {

View File

@@ -1,5 +1,7 @@
package me.topchetoeu.jscript.events; package me.topchetoeu.jscript.events;
import me.topchetoeu.jscript.exceptions.InterruptException;
public class Notifier { public class Notifier {
private boolean ok = false; private boolean ok = false;
@@ -7,8 +9,11 @@ public class Notifier {
ok = true; ok = true;
notifyAll(); notifyAll();
} }
public synchronized void await() throws InterruptedException { public synchronized void await() {
while (!ok) wait(); try {
ok = false; while (!ok) wait();
ok = false;
}
catch (InterruptedException e) { throw new InterruptException(e); }
} }
} }

View File

@@ -5,6 +5,8 @@ import java.util.List;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Engine;
import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto; import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto;
@@ -12,7 +14,8 @@ import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto;
public class EngineException extends RuntimeException { public class EngineException extends RuntimeException {
public final Object value; public final Object value;
public EngineException cause; public EngineException cause;
public Context ctx = null; public Environment env = null;
public Engine engine = null;
public final List<String> stackTrace = new ArrayList<>(); public final List<String> stackTrace = new ArrayList<>();
public EngineException add(String name, Location location) { public EngineException add(String name, Location location) {
@@ -28,12 +31,13 @@ public class EngineException extends RuntimeException {
this.cause = cause; this.cause = cause;
return this; return this;
} }
public EngineException setContext(Context ctx) { public EngineException setCtx(Environment env, Engine engine) {
this.ctx = ctx; if (this.env == null) this.env = env;
if (this.engine == null) this.engine = engine;
return this; return this;
} }
public String toString(Context ctx) throws InterruptedException { public String toString(Context ctx) {
var ss = new StringBuilder(); var ss = new StringBuilder();
try { try {
ss.append(Values.toString(ctx, value)).append('\n'); ss.append(Values.toString(ctx, value)).append('\n');
@@ -41,10 +45,10 @@ public class EngineException extends RuntimeException {
catch (EngineException e) { catch (EngineException e) {
ss.append("[Error while stringifying]\n"); ss.append("[Error while stringifying]\n");
} }
// for (var line : stackTrace) { for (var line : stackTrace) {
// ss.append(" ").append(line).append('\n'); ss.append(" ").append(line).append('\n');
// } }
// if (cause != null) ss.append("Caused by ").append(cause.toString(ctx)).append('\n'); if (cause != null) ss.append("Caused by ").append(cause.toString(ctx)).append('\n');
ss.deleteCharAt(ss.length() - 1); ss.deleteCharAt(ss.length() - 1);
return ss.toString(); return ss.toString();
} }

View File

@@ -0,0 +1,8 @@
package me.topchetoeu.jscript.exceptions;
public class InterruptException extends RuntimeException {
public InterruptException() { }
public InterruptException(Throwable e) {
super(e);
}
}

View File

@@ -0,0 +1,7 @@
package me.topchetoeu.jscript.exceptions;
public class UncheckedException extends RuntimeException {
public UncheckedException(Throwable err) {
super(err);
}
}

View File

@@ -0,0 +1,9 @@
package me.topchetoeu.jscript.exceptions;
import java.io.IOException;
public class UncheckedIOException extends RuntimeException {
public UncheckedIOException(IOException e) {
super(e);
}
}

View File

@@ -0,0 +1,41 @@
package me.topchetoeu.jscript.filesystem;
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 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);
}
}

View File

@@ -1,7 +1,13 @@
package me.topchetoeu.jscript.filesystem; package me.topchetoeu.jscript.filesystem;
public enum EntryType { public enum EntryType {
NONE, NONE("none"),
FILE, FILE("file"),
FOLDER, FOLDER("folder");
public final String name;
private EntryType(String name) {
this.name = name;
}
} }

View File

@@ -1,27 +1,39 @@
package me.topchetoeu.jscript.filesystem; package me.topchetoeu.jscript.filesystem;
import java.io.IOException;
public interface File { public interface File {
int read() throws IOException, InterruptedException; int read(byte[] buff);
boolean write(byte val) throws IOException, InterruptedException; void write(byte[] buff);
long tell() throws IOException, InterruptedException; long getPtr();
void seek(long offset, int pos) throws IOException, InterruptedException; void setPtr(long offset, int pos);
void close() throws IOException, InterruptedException; void close();
Permissions perms(); Mode mode();
default String readToString() throws IOException, InterruptedException { default String readToString() {
seek(0, 2); setPtr(0, 2);
long len = tell(); long len = getPtr();
if (len < 0) return null; if (len < 0) return null;
seek(0, 0); setPtr(0, 0);
byte[] res = new byte[(int)len];
for (var i = 0; i < len; i++) { byte[] res = new byte[(int)len];
res[i] = (byte)read(); read(res);
}
return new String(res); return new String(res);
} }
default String readLine() {
var res = new Buffer(new byte[0]);
var buff = new byte[1];
while (true) {
if (read(buff) == 0) {
if (res.length() == 0) return null;
else break;
}
if (buff[0] == '\n') break;
res.write(res.length(), buff);
}
return new String(res.data());
}
} }

View File

@@ -0,0 +1,11 @@
package me.topchetoeu.jscript.filesystem;
public class FileStat {
public final Mode mode;
public final EntryType type;
public FileStat(Mode mode, EntryType type) {
this.mode = mode;
this.type = type;
}
}

View File

@@ -1,10 +1,7 @@
package me.topchetoeu.jscript.filesystem; package me.topchetoeu.jscript.filesystem;
import java.io.IOException;
public interface Filesystem { public interface Filesystem {
File open(String path) throws IOException, InterruptedException; File open(String path, Mode mode) throws FilesystemException;
boolean mkdir(String path) throws IOException, InterruptedException; void create(String path, EntryType type) throws FilesystemException;
EntryType type(String path) throws IOException, InterruptedException; FileStat stat(String path) throws FilesystemException;
boolean rm(String path) throws IOException, InterruptedException; }
}

View File

@@ -0,0 +1,55 @@
package me.topchetoeu.jscript.filesystem;
import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException;
public class FilesystemException extends RuntimeException {
public static enum FSCode {
DOESNT_EXIST(0x1),
NOT_FILE(0x2),
NOT_FOLDER(0x3),
NO_PERMISSIONS_R(0x4),
NO_PERMISSIONS_RW(0x5),
FOLDER_NOT_EMPTY(0x6),
ALREADY_EXISTS(0x7),
FOLDER_EXISTS(0x8);
public final int code;
private FSCode(int code) { this.code = code; }
}
public static final String[] MESSAGES = {
"How did we get here?",
"The file or folder '%s' doesn't exist or is inaccessible.",
"'%s' is not a file",
"'%s' is not a folder",
"No permissions to read '%s'",
"No permissions to write '%s'",
"Can't delete '%s', since it is a full folder.",
"'%s' already exists."
};
public final String message, filename;
public final FSCode code;
public FilesystemException(String message, String filename, FSCode code) {
super(code + ": " + String.format(message, filename));
this.message = message;
this.code = code;
this.filename = filename;
}
public FilesystemException(String filename, FSCode code) {
super(code + ": " + String.format(MESSAGES[code.code], filename));
this.message = MESSAGES[code.code];
this.code = code;
this.filename = filename;
}
public EngineException toEngineException() {
var res = EngineException.ofError("IOError", getMessage());
Values.setMember(null, res.value, "code", code);
Values.setMember(null, res.value, "filename", filename.toString());
return res;
}
}

View File

@@ -1,35 +0,0 @@
package me.topchetoeu.jscript.filesystem;
import java.io.IOException;
public class InaccessibleFile implements File {
public static final InaccessibleFile INSTANCE = new InaccessibleFile();
@Override
public int read() throws IOException, InterruptedException {
return -1;
}
@Override
public boolean write(byte val) throws IOException, InterruptedException {
return false;
}
@Override
public long tell() throws IOException, InterruptedException {
return 0;
}
@Override
public void seek(long offset, int pos) throws IOException, InterruptedException { }
@Override
public void close() throws IOException, InterruptedException { }
@Override
public Permissions perms() {
return Permissions.NONE;
}
private InaccessibleFile() { }
}

View File

@@ -1,53 +1,66 @@
package me.topchetoeu.jscript.filesystem; package me.topchetoeu.jscript.filesystem;
import java.io.IOException; import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode;
public class MemoryFile implements File { public class MemoryFile implements File {
private int ptr; private int ptr;
private Permissions mode; private Mode mode;
public final byte[] data; private Buffer data;
private String filename;
public Buffer data() { return data; }
@Override @Override
public int read() throws IOException, InterruptedException { public int read(byte[] buff) {
if (data == null || !mode.readable || ptr >= data.length) return -1; if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
return data[ptr++]; var res = data.read(ptr, buff);
ptr += res;
return res;
}
@Override
public void write(byte[] buff) {
if (data == null || !mode.writable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW);
data.write(ptr, buff);
ptr += buff.length;
} }
@Override @Override
public boolean write(byte val) throws IOException, InterruptedException { public long getPtr() {
if (data == null || !mode.writable || ptr >= data.length) return false; if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
data[ptr++] = val;
return true;
}
@Override
public long tell() throws IOException, InterruptedException {
return ptr; return ptr;
} }
@Override @Override
public void seek(long offset, int pos) throws IOException, InterruptedException { public void setPtr(long offset, int pos) {
if (data == null) return; if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
if (pos == 0) ptr = (int)offset; if (pos == 0) ptr = (int)offset;
else if (pos == 1) ptr += (int)offset; else if (pos == 1) ptr += (int)offset;
else if (pos == 2) ptr = data.length - (int)offset; else if (pos == 2) ptr = data.length() - (int)offset;
} }
@Override @Override
public void close() throws IOException, InterruptedException { public void close() {
mode = null; mode = Mode.NONE;
ptr = 0; ptr = 0;
} }
@Override @Override
public Permissions perms() { public Mode mode() {
if (data == null) return Permissions.NONE; if (data == null) return Mode.NONE;
return mode; return mode;
} }
public MemoryFile(byte[] buff, Permissions mode) { public MemoryFile(String filename, Buffer buff, Mode mode) {
this.filename = filename;
this.data = buff; this.data = buff;
this.mode = mode; this.mode = mode;
} }
public static MemoryFile fromFileList(String filename, java.io.File[] list) {
var res = new StringBuilder();
for (var el : list) res.append(el.getName()).append('\n');
return new MemoryFile(filename, new Buffer(res.toString().getBytes()), Mode.READ);
}
} }

View File

@@ -0,0 +1,89 @@
package me.topchetoeu.jscript.filesystem;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.HashSet;
import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode;
public class MemoryFilesystem implements Filesystem {
public final Mode mode;
private HashMap<Path, Buffer> files = new HashMap<>();
private HashSet<Path> folders = new HashSet<>();
private Path getPath(String name) {
return Path.of("/" + name.replace("\\", "/")).normalize();
}
@Override
public void create(String path, EntryType type) {
var _path = getPath(path);
switch (type) {
case FILE:
if (!folders.contains(_path.getParent())) throw new FilesystemException(path, FSCode.DOESNT_EXIST);
if (folders.contains(_path) || files.containsKey(_path)) throw new FilesystemException(path, FSCode.ALREADY_EXISTS);
if (folders.contains(_path)) throw new FilesystemException(path, FSCode.ALREADY_EXISTS);
files.put(_path, new Buffer(new byte[0]));
break;
case FOLDER:
if (!folders.contains(_path.getParent())) throw new FilesystemException(path, FSCode.DOESNT_EXIST);
if (folders.contains(_path) || files.containsKey(_path)) throw new FilesystemException(path, FSCode.ALREADY_EXISTS);
folders.add(_path);
break;
default:
case NONE:
if (!folders.remove(_path) && files.remove(_path) == null) throw new FilesystemException(path, FSCode.DOESNT_EXIST);
}
}
@Override
public File open(String path, Mode perms) {
var _path = getPath(path);
var pcount = _path.getNameCount();
if (files.containsKey(_path)) return new MemoryFile(path, files.get(_path), perms);
else if (folders.contains(_path)) {
var res = new StringBuilder();
for (var folder : folders) {
if (pcount + 1 != folder.getNameCount()) continue;
if (!folder.startsWith(_path)) continue;
res.append(folder.toFile().getName()).append('\n');
}
for (var file : files.keySet()) {
if (pcount + 1 != file.getNameCount()) continue;
if (!file.startsWith(_path)) continue;
res.append(file.toFile().getName()).append('\n');
}
return new MemoryFile(path, new Buffer(res.toString().getBytes()), perms.intersect(Mode.READ));
}
else throw new FilesystemException(path, FSCode.DOESNT_EXIST);
}
@Override
public FileStat stat(String path) {
var _path = getPath(path);
if (files.containsKey(_path)) return new FileStat(mode, EntryType.FILE);
else if (folders.contains(_path)) return new FileStat(mode, EntryType.FOLDER);
else throw new FilesystemException(path, FSCode.DOESNT_EXIST);
}
public MemoryFilesystem put(String path, byte[] data) {
var _path = getPath(path);
var _curr = "/";
for (var seg : _path) {
create(_curr, EntryType.FOLDER);
_curr += seg + "/";
}
files.put(_path, new Buffer(data));
return this;
}
public MemoryFilesystem(Mode mode) {
this.mode = mode;
folders.add(Path.of("/"));
}
}

View File

@@ -0,0 +1,31 @@
package me.topchetoeu.jscript.filesystem;
public enum Mode {
NONE("", false, false),
READ("r", true, false),
READ_WRITE("rw", true, true);
public final String name;
public final boolean readable;
public final boolean writable;
public Mode intersect(Mode other) {
if (this == NONE || other == NONE) return NONE;
if (this == READ_WRITE && other == READ_WRITE) return READ_WRITE;
return READ;
}
private Mode(String mode, boolean r, boolean w) {
this.name = mode;
this.readable = r;
this.writable = w;
}
public static Mode parse(String mode) {
switch (mode) {
case "r": return READ;
case "rw": return READ_WRITE;
default: return NONE;
}
}
}

View File

@@ -1,17 +0,0 @@
package me.topchetoeu.jscript.filesystem;
public enum Permissions {
NONE("", false, false),
READ("r", true, false),
READ_WRITE("rw", true, true);
public final String readMode;
public final boolean readable;
public final boolean writable;
private Permissions(String mode, boolean r, boolean w) {
this.readMode = mode;
this.readable = r;
this.writable = w;
}
}

View File

@@ -1,50 +1,62 @@
package me.topchetoeu.jscript.filesystem; package me.topchetoeu.jscript.filesystem;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.RandomAccessFile; import java.io.RandomAccessFile;
import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode;
public class PhysicalFile implements File { public class PhysicalFile implements File {
private String filename;
private RandomAccessFile file; private RandomAccessFile file;
private Permissions perms; private Mode perms;
@Override @Override
public int read() throws IOException, InterruptedException { public int read(byte[] buff) {
if (file == null || !perms.readable) return -1; if (file == null || !perms.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
else return file.read(); else try { return file.read(buff); }
catch (IOException e) { throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); }
}
@Override
public void write(byte[] buff) {
if (file == null || !perms.writable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW);
else try { file.write(buff); }
catch (IOException e) { throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_RW); }
} }
@Override @Override
public boolean write(byte val) throws IOException, InterruptedException { public long getPtr() {
if (file == null || !perms.writable) return false; if (file == null || !perms.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
file.write(val); else try { return file.getFilePointer(); }
return true; catch (IOException e) { throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); }
}
@Override
public void setPtr(long offset, int pos) {
if (file == null || !perms.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
try {
if (pos == 1) pos += file.getFilePointer();
else if (pos == 2) pos += file.length();
file.seek(pos);
}
catch (IOException e) { throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R); }
} }
@Override @Override
public long tell() throws IOException, InterruptedException { public void close() {
if (file == null) return 0;
return file.getFilePointer();
}
@Override
public void seek(long offset, int pos) throws IOException, InterruptedException {
if (file == null) return; if (file == null) return;
if (pos == 0) file.seek(pos); try { file.close(); }
else if (pos == 1) file.seek(file.getFilePointer() + pos); catch (IOException e) {} // SHUT
else if (pos == 2) file.seek(file.length() + pos); file = null;
perms = Mode.NONE;
} }
@Override @Override
public void close() throws IOException, InterruptedException { public Mode mode() { return perms; }
if (file == null) return;
file.close();
}
@Override public PhysicalFile(String path, Mode mode) throws FileNotFoundException {
public Permissions perms() { return perms; } if (mode == Mode.NONE) file = null;
else try { file = new RandomAccessFile(path, mode.name); }
public PhysicalFile(String path, Permissions mode) throws IOException { catch (FileNotFoundException e) { throw new FilesystemException(filename, FSCode.DOESNT_EXIST); }
if (mode == Permissions.NONE) file = null;
else file = new RandomAccessFile(path, mode.readMode);
perms = mode; perms = mode;
} }

View File

@@ -1,74 +1,74 @@
package me.topchetoeu.jscript.filesystem; package me.topchetoeu.jscript.filesystem;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Path; import java.nio.file.Path;
import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode;
public class PhysicalFilesystem implements Filesystem { public class PhysicalFilesystem implements Filesystem {
public final Path root; public final Path root;
private Permissions getPerms(Path path) {
var file = path.toFile();
if (!path.startsWith(root)) return Permissions.NONE;
if (file.canRead() && file.canWrite()) return Permissions.READ_WRITE;
if (file.canRead()) return Permissions.READ;
return Permissions.NONE;
}
private Path getPath(String name) { private Path getPath(String name) {
return root.resolve(name); return root.resolve(name.replace("\\", "/")).normalize();
}
private void checkMode(Path path, Mode mode) {
if (!path.startsWith(root)) throw new FilesystemException(path.toString(), FSCode.NO_PERMISSIONS_R);
if (mode.readable && !path.toFile().canRead()) throw new FilesystemException(path.toString(), FSCode.NO_PERMISSIONS_R);
if (mode.writable && !path.toFile().canWrite()) throw new FilesystemException(path.toString(), FSCode.NO_PERMISSIONS_RW);
} }
@Override @Override
public File open(String path) throws IOException, InterruptedException { public File open(String path, Mode perms) {
var _path = root.resolve(path); var _path = getPath(path);
var perms = getPerms(_path);
if (perms == Permissions.NONE) return InaccessibleFile.INSTANCE;
var f = _path.toFile(); var f = _path.toFile();
if (f.isDirectory()) { checkMode(_path, perms);
var res = new StringBuilder();
for (var child : f.listFiles()) res.append(child.toString()).append('\n');
return new MemoryFile(res.toString().getBytes(), Permissions.READ); if (f.isDirectory()) return MemoryFile.fromFileList(path, f.listFiles());
else try { return new PhysicalFile(path, perms); }
catch (FileNotFoundException e) { throw new FilesystemException(_path.toString(), FSCode.DOESNT_EXIST); }
}
@Override
public void create(String path, EntryType type) {
var _path = getPath(path);
var f = _path.toFile();
switch (type) {
case FILE:
try {
if (!f.createNewFile()) throw new FilesystemException(_path.toString(), FSCode.ALREADY_EXISTS);
else break;
}
catch (IOException e) { throw new FilesystemException(_path.toString(), FSCode.NO_PERMISSIONS_RW); }
case FOLDER:
if (!f.mkdir()) throw new FilesystemException(_path.toString(), FSCode.ALREADY_EXISTS);
else break;
case NONE:
default:
if (!f.delete()) throw new FilesystemException(_path.toString(), FSCode.DOESNT_EXIST);
else break;
} }
else return new PhysicalFile(path, perms);
} }
@Override @Override
public boolean mkdir(String path) throws IOException, InterruptedException { public FileStat stat(String path) {
var _path = getPath(path); var _path = getPath(path);
var perms = getPerms(_path);
var f = _path.toFile(); var f = _path.toFile();
if (!perms.writable) return false; if (!f.exists()) throw new FilesystemException(_path.toString(), FSCode.DOESNT_EXIST);
else return f.mkdir(); checkMode(_path, Mode.READ);
}
@Override return new FileStat(
public EntryType type(String path) throws IOException, InterruptedException { f.canWrite() ? Mode.READ_WRITE : Mode.READ,
var _path = getPath(path); f.isFile() ? EntryType.FILE : EntryType.FOLDER
var perms = getPerms(_path); );
var f = _path.toFile();
if (perms == Permissions.NONE) return EntryType.NONE;
else if (f.isFile()) return EntryType.FILE;
else return EntryType.FOLDER;
}
@Override
public boolean rm(String path) throws IOException, InterruptedException {
var _path = getPath(path);
var perms = getPerms(_path);
var f = _path.toFile();
if (!perms.writable) return false;
else return f.delete();
} }
public PhysicalFilesystem(Path root) { public PhysicalFilesystem(Path root) {
this.root = root; this.root = root.toAbsolutePath().normalize();
} }
} }

View File

@@ -0,0 +1,57 @@
package me.topchetoeu.jscript.filesystem;
import java.util.HashMap;
import java.util.Map;
import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode;
import me.topchetoeu.jscript.permissions.PermissionsProvider;
public class RootFilesystem implements Filesystem {
public final Map<String, Filesystem> protocols = new HashMap<>();
public final PermissionsProvider perms;
private boolean canRead(String _path) {
return perms.hasPermission("jscript.file.read:" + _path, '/');
}
private boolean canWrite(String _path) {
return perms.hasPermission("jscript.file.write:" + _path, '/');
}
private void modeAllowed(String _path, Mode mode) throws FilesystemException {
if (mode.readable && perms != null && !canRead(_path)) throw new FilesystemException(_path, FSCode.NO_PERMISSIONS_R);
if (mode.writable && perms != null && !canWrite(_path)) throw new FilesystemException(_path, FSCode.NO_PERMISSIONS_RW);
}
@Override public File open(String path, Mode perms) throws FilesystemException {
var filename = Filename.parse(path);
var protocol = protocols.get(filename.protocol);
if (protocol == null) throw new FilesystemException(filename.toString(), FSCode.DOESNT_EXIST);
modeAllowed(filename.toString(), perms);
try { return protocol.open(filename.path, perms); }
catch (FilesystemException e) { throw new FilesystemException(filename.toString(), e.code); }
}
@Override public void create(String path, EntryType type) throws FilesystemException {
var filename = Filename.parse(path);
var protocol = protocols.get(filename.protocol);
if (protocol == null) throw new FilesystemException(filename.toString(), FSCode.DOESNT_EXIST);
modeAllowed(filename.toString(), Mode.READ_WRITE);
try { protocol.create(filename.path, type); }
catch (FilesystemException e) { throw new FilesystemException(filename.toString(), e.code); }
}
@Override public FileStat stat(String path) throws FilesystemException {
var filename = Filename.parse(path);
var protocol = protocols.get(filename.protocol);
if (protocol == null) throw new FilesystemException(filename.toString(), FSCode.DOESNT_EXIST);
modeAllowed(filename.toString(), Mode.READ);
try { return protocol.stat(path); }
catch (FilesystemException e) { throw new FilesystemException(filename.toString(), e.code); }
}
public RootFilesystem(PermissionsProvider perms) {
this.perms = perms;
}
}

View File

@@ -9,10 +9,12 @@ import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.NativeFunction; import me.topchetoeu.jscript.engine.values.NativeFunction;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.UncheckedException;
public class NativeWrapperProvider implements WrappersProvider { public class NativeWrapperProvider implements WrappersProvider {
private final HashMap<Class<?>, FunctionValue> constructors = new HashMap<>(); private final HashMap<Class<?>, FunctionValue> constructors = new HashMap<>();
private final HashMap<Class<?>, ObjectValue> prototypes = new HashMap<>(); private final HashMap<Class<?>, ObjectValue> prototypes = new HashMap<>();
private final HashMap<Class<?>, ObjectValue> namespaces = new HashMap<>();
private final Environment env; private final Environment env;
private static void applyMethods(Environment env, boolean member, ObjectValue target, Class<?> clazz) { private static void applyMethods(Environment env, boolean member, ObjectValue target, Class<?> clazz) {
@@ -31,7 +33,7 @@ public class NativeWrapperProvider implements WrappersProvider {
var val = target.values.get(name); var val = target.values.get(name);
if (!(val instanceof OverloadFunction)) target.defineProperty(null, name, val = new OverloadFunction(name.toString())); if (!(val instanceof OverloadFunction)) target.defineProperty(null, name, val = new OverloadFunction(name.toString()), true, true, false);
((OverloadFunction)val).add(Overload.fromMethod(method, nat.thisArg())); ((OverloadFunction)val).add(Overload.fromMethod(method, nat.thisArg()));
} }
@@ -51,7 +53,7 @@ public class NativeWrapperProvider implements WrappersProvider {
else getter = new OverloadFunction("get " + name); else getter = new OverloadFunction("get " + name);
getter.add(Overload.fromMethod(method, get.thisArg())); getter.add(Overload.fromMethod(method, get.thisArg()));
target.defineProperty(null, name, getter, setter, true, true); target.defineProperty(null, name, getter, setter, true, false);
} }
if (set != null) { if (set != null) {
if (set.thisArg() && !member || !set.thisArg() && !memberMatch) continue; if (set.thisArg() && !member || !set.thisArg() && !memberMatch) continue;
@@ -68,7 +70,7 @@ public class NativeWrapperProvider implements WrappersProvider {
else setter = new OverloadFunction("set " + name); else setter = new OverloadFunction("set " + name);
setter.add(Overload.fromMethod(method, set.thisArg())); setter.add(Overload.fromMethod(method, set.thisArg()));
target.defineProperty(null, name, getter, setter, true, true); target.defineProperty(null, name, getter, setter, true, false);
} }
} }
} }
@@ -106,6 +108,12 @@ public class NativeWrapperProvider implements WrappersProvider {
} }
} }
public static String getName(Class<?> clazz) {
var classNat = clazz.getAnnotation(Native.class);
if (classNat != null && !classNat.value().trim().equals("")) return classNat.value().trim();
else return clazz.getSimpleName();
}
/** /**
* Generates a prototype for the given class. * Generates a prototype for the given class.
* The returned object will have appropriate wrappers for all instance members. * The returned object will have appropriate wrappers for all instance members.
@@ -115,11 +123,13 @@ public class NativeWrapperProvider implements WrappersProvider {
public static ObjectValue makeProto(Environment ctx, Class<?> clazz) { public static ObjectValue makeProto(Environment ctx, Class<?> clazz) {
var res = new ObjectValue(); var res = new ObjectValue();
res.defineProperty(null, ctx.symbol("Symbol.typeName"), getName(clazz));
for (var overload : clazz.getDeclaredMethods()) { for (var overload : clazz.getDeclaredMethods()) {
var init = overload.getAnnotation(NativeInit.class); var init = overload.getAnnotation(NativeInit.class);
if (init == null || init.value() != InitType.PROTOTYPE) continue; if (init == null || init.value() != InitType.PROTOTYPE) continue;
try { overload.invoke(null, ctx, res); } try { overload.invoke(null, ctx, res); }
catch (ReflectiveOperationException e) { e.printStackTrace(); } catch (Throwable e) { throw new UncheckedException(e); }
} }
applyMethods(ctx, true, res, clazz); applyMethods(ctx, true, res, clazz);
@@ -135,7 +145,7 @@ public class NativeWrapperProvider implements WrappersProvider {
* @param clazz The class for which a constructor should be generated * @param clazz The class for which a constructor should be generated
*/ */
public static FunctionValue makeConstructor(Environment ctx, Class<?> clazz) { public static FunctionValue makeConstructor(Environment ctx, Class<?> clazz) {
FunctionValue func = new OverloadFunction(clazz.getName()); FunctionValue func = new OverloadFunction(getName(clazz));
for (var overload : clazz.getDeclaredConstructors()) { for (var overload : clazz.getDeclaredConstructors()) {
var nat = overload.getAnnotation(Native.class); var nat = overload.getAnnotation(Native.class);
@@ -151,7 +161,7 @@ public class NativeWrapperProvider implements WrappersProvider {
var init = overload.getAnnotation(NativeInit.class); var init = overload.getAnnotation(NativeInit.class);
if (init == null || init.value() != InitType.CONSTRUCTOR) continue; if (init == null || init.value() != InitType.CONSTRUCTOR) continue;
try { overload.invoke(null, ctx, func); } try { overload.invoke(null, ctx, func); }
catch (ReflectiveOperationException e) { e.printStackTrace(); } catch (Throwable e) { throw new UncheckedException(e); }
} }
if (((OverloadFunction)func).overloads.size() == 0) { if (((OverloadFunction)func).overloads.size() == 0) {
@@ -179,7 +189,7 @@ public class NativeWrapperProvider implements WrappersProvider {
var init = overload.getAnnotation(NativeInit.class); var init = overload.getAnnotation(NativeInit.class);
if (init == null || init.value() != InitType.NAMESPACE) continue; if (init == null || init.value() != InitType.NAMESPACE) continue;
try { overload.invoke(null, ctx, res); } try { overload.invoke(null, ctx, res); }
catch (ReflectiveOperationException e) { e.printStackTrace(); } catch (Throwable e) { throw new UncheckedException(e); }
} }
applyMethods(ctx, false, res, clazz); applyMethods(ctx, false, res, clazz);
@@ -209,8 +219,8 @@ public class NativeWrapperProvider implements WrappersProvider {
if (constr == null) constr = makeConstructor(env, clazz); if (constr == null) constr = makeConstructor(env, clazz);
if (proto == null) proto = makeProto(env, clazz); if (proto == null) proto = makeProto(env, clazz);
proto.values.put("constructor", constr); proto.defineProperty(null, "constructor", constr, true, false, false);
constr.values.put("prototype", proto); constr.defineProperty(null, "prototype", proto, true, false, false);
prototypes.put(clazz, proto); prototypes.put(clazz, proto);
constructors.put(clazz, constr); constructors.put(clazz, constr);
@@ -229,11 +239,20 @@ public class NativeWrapperProvider implements WrappersProvider {
initType(clazz, constructors.get(clazz), prototypes.get(clazz)); initType(clazz, constructors.get(clazz), prototypes.get(clazz));
return prototypes.get(clazz); return prototypes.get(clazz);
} }
public ObjectValue getNamespace(Class<?> clazz) {
if (!namespaces.containsKey(clazz)) namespaces.put(clazz, makeNamespace(env, clazz));
return namespaces.get(clazz);
}
public FunctionValue getConstr(Class<?> clazz) { public FunctionValue getConstr(Class<?> clazz) {
initType(clazz, constructors.get(clazz), prototypes.get(clazz)); initType(clazz, constructors.get(clazz), prototypes.get(clazz));
return constructors.get(clazz); return constructors.get(clazz);
} }
@Override
public WrappersProvider fork(Environment env) {
return new NativeWrapperProvider(env);
}
public void setProto(Class<?> clazz, ObjectValue value) { public void setProto(Class<?> clazz, ObjectValue value) {
prototypes.put(clazz, value); prototypes.put(clazz, value);
} }
@@ -241,7 +260,27 @@ public class NativeWrapperProvider implements WrappersProvider {
constructors.put(clazz, value); constructors.put(clazz, value);
} }
private void initError() {
var proto = new ObjectValue();
proto.defineProperty(null, "message", new NativeFunction("message", (ctx, thisArg, args) -> {
if (thisArg instanceof Throwable) return ((Throwable)thisArg).getMessage();
else return null;
}));
proto.defineProperty(null, "name", new NativeFunction("name", (ctx, thisArg, args) -> getName(thisArg.getClass())));
var constr = makeConstructor(null, Throwable.class);
proto.defineProperty(null, "constructor", constr, true, false, false);
constr.defineProperty(null, "prototype", proto, true, false, false);
proto.setPrototype(null, getProto(Object.class));
constr.setPrototype(null, getConstr(Object.class));
setProto(Throwable.class, proto);
setConstr(Throwable.class, constr);
}
public NativeWrapperProvider(Environment env) { public NativeWrapperProvider(Environment env) {
this.env = env; this.env = env;
initError();
} }
} }

View File

@@ -9,10 +9,7 @@ import me.topchetoeu.jscript.engine.Context;
public class Overload { public class Overload {
public static interface OverloadRunner { public static interface OverloadRunner {
Object run(Context ctx, Object thisArg, Object[] args) throws Object run(Context ctx, Object thisArg, Object[] args) throws ReflectiveOperationException, IllegalArgumentException;
InterruptedException,
ReflectiveOperationException,
IllegalArgumentException;
} }
public final OverloadRunner runner; public final OverloadRunner runner;
@@ -47,9 +44,11 @@ public class Overload {
public static Overload setterFromField(Field field) { public static Overload setterFromField(Field field) {
if (Modifier.isFinal(field.getModifiers())) return null; if (Modifier.isFinal(field.getModifiers())) return null;
return new Overload( return new Overload(
(ctx, th, args) -> { field.set(th, args[0]); return null; }, false, false, (ctx, th, args) -> {
field.set(th, args[0]); return null;
}, false, false,
Modifier.isStatic(field.getModifiers()) ? null : field.getDeclaringClass(), Modifier.isStatic(field.getModifiers()) ? null : field.getDeclaringClass(),
new Class[0] new Class[] { field.getType() }
); );
} }

View File

@@ -8,14 +8,16 @@ import java.util.List;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.NativeWrapper;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.ConvertException; import me.topchetoeu.jscript.exceptions.ConvertException;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.InterruptException;
public class OverloadFunction extends FunctionValue { public class OverloadFunction extends FunctionValue {
public final List<Overload> overloads = new ArrayList<>(); public final List<Overload> overloads = new ArrayList<>();
public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException { public Object call(Context ctx, Object thisArg, Object ...args) {
loop: for (var overload : overloads) { loop: for (var overload : overloads) {
Object[] newArgs = new Object[overload.params.length]; Object[] newArgs = new Object[overload.params.length];
@@ -76,29 +78,33 @@ public class OverloadFunction extends FunctionValue {
try { try {
return Values.normalize(ctx, overload.runner.run(ctx, _this, newArgs)); return Values.normalize(ctx, overload.runner.run(ctx, _this, newArgs));
} }
catch (InstantiationException e) { catch (InstantiationException e) { throw EngineException.ofError("The class may not be instantiated."); }
throw EngineException.ofError("The class may not be instantiated."); catch (IllegalAccessException | IllegalArgumentException e) { continue; }
}
catch (IllegalAccessException | IllegalArgumentException e) {
continue;
}
catch (InvocationTargetException e) { catch (InvocationTargetException e) {
var loc = new Location(0, 0, "<internal>"); var loc = Location.INTERNAL;
if (e.getTargetException() instanceof EngineException) { if (e.getTargetException() instanceof EngineException) {
throw ((EngineException)e.getTargetException()).add(name, loc); throw ((EngineException)e.getTargetException()).add(name, loc);
} }
else if (e.getTargetException() instanceof NullPointerException) { else if (e.getTargetException() instanceof NullPointerException) {
e.printStackTrace();
throw EngineException.ofType("Unexpected value of 'undefined'.").add(name, loc); throw EngineException.ofType("Unexpected value of 'undefined'.").add(name, loc);
} }
else if (e.getTargetException() instanceof InterruptException || e.getTargetException() instanceof InterruptedException) {
throw new InterruptException();
}
else { else {
throw EngineException.ofError(e.getTargetException().getMessage()).add(name, loc); var target = e.getTargetException();
var targetClass = target.getClass();
var err = new NativeWrapper(e.getTargetException());
err.defineProperty(ctx, "message", target.getMessage());
err.defineProperty(ctx, "name", NativeWrapperProvider.getName(targetClass));
throw new EngineException(err).add(name, loc);
} }
} }
catch (ReflectiveOperationException e) { catch (ReflectiveOperationException e) {
throw EngineException.ofError(e.getMessage()).add(name, new Location(0, 0, "<internal>")); throw EngineException.ofError(e.getMessage()).add(name, Location.INTERNAL);
}
catch (Exception e) {
throw e;
} }
} }

View File

@@ -1,86 +1,111 @@
// TODO: load this in java (function (_arguments) {
var ts = require('./ts'); var ts = _arguments[0];
log("Loaded typescript!"); var src = '', version = 0;
var lib = _arguments[2].concat([
'declare const exit: never; declare const go: any;',
'declare function getTsDeclarations(): string[];'
]).join('');
var libSnapshot = ts.ScriptSnapshot.fromString(lib);
var environments = {};
var declSnapshots = [];
var src = '', lib = libs.concat([ 'declare const exit: never;' ]).join(''), decls = '', version = 0; var settings = {
var libSnapshot = ts.ScriptSnapshot.fromString(lib); 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,
};
var settings = { var reg = ts.createDocumentRegistry();
outDir: "/out", var service = ts.createLanguageService({
declarationDir: "/out", getCurrentDirectory: function() { return "/"; },
target: ts.ScriptTarget.ES5, getDefaultLibFileName: function() { return "/lib.d.ts"; },
lib: [ ], getScriptFileNames: function() {
module: ts.ModuleKind.None, var res = [ "/src.ts", "/lib.d.ts" ];
declaration: true, for (var i = 0; i < declSnapshots.length; i++) res.push("/glob." + (i + 1) + ".d.ts");
stripInternal: true, return res;
downlevelIteration: true, },
forceConsistentCasingInFileNames: true, getCompilationSettings: function () { return settings; },
experimentalDecorators: true, fileExists: function(filename) { return filename === "/lib.d.ts" || filename === "/src.ts" || filename === "/glob.d.ts"; },
strict: true,
};
var reg = ts.createDocumentRegistry(); getScriptSnapshot: function(filename) {
var service = ts.createLanguageService({ if (filename === "/lib.d.ts") return libSnapshot;
getCurrentDirectory: function() { return "/"; }, if (filename === "/src.ts") return ts.ScriptSnapshot.fromString(src);
getDefaultLibFileName: function() { return "/lib_.d.ts"; },
getScriptFileNames: function() { return [ "/src.ts", "/lib.d.ts", "/glob.d.ts" ]; },
getCompilationSettings: function () { return settings; },
fileExists: function(filename) { return filename === "/lib.d.ts" || filename === "/src.ts" || filename === "/glob.d.ts"; },
getScriptSnapshot: function(filename) { var index = /\/glob\.(\d+)\.d\.ts/g.exec(filename);
if (filename === "/lib.d.ts") return libSnapshot; if (index && index[1] && (index = Number(index[1])) && index > 0 && index <= declSnapshots.length) {
if (filename === "/src.ts") return ts.ScriptSnapshot.fromString(src); return declSnapshots[index - 1];
if (filename === "/glob.d.ts") return ts.ScriptSnapshot.fromString(decls);
throw new Error("File '" + filename + "' doesn't exist.");
},
getScriptVersion: function (filename) {
if (filename === "/lib.d.ts") return 0;
else return version;
},
}, reg);
service.getEmitOutput('/lib.d.ts');
log('Loaded libraries!');
function compile(filename, code) {
src = code, version++;
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 "Error: " + message;
});
if (diagnostics.length > 0) { throw new Error("File '" + filename + "' doesn't exist.");
throw new SyntaxError(diagnostics.join('\n')); },
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!");
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 result = emit.outputFiles[0].text;
var declaration = emit.outputFiles[1].text;
return {
source: result,
runner: function(func) {
return function() {
var val = func.apply(this, arguments);
if (declaration !== '') {
declSnapshots.push(ts.ScriptSnapshot.fromString(declaration));
}
return val;
}
}
};
} }
return { function apply(env) {
result: emit.outputFiles[0].text, env.compile = compile;
declaration: emit.outputFiles[1].text env.global.getTsDeclarations = function() {
}; return environments[env.id];
}
init(function (filename, code) {
var res = compile(filename, code);
return [
res.result,
function(func, th, args) {
var val = func.apply(th, args);
decls += res.declaration;
return val;
} }
]; }
});
apply(_arguments[1]);
})(arguments);

View File

@@ -482,6 +482,35 @@ interface PromiseConstructor {
allSettled<T extends any[]>(...promises: T): Promise<[...{ [P in keyof T]: PromiseResult<Awaited<T[P]>>}]>; allSettled<T extends any[]>(...promises: T): Promise<[...{ [P in keyof T]: PromiseResult<Awaited<T[P]>>}]>;
} }
interface FileStat {
type: 'file' | 'folder';
mode: 'r' | 'rw';
}
interface File {
readonly pointer: Promise<number>;
readonly length: Promise<number>;
readonly mode: Promise<'' | 'r' | 'rw'>;
read(n: number): Promise<number[]>;
write(buff: number[]): Promise<void>;
close(): Promise<void>;
setPointer(val: number): Promise<void>;
}
interface Filesystem {
open(path: string, mode: 'r' | 'rw'): Promise<File>;
ls(path: string): AsyncIterableIterator<string>;
mkdir(path: string): Promise<void>;
mkfile(path: string): Promise<void>;
rm(path: string, recursive?: boolean): Promise<void>;
stat(path: string): Promise<FileStat>;
exists(path: string): Promise<boolean>;
}
interface Encoding {
encode(val: string): number[];
decode(val: number[]): string;
}
declare var String: StringConstructor; declare var String: StringConstructor;
//@ts-ignore //@ts-ignore
declare const arguments: IArguments; declare const arguments: IArguments;
@@ -508,6 +537,8 @@ declare var Object: ObjectConstructor;
declare var Symbol: SymbolConstructor; declare var Symbol: SymbolConstructor;
declare var Promise: PromiseConstructor; declare var Promise: PromiseConstructor;
declare var Math: MathObject; declare var Math: MathObject;
declare var Encoding: Encoding;
declare var Filesystem: Filesystem;
declare var Error: ErrorConstructor; declare var Error: ErrorConstructor;
declare var RangeError: RangeErrorConstructor; declare var RangeError: RangeErrorConstructor;

File diff suppressed because one or more lines are too long

View File

@@ -1,8 +1,15 @@
package me.topchetoeu.jscript.json; package me.topchetoeu.jscript.json;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.SyntaxException; import me.topchetoeu.jscript.exceptions.SyntaxException;
import me.topchetoeu.jscript.parsing.Operator; import me.topchetoeu.jscript.parsing.Operator;
import me.topchetoeu.jscript.parsing.ParseRes; import me.topchetoeu.jscript.parsing.ParseRes;
@@ -10,20 +17,80 @@ import me.topchetoeu.jscript.parsing.Parsing;
import me.topchetoeu.jscript.parsing.Token; import me.topchetoeu.jscript.parsing.Token;
public class JSON { public class JSON {
public static Object toJs(JSONElement val) {
if (val.isBoolean()) return val.bool();
if (val.isString()) return val.string();
if (val.isNumber()) return val.number();
if (val.isList()) return ArrayValue.of(null, val.list().stream().map(JSON::toJs).collect(Collectors.toList()));
if (val.isMap()) {
var res = new ObjectValue();
for (var el : val.map().entrySet()) {
res.defineProperty(null, el.getKey(), toJs(el.getValue()));
}
return res;
}
if (val.isNull()) return Values.NULL;
return null;
}
private static JSONElement fromJs(Context ctx, Object val, HashSet<Object> prev) {
if (val instanceof Boolean) return JSONElement.bool((boolean)val);
if (val instanceof Number) return JSONElement.number(((Number)val).doubleValue());
if (val instanceof String) return JSONElement.string((String)val);
if (val == Values.NULL) return JSONElement.NULL;
if (val instanceof ObjectValue) {
if (prev.contains(val)) throw new EngineException("Circular dependency in JSON.");
prev.add(val);
var res = new JSONMap();
for (var el : ((ObjectValue)val).keys(false)) {
var jsonEl = fromJs(ctx, ((ObjectValue)val).getMember(ctx, el), prev);
if (jsonEl == null) continue;
if (el instanceof String || el instanceof Number) res.put(el.toString(), jsonEl);
}
prev.remove(val);
return JSONElement.of(res);
}
if (val instanceof ArrayValue) {
if (prev.contains(val)) throw new EngineException("Circular dependency in JSON.");
prev.add(val);
var res = new JSONList();
for (var el : ((ArrayValue)val).toArray()) {
var jsonEl = fromJs(ctx, el, prev);
if (jsonEl == null) jsonEl = JSONElement.NULL;
res.add(jsonEl);
}
prev.remove(val);
return JSONElement.of(res);
}
if (val == null) return null;
return null;
}
public static JSONElement fromJs(Context ctx, Object val) {
return fromJs(ctx, val, new HashSet<>());
}
public static ParseRes<String> parseIdentifier(List<Token> tokens, int i) { public static ParseRes<String> parseIdentifier(List<Token> tokens, int i) {
return Parsing.parseIdentifier(tokens, i); return Parsing.parseIdentifier(tokens, i);
} }
public static ParseRes<String> parseString(String filename, List<Token> tokens, int i) { public static ParseRes<String> parseString(Filename filename, List<Token> tokens, int i) {
var res = Parsing.parseString(filename, tokens, i); var res = Parsing.parseString(filename, tokens, i);
if (res.isSuccess()) return ParseRes.res((String)res.result.value, res.n); if (res.isSuccess()) return ParseRes.res((String)res.result.value, res.n);
else return res.transform(); else return res.transform();
} }
public static ParseRes<Double> parseNumber(String filename, List<Token> tokens, int i) { public static ParseRes<Double> parseNumber(Filename filename, List<Token> tokens, int i) {
var minus = Parsing.isOperator(tokens, i, Operator.SUBTRACT);
if (minus) i++;
var res = Parsing.parseNumber(filename, tokens, i); var res = Parsing.parseNumber(filename, tokens, i);
if (res.isSuccess()) return ParseRes.res((Double)res.result.value, res.n); if (res.isSuccess()) return ParseRes.res((minus ? -1 : 1) * (Double)res.result.value, res.n + (minus ? 1 : 0));
else return res.transform(); else return res.transform();
} }
public static ParseRes<Boolean> parseBool(String filename, List<Token> tokens, int i) { public static ParseRes<Boolean> parseBool(Filename filename, List<Token> tokens, int i) {
var id = parseIdentifier(tokens, i); var id = parseIdentifier(tokens, i);
if (!id.isSuccess()) return ParseRes.failed(); if (!id.isSuccess()) return ParseRes.failed();
@@ -32,7 +99,7 @@ public class JSON {
else return ParseRes.failed(); else return ParseRes.failed();
} }
public static ParseRes<?> parseValue(String filename, List<Token> tokens, int i) { public static ParseRes<?> parseValue(Filename filename, List<Token> tokens, int i) {
return ParseRes.any( return ParseRes.any(
parseString(filename, tokens, i), parseString(filename, tokens, i),
parseNumber(filename, tokens, i), parseNumber(filename, tokens, i),
@@ -42,7 +109,7 @@ public class JSON {
); );
} }
public static ParseRes<JSONMap> parseMap(String filename, List<Token> tokens, int i) { public static ParseRes<JSONMap> parseMap(Filename filename, List<Token> tokens, int i) {
int n = 0; int n = 0;
if (!Parsing.isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed(); if (!Parsing.isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed();
@@ -82,7 +149,7 @@ public class JSON {
return ParseRes.res(values, n); return ParseRes.res(values, n);
} }
public static ParseRes<JSONList> parseList(String filename, List<Token> tokens, int i) { public static ParseRes<JSONList> parseList(Filename filename, List<Token> tokens, int i) {
int n = 0; int n = 0;
if (!Parsing.isOperator(tokens, i + n++, Operator.BRACKET_OPEN)) return ParseRes.failed(); if (!Parsing.isOperator(tokens, i + n++, Operator.BRACKET_OPEN)) return ParseRes.failed();
@@ -109,7 +176,8 @@ public class JSON {
return ParseRes.res(values, n); return ParseRes.res(values, n);
} }
public static JSONElement parse(String filename, String raw) { public static JSONElement parse(Filename filename, String raw) {
if (filename == null) filename = new Filename("jscript", "json");
var res = parseValue(filename, Parsing.tokenize(filename, raw), 0); var res = parseValue(filename, Parsing.tokenize(filename, raw), 0);
if (res.isFailed()) throw new SyntaxException(null, "Invalid JSON given."); if (res.isFailed()) throw new SyntaxException(null, "Invalid JSON given.");
else if (res.isError()) throw new SyntaxException(null, res.error); else if (res.isError()) throw new SyntaxException(null, res.error);
@@ -120,7 +188,28 @@ public class JSON {
if (el.isNumber()) return Double.toString(el.number()); if (el.isNumber()) return Double.toString(el.number());
if (el.isBoolean()) return el.bool() ? "true" : "false"; if (el.isBoolean()) return el.bool() ? "true" : "false";
if (el.isNull()) return "null"; if (el.isNull()) return "null";
if (el.isString()) return "\"" + el.string().replace("\\", "\\\\").replace("\"", "\\\"") + "\""; if (el.isString()) {
var res = new StringBuilder("\"");
var alphabet = "0123456789ABCDEF".toCharArray();
for (var c : el.string().toCharArray()) {
if (c < 32 || c >= 127) {
res
.append("\\u")
.append(alphabet[(c >> 12) & 0xF])
.append(alphabet[(c >> 8) & 0xF])
.append(alphabet[(c >> 4) & 0xF])
.append(alphabet[(c >> 0) & 0xF]);
}
else if (c == '\\')
res.append("\\\\");
else if (c == '"')
res.append("\\\"");
else res.append(c);
}
return res.append('"').toString();
}
if (el.isList()) { if (el.isList()) {
var res = new StringBuilder().append("["); var res = new StringBuilder().append("[");
for (int i = 0; i < el.list().size(); i++) { for (int i = 0; i < el.list().size(); i++) {

View File

@@ -65,10 +65,22 @@ public class JSONElement {
return (double)value; return (double)value;
} }
public boolean bool() { public boolean bool() {
if (!isNumber()) throw new IllegalStateException("Element is not a boolean."); if (!isBoolean()) throw new IllegalStateException("Element is not a boolean.");
return (boolean)value; return (boolean)value;
} }
@Override
public String toString() {
if (isMap()) return "{...}";
if (isList()) return "[...]";
if (isString()) return (String)value;
if (isString()) return (String)value;
if (isNumber()) return (double)value + "";
if (isBoolean()) return (boolean)value + "";
if (isNull()) return "null";
return "";
}
private JSONElement(Type type, Object val) { private JSONElement(Type type, Object val) {
this.type = type; this.type = type;
this.value = val; this.value = val;

View File

@@ -10,6 +10,9 @@ public class JSONList extends ArrayList<JSONElement> {
public JSONList(JSONElement ...els) { public JSONList(JSONElement ...els) {
super(List.of(els)); super(List.of(els));
} }
public JSONList(Collection<JSONElement> els) {
super(els);
}
public JSONList addNull() { this.add(JSONElement.NULL); return this; } public JSONList addNull() { this.add(JSONElement.NULL); return this; }
public JSONList add(String val) { this.add(JSONElement.of(val)); return this; } public JSONList add(String val) { this.add(JSONElement.of(val)); return this; }
@@ -17,5 +20,7 @@ public class JSONList extends ArrayList<JSONElement> {
public JSONList add(boolean val) { this.add(JSONElement.of(val)); return this; } public JSONList add(boolean val) { this.add(JSONElement.of(val)); return this; }
public JSONList add(Map<String, JSONElement> val) { this.add(JSONElement.of(val)); return this; } public JSONList add(Map<String, JSONElement> val) { this.add(JSONElement.of(val)); return this; }
public JSONList add(Collection<JSONElement> val) { this.add(JSONElement.of(val)); return this; } public JSONList add(Collection<JSONElement> val) { this.add(JSONElement.of(val)); return this; }
public JSONList add(JSONMap val) { this.add(JSONElement.of(val)); return this; }
public JSONList add(JSONList val) { this.add(JSONElement.of(val)); return this; }
} }

View File

@@ -51,7 +51,7 @@ public class JSONMap implements Map<String, JSONElement> {
public JSONMap map(String path) { public JSONMap map(String path) {
var el = get(path); var el = get(path);
if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path)); if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
return el.map(); return el.map();
} }
public JSONMap map(String path, JSONMap defaultVal) { public JSONMap map(String path, JSONMap defaultVal) {
@@ -63,7 +63,7 @@ public class JSONMap implements Map<String, JSONElement> {
public JSONList list(String path) { public JSONList list(String path) {
var el = get(path); var el = get(path);
if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path)); if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
return el.list(); return el.list();
} }
public JSONList list(String path, JSONList defaultVal) { public JSONList list(String path, JSONList defaultVal) {
@@ -75,7 +75,7 @@ public class JSONMap implements Map<String, JSONElement> {
public String string(String path) { public String string(String path) {
var el = get(path); var el = get(path);
if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path)); if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
return el.string(); return el.string();
} }
public String string(String path, String defaultVal) { public String string(String path, String defaultVal) {
@@ -87,7 +87,7 @@ public class JSONMap implements Map<String, JSONElement> {
public double number(String path) { public double number(String path) {
var el = get(path); var el = get(path);
if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path)); if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
return el.number(); return el.number();
} }
public double number(String path, double defaultVal) { public double number(String path, double defaultVal) {
@@ -99,7 +99,7 @@ public class JSONMap implements Map<String, JSONElement> {
public boolean bool(String path) { public boolean bool(String path) {
var el = get(path); var el = get(path);
if (el == null) throw new IllegalStateException(String.format("'%s' doesn't exist.", path)); if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path));
return el.bool(); return el.bool();
} }
public boolean bool(String path, boolean defaultVal) { public boolean bool(String path, boolean defaultVal) {

View File

@@ -1,34 +1,32 @@
package me.topchetoeu.jscript.polyfills; package me.topchetoeu.jscript.lib;
import java.util.Iterator; import java.util.Iterator;
import java.util.Stack; import java.util.Stack;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.NativeFunction;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.interop.InitType;
import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.NativeConstructor; import me.topchetoeu.jscript.interop.NativeConstructor;
import me.topchetoeu.jscript.interop.NativeGetter; import me.topchetoeu.jscript.interop.NativeGetter;
import me.topchetoeu.jscript.interop.NativeInit;
import me.topchetoeu.jscript.interop.NativeSetter; import me.topchetoeu.jscript.interop.NativeSetter;
public class ArrayPolyfill { @Native("Array") public class ArrayLib {
@NativeGetter(thisArg = true) public static int length(Context ctx, ArrayValue thisArg) throws InterruptedException { @NativeGetter(thisArg = true) public static int length(Context ctx, ArrayValue thisArg) {
return thisArg.size(); return thisArg.size();
} }
@NativeSetter(thisArg = true) public static void length(Context ctx, ArrayValue thisArg, int len) throws InterruptedException { @NativeSetter(thisArg = true) public static void length(Context ctx, ArrayValue thisArg, int len) {
thisArg.setSize(len); thisArg.setSize(len);
} }
@Native(thisArg = true) public static ObjectValue values(Context ctx, ArrayValue thisArg) throws InterruptedException { @Native(thisArg = true) public static ObjectValue values(Context ctx, ArrayValue thisArg) {
return Values.fromJavaIterable(ctx, thisArg); return Values.toJSIterator(ctx, thisArg);
} }
@Native(thisArg = true) public static ObjectValue keys(Context ctx, ArrayValue thisArg) throws InterruptedException { @Native(thisArg = true) public static ObjectValue keys(Context ctx, ArrayValue thisArg) {
return Values.fromJavaIterable(ctx, () -> new Iterator<Object>() { return Values.toJSIterator(ctx, () -> new Iterator<Object>() {
private int i = 0; private int i = 0;
@Override @Override
@@ -42,8 +40,8 @@ public class ArrayPolyfill {
} }
}); });
} }
@Native(thisArg = true) public static ObjectValue entries(Context ctx, ArrayValue thisArg) throws InterruptedException { @Native(thisArg = true) public static ObjectValue entries(Context ctx, ArrayValue thisArg) {
return Values.fromJavaIterable(ctx, () -> new Iterator<Object>() { return Values.toJSIterator(ctx, () -> new Iterator<Object>() {
private int i = 0; private int i = 0;
@Override @Override
@@ -59,17 +57,17 @@ public class ArrayPolyfill {
} }
@Native(value = "@@Symbol.iterator", thisArg = true) @Native(value = "@@Symbol.iterator", thisArg = true)
public static ObjectValue iterator(Context ctx, ArrayValue thisArg) throws InterruptedException { public static ObjectValue iterator(Context ctx, ArrayValue thisArg) {
return values(ctx, thisArg); return values(ctx, thisArg);
} }
@Native(value = "@@Symbol.asyncIterator", thisArg = true) @Native(value = "@@Symbol.asyncIterator", thisArg = true)
public static ObjectValue asyncIterator(Context ctx, ArrayValue thisArg) throws InterruptedException { public static ObjectValue asyncIterator(Context ctx, ArrayValue thisArg) {
return values(ctx, thisArg); return values(ctx, thisArg);
} }
@Native(thisArg = true) public static ArrayValue concat(Context ctx, ArrayValue thisArg, Object ...others) throws InterruptedException { @Native(thisArg = true) public static ArrayValue concat(Context ctx, ArrayValue thisArg, Object ...others) {
// TODO: Fully implement with non-array spreadable objects // TODO: Fully implement with non-array spreadable objects
var size = 0; var size = thisArg.size();
for (int i = 0; i < others.length; i++) { for (int i = 0; i < others.length; i++) {
if (others[i] instanceof ArrayValue) size += ((ArrayValue)others[i]).size(); if (others[i] instanceof ArrayValue) size += ((ArrayValue)others[i]).size();
@@ -77,8 +75,9 @@ public class ArrayPolyfill {
} }
var res = new ArrayValue(size); var res = new ArrayValue(size);
thisArg.copyTo(ctx, res, 0, 0, thisArg.size());
for (int i = 0, j = 0; i < others.length; i++) { for (int i = 0, j = thisArg.size(); i < others.length; i++) {
if (others[i] instanceof ArrayValue) { if (others[i] instanceof ArrayValue) {
int n = ((ArrayValue)others[i]).size(); int n = ((ArrayValue)others[i]).size();
((ArrayValue)others[i]).copyTo(ctx, res, 0, j, n); ((ArrayValue)others[i]).copyTo(ctx, res, 0, j, n);
@@ -92,24 +91,17 @@ public class ArrayPolyfill {
return res; return res;
} }
@Native(thisArg = true) public static void sort(Context ctx, ArrayValue arr, FunctionValue cmp) throws InterruptedException { @Native(thisArg = true) public static ArrayValue sort(Context ctx, ArrayValue arr, FunctionValue cmp) {
try { var defaultCmp = new NativeFunction("", (_ctx, thisArg, args) -> {
arr.sort((a, b) -> { return Values.toString(ctx, args[0]).compareTo(Values.toString(ctx, args[1]));
try { });
var res = Values.toNumber(ctx, cmp.call(ctx, null, a, b)); arr.sort((a, b) -> {
if (res < 0) return -1; var res = Values.toNumber(ctx, (cmp == null ? defaultCmp : cmp).call(ctx, null, a, b));
if (res > 0) return 1; if (res < 0) return -1;
return 0; if (res > 0) return 1;
} return 0;
catch (InterruptedException e) { });
throw new RuntimeException(e); return arr;
}
});
}
catch (RuntimeException e) {
if (e.getCause() instanceof InterruptedException) throw (InterruptedException)e.getCause();
else throw e;
}
} }
private static int normalizeI(int len, int i, boolean clamp) { private static int normalizeI(int len, int i, boolean clamp) {
@@ -121,7 +113,7 @@ public class ArrayPolyfill {
return i; return i;
} }
@Native(thisArg = true) public static ArrayValue fill(Context ctx, ArrayValue arr, Object val, int start, int end) throws InterruptedException { @Native(thisArg = true) public static ArrayValue fill(Context ctx, ArrayValue arr, Object val, int start, int end) {
start = normalizeI(arr.size(), start, true); start = normalizeI(arr.size(), start, true);
end = normalizeI(arr.size(), end, true); end = normalizeI(arr.size(), end, true);
@@ -131,21 +123,21 @@ public class ArrayPolyfill {
return arr; return arr;
} }
@Native(thisArg = true) public static ArrayValue fill(Context ctx, ArrayValue arr, Object val, int start) throws InterruptedException { @Native(thisArg = true) public static ArrayValue fill(Context ctx, ArrayValue arr, Object val, int start) {
return fill(ctx, arr, val, start, arr.size()); return fill(ctx, arr, val, start, arr.size());
} }
@Native(thisArg = true) public static ArrayValue fill(Context ctx, ArrayValue arr, Object val) throws InterruptedException { @Native(thisArg = true) public static ArrayValue fill(Context ctx, ArrayValue arr, Object val) {
return fill(ctx, arr, val, 0, arr.size()); return fill(ctx, arr, val, 0, arr.size());
} }
@Native(thisArg = true) public static boolean every(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) throws InterruptedException { @Native(thisArg = true) public static boolean every(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) {
for (var i = 0; i < arr.size(); i++) { for (var i = 0; i < arr.size(); i++) {
if (!Values.toBoolean(func.call(ctx, thisArg, arr.get(i), i, arr))) return false; if (!Values.toBoolean(func.call(ctx, thisArg, arr.get(i), i, arr))) return false;
} }
return true; return true;
} }
@Native(thisArg = true) public static boolean some(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) throws InterruptedException { @Native(thisArg = true) public static boolean some(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) {
for (var i = 0; i < arr.size(); i++) { for (var i = 0; i < arr.size(); i++) {
if (Values.toBoolean(func.call(ctx, thisArg, arr.get(i), i, arr))) return true; if (Values.toBoolean(func.call(ctx, thisArg, arr.get(i), i, arr))) return true;
} }
@@ -153,7 +145,7 @@ public class ArrayPolyfill {
return false; return false;
} }
@Native(thisArg = true) public static ArrayValue filter(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) throws InterruptedException { @Native(thisArg = true) public static ArrayValue filter(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) {
var res = new ArrayValue(arr.size()); var res = new ArrayValue(arr.size());
for (int i = 0, j = 0; i < arr.size(); i++) { for (int i = 0, j = 0; i < arr.size(); i++) {
@@ -161,20 +153,51 @@ public class ArrayPolyfill {
} }
return res; return res;
} }
@Native(thisArg = true) public static ArrayValue map(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) throws InterruptedException { @Native(thisArg = true) public static ArrayValue map(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) {
var res = new ArrayValue(arr.size()); var res = new ArrayValue(arr.size());
for (int i = 0, j = 0; i < arr.size(); i++) { for (int i = 0, j = 0; i < arr.size(); i++) {
if (arr.has(i)) res.set(ctx, j++, func.call(ctx, thisArg, arr.get(i), i, arr)); if (arr.has(i)) res.set(ctx, j++, func.call(ctx, thisArg, arr.get(i), i, arr));
} }
return res; return res;
} }
@Native(thisArg = true) public static void forEach(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) throws InterruptedException { @Native(thisArg = true) public static void forEach(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) {
for (int i = 0; i < arr.size(); i++) { for (int i = 0; i < arr.size(); i++) {
if (arr.has(i)) func.call(ctx, thisArg, arr.get(i), i, arr); if (arr.has(i)) func.call(ctx, thisArg, arr.get(i), i, arr);
} }
} }
@Native(thisArg = true) public static ArrayValue flat(Context ctx, ArrayValue arr, int depth) throws InterruptedException { @Native(thisArg = true) public static Object reduce(Context ctx, ArrayValue arr, FunctionValue func, Object... args) {
var i = 0;
var res = arr.get(0);
if (args.length > 0) res = args[0];
else for (; !arr.has(i) && i < arr.size(); i++) res = arr.get(i);
for (; i < arr.size(); i++) {
if (arr.has(i)) {
res = func.call(ctx, null, res, arr.get(i), i, arr);
}
}
return res;
}
@Native(thisArg = true) public static Object reduceRight(Context ctx, ArrayValue arr, FunctionValue func, Object... args) {
var i = arr.size();
var res = arr.get(0);
if (args.length > 0) res = args[0];
else while (!arr.has(i--) && i >= 0) res = arr.get(i);
for (; i >= 0; i--) {
if (arr.has(i)) {
res = func.call(ctx, null, res, arr.get(i), i, arr);
}
}
return res;
}
@Native(thisArg = true) public static ArrayValue flat(Context ctx, ArrayValue arr, int depth) {
var res = new ArrayValue(arr.size()); var res = new ArrayValue(arr.size());
var stack = new Stack<Object>(); var stack = new Stack<Object>();
var depths = new Stack<Integer>(); var depths = new Stack<Integer>();
@@ -197,18 +220,18 @@ public class ArrayPolyfill {
return res; return res;
} }
@Native(thisArg = true) public static ArrayValue flatMap(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) throws InterruptedException { @Native(thisArg = true) public static ArrayValue flatMap(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) {
return flat(ctx, map(ctx, arr, cmp, thisArg), 1); return flat(ctx, map(ctx, arr, cmp, thisArg), 1);
} }
@Native(thisArg = true) public static Object find(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) throws InterruptedException { @Native(thisArg = true) public static Object find(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) {
for (int i = 0; i < arr.size(); i++) { for (int i = 0; i < arr.size(); i++) {
if (arr.has(i) && Values.toBoolean(cmp.call(ctx, thisArg, arr.get(i), i, arr))) return arr.get(i); if (arr.has(i) && Values.toBoolean(cmp.call(ctx, thisArg, arr.get(i), i, arr))) return arr.get(i);
} }
return null; return null;
} }
@Native(thisArg = true) public static Object findLast(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) throws InterruptedException { @Native(thisArg = true) public static Object findLast(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) {
for (var i = arr.size() - 1; i >= 0; i--) { for (var i = arr.size() - 1; i >= 0; i--) {
if (arr.has(i) && Values.toBoolean(cmp.call(ctx, thisArg, arr.get(i), i, arr))) return arr.get(i); if (arr.has(i) && Values.toBoolean(cmp.call(ctx, thisArg, arr.get(i), i, arr))) return arr.get(i);
} }
@@ -216,14 +239,14 @@ public class ArrayPolyfill {
return null; return null;
} }
@Native(thisArg = true) public static int findIndex(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) throws InterruptedException { @Native(thisArg = true) public static int findIndex(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) {
for (int i = 0; i < arr.size(); i++) { for (int i = 0; i < arr.size(); i++) {
if (arr.has(i) && Values.toBoolean(cmp.call(ctx, thisArg, arr.get(i), i, arr))) return i; if (arr.has(i) && Values.toBoolean(cmp.call(ctx, thisArg, arr.get(i), i, arr))) return i;
} }
return -1; return -1;
} }
@Native(thisArg = true) public static int findLastIndex(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) throws InterruptedException { @Native(thisArg = true) public static int findLastIndex(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) {
for (var i = arr.size() - 1; i >= 0; i--) { for (var i = arr.size() - 1; i >= 0; i--) {
if (arr.has(i) && Values.toBoolean(cmp.call(ctx, thisArg, arr.get(i), i, arr))) return i; if (arr.has(i) && Values.toBoolean(cmp.call(ctx, thisArg, arr.get(i), i, arr))) return i;
} }
@@ -231,16 +254,16 @@ public class ArrayPolyfill {
return -1; return -1;
} }
@Native(thisArg = true) public static int indexOf(Context ctx, ArrayValue arr, Object val, int start) throws InterruptedException { @Native(thisArg = true) public static int indexOf(Context ctx, ArrayValue arr, Object val, int start) {
start = normalizeI(arr.size(), start, true); start = normalizeI(arr.size(), start, true);
for (int i = 0; i < arr.size() && i < start; i++) { for (int i = start; i < arr.size(); i++) {
if (Values.strictEquals(ctx, arr.get(i), val)) return i; if (Values.strictEquals(ctx, arr.get(i), val)) return i;
} }
return -1; return -1;
} }
@Native(thisArg = true) public static int lastIndexOf(Context ctx, ArrayValue arr, Object val, int start) throws InterruptedException { @Native(thisArg = true) public static int lastIndexOf(Context ctx, ArrayValue arr, Object val, int start) {
start = normalizeI(arr.size(), start, true); start = normalizeI(arr.size(), start, true);
for (int i = arr.size(); i >= start; i--) { for (int i = arr.size(); i >= start; i--) {
@@ -250,48 +273,46 @@ public class ArrayPolyfill {
return -1; return -1;
} }
@Native(thisArg = true) public static boolean includes(Context ctx, ArrayValue arr, Object el, int start) throws InterruptedException { @Native(thisArg = true) public static boolean includes(Context ctx, ArrayValue arr, Object el, int start) {
return indexOf(ctx, arr, el, start) >= 0; return indexOf(ctx, arr, el, start) >= 0;
} }
@Native(thisArg = true) public static Object pop(Context ctx, ArrayValue arr) throws InterruptedException { @Native(thisArg = true) public static Object pop(Context ctx, ArrayValue arr) {
if (arr.size() == 0) return null; if (arr.size() == 0) return null;
var val = arr.get(arr.size() - 1); var val = arr.get(arr.size() - 1);
arr.shrink(1); arr.shrink(1);
return val; return val;
} }
@Native(thisArg = true) public static int push(Context ctx, ArrayValue arr, Object ...values) throws InterruptedException { @Native(thisArg = true) public static int push(Context ctx, ArrayValue arr, Object ...values) {
arr.copyFrom(ctx, values, 0, arr.size(), values.length); arr.copyFrom(ctx, values, 0, arr.size(), values.length);
return arr.size(); return arr.size();
} }
@Native(thisArg = true) public static Object shift(Context ctx, ArrayValue arr) throws InterruptedException { @Native(thisArg = true) public static Object shift(Context ctx, ArrayValue arr) {
if (arr.size() == 0) return null; if (arr.size() == 0) return null;
var val = arr.get(0); var val = arr.get(0);
arr.move(1, 0, arr.size()); arr.move(1, 0, arr.size());
arr.shrink(1); arr.shrink(1);
return val; return val;
} }
@Native(thisArg = true) public static int unshift(Context ctx, ArrayValue arr, Object ...values) throws InterruptedException { @Native(thisArg = true) public static int unshift(Context ctx, ArrayValue arr, Object ...values) {
arr.move(0, values.length, arr.size()); arr.move(0, values.length, arr.size());
arr.copyFrom(ctx, values, 0, 0, values.length); arr.copyFrom(ctx, values, 0, 0, values.length);
return arr.size(); return arr.size();
} }
@Native(thisArg = true) public static ArrayValue slice(Context ctx, ArrayValue arr, int start, int end) { @Native(thisArg = true) public static ArrayValue slice(Context ctx, ArrayValue arr, int start, Object _end) {
start = normalizeI(arr.size(), start, true); start = normalizeI(arr.size(), start, true);
end = normalizeI(arr.size(), end, true); int end = normalizeI(arr.size(), (int)(_end == null ? arr.size() : Values.toNumber(ctx, _end)), true);
var res = new ArrayValue(end - start); var res = new ArrayValue(end - start);
arr.copyTo(ctx, res, start, 0, end - start); arr.copyTo(ctx, res, start, 0, end - start);
return res; return res;
} }
@Native(thisArg = true) public static ArrayValue slice(Context ctx, ArrayValue arr, int start) {
return slice(ctx, arr, start, arr.size());
}
@Native(thisArg = true) public static ArrayValue splice(Context ctx, ArrayValue arr, int start, int deleteCount, Object ...items) throws InterruptedException { @Native(thisArg = true) public static ArrayValue splice(Context ctx, ArrayValue arr, int start, Object _deleteCount, Object ...items) {
start = normalizeI(arr.size(), start, true); start = normalizeI(arr.size(), start, true);
int deleteCount = _deleteCount == null ? arr.size() - 1 : (int)Values.toNumber(ctx, _deleteCount);
deleteCount = normalizeI(arr.size(), deleteCount, true); deleteCount = normalizeI(arr.size(), deleteCount, true);
if (start + deleteCount >= arr.size()) deleteCount = arr.size() - start; if (start + deleteCount >= arr.size()) deleteCount = arr.size() - start;
@@ -304,21 +325,20 @@ public class ArrayPolyfill {
return res; return res;
} }
@Native(thisArg = true) public static ArrayValue splice(Context ctx, ArrayValue arr, int start) throws InterruptedException { @Native(thisArg = true) public static String toString(Context ctx, ArrayValue arr) {
return splice(ctx, arr, start, arr.size() - start);
}
@Native(thisArg = true) public static String toString(Context ctx, ArrayValue arr) throws InterruptedException {
return join(ctx, arr, ","); return join(ctx, arr, ",");
} }
@Native(thisArg = true) public static String join(Context ctx, ArrayValue arr, String sep) throws InterruptedException { @Native(thisArg = true) public static String join(Context ctx, ArrayValue arr, String sep) {
var res = new StringBuilder(); var res = new StringBuilder();
var comma = true; var comma = false;
for (int i = 0; i < arr.size(); i++) { for (int i = 0; i < arr.size(); i++) {
if (!arr.has(i)) continue; if (!arr.has(i)) continue;
if (comma) res.append(sep); if (comma) res.append(sep);
comma = false; comma = true;
var el = arr.get(i); var el = arr.get(i);
if (el == null || el == Values.NULL) continue; if (el == null || el == Values.NULL) continue;
@@ -350,8 +370,4 @@ public class ArrayPolyfill {
return res; return res;
} }
@NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) {
target.defineProperty(null, env.symbol("Symbol.typeName"), "Array");
}
} }

View File

@@ -1,4 +1,4 @@
package me.topchetoeu.jscript.polyfills; package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.frame.CodeFrame;
@@ -7,19 +7,21 @@ import me.topchetoeu.jscript.engine.values.CodeFunction;
import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.NativeFunction; import me.topchetoeu.jscript.engine.values.NativeFunction;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.interop.Native;
public class AsyncFunctionPolyfill extends FunctionValue { @Native("AsyncFunction") public class AsyncFunctionLib extends FunctionValue {
public final FunctionValue factory; public final FunctionValue factory;
public static class AsyncHelper { public static class AsyncHelper {
public PromisePolyfill promise = new PromisePolyfill(); public PromiseLib promise = new PromiseLib();
public CodeFrame frame; public CodeFrame frame;
private boolean awaiting = false; private boolean awaiting = false;
private void next(Context ctx, Object inducedValue, Object inducedError) throws InterruptedException { private void next(Context ctx, Object inducedValue, Object inducedError) {
Object res = null; Object res = null;
ctx.message.pushFrame(ctx, frame); ctx.pushFrame(frame);
ctx.pushEnv(frame.function.environment);
awaiting = false; awaiting = false;
while (!awaiting) { while (!awaiting) {
@@ -37,18 +39,18 @@ public class AsyncFunctionPolyfill extends FunctionValue {
} }
} }
ctx.message.popFrame(frame); ctx.popFrame(frame);
if (awaiting) { if (awaiting) {
PromisePolyfill.then(ctx, frame.pop(), new NativeFunction(this::fulfill), new NativeFunction(this::reject)); PromiseLib.then(ctx, frame.pop(), new NativeFunction(this::fulfill), new NativeFunction(this::reject));
} }
} }
public Object fulfill(Context ctx, Object thisArg, Object ...args) throws InterruptedException { public Object fulfill(Context ctx, Object thisArg, Object ...args) {
next(ctx, args.length > 0 ? args[0] : null, Runners.NO_RETURN); next(ctx, args.length > 0 ? args[0] : null, Runners.NO_RETURN);
return null; return null;
} }
public Object reject(Context ctx, Object thisArg, Object ...args) throws InterruptedException { public Object reject(Context ctx, Object thisArg, Object ...args) {
next(ctx, Runners.NO_RETURN, args.length > 0 ? args[0] : null); next(ctx, Runners.NO_RETURN, args.length > 0 ? args[0] : null);
return null; return null;
} }
@@ -60,7 +62,7 @@ public class AsyncFunctionPolyfill extends FunctionValue {
} }
@Override @Override
public Object call(Context ctx, Object thisArg, Object ...args) throws InterruptedException { public Object call(Context ctx, Object thisArg, Object ...args) {
var handler = new AsyncHelper(); var handler = new AsyncHelper();
var func = factory.call(ctx, thisArg, new NativeFunction("await", handler::await)); var func = factory.call(ctx, thisArg, new NativeFunction("await", handler::await));
if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function."); if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function.");
@@ -69,7 +71,7 @@ public class AsyncFunctionPolyfill extends FunctionValue {
return handler.promise; return handler.promise;
} }
public AsyncFunctionPolyfill(FunctionValue factory) { public AsyncFunctionLib(FunctionValue factory) {
super(factory.name, factory.length); super(factory.name, factory.length);
this.factory = factory; this.factory = factory;
} }

View File

@@ -0,0 +1,30 @@
package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.engine.values.CodeFunction;
import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.NativeFunction;
import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.interop.Native;
@Native("AsyncGeneratorFunction") public class AsyncGeneratorFunctionLib extends FunctionValue {
public final FunctionValue factory;
@Override
public Object call(Context ctx, Object thisArg, Object ...args) {
var handler = new AsyncGeneratorLib();
var func = factory.call(ctx, thisArg,
new NativeFunction("await", handler::await),
new NativeFunction("yield", handler::yield)
);
if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function.");
handler.frame = new CodeFrame(ctx, thisArg, args, (CodeFunction)func);
return handler;
}
public AsyncGeneratorFunctionLib(FunctionValue factory) {
super(factory.name, factory.length);
this.factory = factory;
}
}

View File

@@ -0,0 +1,110 @@
package me.topchetoeu.jscript.lib;
import java.util.Map;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.engine.frame.Runners;
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;
@Native("AsyncGenerator") public class AsyncGeneratorLib {
@Native("@@Symbol.typeName") public final String name = "AsyncGenerator";
private int state = 0;
private boolean done = false;
private PromiseLib currPromise;
public CodeFrame frame;
private void next(Context ctx, Object inducedValue, Object inducedReturn, Object inducedError) {
if (done) {
if (inducedError != Runners.NO_RETURN) throw new EngineException(inducedError);
currPromise.fulfill(ctx, new ObjectValue(ctx, Map.of(
"done", true,
"value", inducedReturn == Runners.NO_RETURN ? null : inducedReturn
)));
return;
}
Object res = null;
ctx.pushFrame(frame);
state = 0;
while (state == 0) {
try {
res = frame.next(ctx, inducedValue, inducedReturn, inducedError == Runners.NO_RETURN ? null : new EngineException(inducedError));
inducedValue = inducedReturn = inducedError = Runners.NO_RETURN;
if (res != Runners.NO_RETURN) {
var obj = new ObjectValue();
obj.defineProperty(ctx, "done", true);
obj.defineProperty(ctx, "value", res);
currPromise.fulfill(ctx, obj);
break;
}
}
catch (EngineException e) {
currPromise.reject(ctx, e.value);
break;
}
}
ctx.popFrame(frame);
if (state == 1) {
PromiseLib.then(ctx, frame.pop(), new NativeFunction(this::fulfill), new NativeFunction(this::reject));
}
else if (state == 2) {
var obj = new ObjectValue();
obj.defineProperty(ctx, "done", false);
obj.defineProperty(ctx, "value", frame.pop());
currPromise.fulfill(ctx, obj);
}
}
@Override
public String toString() {
if (done) return "Generator [closed]";
if (state != 0) return "Generator [suspended]";
return "Generator [running]";
}
public Object fulfill(Context ctx, Object thisArg, Object ...args) {
next(ctx, args.length > 0 ? args[0] : null, Runners.NO_RETURN, Runners.NO_RETURN);
return null;
}
public Object reject(Context ctx, Object thisArg, Object ...args) {
next(ctx, Runners.NO_RETURN, args.length > 0 ? args[0] : null, Runners.NO_RETURN);
return null;
}
@Native
public PromiseLib next(Context ctx, Object ...args) {
this.currPromise = new PromiseLib();
if (args.length == 0) next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, Runners.NO_RETURN);
else next(ctx, args[0], Runners.NO_RETURN, Runners.NO_RETURN);
return this.currPromise;
}
@Native("throw")
public PromiseLib _throw(Context ctx, Object error) {
this.currPromise = new PromiseLib();
next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, error);
return this.currPromise;
}
@Native("return")
public PromiseLib _return(Context ctx, Object value) {
this.currPromise = new PromiseLib();
next(ctx, Runners.NO_RETURN, value, Runners.NO_RETURN);
return this.currPromise;
}
public Object await(Context ctx, Object thisArg, Object[] args) {
this.state = 1;
return args.length > 0 ? args[0] : null;
}
public Object yield(Context ctx, Object thisArg, Object[] args) {
this.state = 2;
return args.length > 0 ? args[0] : null;
}
}

View File

@@ -1,17 +1,14 @@
package me.topchetoeu.jscript.polyfills; package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.interop.InitType;
import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.NativeConstructor; import me.topchetoeu.jscript.interop.NativeConstructor;
import me.topchetoeu.jscript.interop.NativeInit;
public class BooleanPolyfill { @Native("Boolean") public class BooleanLib {
public static final BooleanPolyfill TRUE = new BooleanPolyfill(true); public static final BooleanLib TRUE = new BooleanLib(true);
public static final BooleanPolyfill FALSE = new BooleanPolyfill(false); public static final BooleanLib FALSE = new BooleanLib(false);
public final boolean value; public final boolean value;
@@ -27,10 +24,7 @@ public class BooleanPolyfill {
return Values.toBoolean(thisArg); return Values.toBoolean(thisArg);
} }
public BooleanPolyfill(boolean val) { public BooleanLib(boolean val) {
this.value = val; this.value = val;
} }
@NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) {
target.defineProperty(null, env.symbol("Symbol.typeName"), "Boolean");
}
} }

View File

@@ -1,302 +1,275 @@
package me.topchetoeu.jscript.polyfills; package me.topchetoeu.jscript.lib;
import java.util.Calendar; import java.util.Calendar;
import java.util.TimeZone; import java.util.TimeZone;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.Native;
@Native("Date") public class DateLib {
public class Date { private Calendar normal;
private Calendar normal; private Calendar utc;
private Calendar utc;
private void updateUTC() {
public Date(long timestamp) { if (utc == null || normal == null) return;
normal = Calendar.getInstance(); utc.setTimeInMillis(normal.getTimeInMillis());
utc = Calendar.getInstance(); }
normal.setTimeInMillis(timestamp); private void updateNormal() {
utc.setTimeZone(TimeZone.getTimeZone("UTC")); if (utc == null || normal == null) return;
utc.setTimeInMillis(timestamp); normal.setTimeInMillis(utc.getTimeInMillis());
} }
private void invalidate() {
@Native normal = utc = null;
public Date() { }
this(new java.util.Date().getTime());
} @Native
public static double now() {
private void updateUTC() { return new DateLib().getTime();
if (utc == null || normal == null) return; }
utc.setTimeInMillis(normal.getTimeInMillis());
} @Native
private void updateNormal() { public double getYear() {
if (utc == null || normal == null) return; if (normal == null) return Double.NaN;
normal.setTimeInMillis(utc.getTimeInMillis()); return normal.get(Calendar.YEAR) - 1900;
} }
private void invalidate() { @Native
normal = utc = null; public double setYear(Context ctx, double real) {
} if (real >= 0 && real <= 99) real = real + 1900;
if (Double.isNaN(real)) invalidate();
@Native else normal.set(Calendar.YEAR, (int)real);
public static double now() { updateUTC();
return new Date().getTime(); return getTime();
} }
@Native @Native
public double getYear() { public double getFullYear() {
if (normal == null) return Double.NaN; if (normal == null) return Double.NaN;
return normal.get(Calendar.YEAR) - 1900; return normal.get(Calendar.YEAR);
} }
@Native @Native
public double setYear(Context ctx, Object val) throws InterruptedException { public double getMonth() {
var real = Values.toNumber(ctx, val); if (normal == null) return Double.NaN;
if (real >= 0 && real <= 99) real = real + 1900; return normal.get(Calendar.MONTH);
if (Double.isNaN(real)) invalidate(); }
else normal.set(Calendar.YEAR, (int)real); @Native
updateUTC(); public double getDate() {
return getTime(); if (normal == null) return Double.NaN;
} return normal.get(Calendar.DAY_OF_MONTH);
}
@Native @Native
public double getFullYear() { public double getDay() {
if (normal == null) return Double.NaN; if (normal == null) return Double.NaN;
return normal.get(Calendar.YEAR); return normal.get(Calendar.DAY_OF_WEEK);
} }
@Native @Native
public double getMonth() { public double getHours() {
if (normal == null) return Double.NaN; if (normal == null) return Double.NaN;
return normal.get(Calendar.MONTH); return normal.get(Calendar.HOUR_OF_DAY);
} }
@Native @Native
public double getDate() { public double getMinutes() {
if (normal == null) return Double.NaN; if (normal == null) return Double.NaN;
return normal.get(Calendar.DAY_OF_MONTH); return normal.get(Calendar.MINUTE);
} }
@Native @Native
public double getDay() { public double getSeconds() {
if (normal == null) return Double.NaN; if (normal == null) return Double.NaN;
return normal.get(Calendar.DAY_OF_WEEK); return normal.get(Calendar.SECOND);
} }
@Native @Native
public double getHours() { public double getMilliseconds() {
if (normal == null) return Double.NaN; if (normal == null) return Double.NaN;
return normal.get(Calendar.HOUR_OF_DAY); return normal.get(Calendar.MILLISECOND);
} }
@Native
public double getMinutes() { @Native
if (normal == null) return Double.NaN; public double getUTCFullYear() {
return normal.get(Calendar.MINUTE); if (utc == null) return Double.NaN;
} return utc.get(Calendar.YEAR);
@Native }
public double getSeconds() { @Native
if (normal == null) return Double.NaN; public double getUTCMonth() {
return normal.get(Calendar.SECOND); if (utc == null) return Double.NaN;
} return utc.get(Calendar.MONTH);
@Native }
public double getMilliseconds() { @Native
if (normal == null) return Double.NaN; public double getUTCDate() {
return normal.get(Calendar.MILLISECOND); if (utc == null) return Double.NaN;
} return utc.get(Calendar.DAY_OF_MONTH);
}
@Native @Native
public double getUTCFullYear() { public double getUTCDay() {
if (utc == null) return Double.NaN; if (utc == null) return Double.NaN;
return utc.get(Calendar.YEAR); return utc.get(Calendar.DAY_OF_WEEK);
} }
@Native @Native
public double getUTCMonth() { public double getUTCHours() {
if (utc == null) return Double.NaN; if (utc == null) return Double.NaN;
return utc.get(Calendar.MONTH); return utc.get(Calendar.HOUR_OF_DAY);
} }
@Native @Native
public double getUTCDate() { public double getUTCMinutes() {
if (utc == null) return Double.NaN; if (utc == null) return Double.NaN;
return utc.get(Calendar.DAY_OF_MONTH); return utc.get(Calendar.MINUTE);
} }
@Native @Native
public double getUTCDay() { public double getUTCSeconds() {
if (utc == null) return Double.NaN; if (utc == null) return Double.NaN;
return utc.get(Calendar.DAY_OF_WEEK); return utc.get(Calendar.SECOND);
} }
@Native @Native
public double getUTCHours() { public double getUTCMilliseconds() {
if (utc == null) return Double.NaN; if (utc == null) return Double.NaN;
return utc.get(Calendar.HOUR_OF_DAY); return utc.get(Calendar.MILLISECOND);
} }
@Native
public double getUTCMinutes() { @Native
if (utc == null) return Double.NaN; public double setFullYear(Context ctx, double real) {
return utc.get(Calendar.MINUTE); if (Double.isNaN(real)) invalidate();
} else normal.set(Calendar.YEAR, (int)real);
@Native updateUTC();
public double getUTCSeconds() { return getTime();
if (utc == null) return Double.NaN; }
return utc.get(Calendar.SECOND); @Native
} public double setMonth(Context ctx, double real) {
@Native if (Double.isNaN(real)) invalidate();
public double getUTCMilliseconds() { else normal.set(Calendar.MONTH, (int)real);
if (utc == null) return Double.NaN; updateUTC();
return utc.get(Calendar.MILLISECOND); return getTime();
} }
@Native
@Native public double setDate(Context ctx, double real) {
public double setFullYear(Context ctx, Object val) throws InterruptedException { if (Double.isNaN(real)) invalidate();
var real = Values.toNumber(ctx, val); else normal.set(Calendar.DAY_OF_MONTH, (int)real);
if (Double.isNaN(real)) invalidate(); updateUTC();
else normal.set(Calendar.YEAR, (int)real); return getTime();
updateUTC(); }
return getTime(); @Native
} public double setDay(Context ctx, double real) {
@Native if (Double.isNaN(real)) invalidate();
public double setMonth(Context ctx, Object val) throws InterruptedException { else normal.set(Calendar.DAY_OF_WEEK, (int)real);
var real = Values.toNumber(ctx, val); updateUTC();
if (Double.isNaN(real)) invalidate(); return getTime();
else normal.set(Calendar.MONTH, (int)real); }
updateUTC(); @Native
return getTime(); public double setHours(Context ctx, double real) {
} if (Double.isNaN(real)) invalidate();
@Native else normal.set(Calendar.HOUR_OF_DAY, (int)real);
public double setDate(Context ctx, Object val) throws InterruptedException { updateUTC();
var real = Values.toNumber(ctx, val); return getTime();
if (Double.isNaN(real)) invalidate(); }
else normal.set(Calendar.DAY_OF_MONTH, (int)real); @Native
updateUTC(); public double setMinutes(Context ctx, double real) {
return getTime(); if (Double.isNaN(real)) invalidate();
} else normal.set(Calendar.MINUTE, (int)real);
@Native updateUTC();
public double setDay(Context ctx, Object val) throws InterruptedException { return getTime();
var real = Values.toNumber(ctx, val); }
if (Double.isNaN(real)) invalidate(); @Native
else normal.set(Calendar.DAY_OF_WEEK, (int)real); public double setSeconds(Context ctx, double real) {
updateUTC(); if (Double.isNaN(real)) invalidate();
return getTime(); else normal.set(Calendar.SECOND, (int)real);
} updateUTC();
@Native return getTime();
public double setHours(Context ctx, Object val) throws InterruptedException { }
var real = Values.toNumber(ctx, val); @Native
if (Double.isNaN(real)) invalidate(); public double setMilliseconds(Context ctx, double real) {
else normal.set(Calendar.HOUR_OF_DAY, (int)real); if (Double.isNaN(real)) invalidate();
updateUTC(); else normal.set(Calendar.MILLISECOND, (int)real);
return getTime(); updateUTC();
} return getTime();
@Native }
public double setMinutes(Context ctx, Object val) throws InterruptedException {
var real = Values.toNumber(ctx, val); @Native
if (Double.isNaN(real)) invalidate(); public double setUTCFullYear(Context ctx, double real) {
else normal.set(Calendar.MINUTE, (int)real); if (Double.isNaN(real)) invalidate();
updateUTC(); else utc.set(Calendar.YEAR, (int)real);
return getTime(); updateNormal();
} return getTime();
@Native }
public double setSeconds(Context ctx, Object val) throws InterruptedException { @Native
var real = Values.toNumber(ctx, val); public double setUTCMonth(Context ctx, double real) {
if (Double.isNaN(real)) invalidate(); if (Double.isNaN(real)) invalidate();
else normal.set(Calendar.SECOND, (int)real); else utc.set(Calendar.MONTH, (int)real);
updateUTC(); updateNormal();
return getTime(); return getTime();
} }
@Native @Native
public double setMilliseconds(Context ctx, Object val) throws InterruptedException { public double setUTCDate(Context ctx, double real) {
var real = Values.toNumber(ctx, val); if (Double.isNaN(real)) invalidate();
if (Double.isNaN(real)) invalidate(); else utc.set(Calendar.DAY_OF_MONTH, (int)real);
else normal.set(Calendar.MILLISECOND, (int)real); updateNormal();
updateUTC(); return getTime();
return getTime(); }
} @Native
public double setUTCDay(Context ctx, double real) {
@Native if (Double.isNaN(real)) invalidate();
public double setUTCFullYear(Context ctx, Object val) throws InterruptedException { else utc.set(Calendar.DAY_OF_WEEK, (int)real);
var real = Values.toNumber(ctx, val); updateNormal();
if (Double.isNaN(real)) invalidate(); return getTime();
else utc.set(Calendar.YEAR, (int)real); }
updateNormal(); @Native
return getTime(); public double setUTCHours(Context ctx, double real) {
} if (Double.isNaN(real)) invalidate();
@Native else utc.set(Calendar.HOUR_OF_DAY, (int)real);
public double setUTCMonth(Context ctx, Object val) throws InterruptedException { updateNormal();
var real = Values.toNumber(ctx, val); return getTime();
if (Double.isNaN(real)) invalidate(); }
else utc.set(Calendar.MONTH, (int)real); @Native
updateNormal(); public double setUTCMinutes(Context ctx, double real) {
return getTime(); if (Double.isNaN(real)) invalidate();
} else utc.set(Calendar.MINUTE, (int)real);
@Native updateNormal();
public double setUTCDate(Context ctx, Object val) throws InterruptedException { return getTime();
var real = Values.toNumber(ctx, val); }
if (Double.isNaN(real)) invalidate(); @Native
else utc.set(Calendar.DAY_OF_MONTH, (int)real); public double setUTCSeconds(Context ctx, double real) {
updateNormal(); if (Double.isNaN(real)) invalidate();
return getTime(); else utc.set(Calendar.SECOND, (int)real);
} updateNormal();
@Native return getTime();
public double setUTCDay(Context ctx, Object val) throws InterruptedException { }
var real = Values.toNumber(ctx, val); @Native
if (Double.isNaN(real)) invalidate(); public double setUTCMilliseconds(Context ctx, double real) {
else utc.set(Calendar.DAY_OF_WEEK, (int)real); if (Double.isNaN(real)) invalidate();
updateNormal(); else utc.set(Calendar.MILLISECOND, (int)real);
return getTime(); updateNormal();
} return getTime();
@Native }
public double setUTCHours(Context ctx, Object val) throws InterruptedException {
var real = Values.toNumber(ctx, val); @Native
if (Double.isNaN(real)) invalidate(); public double getTime() {
else utc.set(Calendar.HOUR_OF_DAY, (int)real); if (utc == null) return Double.NaN;
updateNormal(); return utc.getTimeInMillis();
return getTime(); }
} @Native
@Native public double getTimezoneOffset() {
public double setUTCMinutes(Context ctx, Object val) throws InterruptedException { if (normal == null) return Double.NaN;
var real = Values.toNumber(ctx, val); return normal.getTimeZone().getRawOffset() / 60000;
if (Double.isNaN(real)) invalidate(); }
else utc.set(Calendar.MINUTE, (int)real);
updateNormal(); @Native
return getTime(); public double valueOf() {
} if (normal == null) return Double.NaN;
@Native else return normal.getTimeInMillis();
public double setUTCSeconds(Context ctx, Object val) throws InterruptedException { }
var real = Values.toNumber(ctx, val);
if (Double.isNaN(real)) invalidate(); @Native
else utc.set(Calendar.SECOND, (int)real); public String toString() {
updateNormal(); return normal.getTime().toString();
return getTime(); }
}
@Native @Native
public double setUTCMilliseconds(Context ctx, Object val) throws InterruptedException { public DateLib(long timestamp) {
var real = Values.toNumber(ctx, val); normal = Calendar.getInstance();
if (Double.isNaN(real)) invalidate(); utc = Calendar.getInstance();
else utc.set(Calendar.MILLISECOND, (int)real); normal.setTimeInMillis(timestamp);
updateNormal(); utc.setTimeZone(TimeZone.getTimeZone("UTC"));
return getTime(); utc.setTimeInMillis(timestamp);
} }
@Native @Native
public double getTime() { public DateLib() {
if (utc == null) return Double.NaN; this(new java.util.Date().getTime());
return utc.getTimeInMillis(); }
} }
@Native
public double getTimezoneOffset() {
if (normal == null) return Double.NaN;
return normal.getTimeZone().getRawOffset() / 60000;
}
@Native
public double valueOf() {
if (normal == null) return Double.NaN;
else return normal.getTimeInMillis();
}
// I'm not dealing with locales rn
// @Native
// public String toTimeString() {
// if (normal == null || utc == null) return "Invalid date";
// var res = "";
// }
// @Native @Override
// public String toString() {
// if (normal == null || utc == null) return "Invalid date";
// else return DateFormat..format(normal.getTime());
// }
}

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