Compare commits

..

41 Commits

Author SHA1 Message Date
4f22e76d2b fix: make code java 11 compatible 2023-11-25 20:22:16 +02:00
8924e7aadc build: fix build script to exit with code when error occurs 2023-11-25 20:16:06 +02:00
1d0e31a423 Merge pull request #9 from TopchetoEU/TopchetoEU/perms-and-fs
Permissions and filesystems
2023-11-25 20:10:59 +02:00
ab56908171 fix: faulty insufficient permissions error 2023-11-25 20:09:59 +02:00
eb14bb080c fix: debugger breakpoints not registered 2023-11-25 20:09:47 +02:00
f52f47cdb4 feat: properly implement filesystems 2023-11-25 19:36:18 +02:00
567eaa8514 fix: incorrect declarations 2023-11-25 19:13:20 +02:00
2cfdd8e335 feat: add utf8 encoding and decoding 2023-11-25 19:12:56 +02:00
4b1ec671e2 fix: micro tasks not handled properly 2023-11-25 18:58:35 +02:00
b127aadcf6 fix: promise was sending macro tasks instead of micro 2023-11-25 18:58:24 +02:00
b6eaff65ca style: remove unnececeary import 2023-11-25 18:48:46 +02:00
443dc0ffa1 feat: add await function to promise 2023-11-25 18:47:51 +02:00
e107dd3507 fix: promise doesn't use microtasks in some cases 2023-11-25 18:47:36 +02:00
6af3c70fce feat: add filesystem libs 2023-11-25 18:47:01 +02:00
8b743f49d1 feat: add toString, equals and hashCode overrides to wrappers and objects 2023-11-25 18:46:13 +02:00
e1ce384815 feat: add parse function to Filename 2023-11-25 18:44:27 +02:00
86d205e521 fix: parsing files won't modify last instruction to RET, instead will just append it 2023-11-25 18:44:11 +02:00
f0ad936e5b refactor: use new iterable names 2023-11-25 18:43:43 +02:00
4a1473c5be fix: sorting with no comparator now works 2023-11-25 18:42:54 +02:00
4111dbf5c4 fix: functions now print their name when stringified 2023-11-25 18:41:47 +02:00
1666682dc2 refactor: get rid of data parent hierarchy 2023-11-25 18:41:18 +02:00
f2b33d0233 feat: add support for async iterators
fix: comparasions had small behavioural issue
2023-11-25 18:40:49 +02:00
f5a0b6eaf7 fix: some improvements of compiled bytecode 2023-11-25 18:38:43 +02:00
829bea755d fix: CodeFrame didnt't handle out of bounds jumps well 2023-11-25 18:38:16 +02:00
4b0dcffd13 feat: add fs declarations in lib.d.ts 2023-11-25 18:37:40 +02:00
987f8b8f00 fix: handle native errors properly-ish 2023-11-25 14:18:46 +02:00
55e3d46bc2 feat: implement memory and root fs 2023-11-25 14:18:04 +02:00
3e25068219 feat: implement bulk of fs 2023-11-25 14:17:37 +02:00
7ecb8bfabb feat: implement permissions 2023-11-25 14:16:02 +02:00
488deea164 fix: improve performance of typescript by caching separate declarations 2023-11-14 09:24:39 +02:00
ed08041335 fix: internal error when trying to use key of "undefined" 2023-11-14 09:24:00 +02:00
0a4149ba81 fix: remove double space in "Uncaught ..." 2023-11-14 09:23:15 +02:00
30f5d619c3 fix: errors now have the right prototype and name 2023-11-14 09:22:56 +02:00
e7dbe91374 refactor: clean up protocol.json 2023-11-13 20:06:07 +02:00
455f5a613e feat: implement simple permission matching 2023-11-13 19:09:33 +02:00
1eeac3ae97 fix: replace templates in Metadata class with placeholder data 2023-11-13 19:08:56 +02:00
1acd78e119 refactor: clean up Main class 2023-11-13 18:56:59 +02:00
df9932874d feat: remove unnececeary @NativeInit directives 2023-11-06 14:03:15 +02:00
b47d1a7576 refactor: remove StackData and Data usage 2023-11-06 13:53:36 +02:00
fdfa8d7713 refactor: some minor fixes, rewrite README example 2023-11-06 10:55:38 +02:00
f5d1287948 fix: some annoying bugs, as well as splice 2023-11-06 00:55:30 +02:00
68 changed files with 1689 additions and 1831 deletions

