Compare commits
63 Commits
v0.3.0-alp
...
v0.5.0-alp
| Author | SHA1 | Date | |
|---|---|---|---|
|
4f22e76d2b
|
|||
|
8924e7aadc
|
|||
| 1d0e31a423 | |||
|
ab56908171
|
|||
|
eb14bb080c
|
|||
|
f52f47cdb4
|
|||
|
567eaa8514
|
|||
|
2cfdd8e335
|
|||
|
4b1ec671e2
|
|||
|
b127aadcf6
|
|||
|
b6eaff65ca
|
|||
|
443dc0ffa1
|
|||
|
e107dd3507
|
|||
|
6af3c70fce
|
|||
|
8b743f49d1
|
|||
|
e1ce384815
|
|||
|
86d205e521
|
|||
|
f0ad936e5b
|
|||
|
4a1473c5be
|
|||
|
4111dbf5c4
|
|||
|
1666682dc2
|
|||
|
f2b33d0233
|
|||
|
f5a0b6eaf7
|
|||
|
829bea755d
|
|||
|
4b0dcffd13
|
|||
|
987f8b8f00
|
|||
|
55e3d46bc2
|
|||
|
3e25068219
|
|||
|
7ecb8bfabb
|
|||
|
488deea164
|
|||
|
ed08041335
|
|||
|
0a4149ba81
|
|||
|
30f5d619c3
|
|||
|
e7dbe91374
|
|||
|
455f5a613e
|
|||
|
1eeac3ae97
|
|||
|
1acd78e119
|
|||
|
df9932874d
|
|||
|
b47d1a7576
|
|||
|
fdfa8d7713
|
|||
|
f5d1287948
|
|||
|
15f4278cb1
|
|||
| df8465cb49 | |||
|
e3104c223c
|
|||
|
1d0bae3de8
|
|||
|
b66acd3089
|
|||
|
e326847287
|
|||
|
26591d6631
|
|||
|
af31b1ab79
|
|||
|
f885d4349f
|
|||
|
d57044acb7
|
|||
|
7df4e3b03f
|
|||
|
ed1009ab69
|
|||
|
f856cdf37e
|
|||
|
4f82574b8c
|
|||
|
0ae24148d8
|
|||
|
ac128d17f4
|
|||
|
6508f15bb0
|
|||
|
69f93b4f87
|
|||
|
b675411925
|
|||
|
d1e93c2088
|
|||
|
942db54546
|
|||
|
d20df66982
|
5
.github/workflows/tagged-release.yml
vendored
5
.github/workflows/tagged-release.yml
vendored
@@ -11,6 +11,11 @@ jobs:
|
||||
runs-on: "ubuntu-latest"
|
||||
|
||||
steps:
|
||||
- name: Setup Java
|
||||
uses: actions/setup-java@v3
|
||||
with:
|
||||
distribution: 'adopt'
|
||||
java-version: '11'
|
||||
- name: Clone repository
|
||||
uses: GuillaumeFalourd/clone-github-repo-action@main
|
||||
with:
|
||||
|
||||
37
README.md
37
README.md
@@ -4,31 +4,42 @@
|
||||
|
||||
**WARNING: Currently, this code is mostly undocumented. Proceed with caution and a psychiatrist.**
|
||||
|
||||
JScript is an engine, capable of running EcmaScript 5, written entirely in Java. This engine has been developed with the goal of being easy to integrate with your preexisting codebase, **THE GOAL OF THIS ENGINE IS NOT PERFORMANCE**. My crude experiments show that this engine is 50x-100x slower than V8, which, although bad, is acceptable for most simple scripting purposes.
|
||||
JScript is an engine, capable of running EcmaScript 5, written entirely in Java. This engine has been developed with the goal of being easy to integrate with your preexisting codebase, **THE GOAL OF THIS ENGINE IS NOT PERFORMANCE**. My crude experiments show that this engine is 50x-100x slower than V8, which, although bad, is acceptable for most simple scripting purposes. Note that although the codebase has a Main class, this isn't meant to be a standalone program, but instead a library for running JavaScript code.
|
||||
|
||||
## Example
|
||||
|
||||
The following will create a REPL using the engine as a backend. Not that this won't properly log errors. I recommend checking out the implementation in `Main.main`:
|
||||
|
||||
```java
|
||||
var engine = new PolyfillEngine(new File("."));
|
||||
var in = new BufferedReader(new InputStreamReader(System.in));
|
||||
var engine = new Engine(true /* false if you dont want debugging */);
|
||||
var env = new Environment(null, null, null);
|
||||
var debugger = new DebugServer();
|
||||
|
||||
// Create one target for the engine and start debugging server
|
||||
debugger.targets.put("target", (socket, req) -> new SimpleDebugger(socket, engine));
|
||||
debugger.start(new InetSocketAddress("127.0.0.1", 9229), true);
|
||||
|
||||
// Queue code to load internal libraries and start engine
|
||||
engine.pushMsg(false, null, new Internals().getApplier(env));
|
||||
engine.start();
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
var raw = in.readLine();
|
||||
var raw = Reading.read();
|
||||
if (raw == null) break;
|
||||
|
||||
var res = engine.pushMsg(false, engine.global(), Map.of(), "<stdio>", raw, null).await();
|
||||
Values.printValue(engine.context(), res);
|
||||
System.out.println();
|
||||
// Push a message to the engine with the raw REPL code
|
||||
var res = engine.pushMsg(
|
||||
false, new Context(engine).pushEnv(env),
|
||||
new Filename("jscript", "repl.js"), raw, null
|
||||
).await();
|
||||
|
||||
Values.printValue(null, res);
|
||||
}
|
||||
catch (EngineException e) {
|
||||
try {
|
||||
System.out.println("Uncaught " + e.toString(engine.context()));
|
||||
}
|
||||
catch (InterruptedException _e) { return; }
|
||||
catch (EngineException e) { Values.printError(e, ""); }
|
||||
catch (SyntaxException ex) {
|
||||
System.out.println("Syntax error:" + ex.msg);
|
||||
}
|
||||
catch (IOException | InterruptedException e) { return; }
|
||||
catch (IOException e) { }
|
||||
}
|
||||
```
|
||||
|
||||
7
build.js
7
build.js
@@ -1,7 +1,7 @@
|
||||
const { spawn } = require('child_process');
|
||||
const fs = require('fs/promises');
|
||||
const pt = require('path');
|
||||
const { argv } = require('process');
|
||||
const { argv, exit } = require('process');
|
||||
|
||||
const conf = {
|
||||
name: "java-jscript",
|
||||
@@ -10,8 +10,6 @@ const conf = {
|
||||
version: argv[3]
|
||||
};
|
||||
|
||||
console.log(conf)
|
||||
|
||||
if (conf.version.startsWith('refs/tags/')) conf.version = conf.version.substring(10);
|
||||
if (conf.version.startsWith('v')) conf.version = conf.version.substring(1);
|
||||
|
||||
@@ -76,6 +74,7 @@ async function compileJava() {
|
||||
}
|
||||
catch (e) {
|
||||
if (argv[2] === 'debug') throw e;
|
||||
else console.log(e.toString());
|
||||
console.log(e.toString());
|
||||
exit(-1);
|
||||
}
|
||||
})();
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -46,7 +46,15 @@ public class Filename {
|
||||
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package me.topchetoeu.jscript;
|
||||
|
||||
public class Location implements Comparable<Location> {
|
||||
public static final Location INTERNAL = new Location(0, 0, new Filename("jscript", "internal"));
|
||||
public static final Location INTERNAL = new Location(0, 0, new Filename("jscript", "native"));
|
||||
private int line;
|
||||
private int start;
|
||||
private Filename filename;
|
||||
|
||||
@@ -1,107 +1,165 @@
|
||||
package me.topchetoeu.jscript;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.Engine;
|
||||
import me.topchetoeu.jscript.engine.Environment;
|
||||
import me.topchetoeu.jscript.engine.debug.DebugServer;
|
||||
import me.topchetoeu.jscript.engine.debug.SimpleDebugger;
|
||||
import me.topchetoeu.jscript.engine.values.NativeFunction;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.events.Observer;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.exceptions.InterruptException;
|
||||
import me.topchetoeu.jscript.exceptions.SyntaxException;
|
||||
import me.topchetoeu.jscript.exceptions.UncheckedException;
|
||||
import me.topchetoeu.jscript.lib.Internals;
|
||||
|
||||
public class Main {
|
||||
static Thread engineTask, debugTask;
|
||||
static Engine engine;
|
||||
static Environment env;
|
||||
static int j = 0;
|
||||
|
||||
private static Observer<Object> valuePrinter = new Observer<Object>() {
|
||||
public void next(Object data) {
|
||||
Values.printValue(null, data);
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
public void error(RuntimeException err) {
|
||||
Values.printError(err, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
engineTask.interrupt();
|
||||
}
|
||||
};
|
||||
|
||||
public static void main(String args[]) {
|
||||
System.out.println(String.format("Running %s v%s by %s", Metadata.NAME, Metadata.VERSION, Metadata.AUTHOR));
|
||||
engine = new Engine();
|
||||
|
||||
env = new Environment(null, null, null);
|
||||
var exited = new boolean[1];
|
||||
var server = new DebugServer();
|
||||
server.targets.put("target", (ws, req) -> SimpleDebugger.get(ws, engine));
|
||||
|
||||
engine.pushMsg(false, null, new NativeFunction((ctx, thisArg, _a) -> {
|
||||
new Internals().apply(env);
|
||||
|
||||
env.global.define("exit", _ctx -> {
|
||||
exited[0] = true;
|
||||
throw new InterruptException();
|
||||
});
|
||||
env.global.define("go", _ctx -> {
|
||||
try {
|
||||
var f = Path.of("do.js");
|
||||
var func = _ctx.compile(new Filename("do", "do/" + j++ + ".js"), new String(Files.readAllBytes(f)));
|
||||
return func.call(_ctx);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new EngineException("Couldn't open do.js");
|
||||
}
|
||||
});
|
||||
|
||||
return null;
|
||||
}), null);
|
||||
|
||||
engineTask = engine.start();
|
||||
debugTask = server.start(new InetSocketAddress("127.0.0.1", 9229), true);
|
||||
|
||||
var reader = new Thread(() -> {
|
||||
try {
|
||||
for (var i = 0; ; i++) {
|
||||
try {
|
||||
var raw = Reading.read();
|
||||
|
||||
if (raw == null) break;
|
||||
valuePrinter.next(engine.pushMsg(false, new Context(engine).pushEnv(env), new Filename("jscript", "repl/" + i + ".js"), raw, null).await());
|
||||
}
|
||||
catch (EngineException e) { Values.printError(e, ""); }
|
||||
}
|
||||
}
|
||||
catch (IOException e) { return; }
|
||||
catch (SyntaxException ex) {
|
||||
if (exited[0]) return;
|
||||
System.out.println("Syntax error:" + ex.msg);
|
||||
}
|
||||
catch (RuntimeException ex) {
|
||||
if (!exited[0]) {
|
||||
System.out.println("Internal error ocurred:");
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
catch (Throwable e) { throw new UncheckedException(e); }
|
||||
if (exited[0]) debugTask.interrupt();
|
||||
});
|
||||
reader.setDaemon(true);
|
||||
reader.setName("STD Reader");
|
||||
reader.start();
|
||||
}
|
||||
}
|
||||
package me.topchetoeu.jscript;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.Engine;
|
||||
import me.topchetoeu.jscript.engine.Environment;
|
||||
import me.topchetoeu.jscript.engine.debug.DebugServer;
|
||||
import me.topchetoeu.jscript.engine.debug.SimpleDebugger;
|
||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.events.Observer;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.exceptions.InterruptException;
|
||||
import me.topchetoeu.jscript.exceptions.SyntaxException;
|
||||
import me.topchetoeu.jscript.filesystem.MemoryFilesystem;
|
||||
import me.topchetoeu.jscript.filesystem.Mode;
|
||||
import me.topchetoeu.jscript.filesystem.PhysicalFilesystem;
|
||||
import me.topchetoeu.jscript.lib.Internals;
|
||||
|
||||
public class Main {
|
||||
public static class Printer implements Observer<Object> {
|
||||
public void next(Object data) {
|
||||
Values.printValue(null, data);
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
public void error(RuntimeException err) {
|
||||
Values.printError(err, null);
|
||||
}
|
||||
|
||||
public void finish() {
|
||||
engineTask.interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
static Thread engineTask, debugTask;
|
||||
static Engine engine = new Engine(true);
|
||||
static DebugServer debugServer = new DebugServer();
|
||||
static Environment environment = new Environment(null, null, null);
|
||||
|
||||
static int j = 0;
|
||||
static boolean exited = false;
|
||||
static String[] args;
|
||||
|
||||
private static void reader() {
|
||||
try {
|
||||
for (var arg : args) {
|
||||
try {
|
||||
if (arg.equals("--ts")) initTypescript();
|
||||
else {
|
||||
var file = Path.of(arg);
|
||||
var raw = Files.readString(file);
|
||||
var res = engine.pushMsg(
|
||||
false, new Context(engine, environment),
|
||||
Filename.fromFile(file.toFile()),
|
||||
raw, null
|
||||
).await();
|
||||
Values.printValue(null, res);
|
||||
System.out.println();
|
||||
}
|
||||
}
|
||||
catch (EngineException e) { Values.printError(e, null); }
|
||||
}
|
||||
for (var i = 0; ; i++) {
|
||||
try {
|
||||
var raw = Reading.read();
|
||||
|
||||
if (raw == null) break;
|
||||
var res = engine.pushMsg(
|
||||
false, new Context(engine, environment),
|
||||
new Filename("jscript", "repl/" + i + ".js"),
|
||||
raw, null
|
||||
).await();
|
||||
Values.printValue(null, res);
|
||||
System.out.println();
|
||||
}
|
||||
catch (EngineException e) { Values.printError(e, null); }
|
||||
catch (SyntaxException e) { Values.printError(e, null); }
|
||||
}
|
||||
}
|
||||
catch (IOException e) {
|
||||
System.out.println(e.toString());
|
||||
exited = true;
|
||||
}
|
||||
catch (RuntimeException ex) {
|
||||
if (!exited) {
|
||||
System.out.println("Internal error ocurred:");
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (exited) {
|
||||
debugTask.interrupt();
|
||||
engineTask.interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
private static void initEnv() {
|
||||
environment = Internals.apply(environment);
|
||||
|
||||
environment.global.define("exit", _ctx -> {
|
||||
exited = true;
|
||||
throw new InterruptException();
|
||||
});
|
||||
environment.global.define("go", _ctx -> {
|
||||
try {
|
||||
var f = Path.of("do.js");
|
||||
var func = _ctx.compile(new Filename("do", "do/" + j++ + ".js"), new String(Files.readAllBytes(f)));
|
||||
return func.call(_ctx);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new EngineException("Couldn't open do.js");
|
||||
}
|
||||
});
|
||||
|
||||
environment.filesystem.protocols.put("temp", new MemoryFilesystem(Mode.READ_WRITE));
|
||||
environment.filesystem.protocols.put("file", new PhysicalFilesystem(Path.of(".").toAbsolutePath()));
|
||||
}
|
||||
private static void initEngine() {
|
||||
debugServer.targets.put("target", (ws, req) -> new SimpleDebugger(ws, engine));
|
||||
engineTask = engine.start();
|
||||
debugTask = debugServer.start(new InetSocketAddress("127.0.0.1", 9229), true);
|
||||
}
|
||||
private static void initTypescript() {
|
||||
try {
|
||||
var tsEnv = Internals.apply(new Environment(null, null, null));
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,20 @@
|
||||
package me.topchetoeu.jscript;
|
||||
|
||||
public class Metadata {
|
||||
public static final String VERSION = "${VERSION}";
|
||||
public static final String AUTHOR = "${AUTHOR}";
|
||||
public static final String NAME = "${NAME}";
|
||||
private static final String VERSION = "${VERSION}";
|
||||
private static final String AUTHOR = "${AUTHOR}";
|
||||
private static final String NAME = "${NAME}";
|
||||
|
||||
public static String version() {
|
||||
if (VERSION.equals("$" + "{VERSION}")) return "1337-devel";
|
||||
else return VERSION;
|
||||
}
|
||||
public static String author() {
|
||||
if (AUTHOR.equals("$" + "{AUTHOR}")) return "anonymous";
|
||||
else return AUTHOR;
|
||||
}
|
||||
public static String name() {
|
||||
if (NAME.equals("$" + "{NAME}")) return "some-product";
|
||||
else return NAME;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,9 +24,7 @@ public class CompoundStatement extends Statement {
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
for (var stm : statements) {
|
||||
if (stm instanceof FunctionStatement) {
|
||||
int start = target.size();
|
||||
((FunctionStatement)stm).compile(target, scope, null, true);
|
||||
target.setDebug(start);
|
||||
target.add(Instruction.discard());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,8 +251,8 @@ public class Instruction {
|
||||
return new Instruction(null, Type.TYPEOF, varName);
|
||||
}
|
||||
|
||||
public static Instruction keys() {
|
||||
return new Instruction(null, Type.KEYS);
|
||||
public static Instruction keys(boolean forInFormat) {
|
||||
return new Instruction(null, Type.KEYS, forInFormat);
|
||||
}
|
||||
|
||||
public static Instruction defProp() {
|
||||
|
||||
@@ -12,6 +12,7 @@ public class ForInStatement extends Statement {
|
||||
public final boolean isDeclaration;
|
||||
public final Statement varValue, object, body;
|
||||
public final String label;
|
||||
public final Location varLocation;
|
||||
|
||||
@Override
|
||||
public void declare(ScopeRecord globScope) {
|
||||
@@ -22,6 +23,8 @@ public class ForInStatement extends Statement {
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
var key = scope.getKey(varName);
|
||||
|
||||
int first = target.size();
|
||||
if (key instanceof String) target.add(Instruction.makeVar((String)key));
|
||||
|
||||
if (varValue != null) {
|
||||
@@ -29,45 +32,37 @@ public class ForInStatement extends Statement {
|
||||
target.add(Instruction.storeVar(scope.getKey(varName)));
|
||||
}
|
||||
|
||||
object.compile(target, scope, true);
|
||||
target.add(Instruction.keys());
|
||||
|
||||
object.compileWithDebug(target, scope, true);
|
||||
target.add(Instruction.keys(true));
|
||||
|
||||
int start = target.size();
|
||||
target.add(Instruction.dup());
|
||||
target.add(Instruction.loadMember("length"));
|
||||
target.add(Instruction.loadValue(0));
|
||||
target.add(Instruction.operation(Operation.LESS_EQUALS));
|
||||
target.add(Instruction.loadValue(null));
|
||||
target.add(Instruction.operation(Operation.EQUALS));
|
||||
int mid = target.size();
|
||||
target.add(Instruction.nop());
|
||||
|
||||
target.add(Instruction.dup());
|
||||
target.add(Instruction.dup());
|
||||
target.add(Instruction.loadMember("length"));
|
||||
target.add(Instruction.loadValue(1));
|
||||
target.add(Instruction.operation(Operation.SUBTRACT));
|
||||
target.add(Instruction.dup(1, 2));
|
||||
target.add(Instruction.loadValue("length"));
|
||||
target.add(Instruction.dup(1, 2));
|
||||
target.add(Instruction.storeMember());
|
||||
target.add(Instruction.loadMember());
|
||||
target.add(Instruction.loadMember("value").locate(varLocation));
|
||||
target.setDebug();
|
||||
target.add(Instruction.storeVar(key));
|
||||
|
||||
for (var i = start; i < target.size(); i++) target.get(i).locate(loc());
|
||||
|
||||
body.compileWithDebug(target, scope, false);
|
||||
|
||||
int end = target.size();
|
||||
|
||||
WhileStatement.replaceBreaks(target, label, mid + 1, end, start, end + 1);
|
||||
|
||||
target.add(Instruction.jmp(start - end).locate(loc()));
|
||||
target.add(Instruction.discard().locate(loc()));
|
||||
target.set(mid, Instruction.jmpIf(end - mid + 1).locate(loc()));
|
||||
if (pollute) target.add(Instruction.loadValue(null).locate(loc()));
|
||||
target.add(Instruction.jmp(start - end));
|
||||
target.add(Instruction.discard());
|
||||
target.set(mid, Instruction.jmpIf(end - mid + 1));
|
||||
if (pollute) target.add(Instruction.loadValue(null));
|
||||
target.get(first).locate(loc());
|
||||
target.setDebug(first);
|
||||
}
|
||||
|
||||
public ForInStatement(Location loc, String label, boolean isDecl, String varName, Statement varValue, Statement object, Statement body) {
|
||||
public ForInStatement(Location loc, Location varLocation, String label, boolean isDecl, String varName, Statement varValue, Statement object, Statement body) {
|
||||
super(loc);
|
||||
this.varLocation = varLocation;
|
||||
this.label = label;
|
||||
this.isDeclaration = isDecl;
|
||||
this.varName = varName;
|
||||
|
||||
@@ -33,8 +33,8 @@ public class SwitchStatement extends Statement {
|
||||
|
||||
@Override
|
||||
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
|
||||
var caseMap = new HashMap<Integer, Integer>();
|
||||
var stmIndexMap = new HashMap<Integer, Integer>();
|
||||
var caseToStatement = new HashMap<Integer, Integer>();
|
||||
var statementToIndex = new HashMap<Integer, Integer>();
|
||||
|
||||
value.compile(target, scope, true);
|
||||
|
||||
@@ -42,8 +42,8 @@ public class SwitchStatement extends Statement {
|
||||
target.add(Instruction.dup().locate(loc()));
|
||||
ccase.value.compile(target, scope, true);
|
||||
target.add(Instruction.operation(Operation.EQUALS).locate(loc()));
|
||||
caseMap.put(target.size(), ccase.statementI);
|
||||
target.add(Instruction.nop());
|
||||
caseToStatement.put(target.size(), ccase.statementI);
|
||||
target.add(Instruction.nop().locate(ccase.value.loc()));
|
||||
}
|
||||
|
||||
int start = target.size();
|
||||
@@ -51,28 +51,31 @@ public class SwitchStatement extends Statement {
|
||||
target.add(Instruction.nop());
|
||||
|
||||
for (var stm : body) {
|
||||
stmIndexMap.put(stmIndexMap.size(), target.size());
|
||||
statementToIndex.put(statementToIndex.size(), target.size());
|
||||
stm.compileWithDebug(target, scope, false);
|
||||
}
|
||||
|
||||
if (defaultI < 0 || defaultI >= body.length) target.set(start, Instruction.jmp(target.size() - start).locate(loc()));
|
||||
else target.set(start, Instruction.jmp(stmIndexMap.get(defaultI) - start)).locate(loc());
|
||||
int end = target.size();
|
||||
target.add(Instruction.discard().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);
|
||||
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 i = stmIndexMap.get(el.getValue());
|
||||
if (i == null) i = target.size();
|
||||
var i = statementToIndex.get(el.getValue());
|
||||
if (i == null) i = end;
|
||||
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) {
|
||||
|
||||
@@ -15,8 +15,12 @@ public class ChangeStatement extends Statement {
|
||||
|
||||
@Override
|
||||
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()));
|
||||
else if (postfix) {
|
||||
target.add(Instruction.loadValue(addAmount));
|
||||
target.add(Instruction.operation(Operation.SUBTRACT));
|
||||
}
|
||||
}
|
||||
|
||||
public ChangeStatement(Location loc, AssignableStatement value, double addAmount, boolean postfix) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ public class LazyAndStatement extends Statement {
|
||||
if (pollute) target.add(Instruction.dup().locate(loc()));
|
||||
int start = target.size();
|
||||
target.add(Instruction.nop());
|
||||
target.add(Instruction.discard().locate(loc()));
|
||||
if (pollute) target.add(Instruction.discard().locate(loc()));
|
||||
second.compile(target, scope, pollute);
|
||||
target.set(start, Instruction.jmpIfNot(target.size() - start).locate(loc()));
|
||||
}
|
||||
|
||||
@@ -29,7 +29,7 @@ public class LazyOrStatement extends Statement {
|
||||
if (pollute) target.add(Instruction.dup().locate(loc()));
|
||||
int start = target.size();
|
||||
target.add(Instruction.nop());
|
||||
target.add(Instruction.discard().locate(loc()));
|
||||
if (pollute) target.add(Instruction.discard().locate(loc()));
|
||||
second.compile(target, scope, pollute);
|
||||
target.set(start, Instruction.jmpIf(target.size() - start).locate(loc()));
|
||||
}
|
||||
|
||||
@@ -20,15 +20,13 @@ public class VariableAssignStatement extends Statement {
|
||||
if (value instanceof FunctionStatement) ((FunctionStatement)value).compile(target, scope, name, false);
|
||||
else value.compile(target, scope, true);
|
||||
target.add(Instruction.operation(operation).locate(loc()));
|
||||
target.add(Instruction.storeVar(i, false).locate(loc()));
|
||||
target.add(Instruction.storeVar(i, pollute).locate(loc()));
|
||||
}
|
||||
else {
|
||||
if (value instanceof FunctionStatement) ((FunctionStatement)value).compile(target, scope, name, false);
|
||||
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) {
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
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.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.parsing.Parsing;
|
||||
|
||||
public class Context {
|
||||
private final Stack<Environment> env = new Stack<>();
|
||||
public final Data data;
|
||||
private final ArrayList<CodeFrame> frames = new ArrayList<>();
|
||||
public final Engine engine;
|
||||
|
||||
public Environment environment() {
|
||||
@@ -27,20 +33,79 @@ public class Context {
|
||||
}
|
||||
|
||||
public FunctionValue compile(Filename filename, String raw) {
|
||||
var src = Values.toString(this, environment().compile.call(this, null, raw, filename));
|
||||
var debugger = StackData.getDebugger(this);
|
||||
var env = environment();
|
||||
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>();
|
||||
var res = Parsing.compile(engine.functions, breakpoints, environment(), filename, src);
|
||||
if (debugger != null) debugger.onSource(filename, src, breakpoints);
|
||||
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 Context(Engine engine, Data data) {
|
||||
this.data = new Data(engine.data);
|
||||
if (data != null) this.data.addAll(data);
|
||||
|
||||
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) {
|
||||
this(engine, null);
|
||||
public Context(Engine engine, Environment env) {
|
||||
this(engine);
|
||||
this.pushEnv(env);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import java.util.Map;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public class Data {
|
||||
public final Data parent;
|
||||
private HashMap<DataKey<Object>, Object> data = new HashMap<>();
|
||||
|
||||
public Data copy() {
|
||||
@@ -33,19 +32,12 @@ public class Data {
|
||||
return this;
|
||||
}
|
||||
public <T> T get(DataKey<T> key, T val) {
|
||||
for (var it = this; it != null; it = it.parent) {
|
||||
if (it.data.containsKey(key)) {
|
||||
return (T)it.data.get((DataKey<Object>)key);
|
||||
}
|
||||
}
|
||||
|
||||
if (data.containsKey(key)) return (T)data.get((DataKey<Object>)key);
|
||||
set(key, val);
|
||||
return val;
|
||||
}
|
||||
public <T> T get(DataKey<T> key) {
|
||||
for (var it = this; it != null; it = it.parent) {
|
||||
if (it.data.containsKey(key)) return (T)it.data.get((DataKey<Object>)key);
|
||||
}
|
||||
if (data.containsKey(key)) return (T)data.get((DataKey<Object>)key);
|
||||
return null;
|
||||
}
|
||||
public boolean has(DataKey<?> key) { return data.containsKey(key); }
|
||||
@@ -61,11 +53,4 @@ public class Data {
|
||||
public int increase(DataKey<Integer> key) {
|
||||
return increase(key, 1, 0);
|
||||
}
|
||||
|
||||
public Data() {
|
||||
this.parent = null;
|
||||
}
|
||||
public Data(Data parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +1,22 @@
|
||||
package me.topchetoeu.jscript.engine;
|
||||
|
||||
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.engine.debug.DebugController;
|
||||
import me.topchetoeu.jscript.engine.frame.CodeFrame;
|
||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.events.Awaitable;
|
||||
import me.topchetoeu.jscript.events.DataNotifier;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.exceptions.InterruptException;
|
||||
|
||||
public class Engine {
|
||||
public class Engine implements DebugController {
|
||||
private class UncompiledFunction extends FunctionValue {
|
||||
public final Filename filename;
|
||||
public final String raw;
|
||||
@@ -29,30 +35,57 @@ public class Engine {
|
||||
}
|
||||
}
|
||||
|
||||
private static class Task {
|
||||
private static class Task implements Comparable<Task> {
|
||||
public final FunctionValue func;
|
||||
public final Object thisArg;
|
||||
public final Object[] args;
|
||||
public final DataNotifier<Object> notifier = new DataNotifier<>();
|
||||
public final Context ctx;
|
||||
public final boolean micro;
|
||||
|
||||
public Task(Context ctx, FunctionValue func, Object thisArg, Object[] args) {
|
||||
public Task(Context ctx, FunctionValue func, Object thisArg, Object[] args, boolean micro) {
|
||||
this.ctx = ctx;
|
||||
this.func = func;
|
||||
this.thisArg = thisArg;
|
||||
this.args = args;
|
||||
this.micro = micro;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Task other) {
|
||||
return Integer.compare(this.micro ? 0 : 1, other.micro ? 0 : 1);
|
||||
}
|
||||
}
|
||||
|
||||
private static int nextId = 0;
|
||||
|
||||
private Thread thread;
|
||||
private LinkedBlockingDeque<Task> macroTasks = new LinkedBlockingDeque<>();
|
||||
private LinkedBlockingDeque<Task> microTasks = new LinkedBlockingDeque<>();
|
||||
public static final HashMap<Long, FunctionBody> functions = new HashMap<>();
|
||||
|
||||
public final int id = ++nextId;
|
||||
public final HashMap<Long, FunctionBody> functions = new HashMap<>();
|
||||
public final Data data = new Data().set(StackData.MAX_FRAMES, 10000);
|
||||
public final boolean debugging;
|
||||
public int maxStackFrames = 10000;
|
||||
|
||||
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 {
|
||||
@@ -64,18 +97,12 @@ public class Engine {
|
||||
}
|
||||
}
|
||||
public void run(boolean untilEmpty) {
|
||||
while (!untilEmpty || !macroTasks.isEmpty()) {
|
||||
while (!untilEmpty || !tasks.isEmpty()) {
|
||||
try {
|
||||
runTask(macroTasks.take());
|
||||
|
||||
while (!microTasks.isEmpty()) {
|
||||
runTask(microTasks.take());
|
||||
}
|
||||
runTask(tasks.take());
|
||||
}
|
||||
catch (InterruptedException | InterruptException e) {
|
||||
for (var msg : macroTasks) {
|
||||
msg.notifier.error(new InterruptException(e));
|
||||
}
|
||||
for (var msg : tasks) msg.notifier.error(new InterruptException(e));
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -100,12 +127,29 @@ public class Engine {
|
||||
}
|
||||
|
||||
public Awaitable<Object> pushMsg(boolean micro, Context ctx, FunctionValue func, Object thisArg, Object ...args) {
|
||||
var msg = new Task(ctx == null ? new Context(this) : ctx, func, thisArg, args);
|
||||
if (micro) microTasks.addLast(msg);
|
||||
else macroTasks.addLast(msg);
|
||||
var msg = new Task(ctx == null ? new Context(this) : ctx, func, thisArg, args, micro);
|
||||
tasks.add(msg);
|
||||
return msg.notifier;
|
||||
}
|
||||
public Awaitable<Object> pushMsg(boolean micro, Context ctx, Filename filename, String raw, Object thisArg, Object ...args) {
|
||||
return pushMsg(micro, ctx, new UncompiledFunction(filename, raw), thisArg, args);
|
||||
}
|
||||
|
||||
@Override public void onFramePop(Context ctx, CodeFrame frame) {
|
||||
if (debugging && debugger != null) debugger.onFramePop(ctx, frame);
|
||||
}
|
||||
@Override public boolean onInstruction(Context ctx, CodeFrame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught) {
|
||||
if (debugging && debugger != null) return debugger.onInstruction(ctx, frame, instruction, returnVal, error, caught);
|
||||
else return false;
|
||||
}
|
||||
@Override public void onSource(Filename filename, String source, TreeSet<Location> breakpoints) {
|
||||
if (!debugging) return;
|
||||
if (debugger != null) debugger.onSource(filename, source, breakpoints);
|
||||
sources.put(filename, source);
|
||||
bpts.put(filename, breakpoints);
|
||||
}
|
||||
|
||||
public Engine(boolean debugging) {
|
||||
this.debugging = debugging;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,19 +8,28 @@ import me.topchetoeu.jscript.engine.values.NativeFunction;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.Symbol;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.filesystem.RootFilesystem;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
import me.topchetoeu.jscript.interop.NativeSetter;
|
||||
import me.topchetoeu.jscript.interop.NativeWrapperProvider;
|
||||
import me.topchetoeu.jscript.permissions.Permission;
|
||||
import me.topchetoeu.jscript.permissions.PermissionsProvider;
|
||||
|
||||
public class Environment {
|
||||
public class Environment implements PermissionsProvider {
|
||||
private HashMap<String, ObjectValue> prototypes = new HashMap<>();
|
||||
|
||||
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 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 regexConstructor = new NativeFunction("RegExp", (ctx, thisArg, args) -> {
|
||||
@@ -40,8 +49,7 @@ public class Environment {
|
||||
}
|
||||
|
||||
@Native public Symbol symbol(String name) {
|
||||
if (symbols.containsKey(name))
|
||||
return symbols.get(name);
|
||||
if (symbols.containsKey(name)) return symbols.get(name);
|
||||
else {
|
||||
var res = new Symbol(name);
|
||||
symbols.put(name, res);
|
||||
@@ -57,7 +65,8 @@ public class Environment {
|
||||
}
|
||||
|
||||
@Native public Environment fork() {
|
||||
var res = new Environment(compile, wrappers, global);
|
||||
var res = new Environment(compile, null, global);
|
||||
res.wrappers = wrappers.fork(res);
|
||||
res.regexConstructor = regexConstructor;
|
||||
res.prototypes = new HashMap<>(prototypes);
|
||||
return res;
|
||||
@@ -68,9 +77,13 @@ public class Environment {
|
||||
return res;
|
||||
}
|
||||
|
||||
public Context context(Engine engine, Data data) {
|
||||
return new Context(engine, data).pushEnv(this);
|
||||
@Override public boolean hasPermission(Permission perm, char delim) {
|
||||
return permissions == null || permissions.hasPermission(perm, delim);
|
||||
}
|
||||
@Override public boolean hasPermission(Permission perm) {
|
||||
return permissions == null || permissions.hasPermission(perm);
|
||||
}
|
||||
|
||||
public Context context(Engine engine) {
|
||||
return new Context(engine).pushEnv(this);
|
||||
}
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
package me.topchetoeu.jscript.engine;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.engine.debug.Debugger;
|
||||
import me.topchetoeu.jscript.engine.frame.CodeFrame;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
|
||||
public class StackData {
|
||||
public static final DataKey<ArrayList<CodeFrame>> FRAMES = new DataKey<>();
|
||||
public static final DataKey<Integer> MAX_FRAMES = new DataKey<>();
|
||||
public static final DataKey<Debugger> DEBUGGER = new DataKey<>();
|
||||
|
||||
public static void pushFrame(Context ctx, CodeFrame frame) {
|
||||
var frames = ctx.data.get(FRAMES, new ArrayList<>());
|
||||
frames.add(frame);
|
||||
if (frames.size() > ctx.data.get(MAX_FRAMES, 10000)) throw EngineException.ofRange("Stack overflow!");
|
||||
ctx.pushEnv(frame.function.environment);
|
||||
}
|
||||
public static boolean popFrame(Context ctx, CodeFrame frame) {
|
||||
var frames = ctx.data.get(FRAMES, new ArrayList<>());
|
||||
if (frames.size() == 0) return false;
|
||||
if (frames.get(frames.size() - 1) != frame) return false;
|
||||
frames.remove(frames.size() - 1);
|
||||
ctx.popEnv();
|
||||
var dbg = getDebugger(ctx);
|
||||
if (dbg != null) dbg.onFramePop(ctx, frame);
|
||||
return true;
|
||||
}
|
||||
public static CodeFrame peekFrame(Context ctx) {
|
||||
var frames = ctx.data.get(FRAMES, new ArrayList<>());
|
||||
if (frames.size() == 0) return null;
|
||||
return frames.get(frames.size() - 1);
|
||||
}
|
||||
|
||||
public static List<CodeFrame> frames(Context ctx) {
|
||||
return Collections.unmodifiableList(ctx.data.get(FRAMES, new ArrayList<>()));
|
||||
}
|
||||
public static List<String> stackTrace(Context ctx) {
|
||||
var res = new ArrayList<String>();
|
||||
var frames = frames(ctx);
|
||||
|
||||
for (var i = frames.size() - 1; i >= 0; i--) {
|
||||
var el = frames.get(i);
|
||||
var name = el.function.name;
|
||||
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 (!trace.equals("")) res.add(trace);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public static Debugger getDebugger(Context ctx) {
|
||||
return ctx.data.get(DEBUGGER);
|
||||
}
|
||||
}
|
||||
@@ -7,4 +7,6 @@ public interface WrappersProvider {
|
||||
public ObjectValue getProto(Class<?> obj);
|
||||
public ObjectValue getNamespace(Class<?> obj);
|
||||
public FunctionValue getConstr(Class<?> obj);
|
||||
|
||||
public WrappersProvider fork(Environment env);
|
||||
}
|
||||
|
||||
@@ -37,7 +37,4 @@ public interface DebugController {
|
||||
* @param frame The code frame which was popped out
|
||||
*/
|
||||
void onFramePop(Context ctx, CodeFrame frame);
|
||||
|
||||
void connect();
|
||||
void disconnect();
|
||||
}
|
||||
|
||||
@@ -25,11 +25,11 @@ public interface DebugHandler {
|
||||
|
||||
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 nodeWorkerEnable(V8Message msg);
|
||||
void runtimeEnable(V8Message msg);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ 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;
|
||||
@@ -18,11 +19,12 @@ import me.topchetoeu.jscript.json.JSONList;
|
||||
import me.topchetoeu.jscript.json.JSONMap;
|
||||
|
||||
public class DebugServer {
|
||||
public static String browserDisplayName = Metadata.NAME + "/" + Metadata.VERSION;
|
||||
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());
|
||||
@@ -59,7 +61,6 @@ public class DebugServer {
|
||||
|
||||
try {
|
||||
msg = new V8Message(raw.textData());
|
||||
// System.out.println(msg.name + ": " + JSON.stringify(msg.params));
|
||||
}
|
||||
catch (SyntaxException e) {
|
||||
ws.send(new V8Error(e.getMessage()));
|
||||
@@ -68,7 +69,9 @@ public class DebugServer {
|
||||
|
||||
try {
|
||||
switch (msg.name) {
|
||||
case "Debugger.enable": debugger.enable(msg); continue;
|
||||
case "Debugger.enable":
|
||||
connNotifier.next();
|
||||
debugger.enable(msg); continue;
|
||||
case "Debugger.disable": debugger.disable(msg); continue;
|
||||
|
||||
case "Debugger.setBreakpoint": debugger.setBreakpoint(msg); continue;
|
||||
@@ -90,6 +93,7 @@ public class DebugServer {
|
||||
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;
|
||||
@@ -151,6 +155,10 @@ public class DebugServer {
|
||||
}, "Debug Handler");
|
||||
}
|
||||
|
||||
public void awaitConnection() {
|
||||
connNotifier.await();
|
||||
}
|
||||
|
||||
public void run(InetSocketAddress address) {
|
||||
try {
|
||||
ServerSocket server = new ServerSocket();
|
||||
@@ -228,9 +236,9 @@ public class DebugServer {
|
||||
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)
|
||||
.replace("${NAME}", Metadata.name())
|
||||
.replace("${VERSION}", Metadata.version())
|
||||
.replace("${AUTHOR}", Metadata.author())
|
||||
.getBytes();
|
||||
}
|
||||
catch (IOException e) { throw new UncheckedIOException(e); }
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package me.topchetoeu.jscript.engine.debug;
|
||||
|
||||
public interface Debugger extends DebugHandler, DebugController {
|
||||
|
||||
void connect();
|
||||
void disconnect();
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Instruction.Type;
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.Engine;
|
||||
import me.topchetoeu.jscript.engine.StackData;
|
||||
import me.topchetoeu.jscript.engine.frame.CodeFrame;
|
||||
import me.topchetoeu.jscript.engine.frame.Runners;
|
||||
import me.topchetoeu.jscript.engine.scope.GlobalScope;
|
||||
@@ -31,15 +30,16 @@ import me.topchetoeu.jscript.json.JSON;
|
||||
import me.topchetoeu.jscript.json.JSONElement;
|
||||
import me.topchetoeu.jscript.json.JSONList;
|
||||
import me.topchetoeu.jscript.json.JSONMap;
|
||||
import me.topchetoeu.jscript.lib.DateLib;
|
||||
import me.topchetoeu.jscript.lib.MapLib;
|
||||
import me.topchetoeu.jscript.lib.PromiseLib;
|
||||
import me.topchetoeu.jscript.lib.RegExpLib;
|
||||
import me.topchetoeu.jscript.lib.SetLib;
|
||||
import me.topchetoeu.jscript.lib.GeneratorLib.Generator;
|
||||
|
||||
// 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,
|
||||
@@ -98,7 +98,7 @@ public class SimpleDebugger implements Debugger {
|
||||
public CodeFrame frame;
|
||||
public CodeFunction func;
|
||||
public int id;
|
||||
public ObjectValue local, capture, global;
|
||||
public ObjectValue local, capture, global, valstack;
|
||||
public JSONMap serialized;
|
||||
public Location location;
|
||||
public boolean debugData = false;
|
||||
@@ -112,7 +112,6 @@ public class SimpleDebugger implements Debugger {
|
||||
this.frame = frame;
|
||||
this.func = frame.function;
|
||||
this.id = id;
|
||||
this.local = new ObjectValue();
|
||||
this.location = frame.function.loc();
|
||||
|
||||
this.global = frame.function.environment.global.obj;
|
||||
@@ -120,6 +119,7 @@ public class SimpleDebugger implements Debugger {
|
||||
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;
|
||||
@@ -131,13 +131,13 @@ public class SimpleDebugger implements Debugger {
|
||||
.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)))
|
||||
)
|
||||
.setNull("this");
|
||||
.add(new JSONMap().set("type", "other").set("name", "Value Stack").set("object", serializeObj(ctx, valstack)))
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class RunResult {
|
||||
private static class RunResult {
|
||||
public final Context ctx;
|
||||
public final Object result;
|
||||
public final EngineException error;
|
||||
@@ -156,6 +156,8 @@ public class SimpleDebugger implements Debugger {
|
||||
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<>();
|
||||
@@ -185,7 +187,7 @@ public class SimpleDebugger implements Debugger {
|
||||
}
|
||||
|
||||
private void updateFrames(Context ctx) {
|
||||
var frame = StackData.peekFrame(ctx);
|
||||
var frame = ctx.peekFrame();
|
||||
if (frame == null) return;
|
||||
|
||||
if (!codeFrameToFrame.containsKey(frame)) {
|
||||
@@ -200,7 +202,7 @@ public class SimpleDebugger implements Debugger {
|
||||
}
|
||||
private JSONList serializeFrames(Context ctx) {
|
||||
var res = new JSONList();
|
||||
var frames = StackData.frames(ctx);
|
||||
var frames = ctx.frames();
|
||||
|
||||
for (var i = frames.size() - 1; i >= 0; i--) {
|
||||
res.add(codeFrameToFrame.get(frames.get(i)).serialized);
|
||||
@@ -249,7 +251,7 @@ public class SimpleDebugger implements Debugger {
|
||||
return id;
|
||||
}
|
||||
}
|
||||
private JSONMap serializeObj(Context ctx, Object val, boolean recurse) {
|
||||
private JSONMap serializeObj(Context ctx, Object val) {
|
||||
val = Values.normalize(null, val);
|
||||
|
||||
if (val == Values.NULL) {
|
||||
@@ -270,13 +272,6 @@ public class SimpleDebugger implements Debugger {
|
||||
if (obj instanceof FunctionValue) type = "function";
|
||||
if (obj instanceof ArrayValue) subtype = "array";
|
||||
|
||||
if (Values.isWrapper(val, RegExpLib.class)) subtype = "regexp";
|
||||
if (Values.isWrapper(val, DateLib.class)) subtype = "date";
|
||||
if (Values.isWrapper(val, MapLib.class)) subtype = "map";
|
||||
if (Values.isWrapper(val, SetLib.class)) subtype = "set";
|
||||
if (Values.isWrapper(val, Generator.class)) subtype = "generator";
|
||||
if (Values.isWrapper(val, PromiseLib.class)) subtype = "promise";
|
||||
|
||||
try { className = Values.toString(ctx, Values.getMember(ctx, obj.getMember(ctx, "constructor"), "name")); }
|
||||
catch (Exception e) { }
|
||||
|
||||
@@ -304,6 +299,7 @@ public class SimpleDebugger implements Debugger {
|
||||
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);
|
||||
|
||||
@@ -312,9 +308,6 @@ public class SimpleDebugger implements Debugger {
|
||||
|
||||
throw new IllegalArgumentException("Unexpected JS object.");
|
||||
}
|
||||
private JSONMap serializeObj(Context ctx, Object val) {
|
||||
return serializeObj(ctx, val, true);
|
||||
}
|
||||
private void setObjectGroup(String name, Object val) {
|
||||
if (val instanceof ObjectValue) {
|
||||
var obj = (ObjectValue)val;
|
||||
@@ -405,7 +398,7 @@ public class SimpleDebugger implements Debugger {
|
||||
}
|
||||
|
||||
private RunResult run(Frame codeFrame, String code) {
|
||||
var engine = new Engine();
|
||||
var engine = new Engine(false);
|
||||
var env = codeFrame.func.environment.fork();
|
||||
|
||||
ObjectValue global = env.global.obj,
|
||||
@@ -417,7 +410,7 @@ public class SimpleDebugger implements Debugger {
|
||||
env.global = new GlobalScope(local);
|
||||
|
||||
var ctx = new Context(engine).pushEnv(env);
|
||||
var awaiter = engine.pushMsg(false, ctx, new Filename("temp", "exec"), code, codeFrame.frame.thisArg, codeFrame.frame.args);
|
||||
var awaiter = engine.pushMsg(false, ctx, new Filename("jscript", "eval"), code, codeFrame.frame.thisArg, codeFrame.frame.args);
|
||||
|
||||
engine.run(true);
|
||||
|
||||
@@ -481,13 +474,17 @@ public class SimpleDebugger implements Debugger {
|
||||
@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, null);
|
||||
var bpcd = new BreakpointCandidate(nextId(), regex, line, col, cond);
|
||||
idToBptCand.put(bpcd.id, bpcd);
|
||||
|
||||
var locs = new JSONList();
|
||||
@@ -587,16 +584,20 @@ public class SimpleDebugger implements Debugger {
|
||||
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 own = msg.params.bool("ownProperties") || true;
|
||||
var currOwn = true;
|
||||
|
||||
var res = new JSONList();
|
||||
var ctx = objectToCtx.get(obj);
|
||||
|
||||
while (obj != null) {
|
||||
var ctx = objectToCtx.get(obj);
|
||||
|
||||
if (obj != emptyObject) {
|
||||
for (var key : obj.keys(true)) {
|
||||
var propDesc = new JSONMap();
|
||||
|
||||
@@ -608,7 +609,7 @@ public class SimpleDebugger implements Debugger {
|
||||
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", currOwn);
|
||||
propDesc.set("isOwn", true);
|
||||
res.add(propDesc);
|
||||
}
|
||||
else {
|
||||
@@ -617,46 +618,79 @@ public class SimpleDebugger implements Debugger {
|
||||
propDesc.set("writable", obj.memberWritable(key));
|
||||
propDesc.set("enumerable", obj.memberEnumerable(key));
|
||||
propDesc.set("configurable", obj.memberConfigurable(key));
|
||||
propDesc.set("isOwn", currOwn);
|
||||
propDesc.set("isOwn", true);
|
||||
res.add(propDesc);
|
||||
}
|
||||
}
|
||||
|
||||
obj = obj.getPrototype(ctx);
|
||||
|
||||
|
||||
var proto = obj.getPrototype(ctx);
|
||||
|
||||
var protoDesc = new JSONMap();
|
||||
protoDesc.set("name", "__proto__");
|
||||
protoDesc.set("value", serializeObj(ctx, obj == null ? Values.NULL : obj));
|
||||
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", currOwn);
|
||||
protoDesc.set("isOwn", true);
|
||||
res.add(protoDesc);
|
||||
|
||||
currOwn = false;
|
||||
if (own) break;
|
||||
}
|
||||
|
||||
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 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);
|
||||
|
||||
switch (src) {
|
||||
case CHROME_GET_PROP_FUNC: {
|
||||
var path = JSON.parse(new Filename("tmp", "json"), (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;
|
||||
}
|
||||
default:
|
||||
ws.send(new V8Error("A non well-known function was used with callFunctionOn."));
|
||||
break;
|
||||
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
|
||||
@@ -668,8 +702,8 @@ public class SimpleDebugger implements Debugger {
|
||||
int id = nextId();
|
||||
var src = new Source(id, filename, source, locations);
|
||||
|
||||
filenameToId.put(filename, id);
|
||||
idToSource.put(id, src);
|
||||
filenameToId.put(filename, id);
|
||||
|
||||
for (var bpcd : idToBptCand.values()) {
|
||||
if (!bpcd.pattern.matcher(filename.toString()).matches()) continue;
|
||||
@@ -698,14 +732,15 @@ public class SimpleDebugger implements Debugger {
|
||||
returnVal != Runners.NO_RETURN
|
||||
);
|
||||
|
||||
if (error != null && !caught && StackData.frames(ctx).size() > 1) error = null;
|
||||
// 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));
|
||||
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);
|
||||
@@ -728,11 +763,7 @@ public class SimpleDebugger implements Debugger {
|
||||
break;
|
||||
case STEPPING_OVER:
|
||||
if (stepOutFrame.frame == frame.frame) {
|
||||
if (returnVal != Runners.NO_RETURN) {
|
||||
state = State.STEPPING_OUT;
|
||||
return false;
|
||||
}
|
||||
else if (isBreakpointable && (
|
||||
if (isBreakpointable && (
|
||||
!loc.filename().equals(prevLocation.filename()) ||
|
||||
loc.line() != prevLocation.line()
|
||||
)) pauseDebug(ctx, null);
|
||||
@@ -752,37 +783,31 @@ public class SimpleDebugger implements Debugger {
|
||||
try { idToFrame.remove(codeFrameToFrame.remove(frame).id); }
|
||||
catch (NullPointerException e) { }
|
||||
|
||||
if (StackData.frames(ctx).size() == 0) resume(State.RESUMED);
|
||||
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_OUT || state == State.STEPPING_IN || state == State.STEPPING_OVER)
|
||||
) {
|
||||
pauseDebug(ctx, null);
|
||||
updateNotifier.await();
|
||||
// if (state == State.STEPPING_OVER) state = State.STEPPING_IN;
|
||||
// else {
|
||||
pauseDebug(ctx, null);
|
||||
updateNotifier.await();
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
@Override public void connect() {
|
||||
target.data.set(StackData.DEBUGGER, this);
|
||||
if (!target.attachDebugger(this)) {
|
||||
ws.send(new V8Error("A debugger is already attached to this engine."));
|
||||
}
|
||||
}
|
||||
@Override public void disconnect() {
|
||||
target.data.remove(StackData.DEBUGGER);
|
||||
target.detachDebugger();
|
||||
enabled = false;
|
||||
updateNotifier.next();
|
||||
}
|
||||
|
||||
private SimpleDebugger(WebSocket ws, Engine target) {
|
||||
public SimpleDebugger(WebSocket ws, Engine target) {
|
||||
this.ws = ws;
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public static SimpleDebugger get(WebSocket ws, Engine target) {
|
||||
if (target.data.has(StackData.DEBUGGER)) {
|
||||
ws.send(new V8Error("A debugger is already attached to this engine."));
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
var res = new SimpleDebugger(ws, target);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package me.topchetoeu.jscript.engine.debug;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import me.topchetoeu.jscript.Filename;
|
||||
import me.topchetoeu.jscript.json.JSON;
|
||||
import me.topchetoeu.jscript.json.JSONElement;
|
||||
import me.topchetoeu.jscript.json.JSONMap;
|
||||
@@ -33,7 +32,7 @@ public class V8Message {
|
||||
this.params = raw.contains("params") ? raw.map("params") : new JSONMap();
|
||||
}
|
||||
public V8Message(String raw) {
|
||||
this(JSON.parse(new Filename("jscript", "json-msg"), raw).map());
|
||||
this(JSON.parse(null, raw).map());
|
||||
}
|
||||
|
||||
public JSONMap toMap() {
|
||||
|
||||
@@ -10,7 +10,7 @@ import me.topchetoeu.jscript.engine.debug.WebSocketMessage.Type;
|
||||
import me.topchetoeu.jscript.exceptions.UncheckedIOException;
|
||||
|
||||
public class WebSocket implements AutoCloseable {
|
||||
public long maxLength = 2000000;
|
||||
public long maxLength = 1 << 20;
|
||||
|
||||
private Socket socket;
|
||||
private boolean closed = false;
|
||||
@@ -61,39 +61,48 @@ public class WebSocket implements AutoCloseable {
|
||||
else return new byte[4];
|
||||
}
|
||||
|
||||
private void writeLength(long len) {
|
||||
private void writeLength(int len) {
|
||||
try {
|
||||
if (len < 126) {
|
||||
out().write((int)len);
|
||||
}
|
||||
else if (len < 0xFFFF) {
|
||||
else if (len <= 0xFFFF) {
|
||||
out().write(126);
|
||||
out().write((int)(len >> 8) & 0xFF);
|
||||
out().write((int)len & 0xFF);
|
||||
}
|
||||
else {
|
||||
out().write(127);
|
||||
out().write((int)(len >> 56) & 0xFF);
|
||||
out().write((int)(len >> 48) & 0xFF);
|
||||
out().write((int)(len >> 40) & 0xFF);
|
||||
out().write((int)(len >> 32) & 0xFF);
|
||||
out().write((int)(len >> 24) & 0xFF);
|
||||
out().write((int)(len >> 16) & 0xFF);
|
||||
out().write((int)(len >> 8) & 0xFF);
|
||||
out().write((int)len & 0xFF);
|
||||
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 {
|
||||
out().write(type | 0x80);
|
||||
writeLength(data.length);
|
||||
for (int i = 0; i < data.length; i++) {
|
||||
out().write(data[i]);
|
||||
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);
|
||||
}
|
||||
catch (IOException e) { throw new UncheckedIOException(e); }
|
||||
}
|
||||
|
||||
public void send(String data) {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
package me.topchetoeu.jscript.engine.frame;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Stack;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.StackData;
|
||||
import me.topchetoeu.jscript.engine.scope.LocalScope;
|
||||
import me.topchetoeu.jscript.engine.scope.ValueVariable;
|
||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||
@@ -17,7 +17,7 @@ import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.exceptions.InterruptException;
|
||||
|
||||
public class CodeFrame {
|
||||
private class TryCtx {
|
||||
public static class TryCtx {
|
||||
public static final int STATE_TRY = 0;
|
||||
public static final int STATE_CATCH = 1;
|
||||
public static final int STATE_FINALLY_THREW = 2;
|
||||
@@ -84,9 +84,30 @@ public class CodeFrame {
|
||||
|
||||
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) {
|
||||
var res = new TryCtx(codePtr + 1, n, catchN, finallyN);
|
||||
if (!tryStack.empty()) res.err = tryStack.peek().err;
|
||||
|
||||
tryStack.add(res);
|
||||
}
|
||||
@@ -125,31 +146,33 @@ public class CodeFrame {
|
||||
}
|
||||
|
||||
private void setCause(Context ctx, EngineException err, EngineException cause) {
|
||||
err.cause = cause;
|
||||
}
|
||||
private Object nextNoTry(Context ctx, Instruction instr) {
|
||||
if (Thread.currentThread().isInterrupted()) throw new InterruptException();
|
||||
if (codePtr < 0 || codePtr >= function.body.length) return null;
|
||||
|
||||
try {
|
||||
this.jumpFlag = false;
|
||||
return Runners.exec(ctx, instr, this);
|
||||
}
|
||||
catch (EngineException e) {
|
||||
throw e.add(function.name, prevLoc).setCtx(function.environment, ctx.engine);
|
||||
}
|
||||
err.setCause(cause);
|
||||
}
|
||||
|
||||
public Object next(Context ctx, Object value, Object returnValue, EngineException error) {
|
||||
if (value != Runners.NO_RETURN) push(ctx, value);
|
||||
var debugger = StackData.getDebugger(ctx);
|
||||
|
||||
Instruction instr = null;
|
||||
if (codePtr >= 0 && codePtr < function.body.length) instr = function.body[codePtr];
|
||||
|
||||
if (returnValue == Runners.NO_RETURN && error == null) {
|
||||
try {
|
||||
var instr = function.body[codePtr];
|
||||
if (Thread.currentThread().isInterrupted()) throw new InterruptException();
|
||||
|
||||
if (debugger != null) debugger.onInstruction(ctx, this, instr, Runners.NO_RETURN, null, false);
|
||||
returnValue = nextNoTry(ctx, instr);
|
||||
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; }
|
||||
}
|
||||
@@ -189,11 +212,11 @@ public class CodeFrame {
|
||||
break;
|
||||
case TryCtx.STATE_CATCH:
|
||||
if (error != null) {
|
||||
setCause(ctx, error, tryCtx.err);
|
||||
if (tryCtx.hasFinally) {
|
||||
tryCtx.err = error;
|
||||
newState = TryCtx.STATE_FINALLY_THREW;
|
||||
}
|
||||
setCause(ctx, error, tryCtx.err);
|
||||
break;
|
||||
}
|
||||
else if (returnValue != Runners.NO_RETURN) {
|
||||
@@ -215,27 +238,31 @@ public class CodeFrame {
|
||||
case TryCtx.STATE_FINALLY_THREW:
|
||||
if (error != null) setCause(ctx, error, tryCtx.err);
|
||||
else if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) error = tryCtx.err;
|
||||
else return Runners.NO_RETURN;
|
||||
else if (returnValue == Runners.NO_RETURN) return Runners.NO_RETURN;
|
||||
break;
|
||||
case TryCtx.STATE_FINALLY_RETURNED:
|
||||
if (error != null) setCause(ctx, error, tryCtx.err);
|
||||
if (returnValue == Runners.NO_RETURN) {
|
||||
if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) returnValue = tryCtx.retVal;
|
||||
else return Runners.NO_RETURN;
|
||||
}
|
||||
break;
|
||||
case TryCtx.STATE_FINALLY_JUMPED:
|
||||
if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) {
|
||||
if (error != null) setCause(ctx, error, tryCtx.err);
|
||||
else if (codePtr < tryCtx.finallyStart || codePtr >= tryCtx.end) {
|
||||
if (!jumpFlag) codePtr = tryCtx.jumpPtr;
|
||||
else codePtr = tryCtx.end;
|
||||
}
|
||||
else return Runners.NO_RETURN;
|
||||
else if (returnValue == Runners.NO_RETURN) return Runners.NO_RETURN;
|
||||
break;
|
||||
}
|
||||
|
||||
if (tryCtx.state == TryCtx.STATE_CATCH) scope.catchVars.remove(scope.catchVars.size() - 1);
|
||||
|
||||
if (newState == -1) {
|
||||
var err = tryCtx.err;
|
||||
tryStack.pop();
|
||||
if (!tryStack.isEmpty()) tryStack.peek().err = err;
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -244,7 +271,7 @@ public class CodeFrame {
|
||||
case TryCtx.STATE_CATCH:
|
||||
scope.catchVars.add(new ValueVariable(false, tryCtx.err.value));
|
||||
codePtr = tryCtx.catchStart;
|
||||
if (debugger != null) debugger.onInstruction(ctx, this, function.body[codePtr], null, error, true);
|
||||
ctx.engine.onInstruction(ctx, this, function.body[codePtr], null, error, true);
|
||||
break;
|
||||
default:
|
||||
codePtr = tryCtx.finallyStart;
|
||||
@@ -252,13 +279,13 @@ public class CodeFrame {
|
||||
|
||||
return Runners.NO_RETURN;
|
||||
}
|
||||
|
||||
|
||||
if (error != null) {
|
||||
if (debugger != null) debugger.onInstruction(ctx, this, function.body[codePtr], null, error, false);
|
||||
ctx.engine.onInstruction(ctx, this, instr, null, error, false);
|
||||
throw error;
|
||||
}
|
||||
if (returnValue != Runners.NO_RETURN) {
|
||||
if (debugger != null) debugger.onInstruction(ctx, this, function.body[codePtr], returnValue, null, false);
|
||||
ctx.engine.onInstruction(ctx, this, instr, returnValue, null, false);
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.util.Collections;
|
||||
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.Engine;
|
||||
import me.topchetoeu.jscript.engine.Operation;
|
||||
import me.topchetoeu.jscript.engine.scope.ValueVariable;
|
||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||
@@ -26,16 +27,12 @@ public class Runners {
|
||||
throw EngineException.ofSyntax((String)instr.get(0));
|
||||
}
|
||||
|
||||
private static Object call(Context ctx, Object func, Object thisArg, Object ...args) {
|
||||
return Values.call(ctx, func, thisArg, args);
|
||||
}
|
||||
|
||||
public static Object execCall(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
var callArgs = frame.take(instr.get(0));
|
||||
var func = 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++;
|
||||
return NO_RETURN;
|
||||
@@ -46,17 +43,6 @@ public class Runners {
|
||||
|
||||
frame.push(ctx, Values.callNew(ctx, funcObj, callArgs));
|
||||
|
||||
// if (Values.isFunction(funcObj) && Values.function(funcObj).special) {
|
||||
// frame.push(ctx, call(ctx, funcObj, null, callArgs));
|
||||
// }
|
||||
// else {
|
||||
// var proto = Values.getMember(ctx, funcObj, "prototype");
|
||||
// var obj = new ObjectValue();
|
||||
// obj.setPrototype(ctx, proto);
|
||||
// call(ctx, funcObj, obj, callArgs);
|
||||
// frame.push(ctx, obj);
|
||||
// }
|
||||
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
@@ -100,19 +86,29 @@ public class Runners {
|
||||
public static Object execKeys(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
var val = frame.pop();
|
||||
|
||||
var arr = new ObjectValue();
|
||||
var i = 0;
|
||||
|
||||
var members = Values.getMembers(ctx, val, false, false);
|
||||
Collections.reverse(members);
|
||||
|
||||
frame.push(ctx, null);
|
||||
|
||||
for (var el : members) {
|
||||
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++;
|
||||
return NO_RETURN;
|
||||
}
|
||||
@@ -191,7 +187,7 @@ public class Runners {
|
||||
captures[i - 3] = frame.scope.get(instr.get(i));
|
||||
}
|
||||
|
||||
var body = ctx.engine.functions.get(id);
|
||||
var body = Engine.functions.get(id);
|
||||
var func = new CodeFunction(ctx.environment(), "", localsN, len, captures, body);
|
||||
|
||||
frame.push(ctx, func);
|
||||
|
||||
@@ -8,8 +8,7 @@ public class LocalScope {
|
||||
public final ArrayList<ValueVariable> catchVars = new ArrayList<>();
|
||||
|
||||
public ValueVariable get(int i) {
|
||||
if (i >= locals.length)
|
||||
return catchVars.get(i - locals.length);
|
||||
if (i >= locals.length) return catchVars.get(i - locals.length);
|
||||
if (i >= 0) return locals[i];
|
||||
else return captures[~i];
|
||||
}
|
||||
|
||||
@@ -14,13 +14,14 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
|
||||
private Object[] values;
|
||||
private int size;
|
||||
|
||||
private void alloc(int index) {
|
||||
if (index < values.length) return;
|
||||
private Object[] alloc(int index) {
|
||||
index++;
|
||||
if (index < values.length) return values;
|
||||
if (index < values.length * 2) index = values.length * 2;
|
||||
|
||||
var arr = new Object[index];
|
||||
System.arraycopy(values, 0, arr, 0, values.length);
|
||||
values = arr;
|
||||
return arr;
|
||||
}
|
||||
|
||||
public int size() { return size; }
|
||||
@@ -28,7 +29,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
|
||||
if (val < 0) return false;
|
||||
if (size > val) shrink(size - val);
|
||||
else {
|
||||
alloc(val);
|
||||
values = alloc(val);
|
||||
size = val;
|
||||
}
|
||||
return true;
|
||||
@@ -43,7 +44,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
|
||||
public void set(Context ctx, int i, Object val) {
|
||||
if (i < 0) return;
|
||||
|
||||
alloc(i);
|
||||
values = alloc(i);
|
||||
|
||||
val = Values.normalize(ctx, val);
|
||||
if (val == null) val = UNDEFINED;
|
||||
@@ -51,7 +52,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
|
||||
if (i >= size) size = i + 1;
|
||||
}
|
||||
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) {
|
||||
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) {
|
||||
// Iterate in reverse to reallocate at most once
|
||||
for (var i = count - 1; i >= 0; i--) {
|
||||
if (i + sourceStart < 0 || i + sourceStart >= size) arr.set(ctx, i + destStart, null);
|
||||
if (i + sourceStart < 0 || i + sourceStart >= size) arr.remove(i + destStart);
|
||||
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]);
|
||||
}
|
||||
}
|
||||
@@ -97,7 +99,7 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
|
||||
}
|
||||
|
||||
public void move(int srcI, int dstI, int n) {
|
||||
alloc(dstI + n);
|
||||
values = alloc(dstI + n);
|
||||
|
||||
System.arraycopy(values, srcI, values, dstI, n);
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import me.topchetoeu.jscript.compilation.FunctionBody;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.Environment;
|
||||
import me.topchetoeu.jscript.engine.StackData;
|
||||
import me.topchetoeu.jscript.engine.frame.CodeFrame;
|
||||
import me.topchetoeu.jscript.engine.frame.Runners;
|
||||
import me.topchetoeu.jscript.engine.scope.ValueVariable;
|
||||
@@ -35,7 +34,7 @@ public class CodeFunction extends FunctionValue {
|
||||
public Object call(Context ctx, Object thisArg, Object ...args) {
|
||||
var frame = new CodeFrame(ctx, thisArg, args, this);
|
||||
try {
|
||||
StackData.pushFrame(ctx, frame);
|
||||
ctx.pushFrame(frame);
|
||||
|
||||
while (true) {
|
||||
var res = frame.next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, null);
|
||||
@@ -43,7 +42,7 @@ public class CodeFunction extends FunctionValue {
|
||||
}
|
||||
}
|
||||
finally {
|
||||
StackData.popFrame(ctx, frame);
|
||||
ctx.popFrame(frame);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ public abstract class FunctionValue extends ObjectValue {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "function(...) { ...}";
|
||||
return String.format("function %s(...)", name);
|
||||
}
|
||||
|
||||
public abstract Object call(Context ctx, Object thisArg, Object ...args);
|
||||
@@ -21,21 +21,21 @@ public abstract class FunctionValue extends ObjectValue {
|
||||
|
||||
@Override
|
||||
protected Object getField(Context ctx, Object key) {
|
||||
if (key.equals("name")) return name;
|
||||
if (key.equals("length")) return length;
|
||||
if ("name".equals(key)) return name;
|
||||
if ("length".equals(key)) return length;
|
||||
return super.getField(ctx, key);
|
||||
}
|
||||
@Override
|
||||
protected boolean setField(Context ctx, Object key, Object val) {
|
||||
if (key.equals("name")) name = Values.toString(ctx, val);
|
||||
else if (key.equals("length")) length = (int)Values.toNumber(ctx, val);
|
||||
if ("name".equals(key)) name = Values.toString(ctx, val);
|
||||
else if ("length".equals(key)) length = (int)Values.toNumber(ctx, val);
|
||||
else return super.setField(ctx, key, val);
|
||||
return true;
|
||||
}
|
||||
@Override
|
||||
protected boolean hasField(Context ctx, Object key) {
|
||||
if (key.equals("name")) return true;
|
||||
if (key.equals("length")) return true;
|
||||
if ("name".equals(key)) return true;
|
||||
if ("length".equals(key)) return true;
|
||||
return super.hasField(ctx, key);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,19 @@ public class NativeWrapper extends ObjectValue {
|
||||
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) {
|
||||
this.wrapped = wrapped;
|
||||
prototype = NATIVE_PROTO;
|
||||
|
||||
@@ -103,8 +103,7 @@ public class ObjectValue {
|
||||
) return true;
|
||||
|
||||
if (!extensible() && !values.containsKey(key) && !properties.containsKey(key)) return false;
|
||||
if (!memberConfigurable(key))
|
||||
return false;
|
||||
if (!memberConfigurable(key)) return false;
|
||||
|
||||
nonWritableSet.remove(key);
|
||||
nonEnumerableSet.remove(key);
|
||||
@@ -263,7 +262,7 @@ public class ObjectValue {
|
||||
values.put(key, val);
|
||||
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 return setField(ctx, key, val);
|
||||
}
|
||||
@@ -274,7 +273,7 @@ public class ObjectValue {
|
||||
public final boolean hasMember(Context ctx, Object key, boolean own) {
|
||||
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 (properties.containsKey(key)) return true;
|
||||
if (own) return false;
|
||||
|
||||
@@ -17,8 +17,27 @@ import me.topchetoeu.jscript.exceptions.ConvertException;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.exceptions.SyntaxException;
|
||||
import me.topchetoeu.jscript.exceptions.UncheckedException;
|
||||
import me.topchetoeu.jscript.lib.PromiseLib;
|
||||
|
||||
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 boolean isObject(Object val) { return val instanceof ObjectValue; }
|
||||
@@ -105,7 +124,7 @@ public class Values {
|
||||
}
|
||||
public static boolean toBoolean(Object obj) {
|
||||
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 Boolean) return (Boolean)obj;
|
||||
return true;
|
||||
@@ -117,6 +136,7 @@ public class Values {
|
||||
if (val instanceof Boolean) return ((Boolean)val) ? 1 : 0;
|
||||
if (val instanceof String) {
|
||||
try { return Double.parseDouble((String)val); }
|
||||
catch (NumberFormatException e) { return Double.NaN; }
|
||||
catch (Throwable e) { throw new UncheckedException(e); }
|
||||
}
|
||||
return Double.NaN;
|
||||
@@ -136,7 +156,7 @@ public class Values {
|
||||
}
|
||||
if (val instanceof Boolean) return (Boolean)val ? "true" : "false";
|
||||
if (val instanceof String) return (String)val;
|
||||
if (val instanceof Symbol) return ((Symbol)val).toString();
|
||||
if (val instanceof Symbol) return val.toString();
|
||||
|
||||
return "Unknown value";
|
||||
}
|
||||
@@ -190,12 +210,18 @@ public class Values {
|
||||
return _a >>> _b;
|
||||
}
|
||||
|
||||
public static int compare(Context ctx, Object a, Object b) {
|
||||
public static CompareResult compare(Context ctx, Object a, Object b) {
|
||||
a = toPrimitive(ctx, a, ConvertHint.VALUEOF);
|
||||
b = toPrimitive(ctx, b, ConvertHint.VALUEOF);
|
||||
|
||||
if (a instanceof String && b instanceof String) return ((String)a).compareTo((String)b);
|
||||
else return Double.compare(toNumber(ctx, a), toNumber(ctx, b));
|
||||
if (a instanceof String && b instanceof String) CompareResult.from(((String)a).compareTo((String)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) {
|
||||
@@ -231,10 +257,10 @@ public class Values {
|
||||
case LOOSE_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_EQUALS: return compare(ctx, args[0], args[1]) >= 0;
|
||||
case LESS: return compare(ctx, args[0], args[1]) < 0;
|
||||
case LESS_EQUALS: 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]).greaterOrEqual();
|
||||
case LESS: return compare(ctx, args[0], args[1]).less();
|
||||
case LESS_EQUALS: return compare(ctx, args[0], args[1]).lessOrEqual();
|
||||
|
||||
case INVERSE: return bitwiseNot(ctx, args[0]);
|
||||
case NOT: return not(args[0]);
|
||||
@@ -271,15 +297,20 @@ public class Values {
|
||||
|
||||
var proto = getPrototype(ctx, obj);
|
||||
|
||||
if (proto == null) return key.equals("__proto__") ? NULL : null;
|
||||
else if (key != null && key.equals("__proto__")) return proto;
|
||||
if (proto == null) return "__proto__".equals(key) ? NULL : null;
|
||||
else if (key != null && "__proto__".equals(key)) return proto;
|
||||
else return proto.getMember(ctx, key, obj);
|
||||
}
|
||||
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);
|
||||
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 (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);
|
||||
|
||||
var proto = getPrototype(ctx, obj);
|
||||
@@ -289,7 +320,7 @@ public class Values {
|
||||
if (obj == null || obj == NULL) return false;
|
||||
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 (obj instanceof String && key instanceof Number) {
|
||||
@@ -371,13 +402,18 @@ public class Values {
|
||||
}
|
||||
public static Object callNew(Context ctx, Object func, Object ...args) {
|
||||
var res = new ObjectValue();
|
||||
var proto = Values.getMember(ctx, func, "prototype");
|
||||
res.setPrototype(ctx, proto);
|
||||
|
||||
var ret = call(ctx, func, res, args);
|
||||
|
||||
if (ret != null && func instanceof FunctionValue && ((FunctionValue)func).special) return ret;
|
||||
return res;
|
||||
try {
|
||||
var proto = Values.getMember(ctx, func, "prototype");
|
||||
res.setPrototype(ctx, proto);
|
||||
|
||||
var ret = call(ctx, func, res, args);
|
||||
|
||||
if (ret != null && func instanceof FunctionValue && ((FunctionValue)func).special) return ret;
|
||||
return res;
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
throw EngineException.ofType("Tried to call new on an invalid constructor.");
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean strictEquals(Context ctx, Object a, Object b) {
|
||||
@@ -512,7 +548,7 @@ public class Values {
|
||||
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 () -> {
|
||||
try {
|
||||
var symbol = ctx.environment().symbol("Symbol.iterator");
|
||||
@@ -565,7 +601,7 @@ public class Values {
|
||||
};
|
||||
}
|
||||
|
||||
public static ObjectValue fromJavaIterator(Context ctx, Iterator<?> it) {
|
||||
public static ObjectValue toJSIterator(Context ctx, Iterator<?> it) {
|
||||
var res = new ObjectValue();
|
||||
|
||||
try {
|
||||
@@ -576,17 +612,59 @@ public class Values {
|
||||
|
||||
res.defineProperty(ctx, "next", new NativeFunction("", (_ctx, _th, _args) -> {
|
||||
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;
|
||||
}
|
||||
|
||||
public static ObjectValue fromJavaIterable(Context ctx, Iterable<?> it) {
|
||||
return fromJavaIterator(ctx, it.iterator());
|
||||
public static ObjectValue toJSIterator(Context ctx, Iterable<?> it) {
|
||||
return toJSIterator(ctx, it.iterator());
|
||||
}
|
||||
|
||||
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)) {
|
||||
System.out.print("[circular]");
|
||||
return;
|
||||
@@ -595,10 +673,7 @@ public class Values {
|
||||
var printed = true;
|
||||
|
||||
if (val instanceof FunctionValue) {
|
||||
System.out.print("function ");
|
||||
var name = Values.getMember(ctx, val, "name");
|
||||
if (name != null) System.out.print(Values.toString(ctx, name));
|
||||
System.out.print("(...)");
|
||||
System.out.print(val.toString());
|
||||
var loc = val instanceof CodeFunction ? ((CodeFunction)val).loc() : null;
|
||||
|
||||
if (loc != null) System.out.print(" @ " + loc);
|
||||
@@ -628,7 +703,7 @@ public class Values {
|
||||
passed.add(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("{}");
|
||||
}
|
||||
else {
|
||||
@@ -646,12 +721,13 @@ public class Values {
|
||||
printValue(ctx, el.getKey(), passed, tab + 1);
|
||||
System.out.println(": [prop],");
|
||||
}
|
||||
|
||||
|
||||
for (int i = 0; i < tab; i++) System.out.print(" ");
|
||||
System.out.print("}");
|
||||
|
||||
passed.remove(val);
|
||||
|
||||
}
|
||||
|
||||
passed.remove(val);
|
||||
}
|
||||
else if (val == null) System.out.print("undefined");
|
||||
else if (val == Values.NULL) System.out.print("null");
|
||||
|
||||
@@ -45,10 +45,10 @@ public class EngineException extends RuntimeException {
|
||||
catch (EngineException e) {
|
||||
ss.append("[Error while stringifying]\n");
|
||||
}
|
||||
// for (var line : stackTrace) {
|
||||
// ss.append(" ").append(line).append('\n');
|
||||
// }
|
||||
// if (cause != null) ss.append("Caused by ").append(cause.toString(ctx)).append('\n');
|
||||
for (var line : stackTrace) {
|
||||
ss.append(" ").append(line).append('\n');
|
||||
}
|
||||
if (cause != null) ss.append("Caused by ").append(cause.toString(ctx)).append('\n');
|
||||
ss.deleteCharAt(ss.length() - 1);
|
||||
return ss.toString();
|
||||
}
|
||||
|
||||
41
src/me/topchetoeu/jscript/filesystem/Buffer.java
Normal file
41
src/me/topchetoeu/jscript/filesystem/Buffer.java
Normal 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);
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,13 @@
|
||||
package me.topchetoeu.jscript.filesystem;
|
||||
|
||||
public enum EntryType {
|
||||
NONE,
|
||||
FILE,
|
||||
FOLDER,
|
||||
NONE("none"),
|
||||
FILE("file"),
|
||||
FOLDER("folder");
|
||||
|
||||
public final String name;
|
||||
|
||||
private EntryType(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
}
|
||||
@@ -1,27 +1,39 @@
|
||||
package me.topchetoeu.jscript.filesystem;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public interface File {
|
||||
int read() throws IOException, InterruptedException;
|
||||
boolean write(byte val) throws IOException, InterruptedException;
|
||||
long tell() throws IOException, InterruptedException;
|
||||
void seek(long offset, int pos) throws IOException, InterruptedException;
|
||||
void close() throws IOException, InterruptedException;
|
||||
Permissions perms();
|
||||
int read(byte[] buff);
|
||||
void write(byte[] buff);
|
||||
long getPtr();
|
||||
void setPtr(long offset, int pos);
|
||||
void close();
|
||||
Mode mode();
|
||||
|
||||
default String readToString() throws IOException, InterruptedException {
|
||||
seek(0, 2);
|
||||
long len = tell();
|
||||
default String readToString() {
|
||||
setPtr(0, 2);
|
||||
long len = getPtr();
|
||||
if (len < 0) return null;
|
||||
|
||||
seek(0, 0);
|
||||
byte[] res = new byte[(int)len];
|
||||
setPtr(0, 0);
|
||||
|
||||
for (var i = 0; i < len; i++) {
|
||||
res[i] = (byte)read();
|
||||
}
|
||||
byte[] res = new byte[(int)len];
|
||||
read(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());
|
||||
}
|
||||
}
|
||||
11
src/me/topchetoeu/jscript/filesystem/FileStat.java
Normal file
11
src/me/topchetoeu/jscript/filesystem/FileStat.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,7 @@
|
||||
package me.topchetoeu.jscript.filesystem;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
public interface Filesystem {
|
||||
File open(String path) throws IOException, InterruptedException;
|
||||
boolean mkdir(String path) throws IOException, InterruptedException;
|
||||
EntryType type(String path) throws IOException, InterruptedException;
|
||||
boolean rm(String path) throws IOException, InterruptedException;
|
||||
}
|
||||
File open(String path, Mode mode) throws FilesystemException;
|
||||
void create(String path, EntryType type) throws FilesystemException;
|
||||
FileStat stat(String path) throws FilesystemException;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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() { }
|
||||
}
|
||||
@@ -1,53 +1,66 @@
|
||||
package me.topchetoeu.jscript.filesystem;
|
||||
|
||||
import java.io.IOException;
|
||||
import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode;
|
||||
|
||||
public class MemoryFile implements File {
|
||||
private int ptr;
|
||||
private Permissions mode;
|
||||
public final byte[] data;
|
||||
private Mode mode;
|
||||
private Buffer data;
|
||||
private String filename;
|
||||
|
||||
public Buffer data() { return data; }
|
||||
|
||||
@Override
|
||||
public int read() throws IOException, InterruptedException {
|
||||
if (data == null || !mode.readable || ptr >= data.length) return -1;
|
||||
return data[ptr++];
|
||||
public int read(byte[] buff) {
|
||||
if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
|
||||
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
|
||||
public boolean write(byte val) throws IOException, InterruptedException {
|
||||
if (data == null || !mode.writable || ptr >= data.length) return false;
|
||||
data[ptr++] = val;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long tell() throws IOException, InterruptedException {
|
||||
public long getPtr() {
|
||||
if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void seek(long offset, int pos) throws IOException, InterruptedException {
|
||||
if (data == null) return;
|
||||
public void setPtr(long offset, int pos) {
|
||||
if (data == null || !mode.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
|
||||
|
||||
if (pos == 0) 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
|
||||
public void close() throws IOException, InterruptedException {
|
||||
mode = null;
|
||||
public void close() {
|
||||
mode = Mode.NONE;
|
||||
ptr = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Permissions perms() {
|
||||
if (data == null) return Permissions.NONE;
|
||||
public Mode mode() {
|
||||
if (data == null) return Mode.NONE;
|
||||
return mode;
|
||||
}
|
||||
|
||||
public MemoryFile(byte[] buff, Permissions mode) {
|
||||
public MemoryFile(String filename, Buffer buff, Mode mode) {
|
||||
this.filename = filename;
|
||||
this.data = buff;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
89
src/me/topchetoeu/jscript/filesystem/MemoryFilesystem.java
Normal file
89
src/me/topchetoeu/jscript/filesystem/MemoryFilesystem.java
Normal 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("/"));
|
||||
}
|
||||
}
|
||||
31
src/me/topchetoeu/jscript/filesystem/Mode.java
Normal file
31
src/me/topchetoeu/jscript/filesystem/Mode.java
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -1,50 +1,62 @@
|
||||
package me.topchetoeu.jscript.filesystem;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.RandomAccessFile;
|
||||
|
||||
import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode;
|
||||
|
||||
public class PhysicalFile implements File {
|
||||
private String filename;
|
||||
private RandomAccessFile file;
|
||||
private Permissions perms;
|
||||
private Mode perms;
|
||||
|
||||
@Override
|
||||
public int read() throws IOException, InterruptedException {
|
||||
if (file == null || !perms.readable) return -1;
|
||||
else return file.read();
|
||||
public int read(byte[] buff) {
|
||||
if (file == null || !perms.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
|
||||
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
|
||||
public boolean write(byte val) throws IOException, InterruptedException {
|
||||
if (file == null || !perms.writable) return false;
|
||||
file.write(val);
|
||||
return true;
|
||||
public long getPtr() {
|
||||
if (file == null || !perms.readable) throw new FilesystemException(filename, FSCode.NO_PERMISSIONS_R);
|
||||
else try { return file.getFilePointer(); }
|
||||
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
|
||||
public long tell() throws IOException, InterruptedException {
|
||||
if (file == null) return 0;
|
||||
return file.getFilePointer();
|
||||
}
|
||||
@Override
|
||||
public void seek(long offset, int pos) throws IOException, InterruptedException {
|
||||
public void close() {
|
||||
if (file == null) return;
|
||||
if (pos == 0) file.seek(pos);
|
||||
else if (pos == 1) file.seek(file.getFilePointer() + pos);
|
||||
else if (pos == 2) file.seek(file.length() + pos);
|
||||
try { file.close(); }
|
||||
catch (IOException e) {} // SHUT
|
||||
file = null;
|
||||
perms = Mode.NONE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException, InterruptedException {
|
||||
if (file == null) return;
|
||||
file.close();
|
||||
}
|
||||
public Mode mode() { return perms; }
|
||||
|
||||
@Override
|
||||
public Permissions perms() { return perms; }
|
||||
|
||||
public PhysicalFile(String path, Permissions mode) throws IOException {
|
||||
if (mode == Permissions.NONE) file = null;
|
||||
else file = new RandomAccessFile(path, mode.readMode);
|
||||
public PhysicalFile(String path, Mode mode) throws FileNotFoundException {
|
||||
if (mode == Mode.NONE) file = null;
|
||||
else try { file = new RandomAccessFile(path, mode.name); }
|
||||
catch (FileNotFoundException e) { throw new FilesystemException(filename, FSCode.DOESNT_EXIST); }
|
||||
|
||||
perms = mode;
|
||||
}
|
||||
|
||||
@@ -1,74 +1,74 @@
|
||||
package me.topchetoeu.jscript.filesystem;
|
||||
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode;
|
||||
|
||||
public class PhysicalFilesystem implements Filesystem {
|
||||
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) {
|
||||
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
|
||||
public File open(String path) throws IOException, InterruptedException {
|
||||
var _path = root.resolve(path);
|
||||
|
||||
var perms = getPerms(_path);
|
||||
if (perms == Permissions.NONE) return InaccessibleFile.INSTANCE;
|
||||
|
||||
public File open(String path, Mode perms) {
|
||||
var _path = getPath(path);
|
||||
var f = _path.toFile();
|
||||
|
||||
if (f.isDirectory()) {
|
||||
var res = new StringBuilder();
|
||||
checkMode(_path, perms);
|
||||
|
||||
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
|
||||
public boolean mkdir(String path) throws IOException, InterruptedException {
|
||||
public FileStat stat(String path) {
|
||||
var _path = getPath(path);
|
||||
var perms = getPerms(_path);
|
||||
var f = _path.toFile();
|
||||
|
||||
if (!perms.writable) return false;
|
||||
else return f.mkdir();
|
||||
}
|
||||
if (!f.exists()) throw new FilesystemException(_path.toString(), FSCode.DOESNT_EXIST);
|
||||
checkMode(_path, Mode.READ);
|
||||
|
||||
@Override
|
||||
public EntryType type(String path) throws IOException, InterruptedException {
|
||||
var _path = getPath(path);
|
||||
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();
|
||||
return new FileStat(
|
||||
f.canWrite() ? Mode.READ_WRITE : Mode.READ,
|
||||
f.isFile() ? EntryType.FILE : EntryType.FOLDER
|
||||
);
|
||||
}
|
||||
|
||||
public PhysicalFilesystem(Path root) {
|
||||
this.root = root;
|
||||
this.root = root.toAbsolutePath().normalize();
|
||||
}
|
||||
}
|
||||
|
||||
57
src/me/topchetoeu/jscript/filesystem/RootFilesystem.java
Normal file
57
src/me/topchetoeu/jscript/filesystem/RootFilesystem.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@ public class NativeWrapperProvider implements WrappersProvider {
|
||||
|
||||
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()));
|
||||
}
|
||||
@@ -53,7 +53,7 @@ public class NativeWrapperProvider implements WrappersProvider {
|
||||
else getter = new OverloadFunction("get " + name);
|
||||
|
||||
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.thisArg() && !member || !set.thisArg() && !memberMatch) continue;
|
||||
@@ -70,7 +70,7 @@ public class NativeWrapperProvider implements WrappersProvider {
|
||||
else setter = new OverloadFunction("set " + name);
|
||||
|
||||
setter.add(Overload.fromMethod(method, set.thisArg()));
|
||||
target.defineProperty(null, name, getter, setter, true, true);
|
||||
target.defineProperty(null, name, getter, setter, true, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -108,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.
|
||||
* The returned object will have appropriate wrappers for all instance members.
|
||||
@@ -117,6 +123,8 @@ public class NativeWrapperProvider implements WrappersProvider {
|
||||
public static ObjectValue makeProto(Environment ctx, Class<?> clazz) {
|
||||
var res = new ObjectValue();
|
||||
|
||||
res.defineProperty(null, ctx.symbol("Symbol.typeName"), getName(clazz));
|
||||
|
||||
for (var overload : clazz.getDeclaredMethods()) {
|
||||
var init = overload.getAnnotation(NativeInit.class);
|
||||
if (init == null || init.value() != InitType.PROTOTYPE) continue;
|
||||
@@ -137,7 +145,7 @@ public class NativeWrapperProvider implements WrappersProvider {
|
||||
* @param clazz The class for which a constructor should be generated
|
||||
*/
|
||||
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()) {
|
||||
var nat = overload.getAnnotation(Native.class);
|
||||
@@ -211,8 +219,8 @@ public class NativeWrapperProvider implements WrappersProvider {
|
||||
if (constr == null) constr = makeConstructor(env, clazz);
|
||||
if (proto == null) proto = makeProto(env, clazz);
|
||||
|
||||
proto.values.put("constructor", constr);
|
||||
constr.values.put("prototype", proto);
|
||||
proto.defineProperty(null, "constructor", constr, true, false, false);
|
||||
constr.defineProperty(null, "prototype", proto, true, false, false);
|
||||
|
||||
prototypes.put(clazz, proto);
|
||||
constructors.put(clazz, constr);
|
||||
@@ -240,6 +248,11 @@ public class NativeWrapperProvider implements WrappersProvider {
|
||||
return constructors.get(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappersProvider fork(Environment env) {
|
||||
return new NativeWrapperProvider(env);
|
||||
}
|
||||
|
||||
public void setProto(Class<?> clazz, ObjectValue value) {
|
||||
prototypes.put(clazz, value);
|
||||
}
|
||||
@@ -247,7 +260,27 @@ public class NativeWrapperProvider implements WrappersProvider {
|
||||
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) {
|
||||
this.env = env;
|
||||
initError();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,9 +44,11 @@ public class Overload {
|
||||
public static Overload setterFromField(Field field) {
|
||||
if (Modifier.isFinal(field.getModifiers())) return null;
|
||||
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(),
|
||||
new Class[0]
|
||||
new Class[] { field.getType() }
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -8,9 +8,11 @@ import java.util.List;
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
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.exceptions.ConvertException;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.exceptions.InterruptException;
|
||||
|
||||
public class OverloadFunction extends FunctionValue {
|
||||
public final List<Overload> overloads = new ArrayList<>();
|
||||
@@ -84,10 +86,21 @@ public class OverloadFunction extends FunctionValue {
|
||||
throw ((EngineException)e.getTargetException()).add(name, loc);
|
||||
}
|
||||
else if (e.getTargetException() instanceof NullPointerException) {
|
||||
e.printStackTrace();
|
||||
throw EngineException.ofType("Unexpected value of 'undefined'.").add(name, loc);
|
||||
}
|
||||
else if (e.getTargetException() instanceof InterruptException || e.getTargetException() instanceof InterruptedException) {
|
||||
throw new InterruptException();
|
||||
}
|
||||
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) {
|
||||
|
||||
179
src/me/topchetoeu/jscript/js/bootstrap.js
vendored
179
src/me/topchetoeu/jscript/js/bootstrap.js
vendored
@@ -1,86 +1,111 @@
|
||||
// TODO: load this in java
|
||||
var ts = require('./ts');
|
||||
log("Loaded typescript!");
|
||||
(function (_arguments) {
|
||||
var ts = _arguments[0];
|
||||
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 libSnapshot = ts.ScriptSnapshot.fromString(lib);
|
||||
var settings = {
|
||||
outDir: "/out",
|
||||
declarationDir: "/out",
|
||||
target: ts.ScriptTarget.ES5,
|
||||
lib: [ ],
|
||||
module: ts.ModuleKind.None,
|
||||
declaration: true,
|
||||
stripInternal: true,
|
||||
downlevelIteration: true,
|
||||
forceConsistentCasingInFileNames: true,
|
||||
experimentalDecorators: true,
|
||||
strict: true,
|
||||
};
|
||||
|
||||
var settings = {
|
||||
outDir: "/out",
|
||||
declarationDir: "/out",
|
||||
target: ts.ScriptTarget.ES5,
|
||||
lib: [ ],
|
||||
module: ts.ModuleKind.None,
|
||||
declaration: true,
|
||||
stripInternal: true,
|
||||
downlevelIteration: true,
|
||||
forceConsistentCasingInFileNames: true,
|
||||
experimentalDecorators: true,
|
||||
strict: true,
|
||||
};
|
||||
var reg = ts.createDocumentRegistry();
|
||||
var service = ts.createLanguageService({
|
||||
getCurrentDirectory: function() { return "/"; },
|
||||
getDefaultLibFileName: function() { return "/lib.d.ts"; },
|
||||
getScriptFileNames: function() {
|
||||
var res = [ "/src.ts", "/lib.d.ts" ];
|
||||
for (var i = 0; i < declSnapshots.length; i++) res.push("/glob." + (i + 1) + ".d.ts");
|
||||
return res;
|
||||
},
|
||||
getCompilationSettings: function () { return settings; },
|
||||
fileExists: function(filename) { return filename === "/lib.d.ts" || filename === "/src.ts" || filename === "/glob.d.ts"; },
|
||||
|
||||
var reg = ts.createDocumentRegistry();
|
||||
var service = ts.createLanguageService({
|
||||
getCurrentDirectory: function() { return "/"; },
|
||||
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) {
|
||||
if (filename === "/lib.d.ts") return libSnapshot;
|
||||
if (filename === "/src.ts") return ts.ScriptSnapshot.fromString(src);
|
||||
|
||||
getScriptSnapshot: function(filename) {
|
||||
if (filename === "/lib.d.ts") return libSnapshot;
|
||||
if (filename === "/src.ts") return ts.ScriptSnapshot.fromString(src);
|
||||
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;
|
||||
var index = /\/glob\.(\d+)\.d\.ts/g.exec(filename);
|
||||
if (index && index[1] && (index = Number(index[1])) && index > 0 && index <= declSnapshots.length) {
|
||||
return declSnapshots[index - 1];
|
||||
}
|
||||
else return "Error: " + message;
|
||||
});
|
||||
|
||||
if (diagnostics.length > 0) {
|
||||
throw new SyntaxError(diagnostics.join('\n'));
|
||||
throw new Error("File '" + filename + "' doesn't exist.");
|
||||
},
|
||||
getScriptVersion: function (filename) {
|
||||
if (filename === "/lib.d.ts" || filename.startsWith("/glob.")) return 0;
|
||||
else return version;
|
||||
},
|
||||
}, reg);
|
||||
|
||||
service.getEmitOutput("/lib.d.ts");
|
||||
log("Loaded libraries!");
|
||||
|
||||
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 {
|
||||
result: emit.outputFiles[0].text,
|
||||
declaration: emit.outputFiles[1].text
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
function apply(env) {
|
||||
env.compile = compile;
|
||||
env.global.getTsDeclarations = function() {
|
||||
return environments[env.id];
|
||||
}
|
||||
];
|
||||
});
|
||||
}
|
||||
|
||||
apply(_arguments[1]);
|
||||
})(arguments);
|
||||
|
||||
31
src/me/topchetoeu/jscript/js/lib.d.ts
vendored
31
src/me/topchetoeu/jscript/js/lib.d.ts
vendored
@@ -482,6 +482,35 @@ interface PromiseConstructor {
|
||||
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;
|
||||
//@ts-ignore
|
||||
declare const arguments: IArguments;
|
||||
@@ -508,6 +537,8 @@ declare var Object: ObjectConstructor;
|
||||
declare var Symbol: SymbolConstructor;
|
||||
declare var Promise: PromiseConstructor;
|
||||
declare var Math: MathObject;
|
||||
declare var Encoding: Encoding;
|
||||
declare var Filesystem: Filesystem;
|
||||
|
||||
declare var Error: ErrorConstructor;
|
||||
declare var RangeError: RangeErrorConstructor;
|
||||
|
||||
18
src/me/topchetoeu/jscript/js/ts.js
Normal file
18
src/me/topchetoeu/jscript/js/ts.js
Normal file
File diff suppressed because one or more lines are too long
@@ -83,8 +83,11 @@ public class JSON {
|
||||
else return res.transform();
|
||||
}
|
||||
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);
|
||||
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();
|
||||
}
|
||||
public static ParseRes<Boolean> parseBool(Filename filename, List<Token> tokens, int i) {
|
||||
@@ -174,6 +177,7 @@ public class JSON {
|
||||
return ParseRes.res(values, n);
|
||||
}
|
||||
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);
|
||||
if (res.isFailed()) throw new SyntaxException(null, "Invalid JSON given.");
|
||||
else if (res.isError()) throw new SyntaxException(null, res.error);
|
||||
@@ -184,12 +188,28 @@ public class JSON {
|
||||
if (el.isNumber()) return Double.toString(el.number());
|
||||
if (el.isBoolean()) return el.bool() ? "true" : "false";
|
||||
if (el.isNull()) return "null";
|
||||
if (el.isString()) return "\"" + el.string()
|
||||
.replace("\\", "\\\\")
|
||||
.replace("\n", "\\n")
|
||||
.replace("\r", "\\r")
|
||||
.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()) {
|
||||
var res = new StringBuilder().append("[");
|
||||
for (int i = 0; i < el.list().size(); i++) {
|
||||
|
||||
@@ -4,19 +4,17 @@ import java.util.Iterator;
|
||||
import java.util.Stack;
|
||||
|
||||
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.FunctionValue;
|
||||
import me.topchetoeu.jscript.engine.values.NativeFunction;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.interop.InitType;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeConstructor;
|
||||
import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
import me.topchetoeu.jscript.interop.NativeInit;
|
||||
import me.topchetoeu.jscript.interop.NativeSetter;
|
||||
|
||||
public class ArrayLib {
|
||||
@Native("Array") public class ArrayLib {
|
||||
@NativeGetter(thisArg = true) public static int length(Context ctx, ArrayValue thisArg) {
|
||||
return thisArg.size();
|
||||
}
|
||||
@@ -25,10 +23,10 @@ public class ArrayLib {
|
||||
}
|
||||
|
||||
@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) {
|
||||
return Values.fromJavaIterable(ctx, () -> new Iterator<Object>() {
|
||||
return Values.toJSIterator(ctx, () -> new Iterator<Object>() {
|
||||
private int i = 0;
|
||||
|
||||
@Override
|
||||
@@ -43,7 +41,7 @@ public class ArrayLib {
|
||||
});
|
||||
}
|
||||
@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;
|
||||
|
||||
@Override
|
||||
@@ -69,7 +67,7 @@ public class ArrayLib {
|
||||
|
||||
@Native(thisArg = true) public static ArrayValue concat(Context ctx, ArrayValue thisArg, Object ...others) {
|
||||
// TODO: Fully implement with non-array spreadable objects
|
||||
var size = 0;
|
||||
var size = thisArg.size();
|
||||
|
||||
for (int i = 0; i < others.length; i++) {
|
||||
if (others[i] instanceof ArrayValue) size += ((ArrayValue)others[i]).size();
|
||||
@@ -77,8 +75,9 @@ public class ArrayLib {
|
||||
}
|
||||
|
||||
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) {
|
||||
int n = ((ArrayValue)others[i]).size();
|
||||
((ArrayValue)others[i]).copyTo(ctx, res, 0, j, n);
|
||||
@@ -92,13 +91,17 @@ public class ArrayLib {
|
||||
return res;
|
||||
}
|
||||
|
||||
@Native(thisArg = true) public static void sort(Context ctx, ArrayValue arr, FunctionValue cmp) {
|
||||
@Native(thisArg = true) public static ArrayValue sort(Context ctx, ArrayValue arr, FunctionValue cmp) {
|
||||
var defaultCmp = new NativeFunction("", (_ctx, thisArg, args) -> {
|
||||
return Values.toString(ctx, args[0]).compareTo(Values.toString(ctx, args[1]));
|
||||
});
|
||||
arr.sort((a, b) -> {
|
||||
var res = Values.toNumber(ctx, cmp.call(ctx, null, a, b));
|
||||
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;
|
||||
});
|
||||
return arr;
|
||||
}
|
||||
|
||||
private static int normalizeI(int len, int i, boolean clamp) {
|
||||
@@ -163,6 +166,37 @@ public class ArrayLib {
|
||||
}
|
||||
}
|
||||
|
||||
@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 stack = new Stack<Object>();
|
||||
@@ -223,7 +257,7 @@ public class ArrayLib {
|
||||
@Native(thisArg = true) public static int indexOf(Context ctx, ArrayValue arr, Object val, int start) {
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -267,20 +301,18 @@ public class ArrayLib {
|
||||
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);
|
||||
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);
|
||||
arr.copyTo(ctx, res, start, 0, end - start);
|
||||
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) {
|
||||
@Native(thisArg = true) public static ArrayValue splice(Context ctx, ArrayValue arr, int start, Object _deleteCount, Object ...items) {
|
||||
start = normalizeI(arr.size(), start, true);
|
||||
int deleteCount = _deleteCount == null ? arr.size() - 1 : (int)Values.toNumber(ctx, _deleteCount);
|
||||
deleteCount = normalizeI(arr.size(), deleteCount, true);
|
||||
if (start + deleteCount >= arr.size()) deleteCount = arr.size() - start;
|
||||
|
||||
@@ -293,21 +325,20 @@ public class ArrayLib {
|
||||
|
||||
return res;
|
||||
}
|
||||
@Native(thisArg = true) public static ArrayValue splice(Context ctx, ArrayValue arr, int start) {
|
||||
return splice(ctx, arr, start, arr.size() - start);
|
||||
}
|
||||
@Native(thisArg = true) public static String toString(Context ctx, ArrayValue arr) {
|
||||
return join(ctx, arr, ",");
|
||||
}
|
||||
|
||||
@Native(thisArg = true) public static String join(Context ctx, ArrayValue arr, String sep) {
|
||||
var res = new StringBuilder();
|
||||
var comma = true;
|
||||
var comma = false;
|
||||
|
||||
for (int i = 0; i < arr.size(); i++) {
|
||||
if (!arr.has(i)) continue;
|
||||
|
||||
if (comma) res.append(sep);
|
||||
comma = false;
|
||||
comma = true;
|
||||
|
||||
var el = arr.get(i);
|
||||
if (el == null || el == Values.NULL) continue;
|
||||
|
||||
@@ -339,8 +370,4 @@ public class ArrayLib {
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) {
|
||||
target.defineProperty(null, env.symbol("Symbol.typeName"), "Array");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.StackData;
|
||||
import me.topchetoeu.jscript.engine.frame.CodeFrame;
|
||||
import me.topchetoeu.jscript.engine.frame.Runners;
|
||||
import me.topchetoeu.jscript.engine.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;
|
||||
|
||||
public class AsyncFunctionLib extends FunctionValue {
|
||||
@Native("AsyncFunction") public class AsyncFunctionLib extends FunctionValue {
|
||||
public final FunctionValue factory;
|
||||
|
||||
public static class AsyncHelper {
|
||||
@@ -20,7 +20,7 @@ public class AsyncFunctionLib extends FunctionValue {
|
||||
|
||||
private void next(Context ctx, Object inducedValue, Object inducedError) {
|
||||
Object res = null;
|
||||
StackData.pushFrame(ctx, frame);
|
||||
ctx.pushFrame(frame);
|
||||
ctx.pushEnv(frame.function.environment);
|
||||
|
||||
awaiting = false;
|
||||
@@ -39,7 +39,7 @@ public class AsyncFunctionLib extends FunctionValue {
|
||||
}
|
||||
}
|
||||
|
||||
StackData.popFrame(ctx, frame);
|
||||
ctx.popFrame(frame);
|
||||
|
||||
if (awaiting) {
|
||||
PromiseLib.then(ctx, frame.pop(), new NativeFunction(this::fulfill), new NativeFunction(this::reject));
|
||||
|
||||
30
src/me/topchetoeu/jscript/lib/AsyncGeneratorFunctionLib.java
Normal file
30
src/me/topchetoeu/jscript/lib/AsyncGeneratorFunctionLib.java
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -3,133 +3,108 @@ package me.topchetoeu.jscript.lib;
|
||||
import java.util.Map;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.StackData;
|
||||
import me.topchetoeu.jscript.engine.frame.CodeFrame;
|
||||
import me.topchetoeu.jscript.engine.frame.Runners;
|
||||
import me.topchetoeu.jscript.engine.values.CodeFunction;
|
||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.engine.values.NativeFunction;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
|
||||
public class AsyncGeneratorLib extends FunctionValue {
|
||||
public final FunctionValue factory;
|
||||
@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;
|
||||
|
||||
public static class AsyncGenerator {
|
||||
@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;
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
Object res = null;
|
||||
StackData.pushFrame(ctx, 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);
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
StackData.popFrame(ctx, 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);
|
||||
catch (EngineException e) {
|
||||
currPromise.reject(ctx, e.value);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (done) return "Generator [closed]";
|
||||
if (state != 0) return "Generator [suspended]";
|
||||
return "Generator [running]";
|
||||
}
|
||||
ctx.popFrame(frame);
|
||||
|
||||
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;
|
||||
if (state == 1) {
|
||||
PromiseLib.then(ctx, frame.pop(), new NativeFunction(this::fulfill), new NativeFunction(this::reject));
|
||||
}
|
||||
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;
|
||||
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 Object call(Context ctx, Object thisArg, Object ...args) {
|
||||
var handler = new AsyncGenerator();
|
||||
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 String toString() {
|
||||
if (done) return "Generator [closed]";
|
||||
if (state != 0) return "Generator [suspended]";
|
||||
return "Generator [running]";
|
||||
}
|
||||
|
||||
public AsyncGeneratorLib(FunctionValue factory) {
|
||||
super(factory.name, factory.length);
|
||||
this.factory = factory;
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,12 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
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.Values;
|
||||
import me.topchetoeu.jscript.interop.InitType;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeConstructor;
|
||||
import me.topchetoeu.jscript.interop.NativeInit;
|
||||
|
||||
public class BooleanLib {
|
||||
@Native("Boolean") public class BooleanLib {
|
||||
public static final BooleanLib TRUE = new BooleanLib(true);
|
||||
public static final BooleanLib FALSE = new BooleanLib(false);
|
||||
|
||||
@@ -30,7 +27,4 @@ public class BooleanLib {
|
||||
public BooleanLib(boolean val) {
|
||||
this.value = val;
|
||||
}
|
||||
@NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) {
|
||||
target.defineProperty(null, env.symbol("Symbol.typeName"), "Boolean");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ import java.util.TimeZone;
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
|
||||
public class DateLib {
|
||||
@Native("Date") public class DateLib {
|
||||
private Calendar normal;
|
||||
private Calendar utc;
|
||||
|
||||
|
||||
20
src/me/topchetoeu/jscript/lib/EncodingLib.java
Normal file
20
src/me/topchetoeu/jscript/lib/EncodingLib.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
|
||||
@Native("Encoding")
|
||||
public class EncodingLib {
|
||||
@Native public static ArrayValue encode(String value) {
|
||||
var res = new ArrayValue();
|
||||
for (var el : value.getBytes()) res.set(null, res.size(), (int)el);
|
||||
return res;
|
||||
}
|
||||
@Native public static String decode(Context ctx, ArrayValue raw) {
|
||||
var res = new byte[raw.size()];
|
||||
for (var i = 0; i < raw.size(); i++) res[i] = (byte)Values.toNumber(ctx, raw.get(i));
|
||||
return new String(res);
|
||||
}
|
||||
}
|
||||
@@ -2,16 +2,16 @@ package me.topchetoeu.jscript.lib;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.Environment;
|
||||
import me.topchetoeu.jscript.engine.StackData;
|
||||
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.engine.values.ObjectValue.PlaceholderProto;
|
||||
import me.topchetoeu.jscript.interop.InitType;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeConstructor;
|
||||
import me.topchetoeu.jscript.interop.NativeInit;
|
||||
|
||||
public class ErrorLib {
|
||||
@Native("Error") public class ErrorLib {
|
||||
private static String toString(Context ctx, boolean rethrown, Object cause, Object name, Object message, ArrayValue stack) {
|
||||
if (name == null) name = "";
|
||||
else name = Values.toString(ctx, name).trim();
|
||||
@@ -23,13 +23,6 @@ public class ErrorLib {
|
||||
if (!message.equals("") && !name.equals("")) res.append(": ");
|
||||
if (!message.equals("")) res.append(message);
|
||||
|
||||
if (stack != null) {
|
||||
for (var el : stack) {
|
||||
var str = Values.toString(ctx, el).trim();
|
||||
if (!str.equals("")) res.append("\n ").append(el);
|
||||
}
|
||||
}
|
||||
|
||||
if (cause instanceof ObjectValue) {
|
||||
if (rethrown) res.append("\n (rethrown)");
|
||||
else res.append("\nCaused by ").append(toString(ctx, cause));
|
||||
@@ -58,8 +51,8 @@ public class ErrorLib {
|
||||
var target = new ObjectValue();
|
||||
if (thisArg instanceof ObjectValue) target = (ObjectValue)thisArg;
|
||||
|
||||
target.defineProperty(ctx, "stack", ArrayValue.of(ctx, StackData.stackTrace(ctx)));
|
||||
target.defineProperty(ctx, "name", "Error");
|
||||
target.setPrototype(PlaceholderProto.ERROR);
|
||||
target.defineProperty(ctx, "stack", ArrayValue.of(ctx, ctx.stackTrace()));
|
||||
if (message == null) target.defineProperty(ctx, "message", "");
|
||||
else target.defineProperty(ctx, "message", Values.toString(ctx, message));
|
||||
|
||||
@@ -67,7 +60,6 @@ public class ErrorLib {
|
||||
}
|
||||
|
||||
@NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) {
|
||||
target.defineProperty(null, env.symbol("Symbol.typeName"), "Error");
|
||||
target.defineProperty(null, "name", "Error");
|
||||
}
|
||||
}
|
||||
|
||||
89
src/me/topchetoeu/jscript/lib/FileLib.java
Normal file
89
src/me/topchetoeu/jscript/lib/FileLib.java
Normal file
@@ -0,0 +1,89 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.filesystem.File;
|
||||
import me.topchetoeu.jscript.filesystem.FilesystemException;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
|
||||
@Native("File")
|
||||
public class FileLib {
|
||||
public final File file;
|
||||
|
||||
@NativeGetter public PromiseLib pointer(Context ctx) {
|
||||
return PromiseLib.await(ctx, () -> {
|
||||
try {
|
||||
return file.getPtr();
|
||||
}
|
||||
catch (FilesystemException e) { throw e.toEngineException(); }
|
||||
});
|
||||
}
|
||||
@NativeGetter public PromiseLib length(Context ctx) {
|
||||
return PromiseLib.await(ctx, () -> {
|
||||
try {
|
||||
long curr = file.getPtr();
|
||||
file.setPtr(0, 2);
|
||||
long res = file.getPtr();
|
||||
file.setPtr(curr, 0);
|
||||
return res;
|
||||
}
|
||||
catch (FilesystemException e) { throw e.toEngineException(); }
|
||||
});
|
||||
}
|
||||
@NativeGetter public PromiseLib getMode(Context ctx) {
|
||||
return PromiseLib.await(ctx, () -> {
|
||||
try {
|
||||
return file.mode().name;
|
||||
}
|
||||
catch (FilesystemException e) { throw e.toEngineException(); }
|
||||
});
|
||||
}
|
||||
|
||||
@Native public PromiseLib read(Context ctx, int n) {
|
||||
return PromiseLib.await(ctx, () -> {
|
||||
try {
|
||||
var buff = new byte[n];
|
||||
var res = new ArrayValue();
|
||||
int resI = file.read(buff);
|
||||
|
||||
for (var i = resI - 1; i >= 0; i--) res.set(ctx, i, (int)buff[i]);
|
||||
return res;
|
||||
}
|
||||
catch (FilesystemException e) { throw e.toEngineException(); }
|
||||
});
|
||||
}
|
||||
@Native public PromiseLib write(Context ctx, ArrayValue val) {
|
||||
return PromiseLib.await(ctx, () -> {
|
||||
try {
|
||||
var res = new byte[val.size()];
|
||||
|
||||
for (var i = 0; i < val.size(); i++) res[i] = (byte)Values.toNumber(ctx, val.get(i));
|
||||
file.write(res);
|
||||
|
||||
return null;
|
||||
}
|
||||
catch (FilesystemException e) { throw e.toEngineException(); }
|
||||
});
|
||||
}
|
||||
@Native public PromiseLib close(Context ctx) {
|
||||
return PromiseLib.await(ctx, () -> {
|
||||
file.close();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
@Native public PromiseLib setPointer(Context ctx, long ptr) {
|
||||
return PromiseLib.await(ctx, () -> {
|
||||
try {
|
||||
file.setPtr(ptr, 0);
|
||||
return null;
|
||||
}
|
||||
catch (FilesystemException e) { throw e.toEngineException(); }
|
||||
});
|
||||
}
|
||||
|
||||
public FileLib(File file) {
|
||||
this.file = file;
|
||||
}
|
||||
}
|
||||
165
src/me/topchetoeu/jscript/lib/FilesystemLib.java
Normal file
165
src/me/topchetoeu/jscript/lib/FilesystemLib.java
Normal file
@@ -0,0 +1,165 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
import java.util.Stack;
|
||||
|
||||
import me.topchetoeu.jscript.Filename;
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.filesystem.EntryType;
|
||||
import me.topchetoeu.jscript.filesystem.File;
|
||||
import me.topchetoeu.jscript.filesystem.FileStat;
|
||||
import me.topchetoeu.jscript.filesystem.Filesystem;
|
||||
import me.topchetoeu.jscript.filesystem.FilesystemException;
|
||||
import me.topchetoeu.jscript.filesystem.Mode;
|
||||
import me.topchetoeu.jscript.filesystem.FilesystemException.FSCode;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
|
||||
@Native("Filesystem")
|
||||
public class FilesystemLib {
|
||||
private static Filesystem fs(Context ctx) {
|
||||
var env = ctx.environment();
|
||||
if (env != null) {
|
||||
var fs = ctx.environment().filesystem;
|
||||
if (fs != null) return fs;
|
||||
}
|
||||
throw EngineException.ofError("Current environment doesn't have a file system.");
|
||||
}
|
||||
|
||||
@Native public static PromiseLib open(Context ctx, String _path, String mode) {
|
||||
var filename = Filename.parse(_path);
|
||||
var _mode = Mode.parse(mode);
|
||||
|
||||
return PromiseLib.await(ctx, () -> {
|
||||
try {
|
||||
if (fs(ctx).stat(filename.path).type != EntryType.FILE) {
|
||||
throw new FilesystemException(filename.toString(), FSCode.NOT_FILE);
|
||||
}
|
||||
|
||||
var file = fs(ctx).open(filename.path, _mode);
|
||||
return new FileLib(file);
|
||||
}
|
||||
catch (FilesystemException e) { throw e.toEngineException(); }
|
||||
});
|
||||
}
|
||||
@Native public static ObjectValue ls(Context ctx, String _path) throws IOException {
|
||||
var filename = Filename.parse(_path);
|
||||
|
||||
return Values.toJSAsyncIterator(ctx, new Iterator<>() {
|
||||
private boolean failed, done;
|
||||
private File file;
|
||||
private String nextLine;
|
||||
|
||||
private void update() {
|
||||
if (done) return;
|
||||
if (!failed) {
|
||||
if (file == null) {
|
||||
if (fs(ctx).stat(filename.path).type != EntryType.FOLDER) {
|
||||
throw new FilesystemException(filename.toString(), FSCode.NOT_FOLDER);
|
||||
}
|
||||
|
||||
file = fs(ctx).open(filename.path, Mode.READ);
|
||||
}
|
||||
|
||||
if (nextLine == null) {
|
||||
while (true) {
|
||||
nextLine = file.readLine();
|
||||
if (nextLine == null) {
|
||||
done = true;
|
||||
return;
|
||||
}
|
||||
nextLine = nextLine.trim();
|
||||
if (!nextLine.equals("")) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
try {
|
||||
update();
|
||||
return !done && !failed;
|
||||
}
|
||||
catch (FilesystemException e) { throw e.toEngineException(); }
|
||||
}
|
||||
@Override
|
||||
public String next() {
|
||||
try {
|
||||
update();
|
||||
var res = nextLine;
|
||||
nextLine = null;
|
||||
return res;
|
||||
}
|
||||
catch (FilesystemException e) { throw e.toEngineException(); }
|
||||
}
|
||||
});
|
||||
}
|
||||
@Native public static PromiseLib mkdir(Context ctx, String _path) throws IOException {
|
||||
return PromiseLib.await(ctx, () -> {
|
||||
try {
|
||||
fs(ctx).create(Filename.parse(_path).toString(), EntryType.FOLDER);
|
||||
return null;
|
||||
}
|
||||
catch (FilesystemException e) { throw e.toEngineException(); }
|
||||
});
|
||||
|
||||
}
|
||||
@Native public static PromiseLib mkfile(Context ctx, String _path) throws IOException {
|
||||
return PromiseLib.await(ctx, () -> {
|
||||
try {
|
||||
fs(ctx).create(Filename.parse(_path).toString(), EntryType.FILE);
|
||||
return null;
|
||||
}
|
||||
catch (FilesystemException e) { throw e.toEngineException(); }
|
||||
});
|
||||
}
|
||||
@Native public static PromiseLib rm(Context ctx, String _path, boolean recursive) throws IOException {
|
||||
return PromiseLib.await(ctx, () -> {
|
||||
try {
|
||||
if (!recursive) fs(ctx).create(Filename.parse(_path).toString(), EntryType.NONE);
|
||||
else {
|
||||
var stack = new Stack<String>();
|
||||
stack.push(_path);
|
||||
|
||||
while (!stack.empty()) {
|
||||
var path = Filename.parse(stack.pop()).toString();
|
||||
FileStat stat;
|
||||
|
||||
try { stat = fs(ctx).stat(path); }
|
||||
catch (FilesystemException e) { continue; }
|
||||
|
||||
if (stat.type == EntryType.FOLDER) {
|
||||
for (var el : fs(ctx).open(path, Mode.READ).readToString().split("\n")) stack.push(el);
|
||||
}
|
||||
else fs(ctx).create(path, EntryType.NONE);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
catch (FilesystemException e) { throw e.toEngineException(); }
|
||||
});
|
||||
}
|
||||
@Native public static PromiseLib stat(Context ctx, String _path) throws IOException {
|
||||
return PromiseLib.await(ctx, () -> {
|
||||
try {
|
||||
var stat = fs(ctx).stat(_path);
|
||||
var res = new ObjectValue();
|
||||
|
||||
res.defineProperty(ctx, "type", stat.type.name);
|
||||
res.defineProperty(ctx, "mode", stat.mode.name);
|
||||
return res;
|
||||
}
|
||||
catch (FilesystemException e) { throw e.toEngineException(); }
|
||||
});
|
||||
}
|
||||
@Native public static PromiseLib exists(Context ctx, String _path) throws IOException {
|
||||
return PromiseLib.await(ctx, () -> {
|
||||
try { fs(ctx).stat(_path); return true; }
|
||||
catch (FilesystemException e) { return false; }
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,19 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
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.CodeFunction;
|
||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.engine.values.NativeFunction;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.interop.InitType;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeInit;
|
||||
|
||||
public class FunctionLib {
|
||||
@Native("Function") public class FunctionLib {
|
||||
@Native(thisArg = true) public static Object location(Context ctx, FunctionValue func) {
|
||||
if (func instanceof CodeFunction) return ((CodeFunction)func).loc().toString();
|
||||
else return Location.INTERNAL.toString();
|
||||
}
|
||||
@Native(thisArg = true) public static Object apply(Context ctx, FunctionValue func, Object thisArg, ArrayValue args) {
|
||||
return func.call(ctx, thisArg, args.toArray());
|
||||
}
|
||||
@@ -37,20 +39,16 @@ public class FunctionLib {
|
||||
});
|
||||
}
|
||||
@Native(thisArg = true) public static String toString(Context ctx, Object func) {
|
||||
return "function (...) { ... }";
|
||||
return func.toString();
|
||||
}
|
||||
|
||||
@Native public static FunctionValue async(FunctionValue func) {
|
||||
return new AsyncFunctionLib(func);
|
||||
}
|
||||
@Native public static FunctionValue asyncGenerator(FunctionValue func) {
|
||||
return new AsyncGeneratorLib(func);
|
||||
return new AsyncGeneratorFunctionLib(func);
|
||||
}
|
||||
@Native public static FunctionValue generator(FunctionValue func) {
|
||||
return new GeneratorLib(func);
|
||||
}
|
||||
|
||||
@NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) {
|
||||
target.defineProperty(null, env.symbol("Symbol.typeName"), "Function");
|
||||
return new GeneratorFunctionLib(func);
|
||||
}
|
||||
}
|
||||
|
||||
27
src/me/topchetoeu/jscript/lib/GeneratorFunctionLib.java
Normal file
27
src/me/topchetoeu/jscript/lib/GeneratorFunctionLib.java
Normal file
@@ -0,0 +1,27 @@
|
||||
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("GeneratorFunction") public class GeneratorFunctionLib extends FunctionValue {
|
||||
public final FunctionValue factory;
|
||||
|
||||
@Override
|
||||
public Object call(Context ctx, Object thisArg, Object ...args) {
|
||||
var handler = new GeneratorLib();
|
||||
var func = factory.call(ctx, thisArg, 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 GeneratorFunctionLib(FunctionValue factory) {
|
||||
super(factory.name, factory.length);
|
||||
this.factory = factory;
|
||||
}
|
||||
}
|
||||
@@ -1,102 +1,80 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.StackData;
|
||||
import me.topchetoeu.jscript.engine.frame.CodeFrame;
|
||||
import me.topchetoeu.jscript.engine.frame.Runners;
|
||||
import me.topchetoeu.jscript.engine.values.CodeFunction;
|
||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.engine.values.NativeFunction;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
|
||||
public class GeneratorLib extends FunctionValue {
|
||||
public final FunctionValue factory;
|
||||
@Native("Generator") public class GeneratorLib {
|
||||
private boolean yielding = true;
|
||||
private boolean done = false;
|
||||
public CodeFrame frame;
|
||||
|
||||
public static class Generator {
|
||||
private boolean yielding = true;
|
||||
private boolean done = false;
|
||||
public CodeFrame frame;
|
||||
@Native("@@Symbol.typeName") public final String name = "Generator";
|
||||
|
||||
@Native("@@Symbol.typeName") public final String name = "Generator";
|
||||
private ObjectValue next(Context ctx, Object inducedValue, Object inducedReturn, Object inducedError) {
|
||||
if (done) {
|
||||
if (inducedError != Runners.NO_RETURN) throw new EngineException(inducedError);
|
||||
var res = new ObjectValue();
|
||||
res.defineProperty(ctx, "done", true);
|
||||
res.defineProperty(ctx, "value", inducedReturn == Runners.NO_RETURN ? null : inducedReturn);
|
||||
return res;
|
||||
}
|
||||
|
||||
private ObjectValue next(Context ctx, Object inducedValue, Object inducedReturn, Object inducedError) {
|
||||
if (done) {
|
||||
if (inducedError != Runners.NO_RETURN) throw new EngineException(inducedError);
|
||||
var res = new ObjectValue();
|
||||
res.defineProperty(ctx, "done", true);
|
||||
res.defineProperty(ctx, "value", inducedReturn == Runners.NO_RETURN ? null : inducedReturn);
|
||||
return res;
|
||||
}
|
||||
Object res = null;
|
||||
ctx.pushFrame(frame);
|
||||
yielding = false;
|
||||
|
||||
Object res = null;
|
||||
StackData.pushFrame(ctx, frame);
|
||||
yielding = false;
|
||||
|
||||
while (!yielding) {
|
||||
try {
|
||||
res = frame.next(ctx, inducedValue, inducedReturn, inducedError == Runners.NO_RETURN ? null : new EngineException(inducedError));
|
||||
inducedReturn = inducedError = Runners.NO_RETURN;
|
||||
if (res != Runners.NO_RETURN) {
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (EngineException e) {
|
||||
while (!yielding) {
|
||||
try {
|
||||
res = frame.next(ctx, inducedValue, inducedReturn, inducedError == Runners.NO_RETURN ? null : new EngineException(inducedError));
|
||||
inducedReturn = inducedError = Runners.NO_RETURN;
|
||||
if (res != Runners.NO_RETURN) {
|
||||
done = true;
|
||||
throw e;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
StackData.popFrame(ctx, frame);
|
||||
if (done) frame = null;
|
||||
else res = frame.pop();
|
||||
|
||||
var obj = new ObjectValue();
|
||||
obj.defineProperty(ctx, "done", done);
|
||||
obj.defineProperty(ctx, "value", res);
|
||||
return obj;
|
||||
catch (EngineException e) {
|
||||
done = true;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
@Native
|
||||
public ObjectValue next(Context ctx, Object ...args) {
|
||||
if (args.length == 0) return next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, Runners.NO_RETURN);
|
||||
else return next(ctx, args[0], Runners.NO_RETURN, Runners.NO_RETURN);
|
||||
}
|
||||
@Native("throw")
|
||||
public ObjectValue _throw(Context ctx, Object error) {
|
||||
return next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, error);
|
||||
}
|
||||
@Native("return")
|
||||
public ObjectValue _return(Context ctx, Object value) {
|
||||
return next(ctx, Runners.NO_RETURN, value, Runners.NO_RETURN);
|
||||
}
|
||||
ctx.popFrame(frame);
|
||||
if (done) frame = null;
|
||||
else res = frame.pop();
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (done) return "Generator [closed]";
|
||||
if (yielding) return "Generator [suspended]";
|
||||
return "Generator [running]";
|
||||
}
|
||||
var obj = new ObjectValue();
|
||||
obj.defineProperty(ctx, "done", done);
|
||||
obj.defineProperty(ctx, "value", res);
|
||||
return obj;
|
||||
}
|
||||
|
||||
public Object yield(Context ctx, Object thisArg, Object[] args) {
|
||||
this.yielding = true;
|
||||
return args.length > 0 ? args[0] : null;
|
||||
}
|
||||
@Native
|
||||
public ObjectValue next(Context ctx, Object ...args) {
|
||||
if (args.length == 0) return next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, Runners.NO_RETURN);
|
||||
else return next(ctx, args[0], Runners.NO_RETURN, Runners.NO_RETURN);
|
||||
}
|
||||
@Native("throw")
|
||||
public ObjectValue _throw(Context ctx, Object error) {
|
||||
return next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, error);
|
||||
}
|
||||
@Native("return")
|
||||
public ObjectValue _return(Context ctx, Object value) {
|
||||
return next(ctx, Runners.NO_RETURN, value, Runners.NO_RETURN);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object call(Context ctx, Object thisArg, Object ...args) {
|
||||
var handler = new Generator();
|
||||
var func = factory.call(ctx, thisArg, 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 String toString() {
|
||||
if (done) return "Generator [closed]";
|
||||
if (yielding) return "Generator [suspended]";
|
||||
return "Generator [running]";
|
||||
}
|
||||
|
||||
public GeneratorLib(FunctionValue factory) {
|
||||
super(factory.name, factory.length);
|
||||
this.factory = factory;
|
||||
public Object yield(Context ctx, Object thisArg, Object[] args) {
|
||||
this.yielding = true;
|
||||
return args.length > 0 ? args[0] : null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,17 +11,22 @@ import me.topchetoeu.jscript.engine.scope.GlobalScope;
|
||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
|
||||
public class Internals {
|
||||
private static final DataKey<HashMap<Integer, Thread>> THREADS = new DataKey<>();
|
||||
private static final DataKey<Integer> I = new DataKey<>();
|
||||
|
||||
|
||||
@Native public static void log(Context ctx, Object ...args) {
|
||||
@Native public static Object log(Context ctx, Object ...args) {
|
||||
for (var arg : args) {
|
||||
Values.printValue(ctx, arg);
|
||||
System.out.print(" ");
|
||||
}
|
||||
System.out.println();
|
||||
|
||||
if (args.length == 0) return null;
|
||||
else return args[0];
|
||||
}
|
||||
@Native public static String readline(Context ctx) {
|
||||
try {
|
||||
@@ -91,12 +96,31 @@ public class Internals {
|
||||
return NumberLib.parseFloat(ctx, val);
|
||||
}
|
||||
|
||||
public void apply(Environment env) {
|
||||
@Native public static boolean isNaN(Context ctx, double val) {
|
||||
return NumberLib.isNaN(ctx, val);
|
||||
}
|
||||
@Native public static boolean isFinite(Context ctx, double val) {
|
||||
return NumberLib.isFinite(ctx, val);
|
||||
}
|
||||
@Native public static boolean isInfinite(Context ctx, double val) {
|
||||
return NumberLib.isInfinite(ctx, val);
|
||||
}
|
||||
|
||||
@NativeGetter public static double NaN(Context ctx) {
|
||||
return Double.NaN;
|
||||
}
|
||||
@NativeGetter public static double Infinity(Context ctx) {
|
||||
return Double.POSITIVE_INFINITY;
|
||||
}
|
||||
|
||||
public static Environment apply(Environment env) {
|
||||
var wp = env.wrappers;
|
||||
var glob = env.global = new GlobalScope(wp.getNamespace(Internals.class));
|
||||
|
||||
glob.define(null, "Math", false, wp.getNamespace(MathLib.class));
|
||||
glob.define(null, "JSON", false, wp.getNamespace(JSONLib.class));
|
||||
glob.define(null, "Encoding", false, wp.getNamespace(EncodingLib.class));
|
||||
glob.define(null, "Filesystem", false, wp.getNamespace(FilesystemLib.class));
|
||||
|
||||
glob.define(null, "Date", false, wp.getConstr(DateLib.class));
|
||||
glob.define(null, "Object", false, wp.getConstr(ObjectLib.class));
|
||||
@@ -133,7 +157,8 @@ public class Internals {
|
||||
env.setProto("rangeErr", wp.getProto(RangeErrorLib.class));
|
||||
|
||||
wp.getProto(ObjectLib.class).setPrototype(null, null);
|
||||
env.regexConstructor = wp.getConstr(RegExpLib.class);
|
||||
|
||||
System.out.println("Loaded polyfills!");
|
||||
return env;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import me.topchetoeu.jscript.Filename;
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.exceptions.SyntaxException;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.json.JSON;
|
||||
|
||||
public class JSONLib {
|
||||
@Native("JSON") public class JSONLib {
|
||||
@Native
|
||||
public static Object parse(Context ctx, String val) {
|
||||
try {
|
||||
return JSON.toJs(JSON.parse(new Filename("jscript", "json"), val));
|
||||
return JSON.toJs(JSON.parse(null, val));
|
||||
}
|
||||
catch (SyntaxException e) { throw EngineException.ofSyntax(e.msg); }
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
|
||||
public class MapLib {
|
||||
@Native("Map") public class MapLib {
|
||||
private LinkedHashMap<Object, Object> map = new LinkedHashMap<>();
|
||||
|
||||
@Native("@@Symbol.typeName") public final String name = "Map";
|
||||
@@ -35,15 +35,15 @@ public class MapLib {
|
||||
var res = map.entrySet().stream().map(v -> {
|
||||
return new ArrayValue(ctx, v.getKey(), v.getValue());
|
||||
}).collect(Collectors.toList());
|
||||
return Values.fromJavaIterator(ctx, res.iterator());
|
||||
return Values.toJSIterator(ctx, res.iterator());
|
||||
}
|
||||
@Native public ObjectValue keys(Context ctx) {
|
||||
var res = new ArrayList<>(map.keySet());
|
||||
return Values.fromJavaIterator(ctx, res.iterator());
|
||||
return Values.toJSIterator(ctx, res.iterator());
|
||||
}
|
||||
@Native public ObjectValue values(Context ctx) {
|
||||
var res = new ArrayList<>(map.values());
|
||||
return Values.fromJavaIterator(ctx, res.iterator());
|
||||
return Values.toJSIterator(ctx, res.iterator());
|
||||
}
|
||||
|
||||
@Native public Object get(Object key) {
|
||||
@@ -61,14 +61,14 @@ public class MapLib {
|
||||
return map.size();
|
||||
}
|
||||
|
||||
@NativeGetter public void forEach(Context ctx, FunctionValue func, Object thisArg) {
|
||||
@Native public void forEach(Context ctx, FunctionValue func, Object thisArg) {
|
||||
var keys = new ArrayList<>(map.keySet());
|
||||
|
||||
for (var el : keys) func.call(ctx, thisArg, el, map.get(el), this);
|
||||
for (var el : keys) func.call(ctx, thisArg, map.get(el), el,this);
|
||||
}
|
||||
|
||||
@Native public MapLib(Context ctx, Object iterable) {
|
||||
for (var el : Values.toJavaIterable(ctx, iterable)) {
|
||||
for (var el : Values.fromJSIterator(ctx, iterable)) {
|
||||
try {
|
||||
set(Values.getMember(ctx, el, 0), Values.getMember(ctx, el, 1));
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@ package me.topchetoeu.jscript.lib;
|
||||
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
|
||||
public class MathLib {
|
||||
@Native("Math") public class MathLib {
|
||||
@Native public static final double E = Math.E;
|
||||
@Native public static final double PI = Math.PI;
|
||||
@Native public static final double SQRT2 = Math.sqrt(2);
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
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.Values;
|
||||
import me.topchetoeu.jscript.interop.InitType;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeConstructor;
|
||||
import me.topchetoeu.jscript.interop.NativeInit;
|
||||
|
||||
public class NumberLib {
|
||||
@Native("Number") public class NumberLib {
|
||||
@Native public static final double EPSILON = java.lang.Math.ulp(1.0);
|
||||
@Native public static final double MAX_SAFE_INTEGER = 9007199254740991.;
|
||||
@Native public static final double MIN_SAFE_INTEGER = -MAX_SAFE_INTEGER;
|
||||
@@ -52,8 +49,4 @@ public class NumberLib {
|
||||
public NumberLib(double val) {
|
||||
this.value = val;
|
||||
}
|
||||
|
||||
@NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) {
|
||||
target.defineProperty(null, env.symbol("Symbol.typeName"), "Number");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,16 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
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.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.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.interop.InitType;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeConstructor;
|
||||
import me.topchetoeu.jscript.interop.NativeInit;
|
||||
|
||||
public class ObjectLib {
|
||||
@Native("Object") public class ObjectLib {
|
||||
@Native public static ObjectValue assign(Context ctx, ObjectValue dst, Object... src) {
|
||||
for (var obj : src) {
|
||||
for (var key : Values.getMembers(ctx, obj, true, true)) {
|
||||
@@ -143,7 +140,7 @@ public class ObjectLib {
|
||||
@Native public static ObjectValue fromEntries(Context ctx, Object iterable) {
|
||||
var res = new ObjectValue();
|
||||
|
||||
for (var el : Values.toJavaIterable(ctx, iterable)) {
|
||||
for (var el : Values.fromJSIterator(ctx, iterable)) {
|
||||
if (el instanceof ArrayValue) {
|
||||
res.defineProperty(ctx, ((ArrayValue)el).get(0), ((ArrayValue)el).get(1));
|
||||
}
|
||||
@@ -212,8 +209,4 @@ public class ObjectLib {
|
||||
// else if (arg instanceof Symbol) return SymbolPolyfill.constructor(ctx, thisArg, arg);
|
||||
else return arg;
|
||||
}
|
||||
|
||||
@NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) {
|
||||
target.defineProperty(null, env.symbol("Symbol.typeName"), "Object");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
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.FunctionValue;
|
||||
import me.topchetoeu.jscript.engine.values.NativeFunction;
|
||||
@@ -13,12 +12,12 @@ import me.topchetoeu.jscript.engine.values.NativeWrapper;
|
||||
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.InterruptException;
|
||||
import me.topchetoeu.jscript.interop.InitType;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeInit;
|
||||
|
||||
public class PromiseLib {
|
||||
@Native("Promise") public class PromiseLib {
|
||||
public static interface PromiseRunner {
|
||||
Object run();
|
||||
}
|
||||
private static class Handle {
|
||||
public final Context ctx;
|
||||
public final FunctionValue fulfilled;
|
||||
@@ -171,9 +170,7 @@ public class PromiseLib {
|
||||
}
|
||||
|
||||
var fulfillHandle = new NativeFunction(null, (_ctx, th, a) -> {
|
||||
try {
|
||||
res.fulfill(ctx, Values.convert(ctx, fulfill.call(ctx, null, a[0]), Object.class));
|
||||
}
|
||||
try { res.fulfill(ctx, Values.convert(ctx, fulfill.call(ctx, null, a[0]), Object.class)); }
|
||||
catch (EngineException err) { res.reject(ctx, err.value); }
|
||||
return null;
|
||||
});
|
||||
@@ -234,7 +231,7 @@ public class PromiseLib {
|
||||
private boolean handled = false;
|
||||
private Object val;
|
||||
|
||||
public void fulfill(Context ctx, Object val) {
|
||||
public synchronized void fulfill(Context ctx, Object val) {
|
||||
if (this.state != STATE_PENDING) return;
|
||||
|
||||
if (val instanceof PromiseLib) ((PromiseLib)val).handle(ctx,
|
||||
@@ -242,12 +239,12 @@ public class PromiseLib {
|
||||
new NativeFunction(null, (e, th, a) -> { this.reject(ctx, a[0]); return null; })
|
||||
);
|
||||
else {
|
||||
Object next;
|
||||
try { next = Values.getMember(ctx, val, "next"); }
|
||||
catch (IllegalArgumentException e) { next = null; }
|
||||
Object then;
|
||||
try { then = Values.getMember(ctx, val, "then"); }
|
||||
catch (IllegalArgumentException e) { then = null; }
|
||||
|
||||
try {
|
||||
if (next instanceof FunctionValue) ((FunctionValue)next).call(ctx, val,
|
||||
if (then instanceof FunctionValue) ((FunctionValue)then).call(ctx, val,
|
||||
new NativeFunction((e, _thisArg, a) -> { this.fulfill(ctx, a.length > 0 ? a[0] : null); return null; }),
|
||||
new NativeFunction((e, _thisArg, a) -> { this.reject(ctx, a.length > 0 ? a[0] : null); return null; })
|
||||
);
|
||||
@@ -255,9 +252,13 @@ public class PromiseLib {
|
||||
this.val = val;
|
||||
this.state = STATE_FULFILLED;
|
||||
|
||||
for (var handle : handles) handle.fulfilled.call(handle.ctx, null, val);
|
||||
|
||||
handles = null;
|
||||
ctx.engine.pushMsg(true, ctx, new NativeFunction((_ctx, _thisArg, _args) -> {
|
||||
for (var handle : handles) {
|
||||
handle.fulfilled.call(handle.ctx, null, val);
|
||||
}
|
||||
handles = null;
|
||||
return null;
|
||||
}), null);
|
||||
}
|
||||
}
|
||||
catch (EngineException err) {
|
||||
@@ -265,7 +266,7 @@ public class PromiseLib {
|
||||
}
|
||||
}
|
||||
}
|
||||
public void reject(Context ctx, Object val) {
|
||||
public synchronized void reject(Context ctx, Object val) {
|
||||
if (this.state != STATE_PENDING) return;
|
||||
|
||||
if (val instanceof PromiseLib) ((PromiseLib)val).handle(ctx,
|
||||
@@ -273,12 +274,12 @@ public class PromiseLib {
|
||||
new NativeFunction(null, (e, th, a) -> { this.reject(ctx, a[0]); return null; })
|
||||
);
|
||||
else {
|
||||
Object next;
|
||||
try { next = Values.getMember(ctx, val, "next"); }
|
||||
catch (IllegalArgumentException e) { next = null; }
|
||||
Object then;
|
||||
try { then = Values.getMember(ctx, val, "then"); }
|
||||
catch (IllegalArgumentException e) { then = null; }
|
||||
|
||||
try {
|
||||
if (next instanceof FunctionValue) ((FunctionValue)next).call(ctx, val,
|
||||
if (then instanceof FunctionValue) ((FunctionValue)then).call(ctx, val,
|
||||
new NativeFunction((e, _thisArg, a) -> { this.reject(ctx, a.length > 0 ? a[0] : null); return null; }),
|
||||
new NativeFunction((e, _thisArg, a) -> { this.reject(ctx, a.length > 0 ? a[0] : null); return null; })
|
||||
);
|
||||
@@ -286,19 +287,14 @@ public class PromiseLib {
|
||||
this.val = val;
|
||||
this.state = STATE_REJECTED;
|
||||
|
||||
for (var handle : handles) handle.rejected.call(handle.ctx, null, val);
|
||||
if (handles.size() == 0) {
|
||||
ctx.engine.pushMsg(true, ctx, new NativeFunction((_ctx, _thisArg, _args) -> {
|
||||
if (!handled) {
|
||||
Values.printError(new EngineException(val).setCtx(ctx.environment(), ctx.engine), "(in promise)");
|
||||
throw new InterruptException();
|
||||
}
|
||||
|
||||
return null;
|
||||
}), null);
|
||||
}
|
||||
|
||||
handles = null;
|
||||
ctx.engine.pushMsg(true, ctx, new NativeFunction((_ctx, _thisArg, _args) -> {
|
||||
for (var handle : handles) handle.rejected.call(handle.ctx, null, val);
|
||||
if (!handled) {
|
||||
Values.printError(new EngineException(val).setCtx(ctx.environment(), ctx.engine), "(in promise)");
|
||||
}
|
||||
handles = null;
|
||||
return null;
|
||||
}), null);
|
||||
}
|
||||
}
|
||||
catch (EngineException err) {
|
||||
@@ -353,7 +349,18 @@ public class PromiseLib {
|
||||
this(STATE_PENDING, null);
|
||||
}
|
||||
|
||||
@NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) {
|
||||
target.defineProperty(null, env.symbol("Symbol.typeName"), "Promise");
|
||||
public static PromiseLib await(Context ctx, PromiseRunner runner) {
|
||||
var res = new PromiseLib();
|
||||
|
||||
new Thread(() -> {
|
||||
try {
|
||||
res.fulfill(ctx, runner.run());
|
||||
}
|
||||
catch (EngineException e) {
|
||||
res.reject(ctx, e.value);
|
||||
}
|
||||
}, "Promisifier").start();
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,18 +3,20 @@ package me.topchetoeu.jscript.lib;
|
||||
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.PlaceholderProto;
|
||||
import me.topchetoeu.jscript.interop.InitType;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeConstructor;
|
||||
import me.topchetoeu.jscript.interop.NativeInit;
|
||||
|
||||
public class RangeErrorLib extends ErrorLib {
|
||||
@Native("RangeError") public class RangeErrorLib extends ErrorLib {
|
||||
@NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) {
|
||||
var target = ErrorLib.constructor(ctx, thisArg, message);
|
||||
target.setPrototype(PlaceholderProto.SYNTAX_ERROR);
|
||||
target.defineProperty(ctx, "name", "RangeError");
|
||||
return target;
|
||||
}
|
||||
@NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) {
|
||||
target.defineProperty(null, env.symbol("Symbol.typeName"), "RangeError");
|
||||
target.defineProperty(null, "name", "RangeError");
|
||||
}
|
||||
}
|
||||
@@ -6,13 +6,14 @@ import java.util.regex.Pattern;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.engine.values.NativeWrapper;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
|
||||
public class RegExpLib {
|
||||
@Native("RegExp") public class RegExpLib {
|
||||
// I used Regex to analyze Regex
|
||||
private static final Pattern NAMED_PATTERN = Pattern.compile("\\(\\?<([^=!].*?)>", Pattern.DOTALL);
|
||||
private static final Pattern ESCAPE_PATTERN = Pattern.compile("[/\\-\\\\^$*+?.()|\\[\\]{}]");
|
||||
@@ -81,7 +82,6 @@ public class RegExpLib {
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
@Native public Object exec(String str) {
|
||||
var matcher = pattern.matcher(str);
|
||||
if (lastI > str.length() || !matcher.find(lastI) || sticky && matcher.start() != lastI) {
|
||||
@@ -133,7 +133,7 @@ public class RegExpLib {
|
||||
return "/" + source + "/" + flags();
|
||||
}
|
||||
|
||||
@Native("@@Symvol.match") public Object match(Context ctx, String target) {
|
||||
@Native("@@Symbol.match") public Object match(Context ctx, String target) {
|
||||
if (this.global) {
|
||||
var res = new ArrayValue();
|
||||
Object val;
|
||||
@@ -150,10 +150,10 @@ public class RegExpLib {
|
||||
}
|
||||
}
|
||||
|
||||
@Native("@@Symvol.matchAll") public Object matchAll(Context ctx, String target) {
|
||||
@Native("@@Symbol.matchAll") public Object matchAll(Context ctx, String target) {
|
||||
var pattern = new RegExpLib(this.source, this.flags() + "g");
|
||||
|
||||
return Values.fromJavaIterator(ctx, new Iterator<Object>() {
|
||||
return Values.toJSIterator(ctx, new Iterator<Object>() {
|
||||
private Object val = null;
|
||||
private boolean updated = false;
|
||||
|
||||
@@ -171,8 +171,8 @@ public class RegExpLib {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Native("@@Symvol.split") public ArrayValue split(Context ctx, String target, Object limit, boolean sensible) {
|
||||
|
||||
@Native("@@Symbol.split") public ArrayValue split(Context ctx, String target, Object limit, boolean sensible) {
|
||||
var pattern = new RegExpLib(this.source, this.flags() + "g");
|
||||
Object match;
|
||||
int lastEnd = 0;
|
||||
@@ -214,29 +214,41 @@ public class RegExpLib {
|
||||
}
|
||||
return res;
|
||||
}
|
||||
// [Symbol.replace](target, replacement) {
|
||||
// const pattern = new this.constructor(this, this.flags + "d") as RegExp;
|
||||
// let match: RegExpResult | null;
|
||||
// let lastEnd = 0;
|
||||
// const res: string[] = [];
|
||||
// // log(pattern.toString());
|
||||
// while ((match = pattern.exec(target)) !== null) {
|
||||
// const indices = match.indices![0];
|
||||
// res.push(target.substring(lastEnd, indices[0]));
|
||||
// if (replacement instanceof Function) {
|
||||
// res.push(replacement(target.substring(indices[0], indices[1]), ...match.slice(1), indices[0], target));
|
||||
// }
|
||||
// else {
|
||||
// res.push(replacement);
|
||||
// }
|
||||
// lastEnd = indices[1];
|
||||
// if (!pattern.global) break;
|
||||
// }
|
||||
// if (lastEnd < target.length) {
|
||||
// res.push(target.substring(lastEnd));
|
||||
// }
|
||||
// return res.join('');
|
||||
// },
|
||||
|
||||
@Native("@@Symbol.replace") public String replace(Context ctx, String target, Object replacement) {
|
||||
var pattern = new RegExpLib(this.source, this.flags() + "d");
|
||||
Object match;
|
||||
var lastEnd = 0;
|
||||
var res = new StringBuilder();
|
||||
|
||||
while ((match = pattern.exec(target)) != Values.NULL) {
|
||||
var indices = (ArrayValue)((ArrayValue)Values.getMember(ctx, match, "indices")).get(0);
|
||||
var arrMatch = (ArrayValue)match;
|
||||
|
||||
var start = ((Number)indices.get(0)).intValue();
|
||||
var end = ((Number)indices.get(1)).intValue();
|
||||
|
||||
res.append(target.substring(lastEnd, start));
|
||||
if (replacement instanceof FunctionValue) {
|
||||
var args = new Object[arrMatch.size() + 2];
|
||||
args[0] = target.substring(start, end);
|
||||
arrMatch.copyTo(args, 1, 1, arrMatch.size() - 1);
|
||||
args[args.length - 2] = start;
|
||||
args[args.length - 1] = target;
|
||||
res.append(Values.toString(ctx, ((FunctionValue)replacement).call(ctx, null, args)));
|
||||
}
|
||||
else {
|
||||
res.append(Values.toString(ctx, replacement));
|
||||
}
|
||||
lastEnd = end;
|
||||
if (!pattern.global) break;
|
||||
}
|
||||
if (lastEnd < target.length()) {
|
||||
res.append(target.substring(lastEnd));
|
||||
}
|
||||
return res.toString();
|
||||
}
|
||||
|
||||
// [Symbol.search](target, reverse, start) {
|
||||
// const pattern: RegExp | undefined = new this.constructor(this, this.flags + "g") as RegExp;
|
||||
// if (!reverse) {
|
||||
@@ -258,6 +270,7 @@ public class RegExpLib {
|
||||
// else return -1;
|
||||
// }
|
||||
// },
|
||||
|
||||
@Native public RegExpLib(Context ctx, Object pattern, Object flags) {
|
||||
this(cleanupPattern(ctx, pattern), cleanupFlags(ctx, flags));
|
||||
}
|
||||
@@ -276,6 +289,7 @@ public class RegExpLib {
|
||||
if (flags.contains("s")) this.flags |= Pattern.DOTALL;
|
||||
if (flags.contains("u")) this.flags |= Pattern.UNICODE_CHARACTER_CLASS;
|
||||
|
||||
if (pattern.equals("{(\\d+)}")) pattern = "\\{([0-9]+)\\}";
|
||||
this.pattern = Pattern.compile(pattern.replace("\\d", "[0-9]"), this.flags);
|
||||
|
||||
var matcher = NAMED_PATTERN.matcher(source);
|
||||
|
||||
@@ -12,7 +12,7 @@ import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
|
||||
public class SetLib {
|
||||
@Native("Set") public class SetLib {
|
||||
private LinkedHashSet<Object> set = new LinkedHashSet<>();
|
||||
|
||||
@Native("@@Symbol.typeName") public final String name = "Set";
|
||||
@@ -22,15 +22,15 @@ public class SetLib {
|
||||
|
||||
@Native public ObjectValue entries(Context ctx) {
|
||||
var res = set.stream().map(v -> new ArrayValue(ctx, v, v)).collect(Collectors.toList());
|
||||
return Values.fromJavaIterator(ctx, res.iterator());
|
||||
return Values.toJSIterator(ctx, res.iterator());
|
||||
}
|
||||
@Native public ObjectValue keys(Context ctx) {
|
||||
var res = new ArrayList<>(set);
|
||||
return Values.fromJavaIterator(ctx, res.iterator());
|
||||
return Values.toJSIterator(ctx, res.iterator());
|
||||
}
|
||||
@Native public ObjectValue values(Context ctx) {
|
||||
var res = new ArrayList<>(set);
|
||||
return Values.fromJavaIterator(ctx, res.iterator());
|
||||
return Values.toJSIterator(ctx, res.iterator());
|
||||
}
|
||||
|
||||
@Native public Object add(Object key) {
|
||||
@@ -51,13 +51,13 @@ public class SetLib {
|
||||
return set.size();
|
||||
}
|
||||
|
||||
@NativeGetter public void forEach(Context ctx, FunctionValue func, Object thisArg) {
|
||||
@Native public void forEach(Context ctx, FunctionValue func, Object thisArg) {
|
||||
var keys = new ArrayList<>(set);
|
||||
|
||||
for (var el : keys) func.call(ctx, thisArg, el, el, this);
|
||||
}
|
||||
|
||||
@Native public SetLib(Context ctx, Object iterable) {
|
||||
for (var el : Values.toJavaIterable(ctx, iterable)) add(el);
|
||||
for (var el : Values.fromJSIterator(ctx, iterable)) add(el);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,20 +3,17 @@ package me.topchetoeu.jscript.lib;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
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.FunctionValue;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.interop.InitType;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeConstructor;
|
||||
import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
import me.topchetoeu.jscript.interop.NativeInit;
|
||||
|
||||
// TODO: implement index wrapping properly
|
||||
public class StringLib {
|
||||
@Native("String") public class StringLib {
|
||||
public final String value;
|
||||
|
||||
private static String passThis(Context ctx, String funcName, Object val) {
|
||||
@@ -60,8 +57,18 @@ public class StringLib {
|
||||
@Native(thisArg = true) public static String charAt(Context ctx, Object thisArg, int i) {
|
||||
return passThis(ctx, "charAt", thisArg).charAt(i) + "";
|
||||
}
|
||||
@Native(thisArg = true) public static int charCodeAt(Context ctx, Object thisArg, int i) {
|
||||
return passThis(ctx, "charCodeAt", thisArg).charAt(i);
|
||||
// @Native(thisArg = true) public static int charCodeAt(Context ctx, Object thisArg, int i) {
|
||||
// return passThis(ctx, "charCodeAt", thisArg).charAt(i);
|
||||
// }
|
||||
// @Native(thisArg = true) public static String charAt(Context ctx, Object thisArg, int i) {
|
||||
// var str = passThis(ctx, "charAt", thisArg);
|
||||
// if (i < 0 || i >= str.length()) return "";
|
||||
// else return str.charAt(i) + "";
|
||||
// }
|
||||
@Native(thisArg = true) public static double charCodeAt(Context ctx, Object thisArg, int i) {
|
||||
var str = passThis(ctx, "charCodeAt", thisArg);
|
||||
if (i < 0 || i >= str.length()) return Double.NaN;
|
||||
else return str.charAt(i);
|
||||
}
|
||||
|
||||
@Native(thisArg = true) public static boolean startsWith(Context ctx, Object thisArg, String term, int pos) {
|
||||
@@ -101,7 +108,7 @@ public class StringLib {
|
||||
return lastIndexOf(ctx, passThis(ctx, "includes", thisArg), term, pos) >= 0;
|
||||
}
|
||||
|
||||
@Native(thisArg = true) public static String replace(Context ctx, Object thisArg, Object term, String replacement) {
|
||||
@Native(thisArg = true) public static String replace(Context ctx, Object thisArg, Object term, Object replacement) {
|
||||
var val = passThis(ctx, "replace", thisArg);
|
||||
|
||||
if (term != null && term != Values.NULL && !(term instanceof String)) {
|
||||
@@ -111,9 +118,9 @@ public class StringLib {
|
||||
}
|
||||
}
|
||||
|
||||
return val.replaceFirst(Pattern.quote(Values.toString(ctx, term)), replacement);
|
||||
return val.replaceFirst(Pattern.quote(Values.toString(ctx, term)), Values.toString(ctx, replacement));
|
||||
}
|
||||
@Native(thisArg = true) public static String replaceAll(Context ctx, Object thisArg, Object term, String replacement) {
|
||||
@Native(thisArg = true) public static String replaceAll(Context ctx, Object thisArg, Object term, Object replacement) {
|
||||
var val = passThis(ctx, "replaceAll", thisArg);
|
||||
|
||||
if (term != null && term != Values.NULL && !(term instanceof String)) {
|
||||
@@ -123,7 +130,7 @@ public class StringLib {
|
||||
}
|
||||
}
|
||||
|
||||
return val.replaceFirst(Pattern.quote(Values.toString(ctx, term)), replacement);
|
||||
return val.replaceFirst(Pattern.quote(Values.toString(ctx, term)), Values.toString(ctx, replacement));
|
||||
}
|
||||
|
||||
@Native(thisArg = true) public static ArrayValue match(Context ctx, Object thisArg, Object term, String replacement) {
|
||||
@@ -253,8 +260,4 @@ public class StringLib {
|
||||
public StringLib(String val) {
|
||||
this.value = val;
|
||||
}
|
||||
|
||||
@NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) {
|
||||
target.defineProperty(null, env.symbol("Symbol.typeName"), "String");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,18 +4,15 @@ import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
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.Symbol;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.interop.InitType;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeConstructor;
|
||||
import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
import me.topchetoeu.jscript.interop.NativeInit;
|
||||
|
||||
public class SymbolLib {
|
||||
@Native("Symbol") public class SymbolLib {
|
||||
private static final Map<String, Symbol> symbols = new HashMap<>();
|
||||
|
||||
@NativeGetter public static Symbol typeName(Context ctx) { return ctx.environment().symbol("Symbol.typeName"); }
|
||||
@@ -63,8 +60,4 @@ public class SymbolLib {
|
||||
public SymbolLib(Symbol val) {
|
||||
this.value = val;
|
||||
}
|
||||
|
||||
@NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) {
|
||||
target.defineProperty(null, env.symbol("Symbol.typeName"), "Symbol");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,18 +3,19 @@ package me.topchetoeu.jscript.lib;
|
||||
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.PlaceholderProto;
|
||||
import me.topchetoeu.jscript.interop.InitType;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeConstructor;
|
||||
import me.topchetoeu.jscript.interop.NativeInit;
|
||||
|
||||
public class SyntaxErrorLib extends ErrorLib {
|
||||
@Native("SyntaxError") public class SyntaxErrorLib extends ErrorLib {
|
||||
@NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) {
|
||||
var target = ErrorLib.constructor(ctx, thisArg, message);
|
||||
target.defineProperty(ctx, "name", "SyntaxError");
|
||||
target.setPrototype(PlaceholderProto.SYNTAX_ERROR);
|
||||
return target;
|
||||
}
|
||||
@NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) {
|
||||
target.defineProperty(null, env.symbol("Symbol.typeName"), "SyntaxError");
|
||||
target.defineProperty(null, "name", "SyntaxError");
|
||||
}
|
||||
}
|
||||
@@ -3,18 +3,19 @@ package me.topchetoeu.jscript.lib;
|
||||
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.PlaceholderProto;
|
||||
import me.topchetoeu.jscript.interop.InitType;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeConstructor;
|
||||
import me.topchetoeu.jscript.interop.NativeInit;
|
||||
|
||||
public class TypeErrorLib extends ErrorLib {
|
||||
@Native("TypeError") public class TypeErrorLib extends ErrorLib {
|
||||
@NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) {
|
||||
var target = ErrorLib.constructor(ctx, thisArg, message);
|
||||
target.defineProperty(ctx, "name", "TypeError");
|
||||
target.setPrototype(PlaceholderProto.SYNTAX_ERROR);
|
||||
return target;
|
||||
}
|
||||
@NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) {
|
||||
target.defineProperty(null, env.symbol("Symbol.typeName"), "TypeError");
|
||||
target.defineProperty(null, "name", "TypeError");
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ import java.util.TreeSet;
|
||||
import me.topchetoeu.jscript.Filename;
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.*;
|
||||
import me.topchetoeu.jscript.compilation.Instruction.Type;
|
||||
import me.topchetoeu.jscript.compilation.VariableDeclareStatement.Pair;
|
||||
import me.topchetoeu.jscript.compilation.control.*;
|
||||
import me.topchetoeu.jscript.compilation.control.SwitchStatement.SwitchCase;
|
||||
@@ -82,8 +81,6 @@ public class Parsing {
|
||||
// Although ES5 allow these, we will comply to ES6 here
|
||||
reserved.add("const");
|
||||
reserved.add("let");
|
||||
reserved.add("async");
|
||||
reserved.add("super");
|
||||
// These are allowed too, however our parser considers them keywords
|
||||
reserved.add("undefined");
|
||||
reserved.add("arguments");
|
||||
@@ -259,7 +256,7 @@ public class Parsing {
|
||||
}
|
||||
break;
|
||||
case CURR_LITERAL:
|
||||
if (isAlphanumeric(c) || c == '_') {
|
||||
if (isAlphanumeric(c) || c == '_' || c == '$') {
|
||||
currToken.append(c);
|
||||
continue;
|
||||
}
|
||||
@@ -1060,7 +1057,6 @@ public class Parsing {
|
||||
|
||||
if (!checkVarName(literal.result)) {
|
||||
if (literal.result.equals("await")) return ParseRes.error(loc, "'await' expressions are not supported.");
|
||||
if (literal.result.equals("async")) return ParseRes.error(loc, "'async' is not supported.");
|
||||
if (literal.result.equals("const")) return ParseRes.error(loc, "'const' declarations are not supported.");
|
||||
if (literal.result.equals("let")) return ParseRes.error(loc, "'let' declarations are not supported.");
|
||||
return ParseRes.error(loc, String.format("Unexpected identifier '%s'.", literal.result));
|
||||
@@ -1137,7 +1133,9 @@ public class Parsing {
|
||||
var op = opRes.result;
|
||||
if (!op.isAssign()) return ParseRes.failed();
|
||||
|
||||
if (!(prev instanceof AssignableStatement)) return ParseRes.error(loc, "Invalid expression on left hand side of assign operator.");
|
||||
if (!(prev instanceof AssignableStatement)) {
|
||||
return ParseRes.error(loc, "Invalid expression on left hand side of assign operator.");
|
||||
}
|
||||
|
||||
var res = parseValue(filename, tokens, i + n, 2);
|
||||
if (!res.isSuccess()) return ParseRes.error(loc, String.format("Expected value after assignment operator '%s'.", op.value), res);
|
||||
@@ -1235,7 +1233,7 @@ public class Parsing {
|
||||
|
||||
return ParseRes.res(new OperationStatement(loc, Operation.IN, prev, valRes.result), n);
|
||||
}
|
||||
public static ParseRes<CompoundStatement> parseComma(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) {
|
||||
public static ParseRes<CommaStatement> parseComma(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) {
|
||||
var loc = getLoc(filename, tokens, i);
|
||||
var n = 0;
|
||||
|
||||
@@ -1246,7 +1244,7 @@ public class Parsing {
|
||||
if (!res.isSuccess()) return ParseRes.error(loc, "Expected a value after the comma.", res);
|
||||
n += res.n;
|
||||
|
||||
return ParseRes.res(new CompoundStatement(loc, prev, res.result), n);
|
||||
return ParseRes.res(new CommaStatement(loc, prev, res.result), n);
|
||||
}
|
||||
public static ParseRes<IfStatement> parseTernary(Filename filename, List<Token> tokens, int i, Statement prev, int precedence) {
|
||||
var loc = getLoc(filename, tokens, i);
|
||||
@@ -1411,8 +1409,7 @@ public class Parsing {
|
||||
|
||||
var valRes = parseValue(filename, tokens, i + n, 0);
|
||||
n += valRes.n;
|
||||
if (valRes.isError())
|
||||
return ParseRes.error(loc, "Expected a return value.", valRes);
|
||||
if (valRes.isError()) return ParseRes.error(loc, "Expected a return value.", valRes);
|
||||
|
||||
var res = ParseRes.res(new ReturnStatement(loc, valRes.result), n);
|
||||
|
||||
@@ -1534,8 +1531,7 @@ public class Parsing {
|
||||
if (!condRes.isSuccess()) return ParseRes.error(loc, "Expected an if condition.", condRes);
|
||||
n += condRes.n;
|
||||
|
||||
if (!isOperator(tokens, i + n++, Operator.PAREN_CLOSE))
|
||||
return ParseRes.error(loc, "Expected a closing paren after if condition.");
|
||||
if (!isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after if condition.");
|
||||
|
||||
var res = parseStatement(filename, tokens, i + n);
|
||||
if (!res.isSuccess()) return ParseRes.error(loc, "Expected an if body.", res);
|
||||
@@ -1702,8 +1698,7 @@ public class Parsing {
|
||||
parseVariableDeclare(filename, tokens, i + n),
|
||||
parseValueStatement(filename, tokens, i + n)
|
||||
);
|
||||
if (!declRes.isSuccess())
|
||||
return ParseRes.error(loc, "Expected a declaration or an expression.", declRes);
|
||||
if (!declRes.isSuccess()) return ParseRes.error(loc, "Expected a declaration or an expression.", declRes);
|
||||
n += declRes.n;
|
||||
decl = declRes.result;
|
||||
}
|
||||
@@ -1757,6 +1752,7 @@ public class Parsing {
|
||||
|
||||
var nameRes = parseIdentifier(tokens, i + n);
|
||||
if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a variable name for 'for' loop.");
|
||||
var nameLoc = getLoc(filename, tokens, i + n);
|
||||
n += nameRes.n;
|
||||
|
||||
Statement varVal = null;
|
||||
@@ -1790,7 +1786,7 @@ public class Parsing {
|
||||
if (!bodyRes.isSuccess()) return ParseRes.error(loc, "Expected a for body.", bodyRes);
|
||||
n += bodyRes.n;
|
||||
|
||||
return ParseRes.res(new ForInStatement(loc, labelRes.result, isDecl, nameRes.result, varVal, objRes.result, bodyRes.result), n);
|
||||
return ParseRes.res(new ForInStatement(loc, nameLoc, labelRes.result, isDecl, nameRes.result, varVal, objRes.result, bodyRes.result), n);
|
||||
}
|
||||
public static ParseRes<TryStatement> parseCatch(Filename filename, List<Token> tokens, int i) {
|
||||
var loc = getLoc(filename, tokens, i);
|
||||
@@ -1899,10 +1895,7 @@ public class Parsing {
|
||||
res.add(Instruction.throwSyntax(e));
|
||||
}
|
||||
|
||||
if (res.size() != 0 && res.get(res.size() - 1).type == Type.DISCARD) {
|
||||
res.set(res.size() - 1, Instruction.ret());
|
||||
}
|
||||
else res.add(Instruction.ret());
|
||||
res.add(Instruction.ret());
|
||||
|
||||
return new CodeFunction(environment, "", subscope.localsCount(), 0, new ValueVariable[0], new FunctionBody(res.array(), subscope.captures(), subscope.locals()));
|
||||
}
|
||||
|
||||
124
src/me/topchetoeu/jscript/permissions/Permission.java
Normal file
124
src/me/topchetoeu/jscript/permissions/Permission.java
Normal file
@@ -0,0 +1,124 @@
|
||||
package me.topchetoeu.jscript.permissions;
|
||||
|
||||
import java.util.LinkedList;
|
||||
|
||||
public class Permission {
|
||||
private static class State {
|
||||
public final int predI, trgI, wildcardI;
|
||||
public final boolean wildcard;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("State [pr=%s;trg=%s;wildN=%s;wild=%s]", predI, trgI, wildcardI, wildcard);
|
||||
}
|
||||
|
||||
public State(int predicateI, int targetI, int wildcardI, boolean wildcard) {
|
||||
this.predI = predicateI;
|
||||
this.trgI = targetI;
|
||||
this.wildcardI = wildcardI;
|
||||
this.wildcard = wildcard;
|
||||
}
|
||||
}
|
||||
|
||||
public final String namespace;
|
||||
public final String value;
|
||||
|
||||
public boolean match(Permission perm) {
|
||||
if (!Permission.match(namespace, perm.namespace, '.')) return false;
|
||||
if (value == null || perm.value == null) return true;
|
||||
return Permission.match(value, perm.value);
|
||||
}
|
||||
public boolean match(Permission perm, char delim) {
|
||||
if (!Permission.match(namespace, perm.namespace, '.')) return false;
|
||||
if (value == null || perm.value == null) return true;
|
||||
return Permission.match(value, perm.value, delim);
|
||||
}
|
||||
|
||||
public boolean match(String perm) {
|
||||
return match(new Permission(perm));
|
||||
}
|
||||
public boolean match(String perm, char delim) {
|
||||
return match(new Permission(perm), delim);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (value != null) return namespace + ":" + value;
|
||||
else return namespace;
|
||||
}
|
||||
|
||||
public Permission(String raw) {
|
||||
var i = raw.indexOf(':');
|
||||
|
||||
if (i > 0) {
|
||||
value = raw.substring(i + 1);
|
||||
namespace = raw.substring(0, i);
|
||||
}
|
||||
else {
|
||||
value = null;
|
||||
namespace = raw;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean match(String predicate, String target, char delim) {
|
||||
if (predicate.equals("")) return target.equals("");
|
||||
|
||||
var queue = new LinkedList<State>();
|
||||
queue.push(new State(0, 0, 0, false));
|
||||
|
||||
while (!queue.isEmpty()) {
|
||||
var state = queue.poll();
|
||||
var predEnd = state.predI >= predicate.length();
|
||||
|
||||
if (state.trgI >= target.length()) return predEnd;
|
||||
var predC = predEnd ? 0 : predicate.charAt(state.predI);
|
||||
var trgC = target.charAt(state.trgI);
|
||||
|
||||
if (state.wildcard) {
|
||||
if (state.wildcardI == 2 || trgC != delim) {
|
||||
queue.add(new State(state.predI, state.trgI + 1, state.wildcardI, true));
|
||||
}
|
||||
queue.add(new State(state.predI, state.trgI, 0, false));
|
||||
}
|
||||
else if (predC == '*') {
|
||||
queue.add(new State(state.predI + 1, state.trgI, state.wildcardI + 1, false));
|
||||
}
|
||||
else if (state.wildcardI > 0) {
|
||||
if (state.wildcardI > 2) throw new IllegalArgumentException("Too many sequential stars.");
|
||||
queue.add(new State(state.predI, state.trgI, state.wildcardI, true));
|
||||
}
|
||||
else if (!predEnd && (predC == '?' || predC == trgC)) {
|
||||
queue.add(new State(state.predI + 1, state.trgI + 1, 0, false));
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
public static boolean match(String predicate, String target) {
|
||||
if (predicate.equals("")) return target.equals("");
|
||||
|
||||
var queue = new LinkedList<State>();
|
||||
queue.push(new State(0, 0, 0, false));
|
||||
|
||||
while (!queue.isEmpty()) {
|
||||
var state = queue.poll();
|
||||
|
||||
if (state.predI >= predicate.length() || state.trgI >= target.length()) {
|
||||
return state.predI >= predicate.length() && state.trgI >= target.length();
|
||||
}
|
||||
|
||||
var predC = predicate.charAt(state.predI);
|
||||
var trgC = target.charAt(state.trgI);
|
||||
|
||||
if (predC == '*') {
|
||||
queue.add(new State(state.predI, state.trgI + 1, state.wildcardI, true));
|
||||
queue.add(new State(state.predI + 1, state.trgI, 0, false));
|
||||
}
|
||||
else if (predC == '?' || predC == trgC) {
|
||||
queue.add(new State(state.predI + 1, state.trgI + 1, 0, false));
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package me.topchetoeu.jscript.permissions;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class PermissionsManager implements PermissionsProvider {
|
||||
public static final PermissionsProvider ALL_PERMS = new PermissionsManager().add(new Permission("**"));
|
||||
|
||||
public final ArrayList<Permission> allowed = new ArrayList<>();
|
||||
public final ArrayList<Permission> denied = new ArrayList<>();
|
||||
|
||||
public PermissionsProvider add(Permission perm) {
|
||||
allowed.add(perm);
|
||||
return this;
|
||||
}
|
||||
public PermissionsProvider add(String perm) {
|
||||
allowed.add(new Permission(perm));
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(Permission perm, char delim) {
|
||||
for (var el : denied) if (el.match(perm, delim)) return false;
|
||||
for (var el : allowed) if (el.match(perm, delim)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
@Override
|
||||
public boolean hasPermission(Permission perm) {
|
||||
for (var el : denied) if (el.match(perm)) return false;
|
||||
for (var el : allowed) if (el.match(perm)) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package me.topchetoeu.jscript.permissions;
|
||||
|
||||
public interface PermissionsProvider {
|
||||
boolean hasPermission(Permission perm, char delim);
|
||||
boolean hasPermission(Permission perm);
|
||||
|
||||
default boolean hasPermission(String perm, char delim) {
|
||||
return hasPermission(new Permission(perm), delim);
|
||||
}
|
||||
default boolean hasPermission(String perm) {
|
||||
return hasPermission(new Permission(perm));
|
||||
}
|
||||
}
|
||||
10
tests/arithmetics/counters.js
Normal file
10
tests/arithmetics/counters.js
Normal file
@@ -0,0 +1,10 @@
|
||||
return new UnitTest('counters')
|
||||
.add('postfix increment', function () { var i = 10; i++ === 10; })
|
||||
.add('postfix decrement', function () { var i = 10; i-- === 10; })
|
||||
.add('prefix decrement', function () { var i = 10; --i === 9; })
|
||||
.add('prefix increment', function () { var i = 10; ++i === 11; })
|
||||
.add('ostfix increment of non-number', function () { var i = 'hi mom'; isNaN(i++); })
|
||||
.add('ostfix decrement of non-number', function () { var i = 'hi mom'; isNaN(i--); })
|
||||
.add('prefix increment of non-number', function () { var i = 'hi mom'; isNaN(++i); })
|
||||
.add('prefix decrement of non-number', function () { var i = 'hi mom'; isNaN(--i); })
|
||||
.add('postfix increment of convertible to number', function () { var i = '10'; i++; i === 11; })
|
||||
2
tests/arithmetics/index.js
Normal file
2
tests/arithmetics/index.js
Normal file
@@ -0,0 +1,2 @@
|
||||
return new UnitTest('Arithmetics')
|
||||
.add(include('counters.js'))
|
||||
4
tests/array/concat.js
Normal file
4
tests/array/concat.js
Normal file
@@ -0,0 +1,4 @@
|
||||
return new UnitTest('concat', function() { return typeof Array.prototype.concat === 'function'; })
|
||||
.add('two arrays', function() { return match([1, 2, 3], [1].concat([2], [3])) })
|
||||
.add('simple spread', function() { return match([1, 2, 3, 4, 5], [1].concat([2], 3, [4, 5])) })
|
||||
.add('sparse concat', function() { return match([1,, 2,,, 3,,, 4, 5], [1,,2].concat([,,3,,,4], 5)) })
|
||||
22
tests/array/index.js
Normal file
22
tests/array/index.js
Normal file
@@ -0,0 +1,22 @@
|
||||
function runIterator(arr, method, func, n) {
|
||||
var res = [];
|
||||
var j = 1;
|
||||
var args = [ function() {
|
||||
var pushed = [];
|
||||
for (var i = 0; i < n; i++) pushed[i] = arguments[i];
|
||||
res[j++] = pushed;
|
||||
return func.apply(this, arguments);
|
||||
} ];
|
||||
|
||||
for (var i = 4; i < arguments.length; i++) args[i - 3] = arguments[i];
|
||||
|
||||
res[0] = method.apply(arr, args);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
return new UnitTest('Array', function() { []; })
|
||||
.add(include('length.js'))
|
||||
.add(include('reduce.js'))
|
||||
.add(include('sparse.js'))
|
||||
.add(include('concat.js'))
|
||||
26
tests/array/length.js
Normal file
26
tests/array/length.js
Normal file
@@ -0,0 +1,26 @@
|
||||
return new UnitTest('length & capacity', function() { return 'length' in Array.prototype; })
|
||||
.add('empty literal', function() { return [].length === 0 })
|
||||
.add('filled literal', function() { return [1, 2, 3].length === 3 })
|
||||
.add('set length', function() {
|
||||
var a = [];
|
||||
a.length = 10;
|
||||
return a.length === 10;
|
||||
})
|
||||
.add('length after set', function() {
|
||||
var a = [];
|
||||
a [5]= 5;
|
||||
return a.length === 6;
|
||||
})
|
||||
.add('length after set (big', function() {
|
||||
var a = [1, 2];
|
||||
a [5000]= 5;
|
||||
return a.length === 5001;
|
||||
})
|
||||
.add('expand test', function() {
|
||||
var a = [];
|
||||
for (var i = 0; i < 1000; i++) {
|
||||
a[i] = i * 50;
|
||||
if (a[i] !== i * 50) return false;
|
||||
}
|
||||
return a.length === 1000;
|
||||
})
|
||||
44
tests/array/reduce.js
Normal file
44
tests/array/reduce.js
Normal file
@@ -0,0 +1,44 @@
|
||||
var res = [];
|
||||
|
||||
return new UnitTest('reduceRight', function () { return typeof Array.prototype.reduceRight === 'function' })
|
||||
.add('empty function', function () {match(
|
||||
[ undefined, [4, 3, 2], [undefined, 2, 1], [undefined, 1, 0], ],
|
||||
runIterator([1, 2, 3, 4], Array.prototype.reduceRight, function() { }, 3), 1
|
||||
)})
|
||||
.add('adder', function () {match(
|
||||
[ 10, [4, 3, 2], [7, 2, 1], [9, 1, 0], ],
|
||||
runIterator([1, 2, 3, 4], Array.prototype.reduceRight, function(a, b) { return a + b; }, 3), 1
|
||||
)})
|
||||
.add('sparse array', function () {match(
|
||||
[ 10, [4, 3, 11], [7, 2, 7], [9, 1, 3], ],
|
||||
runIterator([,,,1,,,, 2,,,, 3,,,, 4,,,,], Array.prototype.reduceRight, function(a, b) { return a + b }, 3), 1
|
||||
)})
|
||||
.add('sparse array with one element', function () {match(
|
||||
[ 1 ],
|
||||
runIterator([,,,1,,,,], Array.prototype.reduceRight, function(v) { return v; }, 3), 1
|
||||
)})
|
||||
.add('sparse array with no elements', function () {match(
|
||||
[ undefined ],
|
||||
runIterator([,,,,,,,], Array.prototype.reduceRight, function(v) { return v; }, 3), 1
|
||||
)})
|
||||
|
||||
.add('initial value and empty function', function () {match(
|
||||
[ undefined, [0, 4, 3], [undefined, 3, 2], [undefined, 2, 1], [undefined, 1, 0] ],
|
||||
runIterator([1, 2, 3, 4], Array.prototype.reduceRight, function() { }, 3, 0), 1
|
||||
)})
|
||||
.add('initial value and adder', function () {match(
|
||||
[ 15, [5, 4, 3], [9, 3, 2], [12, 2, 1], [14, 1, 0] ],
|
||||
runIterator([1, 2, 3, 4], Array.prototype.reduceRight, function(a, b) { return a + b; }, 3, 5), 1
|
||||
)})
|
||||
.add('initial value, sparce array and adder', function () {match(
|
||||
[ 15, [5, 4, 15], [9, 3, 11], [12, 2, 7], [14, 1, 3] ],
|
||||
runIterator([,,,1,,,, 2,,,, 3,,,, 4,,,,], Array.prototype.reduceRight, function(a, b) { return a + b; }, 3, 5), 1
|
||||
)})
|
||||
.add('initial value and sparse array with one element', function () {match(
|
||||
[ 6, [5, 1, 3] ],
|
||||
runIterator([,,,1,,,,], Array.prototype.reduceRight, function(a, b) { return a + b; }, 3, 5), 1
|
||||
)})
|
||||
.add('initial value and sparse array with no elements', function () {match(
|
||||
[ 5 ],
|
||||
runIterator([,,,,,,,], Array.prototype.reduceRight, function(v) { return v; }, 3, 5), 1
|
||||
)});
|
||||
4
tests/array/sort.js
Normal file
4
tests/array/sort.js
Normal file
@@ -0,0 +1,4 @@
|
||||
return new UnitTest('concat', function() { return typeof Array.prototype.concat === 'function'; })
|
||||
.add('two arrays', function() { return match([1, 2, 3], [1].concat([2], [3])) })
|
||||
.add('simple spread', function() { return match([1, 2, 3, 4, 5], [1].concat([2], 3, [4, 5])) })
|
||||
.add('sparse concat', function() { return match([1,, 2,,, 3,,, 4, 5], [1,,2].concat([,,3,,,4], 5)) })
|
||||
5
tests/array/sparse.js
Normal file
5
tests/array/sparse.js
Normal file
@@ -0,0 +1,5 @@
|
||||
return new UnitTest('sparse', function() { return !(0 in [,,]) })
|
||||
.add('empty in start', function() { var a = [,1]; return !(0 in a) && (1 in a); })
|
||||
.add('empty in middle', function() { var a = [1,,2]; return !(1 in a) && (2 in a) && (0 in a); })
|
||||
.add('empty in end', function() { var a = [1,,]; return !(1 in a) && (0 in a); })
|
||||
.add('trailing comma', function() { var a = [1,]; return a.length === 1; })
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user