View File

@@ -15,7 +15,7 @@ jobs:
uses: actions/setup-java@v3
with:
distribution: 'adopt'
java-version: '17'
java-version: '11'
- name: Clone repository
uses: GuillaumeFalourd/clone-github-repo-action@main
with:

View File

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

View File

@@ -1,7 +1,7 @@
const { spawn } = require('child_process');
const fs = require('fs/promises');
const pt = require('path');
const { argv } = require('process');
const { argv, exit } = require('process');
const conf = {
name: "java-jscript",
@@ -74,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

View File

@@ -51,4 +51,10 @@ public class Filename {
this.protocol = protocol;
this.path = path;
}
public static Filename parse(String val) {
var i = val.indexOf("://");
if (i >= 0) return new Filename(val.substring(0, i).trim(), val.substring(i + 3).trim());
else return new Filename("file", val.trim());
}
}

View File

@@ -8,26 +8,21 @@ import java.nio.file.Path;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Engine;
import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.StackData;
import me.topchetoeu.jscript.engine.debug.DebugServer;
import me.topchetoeu.jscript.engine.debug.SimpleDebugger;
import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.NativeFunction;
import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.events.Observer;
import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.InterruptException;
import me.topchetoeu.jscript.exceptions.SyntaxException;
import me.topchetoeu.jscript.filesystem.MemoryFilesystem;
import me.topchetoeu.jscript.filesystem.Mode;
import me.topchetoeu.jscript.filesystem.PhysicalFilesystem;
import me.topchetoeu.jscript.lib.Internals;
public class Main {
static Thread engineTask, debugTask;
static Engine engine;
static Environment env;
static int j = 0;
private static Observer<Object> valuePrinter = new Observer<Object>() {
public class Main {
public static class Printer implements Observer<Object> {
public void next(Object data) {
Values.printValue(null, data);
System.out.println();
@@ -37,115 +32,132 @@ public class Main {
Values.printError(err, null);
}
@Override
public void finish() {
engineTask.interrupt();
}
};
}
public static void main(String args[]) {
System.out.println(String.format("Running %s v%s by %s", Metadata.NAME, Metadata.VERSION, Metadata.AUTHOR));
engine = new Engine(true);
static Thread engineTask, debugTask;
static Engine engine = new Engine(true);
static DebugServer debugServer = new DebugServer();
static Environment environment = new Environment(null, null, null);
env = new Environment(null, null, null);
var exited = new boolean[1];
var server = new DebugServer();
server.targets.put("target", (ws, req) -> new SimpleDebugger(ws, engine));
engineTask = engine.start();
debugTask = server.start(new InetSocketAddress("127.0.0.1", 9229), true);
engine.pushMsg(false, null, new NativeFunction((ctx, thisArg, _a) -> {
new Internals().apply(env);
env.global.define("exit", _ctx -> {
exited[0] = true;
throw new InterruptException();
});
env.global.define("go", _ctx -> {
try {
var f = Path.of("do.js");
var func = _ctx.compile(new Filename("do", "do/" + j++ + ".js"), new String(Files.readAllBytes(f)));
return func.call(_ctx);
}
catch (IOException e) {
throw new EngineException("Couldn't open do.js");
}
});
// TODO: make better API
env.global.define(true, new NativeFunction("include", (_ctx, th, __args) -> {
try {
var currFilename = StackData.peekFrame(_ctx).function.loc().filename();
var loc = Path.of("").toAbsolutePath();
if (currFilename.protocol.equals("file")) loc = Path.of(currFilename.path).getParent();
var path = loc.resolve(Path.of(__args.length >= 1 ? Values.toString(_ctx, __args[0]) : ""));
var src = Files.readString(path);
var func = _ctx.compile(Filename.fromFile(path.toFile()), src);
var callArgs = new ArrayValue();
if (__args.length >= 2 && __args[1] instanceof ArrayValue) callArgs = (ArrayValue)__args[1];
return func.call(_ctx, null, callArgs);
}
catch (IOException e) { throw EngineException.ofError("IOError", "Couldn't open file."); }
}));
return null;
}), null).await();
static int j = 0;
static boolean exited = false;
static String[] args;
private static void reader() {
try {
var tsEnv = env.child();
tsEnv.global.define(null, "module", false, new ObjectValue());
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).pushEnv(tsEnv),
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).pushEnv(env.child());
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"), env, new ArrayValue(null, Reading.resourceToString("js/lib.d.ts"))
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)");
}
}
var reader = new Thread(() -> {
try {
for (var arg : args) {
try {
var file = Path.of(arg);
var raw = Files.readString(file);
valuePrinter.next(engine.pushMsg(false, new Context(engine).pushEnv(env), Filename.fromFile(file.toFile()), raw, null).await());
}
catch (EngineException e) { Values.printError(e, ""); }
}
for (var i = 0; ; i++) {
try {
var raw = Reading.read();
public static void main(String args[]) {
System.out.println(String.format("Running %s v%s by %s", Metadata.name(), Metadata.version(), Metadata.author()));
Main.args = args;
var reader = new Thread(Main::reader);
initEnv();
initEngine();
if (raw == null) break;
valuePrinter.next(engine.pushMsg(false, new Context(engine).pushEnv(env), new Filename("jscript", "repl/" + i + ".js"), raw, null).await());
}
catch (EngineException e) { Values.printError(e, ""); }
}
}
catch (IOException e) { exited[0] = true; }
catch (SyntaxException ex) {
if (exited[0]) return;
System.out.println("Syntax error:" + ex.msg);
}
catch (RuntimeException ex) {
if (!exited[0]) {
System.out.println("Internal error ocurred:");
ex.printStackTrace();
}
}
if (exited[0]) debugTask.interrupt();
});
reader.setDaemon(true);
reader.setName("STD Reader");
reader.start();

View File

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

View File

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

View File

@@ -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,7 +42,7 @@ 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);
caseToStatement.put(target.size(), ccase.statementI);
target.add(Instruction.nop().locate(ccase.value.loc()));
}
@@ -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) {

View File

@@ -13,7 +13,7 @@ public class CommaStatement extends Statement {
@Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
for (var i = 0; i < values.length; i++) {
values[i].compile(target, scope, i == values.length - 1 && pollute);
values[i].compileWithDebug(target, scope, i == values.length - 1 && pollute);
}
}

View File

@@ -1,18 +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() {
@@ -28,7 +33,8 @@ public class Context {
}
public FunctionValue compile(Filename filename, String raw) {
var transpiled = environment().compile.call(this, null, raw, filename.toString());
var env = environment();
var transpiled = env.compile.call(this, null, raw, filename.toString(), env);
String source = null;
FunctionValue runner = null;
@@ -40,7 +46,7 @@ public class Context {
else source = Values.toString(this, transpiled);
var breakpoints = new TreeSet<Location>();
FunctionValue res = Parsing.compile(Engine.functions, breakpoints, environment(), filename, source);
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);
@@ -48,12 +54,58 @@ public class Context {
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);
}
}

View File

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

View File

@@ -2,7 +2,7 @@ package me.topchetoeu.jscript.engine;
import java.util.HashMap;
import java.util.TreeSet;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.PriorityBlockingQueue;
import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.Location;
@@ -35,34 +35,41 @@ public class Engine implements DebugController {
}
}
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;
public static final HashMap<Long, FunctionBody> functions = new HashMap<>();
private Thread thread;
private LinkedBlockingDeque<Task> macroTasks = new LinkedBlockingDeque<>();
private LinkedBlockingDeque<Task> microTasks = new LinkedBlockingDeque<>();
public final int id = ++nextId;
public final Data data = new Data().set(StackData.MAX_FRAMES, 200);
public final boolean debugging;
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;
@@ -80,20 +87,6 @@ public class Engine implements DebugController {
return true;
}
@Override public void onFramePop(Context ctx, CodeFrame frame) {
if (debugging && debugger != null) debugger.onFramePop(ctx, frame);
}
@Override public boolean onInstruction(Context ctx, CodeFrame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught) {
if (debugging && debugger != null) return debugger.onInstruction(ctx, frame, instruction, returnVal, error, caught);
else return false;
}
@Override public void onSource(Filename filename, String source, TreeSet<Location> breakpoints) {
if (!debugging) return;
if (debugger != null) debugger.onSource(filename, source, breakpoints);
sources.put(filename, source);
bpts.put(filename, breakpoints);
}
private void runTask(Task task) {
try {
task.notifier.next(task.func.call(task.ctx, task.thisArg, task.args));
@@ -104,18 +97,12 @@ public class Engine implements DebugController {
}
}
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;
}
}
@@ -140,15 +127,28 @@ public class Engine implements DebugController {
}
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;
}

View File

@@ -8,12 +8,15 @@ 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();
@@ -21,6 +24,12 @@ public class Environment {
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) -> {
@@ -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);
}

View File

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

View File

@@ -19,7 +19,7 @@ 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<>();
@@ -236,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); }

View File

@@ -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;
@@ -32,6 +31,7 @@ import me.topchetoeu.jscript.json.JSONElement;
import me.topchetoeu.jscript.json.JSONList;
import me.topchetoeu.jscript.json.JSONMap;
// very simple indeed
public class SimpleDebugger implements Debugger {
public static final String CHROME_GET_PROP_FUNC = "function s(e){let t=this;const n=JSON.parse(e);for(let e=0,i=n.length;e<i;++e)t=t[n[e]];return t}";
public static final String VSCODE_STRINGIFY_VAL = "function(...runtimeArgs){\n let t = 1024; let e = null;\n if(e)try{let r=\"<<default preview>>\",i=e.call(this,r);if(i!==r)return String(i)}catch(r){return`<<indescribable>>${JSON.stringify([String(r),\"object\"])}`}if(typeof this==\"object\"&&this){let r;for(let i of[Symbol.for(\"debug.description\"),Symbol.for(\"nodejs.util.inspect.custom\")])try{r=this[i]();break}catch{}if(!r&&!String(this.toString).includes(\"[native code]\")&&(r=String(this)),r&&!r.startsWith(\"[object \"))return r.length>=t?r.slice(0,t)+\"\\u2026\":r}\n ;\n\n}";
@@ -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;
@@ -138,7 +137,7 @@ public class SimpleDebugger implements Debugger {
}
}
private class RunResult {
private static class RunResult {
public final Context ctx;
public final Object result;
public final EngineException error;
@@ -188,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)) {
@@ -203,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);
@@ -475,8 +474,9 @@ 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", null);
var cond = msg.params.string("condition", "").trim();
if (cond.equals("")) cond = null;
if (cond != null) cond = "(" + cond + ")";
Pattern regex;
@@ -600,10 +600,10 @@ public class SimpleDebugger implements Debugger {
if (obj != emptyObject) {
for (var key : obj.keys(true)) {
var propDesc = new JSONMap();
if (obj.properties.containsKey(key)) {
var prop = obj.properties.get(key);
propDesc.set("name", Values.toString(ctx, key));
if (prop.getter != null) propDesc.set("get", serializeObj(ctx, prop.getter));
if (prop.setter != null) propDesc.set("set", serializeObj(ctx, prop.setter));
@@ -783,7 +783,7 @@ 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_OVER)
) {

View File

@@ -118,8 +118,6 @@ public class WebSocket implements AutoCloseable {
else send(msg.textData());
}
public void send(Object data) {
// TODO: Remove
// System.out.println("SEND: " + data);
if (closed) throw new IllegalStateException("Object is closed.");
write(1, data.toString().getBytes());
}
@@ -201,10 +199,6 @@ public class WebSocket implements AutoCloseable {
if (!fin) continue;
var raw = data.toByteArray();
// TODO: Remove
// System.out.println("RECEIVED: " + new String(raw));
if (type == 1) return new WebSocketMessage(new String(raw));
else return new WebSocketMessage(raw);
}

View File

@@ -4,6 +4,7 @@ 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.scope.LocalScope;
import me.topchetoeu.jscript.engine.scope.ValueVariable;
@@ -151,15 +152,17 @@ public class CodeFrame {
public Object next(Context ctx, Object value, Object returnValue, EngineException error) {
if (value != Runners.NO_RETURN) push(ctx, value);
Instruction instr = null;
if (codePtr >= 0 && codePtr < function.body.length) instr = function.body[codePtr];
if (returnValue == Runners.NO_RETURN && error == null) {
try {
if (Thread.currentThread().isInterrupted()) throw new InterruptException();
var instr = function.body[codePtr];
ctx.engine.onInstruction(ctx, this, instr, Runners.NO_RETURN, null, false);
if (codePtr < 0 || codePtr >= function.body.length) returnValue = null;
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 {
@@ -278,11 +281,11 @@ public class CodeFrame {
}
if (error != null) {
ctx.engine.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) {
ctx.engine.onInstruction(ctx, this, function.body[codePtr], returnValue, null, false);
ctx.engine.onInstruction(ctx, this, instr, returnValue, null, false);
return returnValue;
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -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;
@@ -137,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";
}
@@ -191,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) {
@@ -232,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]);
@@ -272,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);
@@ -290,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) {
@@ -518,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");
@@ -571,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 {
@@ -592,10 +622,43 @@ public class Values {
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);
@@ -610,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);
@@ -643,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 {
@@ -661,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");

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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,11 +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) {
var name = clazz.getName();
var classNat = clazz.getAnnotation(Native.class);
if (classNat != null && !classNat.value().trim().equals("")) name = classNat.value().trim();
FunctionValue func = new OverloadFunction(name);
FunctionValue func = new OverloadFunction(getName(clazz));
for (var overload : clazz.getDeclaredConstructors()) {
var nat = overload.getAnnotation(Native.class);
@@ -256,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();
}
}

View File

@@ -8,6 +8,7 @@ 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;
@@ -92,7 +93,14 @@ public class OverloadFunction extends FunctionValue {
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) {

View File

@@ -1,7 +1,13 @@
(function (_arguments) {
var ts = _arguments[0];
var src = '', lib = _arguments[2].concat([ 'declare const exit: never;' ]).join(''), decls = '', version = 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 settings = {
outDir: "/out",
@@ -20,29 +26,41 @@
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" ]; },
getDefaultLibFileName: function() { return "/lib.d.ts"; },
getScriptFileNames: function() {
var res = [ "/src.ts", "/lib.d.ts" ];
for (var i = 0; i < declSnapshots.length; i++) res.push("/glob." + (i + 1) + ".d.ts");
return res;
},
getCompilationSettings: function () { return settings; },
fileExists: function(filename) { return filename === "/lib.d.ts" || filename === "/src.ts" || filename === "/glob.d.ts"; },
getScriptSnapshot: function(filename) {
if (filename === "/lib.d.ts") return libSnapshot;
if (filename === "/src.ts") return ts.ScriptSnapshot.fromString(src);
if (filename === "/glob.d.ts") return ts.ScriptSnapshot.fromString(decls);
var index = /\/glob\.(\d+)\.d\.ts/g.exec(filename);
if (index && index[1] && (index = Number(index[1])) && index > 0 && index <= declSnapshots.length) {
return declSnapshots[index - 1];
}
throw new Error("File '" + filename + "' doesn't exist.");
},
getScriptVersion: function (filename) {
if (filename === "/lib.d.ts") return 0;
if (filename === "/lib.d.ts" || filename.startsWith("/glob.")) return 0;
else return version;
},
}, reg);
service.getEmitOutput('/lib.d.ts');
log('Loaded libraries!');
service.getEmitOutput("/lib.d.ts");
log("Loaded libraries!");
function compile(code, filename) {
src = code, version++;
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 = []
@@ -57,31 +75,37 @@
if (file === "src.ts") file = filename;
return file + ":" + (pos.line + 1) + ":" + (pos.character + 1) + ": " + message;
}
else return "Error: " + message;
else return message;
});
if (diagnostics.length > 0) {
throw new SyntaxError(diagnostics.join('\n'));
throw new SyntaxError(diagnostics.join("\n"));
}
return {
result: emit.outputFiles[0].text,
declaration: emit.outputFiles[1].text
};
}
_arguments[1].compile = function (filename, code) {
var res = compile(filename, code);
var result = emit.outputFiles[0].text;
var declaration = emit.outputFiles[1].text;
return {
source: res.result,
source: result,
runner: function(func) {
return function() {
var val = func.apply(this, arguments);
decls += res.declaration;
if (declaration !== '') {
declSnapshots.push(ts.ScriptSnapshot.fromString(declaration));
}
return val;
}
}
};
}
function apply(env) {
env.compile = compile;
env.global.getTsDeclarations = function() {
return environments[env.id];
}
}
apply(_arguments[1]);
})(arguments);

View File

@@ -482,6 +482,35 @@ interface PromiseConstructor {
allSettled<T extends any[]>(...promises: T): Promise<[...{ [P in keyof T]: PromiseResult<Awaited<T[P]>>}]>;
}
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;

View File

@@ -4,16 +4,14 @@ 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;
@Native("Array") public class ArrayLib {
@@ -25,10 +23,10 @@ import me.topchetoeu.jscript.interop.NativeSetter;
}
@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 @@ import me.topchetoeu.jscript.interop.NativeSetter;
});
}
@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
@@ -94,8 +92,11 @@ import me.topchetoeu.jscript.interop.NativeSetter;
}
@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;
@@ -309,8 +310,9 @@ import me.topchetoeu.jscript.interop.NativeSetter;
return res;
}
@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;
@@ -323,9 +325,6 @@ import me.topchetoeu.jscript.interop.NativeSetter;
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, ",");
}
@@ -371,8 +370,4 @@ import me.topchetoeu.jscript.interop.NativeSetter;
return res;
}
@NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) {
target.defineProperty(null, env.symbol("Symbol.typeName"), "Array");
}
}

View File

@@ -1,7 +1,6 @@
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;
@@ -21,7 +20,7 @@ import me.topchetoeu.jscript.interop.Native;
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;
@@ -40,7 +39,7 @@ import me.topchetoeu.jscript.interop.Native;
}
}
StackData.popFrame(ctx, frame);
ctx.popFrame(frame);
if (awaiting) {
PromiseLib.then(ctx, frame.pop(), new NativeFunction(this::fulfill), new NativeFunction(this::reject));

View File

@@ -3,7 +3,6 @@ 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.NativeFunction;
@@ -29,7 +28,7 @@ import me.topchetoeu.jscript.interop.Native;
}
Object res = null;
StackData.pushFrame(ctx, frame);
ctx.pushFrame(frame);
state = 0;
while (state == 0) {
@@ -50,7 +49,7 @@ import me.topchetoeu.jscript.interop.Native;
}
}
StackData.popFrame(ctx, frame);
ctx.popFrame(frame);
if (state == 1) {
PromiseLib.then(ctx, frame.pop(), new NativeFunction(this::fulfill), new NativeFunction(this::reject));

View File

@@ -1,13 +1,10 @@
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;
@Native("Boolean") public class BooleanLib {
public static final BooleanLib TRUE = new BooleanLib(true);
@@ -30,7 +27,4 @@ import me.topchetoeu.jscript.interop.NativeInit;
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");
}
}

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

View File

@@ -2,10 +2,10 @@ 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;
@@ -51,8 +51,8 @@ import me.topchetoeu.jscript.interop.NativeInit;
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));
@@ -60,7 +60,6 @@ import me.topchetoeu.jscript.interop.NativeInit;
}
@NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) {
target.defineProperty(null, env.symbol("Symbol.typeName"), "Error");
target.defineProperty(null, "name", "Error");
}
}

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

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

View File

@@ -2,16 +2,12 @@ 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;
@Native("Function") public class FunctionLib {
@Native(thisArg = true) public static Object location(Context ctx, FunctionValue func) {
@@ -43,7 +39,7 @@ import me.topchetoeu.jscript.interop.NativeInit;
});
}
@Native(thisArg = true) public static String toString(Context ctx, Object func) {
return "function (...) { ... }";
return func.toString();
}
@Native public static FunctionValue async(FunctionValue func) {
@@ -55,8 +51,4 @@ import me.topchetoeu.jscript.interop.NativeInit;
@Native public static FunctionValue generator(FunctionValue func) {
return new GeneratorFunctionLib(func);
}
@NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) {
target.defineProperty(null, env.symbol("Symbol.typeName"), "Function");
}
}

View File

@@ -1,7 +1,6 @@
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.ObjectValue;
@@ -25,7 +24,7 @@ import me.topchetoeu.jscript.interop.Native;
}
Object res = null;
StackData.pushFrame(ctx, frame);
ctx.pushFrame(frame);
yielding = false;
while (!yielding) {
@@ -43,7 +42,7 @@ import me.topchetoeu.jscript.interop.Native;
}
}
StackData.popFrame(ctx, frame);
ctx.popFrame(frame);
if (done) frame = null;
else res = frame.pop();

View File

@@ -18,12 +18,15 @@ public class Internals {
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 {
@@ -110,12 +113,14 @@ public class Internals {
return Double.POSITIVE_INFINITY;
}
public void apply(Environment env) {
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));
@@ -154,6 +159,6 @@ public class Internals {
wp.getProto(ObjectLib.class).setPrototype(null, null);
env.regexConstructor = wp.getConstr(RegExpLib.class);
System.out.println("Loaded polyfills!");
return env;
}
}

View File

@@ -35,15 +35,15 @@ import me.topchetoeu.jscript.interop.NativeGetter;
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) {
@@ -68,7 +68,7 @@ import me.topchetoeu.jscript.interop.NativeGetter;
}
@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));
}

View File

@@ -1,13 +1,10 @@
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;
@Native("Number") public class NumberLib {
@Native public static final double EPSILON = java.lang.Math.ulp(1.0);
@@ -52,8 +49,4 @@ import me.topchetoeu.jscript.interop.NativeInit;
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");
}
}

View File

@@ -1,17 +1,14 @@
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;
@Native("Object") public class ObjectLib {
@Native public static ObjectValue assign(Context ctx, ObjectValue dst, Object... src) {
@@ -143,7 +140,7 @@ import me.topchetoeu.jscript.interop.NativeInit;
@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 @@ import me.topchetoeu.jscript.interop.NativeInit;
// 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");
}
}

View File

@@ -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;
@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 @@ import me.topchetoeu.jscript.interop.NativeInit;
}
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 @@ import me.topchetoeu.jscript.interop.NativeInit;
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 @@ import me.topchetoeu.jscript.interop.NativeInit;
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 @@ import me.topchetoeu.jscript.interop.NativeInit;
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 @@ import me.topchetoeu.jscript.interop.NativeInit;
}
}
}
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 @@ import me.topchetoeu.jscript.interop.NativeInit;
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 @@ import me.topchetoeu.jscript.interop.NativeInit;
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 @@ import me.topchetoeu.jscript.interop.NativeInit;
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;
}
}

View File

@@ -3,6 +3,7 @@ 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;
@@ -11,11 +12,11 @@ import me.topchetoeu.jscript.interop.NativeInit;
@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");
}
}

View File

@@ -153,7 +153,7 @@ import me.topchetoeu.jscript.interop.NativeGetter;
@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;

View File

@@ -22,15 +22,15 @@ import me.topchetoeu.jscript.interop.NativeGetter;
@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) {
@@ -58,6 +58,6 @@ import me.topchetoeu.jscript.interop.NativeGetter;
}
@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);
}
}

View File

@@ -3,17 +3,14 @@ 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
@Native("String") public class StringLib {
@@ -263,8 +260,4 @@ import me.topchetoeu.jscript.interop.NativeInit;
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");
}
}

View File

@@ -4,16 +4,13 @@ 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;
@Native("Symbol") public class SymbolLib {
private static final Map<String, Symbol> symbols = new HashMap<>();
@@ -63,8 +60,4 @@ import me.topchetoeu.jscript.interop.NativeInit;
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");
}
}

View File

@@ -3,6 +3,7 @@ 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;
@@ -11,11 +12,10 @@ import me.topchetoeu.jscript.interop.NativeInit;
@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");
}
}

View File

@@ -3,6 +3,7 @@ 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;
@@ -11,11 +12,10 @@ import me.topchetoeu.jscript.interop.NativeInit;
@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");
}
}

View File

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

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

View File

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

View File

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