Permissions and filesystems #9

Merged
TopchetoEU merged 36 commits from TopchetoEU/perms-and-fs into master 2023-11-25 18:10:59 +00:00
64 changed files with 1610 additions and 1758 deletions

File diff suppressed because it is too large Load Diff

View File

@ -51,4 +51,10 @@ public class Filename {
this.protocol = protocol; this.protocol = protocol;
this.path = path; 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

@ -16,15 +16,13 @@ import me.topchetoeu.jscript.events.Observer;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.InterruptException; import me.topchetoeu.jscript.exceptions.InterruptException;
import me.topchetoeu.jscript.exceptions.SyntaxException; 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; import me.topchetoeu.jscript.lib.Internals;
public class Main { public class Main {
static Thread engineTask, debugTask; public static class Printer implements Observer<Object> {
static Engine engine;
static Environment env;
static int j = 0;
private static Observer<Object> valuePrinter = new Observer<Object>() {
public void next(Object data) { public void next(Object data) {
Values.printValue(null, data); Values.printValue(null, data);
System.out.println(); System.out.println();
@ -34,27 +32,80 @@ public class Main {
Values.printError(err, null); Values.printError(err, null);
} }
@Override
public void finish() { public void finish() {
engineTask.interrupt(); engineTask.interrupt();
} }
}; }
public static void main(String args[]) { static Thread engineTask, debugTask;
System.out.println(String.format("Running %s v%s by %s", Metadata.NAME, Metadata.VERSION, Metadata.AUTHOR)); static Engine engine = new Engine(true);
engine = new Engine(true); static DebugServer debugServer = new DebugServer();
static Environment environment = new Environment(null, null, null);
var exited = new boolean[1]; static int j = 0;
var server = new DebugServer(); static boolean exited = false;
server.targets.put("target", (ws, req) -> new SimpleDebugger(ws, engine)); static String[] args;
env = Internals.apply(new Environment(null, null, null)); private static void reader() {
try {
for (var arg : args) {
try {
if (arg.equals("--ts")) initTypescript();
else {
var file = Path.of(arg);
var raw = Files.readString(file);
var res = engine.pushMsg(
false, new Context(engine, environment),
Filename.fromFile(file.toFile()),
raw, null
).await();
Values.printValue(null, res);
System.out.println();
}
}
catch (EngineException e) { Values.printError(e, null); }
}
for (var i = 0; ; i++) {
try {
var raw = Reading.read();
env.global.define("exit", _ctx -> { if (raw == null) break;
exited[0] = true; 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(); throw new InterruptException();
}); });
env.global.define("go", _ctx -> { environment.global.define("go", _ctx -> {
try { try {
var f = Path.of("do.js"); var f = Path.of("do.js");
var func = _ctx.compile(new Filename("do", "do/" + j++ + ".js"), new String(Files.readAllBytes(f))); var func = _ctx.compile(new Filename("do", "do/" + j++ + ".js"), new String(Files.readAllBytes(f)));
@ -65,9 +116,15 @@ public class Main {
} }
}); });
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(); engineTask = engine.start();
debugTask = server.start(new InetSocketAddress("127.0.0.1", 9229), true); debugTask = debugServer.start(new InetSocketAddress("127.0.0.1", 9229), true);
}
private static void initTypescript() {
try { try {
var tsEnv = Internals.apply(new Environment(null, null, null)); var tsEnv = Internals.apply(new Environment(null, null, null));
var bsEnv = Internals.apply(new Environment(null, null, null)); var bsEnv = Internals.apply(new Environment(null, null, null));
@ -84,46 +141,23 @@ public class Main {
engine.pushMsg( engine.pushMsg(
false, ctx, false, ctx,
new Filename("jscript", "internals/bootstrap.js"), Reading.resourceToString("js/bootstrap.js"), null, 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(); ).await();
} }
catch (EngineException e) { catch (EngineException e) {
Values.printError(e, "(while initializing TS)"); Values.printError(e, "(while initializing TS)");
} }
}
var reader = new Thread(() -> { public static void main(String args[]) {
try { System.out.println(String.format("Running %s v%s by %s", Metadata.name(), Metadata.version(), Metadata.author()));
for (var arg : args) {
try { Main.args = args;
var file = Path.of(arg); var reader = new Thread(Main::reader);
var raw = Files.readString(file);
valuePrinter.next(engine.pushMsg(false, new Context(engine).pushEnv(env), Filename.fromFile(file.toFile()), raw, null).await()); initEnv();
} initEngine();
catch (EngineException e) { Values.printError(e, ""); }
}
for (var i = 0; ; i++) {
try {
var raw = Reading.read();
if (raw == null) break;
valuePrinter.next(engine.pushMsg(false, new Context(engine).pushEnv(env), new Filename("jscript", "repl/" + i + ".js"), raw, null).await());
}
catch (EngineException e) { Values.printError(e, ""); }
}
}
catch (IOException e) { exited[0] = true; }
catch (SyntaxException ex) {
if (exited[0]) return;
System.out.println("Syntax error:" + ex.msg);
}
catch (RuntimeException ex) {
if (!exited[0]) {
System.out.println("Internal error ocurred:");
ex.printStackTrace();
}
}
if (exited[0]) debugTask.interrupt();
});
reader.setDaemon(true); reader.setDaemon(true);
reader.setName("STD Reader"); reader.setName("STD Reader");
reader.start(); reader.start();

View File

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

View File

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

View File

@ -13,7 +13,7 @@ public class CommaStatement extends Statement {
@Override @Override
public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) { public void compile(CompileTarget target, ScopeRecord scope, boolean pollute) {
for (var i = 0; i < values.length; i++) { 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; package me.topchetoeu.jscript.engine;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Stack; import java.util.Stack;
import java.util.TreeSet; import java.util.TreeSet;
import me.topchetoeu.jscript.Filename; import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.parsing.Parsing; import me.topchetoeu.jscript.parsing.Parsing;
public class Context { public class Context {
private final Stack<Environment> env = new Stack<>(); private final Stack<Environment> env = new Stack<>();
public final Data data; private final ArrayList<CodeFrame> frames = new ArrayList<>();
public final Engine engine; public final Engine engine;
public Environment environment() { public Environment environment() {
@ -28,7 +33,8 @@ public class Context {
} }
public FunctionValue compile(Filename filename, String raw) { 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; String source = null;
FunctionValue runner = null; FunctionValue runner = null;
@ -40,7 +46,7 @@ public class Context {
else source = Values.toString(this, transpiled); else source = Values.toString(this, transpiled);
var breakpoints = new TreeSet<Location>(); 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); engine.onSource(filename, source, breakpoints);
if (runner != null) res = (FunctionValue)runner.call(this, null, res); if (runner != null) res = (FunctionValue)runner.call(this, null, res);
@ -48,16 +54,57 @@ public class Context {
return res; return res;
} }
public Context(Engine engine, Data data) {
this.data = new Data(engine.data); public void pushFrame(CodeFrame frame) {
if (data != null) this.data.addAll(data); 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; this.engine = engine;
} }
public Context(Engine engine) {
this(engine, (Data)null);
}
public Context(Engine engine, Environment env) { public Context(Engine engine, Environment env) {
this(engine, (Data)null); this(engine);
this.pushEnv(env); this.pushEnv(env);
} }

View File

@ -5,7 +5,6 @@ import java.util.Map;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public class Data { public class Data {
public final Data parent;
private HashMap<DataKey<Object>, Object> data = new HashMap<>(); private HashMap<DataKey<Object>, Object> data = new HashMap<>();
public Data copy() { public Data copy() {
@ -33,19 +32,12 @@ public class Data {
return this; return this;
} }
public <T> T get(DataKey<T> key, T val) { public <T> T get(DataKey<T> key, T val) {
for (var it = this; it != null; it = it.parent) { if (data.containsKey(key)) return (T)data.get((DataKey<Object>)key);
if (it.data.containsKey(key)) {
return (T)it.data.get((DataKey<Object>)key);
}
}
set(key, val); set(key, val);
return val; return val;
} }
public <T> T get(DataKey<T> key) { public <T> T get(DataKey<T> key) {
for (var it = this; it != null; it = it.parent) { if (data.containsKey(key)) return (T)data.get((DataKey<Object>)key);
if (it.data.containsKey(key)) return (T)it.data.get((DataKey<Object>)key);
}
return null; return null;
} }
public boolean has(DataKey<?> key) { return data.containsKey(key); } public boolean has(DataKey<?> key) { return data.containsKey(key); }
@ -61,11 +53,4 @@ public class Data {
public int increase(DataKey<Integer> key) { public int increase(DataKey<Integer> key) {
return increase(key, 1, 0); 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.HashMap;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.concurrent.LinkedBlockingDeque; import java.util.concurrent.PriorityBlockingQueue;
import me.topchetoeu.jscript.Filename; import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.Location; 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 FunctionValue func;
public final Object thisArg; public final Object thisArg;
public final Object[] args; public final Object[] args;
public final DataNotifier<Object> notifier = new DataNotifier<>(); public final DataNotifier<Object> notifier = new DataNotifier<>();
public final Context ctx; 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.ctx = ctx;
this.func = func; this.func = func;
this.thisArg = thisArg; this.thisArg = thisArg;
this.args = args; this.args = args;
this.micro = micro;
}
@Override
public int compareTo(Task other) {
return Integer.compare(this.micro ? 0 : 1, other.micro ? 0 : 1);
} }
} }
private static int nextId = 0; private static int nextId = 0;
public static final HashMap<Long, FunctionBody> functions = new HashMap<>(); public static final HashMap<Long, FunctionBody> functions = new HashMap<>();
private Thread thread;
private LinkedBlockingDeque<Task> macroTasks = new LinkedBlockingDeque<>();
private LinkedBlockingDeque<Task> microTasks = new LinkedBlockingDeque<>();
public final int id = ++nextId; public final int id = ++nextId;
public final Data data = new Data().set(StackData.MAX_FRAMES, 10000);
public final boolean debugging; public final boolean debugging;
public int maxStackFrames = 10000;
private final HashMap<Filename, String> sources = new HashMap<>(); private final HashMap<Filename, String> sources = new HashMap<>();
private final HashMap<Filename, TreeSet<Location>> bpts = new HashMap<>(); private final HashMap<Filename, TreeSet<Location>> bpts = new HashMap<>();
private DebugController debugger; private DebugController debugger;
private Thread thread;
private PriorityBlockingQueue<Task> tasks = new PriorityBlockingQueue<>();
public boolean attachDebugger(DebugController debugger) { public boolean attachDebugger(DebugController debugger) {
if (!debugging || this.debugger != null) return false; if (!debugging || this.debugger != null) return false;
@ -80,20 +87,6 @@ public class Engine implements DebugController {
return true; 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) { private void runTask(Task task) {
try { try {
task.notifier.next(task.func.call(task.ctx, task.thisArg, task.args)); 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) { public void run(boolean untilEmpty) {
while (!untilEmpty || !macroTasks.isEmpty()) { while (!untilEmpty || !tasks.isEmpty()) {
try { try {
runTask(macroTasks.take()); runTask(tasks.take());
while (!microTasks.isEmpty()) {
runTask(microTasks.take());
}
} }
catch (InterruptedException | InterruptException e) { catch (InterruptedException | InterruptException e) {
for (var msg : macroTasks) { for (var msg : tasks) msg.notifier.error(new InterruptException(e));
msg.notifier.error(new InterruptException(e));
}
break; break;
} }
} }
@ -140,15 +127,28 @@ public class Engine implements DebugController {
} }
public Awaitable<Object> pushMsg(boolean micro, Context ctx, FunctionValue func, Object thisArg, Object ...args) { public Awaitable<Object> pushMsg(boolean micro, Context ctx, FunctionValue func, Object thisArg, Object ...args) {
var msg = new Task(ctx == null ? new Context(this) : ctx, func, thisArg, args); var msg = new Task(ctx == null ? new Context(this) : ctx, func, thisArg, args, micro);
if (micro) microTasks.addLast(msg); tasks.add(msg);
else macroTasks.addLast(msg);
return msg.notifier; return msg.notifier;
} }
public Awaitable<Object> pushMsg(boolean micro, Context ctx, Filename filename, String raw, Object thisArg, Object ...args) { public Awaitable<Object> pushMsg(boolean micro, Context ctx, Filename filename, String raw, Object thisArg, Object ...args) {
return pushMsg(micro, ctx, new UncompiledFunction(filename, raw), thisArg, 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) { public Engine(boolean debugging) {
this.debugging = 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.ObjectValue;
import me.topchetoeu.jscript.engine.values.Symbol; import me.topchetoeu.jscript.engine.values.Symbol;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.filesystem.RootFilesystem;
import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.NativeGetter; import me.topchetoeu.jscript.interop.NativeGetter;
import me.topchetoeu.jscript.interop.NativeSetter; import me.topchetoeu.jscript.interop.NativeSetter;
import me.topchetoeu.jscript.interop.NativeWrapperProvider; import me.topchetoeu.jscript.interop.NativeWrapperProvider;
import me.topchetoeu.jscript.permissions.Permission;
import me.topchetoeu.jscript.permissions.PermissionsProvider;
public class Environment { public class Environment implements PermissionsProvider {
private HashMap<String, ObjectValue> prototypes = new HashMap<>(); private HashMap<String, ObjectValue> prototypes = new HashMap<>();
public final Data data = new Data(); public final Data data = new Data();
@ -21,6 +24,12 @@ public class Environment {
public GlobalScope global; public GlobalScope global;
public WrappersProvider wrappers; public WrappersProvider wrappers;
public PermissionsProvider permissions = null;
public final RootFilesystem filesystem = new RootFilesystem(this);
private static int nextId = 0;
@Native public int id = ++nextId;
@Native public FunctionValue compile; @Native public FunctionValue compile;
@Native public FunctionValue regexConstructor = new NativeFunction("RegExp", (ctx, thisArg, args) -> { @Native public FunctionValue regexConstructor = new NativeFunction("RegExp", (ctx, thisArg, args) -> {
@ -68,9 +77,13 @@ public class Environment {
return res; return res;
} }
public Context context(Engine engine, Data data) { @Override public boolean hasPermission(Permission perm, char delim) {
return new Context(engine, data).pushEnv(this); return permissions == null || permissions.hasPermission(perm, delim);
} }
@Override public boolean hasPermission(Permission perm) {
return permissions == null || permissions.hasPermission(perm);
}
public Context context(Engine engine) { public Context context(Engine engine) {
return new Context(engine).pushEnv(this); return new Context(engine).pushEnv(this);
} }

View File

@ -1,66 +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; import me.topchetoeu.jscript.json.JSONMap;
public class DebugServer { 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<>(); public final HashMap<String, DebuggerProvider> targets = new HashMap<>();
@ -236,9 +236,9 @@ public class DebugServer {
this.protocol = getClass().getClassLoader().getResourceAsStream("assets/protocol.json").readAllBytes(); this.protocol = getClass().getClassLoader().getResourceAsStream("assets/protocol.json").readAllBytes();
var index = new String(getClass().getClassLoader().getResourceAsStream("assets/index.html").readAllBytes()); var index = new String(getClass().getClassLoader().getResourceAsStream("assets/index.html").readAllBytes());
this.index = index this.index = index
.replace("${NAME}", Metadata.NAME) .replace("${NAME}", Metadata.name())
.replace("${VERSION}", Metadata.VERSION) .replace("${VERSION}", Metadata.version())
.replace("${AUTHOR}", Metadata.AUTHOR) .replace("${AUTHOR}", Metadata.author())
.getBytes(); .getBytes();
} }
catch (IOException e) { throw new UncheckedIOException(e); } 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.compilation.Instruction.Type;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Engine; 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.CodeFrame;
import me.topchetoeu.jscript.engine.frame.Runners; import me.topchetoeu.jscript.engine.frame.Runners;
import me.topchetoeu.jscript.engine.scope.GlobalScope; 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.JSONList;
import me.topchetoeu.jscript.json.JSONMap; import me.topchetoeu.jscript.json.JSONMap;
// very simple indeed
public class SimpleDebugger implements Debugger { 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 CHROME_GET_PROP_FUNC = "function s(e){let t=this;const n=JSON.parse(e);for(let e=0,i=n.length;e<i;++e)t=t[n[e]];return t}";
public static final String VSCODE_STRINGIFY_VAL = "function(...runtimeArgs){\n let t = 1024; let e = null;\n if(e)try{let r=\"<<default preview>>\",i=e.call(this,r);if(i!==r)return String(i)}catch(r){return`<<indescribable>>${JSON.stringify([String(r),\"object\"])}`}if(typeof this==\"object\"&&this){let r;for(let i of[Symbol.for(\"debug.description\"),Symbol.for(\"nodejs.util.inspect.custom\")])try{r=this[i]();break}catch{}if(!r&&!String(this.toString).includes(\"[native code]\")&&(r=String(this)),r&&!r.startsWith(\"[object \"))return r.length>=t?r.slice(0,t)+\"\\u2026\":r}\n ;\n\n}"; public static final String VSCODE_STRINGIFY_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}";
@ -137,7 +137,7 @@ public class SimpleDebugger implements Debugger {
} }
} }
private class RunResult { private static class RunResult {
public final Context ctx; public final Context ctx;
public final Object result; public final Object result;
public final EngineException error; public final EngineException error;
@ -187,7 +187,7 @@ public class SimpleDebugger implements Debugger {
} }
private void updateFrames(Context ctx) { private void updateFrames(Context ctx) {
var frame = StackData.peekFrame(ctx); var frame = ctx.peekFrame();
if (frame == null) return; if (frame == null) return;
if (!codeFrameToFrame.containsKey(frame)) { if (!codeFrameToFrame.containsKey(frame)) {
@ -202,7 +202,7 @@ public class SimpleDebugger implements Debugger {
} }
private JSONList serializeFrames(Context ctx) { private JSONList serializeFrames(Context ctx) {
var res = new JSONList(); var res = new JSONList();
var frames = StackData.frames(ctx); var frames = ctx.frames();
for (var i = frames.size() - 1; i >= 0; i--) { for (var i = frames.size() - 1; i >= 0; i--) {
res.add(codeFrameToFrame.get(frames.get(i)).serialized); res.add(codeFrameToFrame.get(frames.get(i)).serialized);
@ -474,8 +474,9 @@ public class SimpleDebugger implements Debugger {
@Override public void setBreakpointByUrl(V8Message msg) { @Override public void setBreakpointByUrl(V8Message msg) {
var line = (int)msg.params.number("lineNumber") + 1; var line = (int)msg.params.number("lineNumber") + 1;
var col = (int)msg.params.number("columnNumber", 0) + 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 + ")"; if (cond != null) cond = "(" + cond + ")";
Pattern regex; Pattern regex;
@ -782,7 +783,7 @@ public class SimpleDebugger implements Debugger {
try { idToFrame.remove(codeFrameToFrame.remove(frame).id); } try { idToFrame.remove(codeFrameToFrame.remove(frame).id); }
catch (NullPointerException e) { } 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 && else if (stepOutFrame != null && stepOutFrame.frame == frame &&
(state == State.STEPPING_OUT || state == State.STEPPING_IN || state == State.STEPPING_OVER) (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()); else send(msg.textData());
} }
public void send(Object data) { public void send(Object data) {
// TODO: Remove
// System.out.println("SEND: " + data);
if (closed) throw new IllegalStateException("Object is closed."); if (closed) throw new IllegalStateException("Object is closed.");
write(1, data.toString().getBytes()); write(1, data.toString().getBytes());
} }
@ -201,10 +199,6 @@ public class WebSocket implements AutoCloseable {
if (!fin) continue; if (!fin) continue;
var raw = data.toByteArray(); var raw = data.toByteArray();
// TODO: Remove
// System.out.println("RECEIVED: " + new String(raw));
if (type == 1) return new WebSocketMessage(new String(raw)); if (type == 1) return new WebSocketMessage(new String(raw));
else return new WebSocketMessage(raw); else return new WebSocketMessage(raw);
} }

View File

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

View File

@ -5,7 +5,6 @@ import me.topchetoeu.jscript.compilation.FunctionBody;
import me.topchetoeu.jscript.compilation.Instruction; import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.StackData;
import me.topchetoeu.jscript.engine.frame.CodeFrame; import me.topchetoeu.jscript.engine.frame.CodeFrame;
import me.topchetoeu.jscript.engine.frame.Runners; import me.topchetoeu.jscript.engine.frame.Runners;
import me.topchetoeu.jscript.engine.scope.ValueVariable; 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) { public Object call(Context ctx, Object thisArg, Object ...args) {
var frame = new CodeFrame(ctx, thisArg, args, this); var frame = new CodeFrame(ctx, thisArg, args, this);
try { try {
StackData.pushFrame(ctx, frame); ctx.pushFrame(frame);
while (true) { while (true) {
var res = frame.next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, null); var res = frame.next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, null);
@ -43,7 +42,7 @@ public class CodeFunction extends FunctionValue {
} }
} }
finally { finally {
StackData.popFrame(ctx, frame); ctx.popFrame(frame);
} }
} }

View File

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

View File

@ -12,6 +12,19 @@ public class NativeWrapper extends ObjectValue {
else return super.getPrototype(ctx); else return super.getPrototype(ctx);
} }
@Override
public String toString() {
return wrapped.toString();
}
@Override
public boolean equals(Object obj) {
return wrapped.equals(obj);
}
@Override
public int hashCode() {
return wrapped.hashCode();
}
public NativeWrapper(Object wrapped) { public NativeWrapper(Object wrapped) {
this.wrapped = wrapped; this.wrapped = wrapped;
prototype = NATIVE_PROTO; prototype = NATIVE_PROTO;

View File

@ -262,7 +262,7 @@ public class ObjectValue {
values.put(key, val); values.put(key, val);
return true; return true;
} }
else if (key.equals("__proto__")) return setPrototype(ctx, val); else if ("__proto__".equals(key)) return setPrototype(ctx, val);
else if (nonWritableSet.contains(key)) return false; else if (nonWritableSet.contains(key)) return false;
else return setField(ctx, key, val); else return setField(ctx, key, val);
} }
@ -273,7 +273,7 @@ public class ObjectValue {
public final boolean hasMember(Context ctx, Object key, boolean own) { public final boolean hasMember(Context ctx, Object key, boolean own) {
key = Values.normalize(ctx, key); key = Values.normalize(ctx, key);
if (key != null && key.equals("__proto__")) return true; if (key != null && "__proto__".equals(key)) return true;
if (hasField(ctx, key)) return true; if (hasField(ctx, key)) return true;
if (properties.containsKey(key)) return true; if (properties.containsKey(key)) return true;
if (own) return false; if (own) return false;

View File

@ -17,8 +17,27 @@ import me.topchetoeu.jscript.exceptions.ConvertException;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.SyntaxException; import me.topchetoeu.jscript.exceptions.SyntaxException;
import me.topchetoeu.jscript.exceptions.UncheckedException; import me.topchetoeu.jscript.exceptions.UncheckedException;
import me.topchetoeu.jscript.lib.PromiseLib;
public class Values { public class Values {
public static enum CompareResult {
NOT_EQUAL,
EQUAL,
LESS,
GREATER;
public boolean less() { return this == LESS; }
public boolean greater() { return this == GREATER; }
public boolean lessOrEqual() { return this == LESS || this == EQUAL; }
public boolean greaterOrEqual() { return this == GREATER || this == EQUAL; }
public static CompareResult from(int cmp) {
if (cmp < 0) return LESS;
if (cmp > 0) return GREATER;
return EQUAL;
}
}
public static final Object NULL = new Object(); public static final Object NULL = new Object();
public static boolean isObject(Object val) { return val instanceof ObjectValue; } public static boolean isObject(Object val) { return val instanceof ObjectValue; }
@ -105,7 +124,7 @@ public class Values {
} }
public static boolean toBoolean(Object obj) { public static boolean toBoolean(Object obj) {
if (obj == NULL || obj == null) return false; if (obj == NULL || obj == null) return false;
if (obj instanceof Number && number(obj) == 0) return false; if (obj instanceof Number && (number(obj) == 0 || Double.isNaN(number(obj)))) return false;
if (obj instanceof String && ((String)obj).equals("")) return false; if (obj instanceof String && ((String)obj).equals("")) return false;
if (obj instanceof Boolean) return (Boolean)obj; if (obj instanceof Boolean) return (Boolean)obj;
return true; return true;
@ -137,7 +156,7 @@ public class Values {
} }
if (val instanceof Boolean) return (Boolean)val ? "true" : "false"; if (val instanceof Boolean) return (Boolean)val ? "true" : "false";
if (val instanceof String) return (String)val; if (val instanceof String) return (String)val;
if (val instanceof Symbol) return ((Symbol)val).toString(); if (val instanceof Symbol) return val.toString();
return "Unknown value"; return "Unknown value";
} }
@ -191,12 +210,18 @@ public class Values {
return _a >>> _b; 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); a = toPrimitive(ctx, a, ConvertHint.VALUEOF);
b = toPrimitive(ctx, b, ConvertHint.VALUEOF); b = toPrimitive(ctx, b, ConvertHint.VALUEOF);
if (a instanceof String && b instanceof String) return ((String)a).compareTo((String)b); if (a instanceof String && b instanceof String) CompareResult.from(((String)a).compareTo((String)b));
else return Double.compare(toNumber(ctx, a), toNumber(ctx, b));
var _a = toNumber(ctx, a);
var _b = toNumber(ctx, b);
if (Double.isNaN(_a) || Double.isNaN(_b)) return CompareResult.NOT_EQUAL;
return CompareResult.from(Double.compare(_a, _b));
} }
public static boolean not(Object obj) { public static boolean not(Object obj) {
@ -232,10 +257,10 @@ public class Values {
case LOOSE_EQUALS: return looseEqual(ctx, args[0], args[1]); case LOOSE_EQUALS: return looseEqual(ctx, args[0], args[1]);
case LOOSE_NOT_EQUALS: return !looseEqual(ctx, args[0], args[1]); case LOOSE_NOT_EQUALS: return !looseEqual(ctx, args[0], args[1]);
case GREATER: return compare(ctx, args[0], args[1]) > 0; case GREATER: return compare(ctx, args[0], args[1]).greater();
case GREATER_EQUALS: return compare(ctx, args[0], args[1]) >= 0; case GREATER_EQUALS: return compare(ctx, args[0], args[1]).greaterOrEqual();
case LESS: return compare(ctx, args[0], args[1]) < 0; case LESS: return compare(ctx, args[0], args[1]).less();
case LESS_EQUALS: return compare(ctx, args[0], args[1]) <= 0; case LESS_EQUALS: return compare(ctx, args[0], args[1]).lessOrEqual();
case INVERSE: return bitwiseNot(ctx, args[0]); case INVERSE: return bitwiseNot(ctx, args[0]);
case NOT: return not(args[0]); case NOT: return not(args[0]);
@ -272,15 +297,20 @@ public class Values {
var proto = getPrototype(ctx, obj); var proto = getPrototype(ctx, obj);
if (proto == null) return key.equals("__proto__") ? NULL : null; if (proto == null) return "__proto__".equals(key) ? NULL : null;
else if (key != null && key.equals("__proto__")) return proto; else if (key != null && "__proto__".equals(key)) return proto;
else return proto.getMember(ctx, key, obj); else return proto.getMember(ctx, key, obj);
} }
public static 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) { public static boolean setMember(Context ctx, Object obj, Object key, Object val) {
obj = normalize(ctx, obj); key = normalize(ctx, key); val = normalize(ctx, val); obj = normalize(ctx, obj); key = normalize(ctx, key); val = normalize(ctx, val);
if (obj == null) throw EngineException.ofType("Tried to access member of undefined."); if (obj == null) throw EngineException.ofType("Tried to access member of undefined.");
if (obj == NULL) throw EngineException.ofType("Tried to access member of null."); if (obj == NULL) throw EngineException.ofType("Tried to access member of null.");
if (key.equals("__proto__")) return setPrototype(ctx, obj, val); if (key != null && "__proto__".equals(key)) return setPrototype(ctx, obj, val);
if (isObject(obj)) return object(obj).setMember(ctx, key, val, false); if (isObject(obj)) return object(obj).setMember(ctx, key, val, false);
var proto = getPrototype(ctx, obj); var proto = getPrototype(ctx, obj);
@ -290,7 +320,7 @@ public class Values {
if (obj == null || obj == NULL) return false; if (obj == null || obj == NULL) return false;
obj = normalize(ctx, obj); key = normalize(ctx, key); obj = normalize(ctx, obj); key = normalize(ctx, key);
if (key.equals("__proto__")) return true; if ("__proto__".equals(key)) return true;
if (isObject(obj)) return object(obj).hasMember(ctx, key, own); if (isObject(obj)) return object(obj).hasMember(ctx, key, own);
if (obj instanceof String && key instanceof Number) { if (obj instanceof String && key instanceof Number) {
@ -518,7 +548,7 @@ public class Values {
throw new ConvertException(type(obj), clazz.getSimpleName()); throw new ConvertException(type(obj), clazz.getSimpleName());
} }
public static Iterable<Object> toJavaIterable(Context ctx, Object obj) { public static Iterable<Object> fromJSIterator(Context ctx, Object obj) {
return () -> { return () -> {
try { try {
var symbol = ctx.environment().symbol("Symbol.iterator"); 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(); var res = new ObjectValue();
try { try {
@ -592,8 +622,31 @@ public class Values {
return res; return res;
} }
public static ObjectValue fromJavaIterable(Context ctx, Iterable<?> it) { public static ObjectValue toJSIterator(Context ctx, Iterable<?> it) {
return fromJavaIterator(ctx, it.iterator()); 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) { private static boolean isEmptyFunc(ObjectValue val) {
@ -620,10 +673,7 @@ public class Values {
var printed = true; var printed = true;
if (val instanceof FunctionValue) { if (val instanceof FunctionValue) {
System.out.print("function "); System.out.print(val.toString());
var name = Values.getMember(ctx, val, "name");
if (name != null) System.out.print(Values.toString(ctx, name));
System.out.print("(...)");
var loc = val instanceof CodeFunction ? ((CodeFunction)val).loc() : null; var loc = val instanceof CodeFunction ? ((CodeFunction)val).loc() : null;
if (loc != null) System.out.print(" @ " + loc); if (loc != null) System.out.print(" @ " + loc);

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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. * Generates a prototype for the given class.
* The returned object will have appropriate wrappers for all instance members. * The returned object will have appropriate wrappers for all instance members.
@ -117,6 +123,8 @@ public class NativeWrapperProvider implements WrappersProvider {
public static ObjectValue makeProto(Environment ctx, Class<?> clazz) { public static ObjectValue makeProto(Environment ctx, Class<?> clazz) {
var res = new ObjectValue(); var res = new ObjectValue();
res.defineProperty(null, ctx.symbol("Symbol.typeName"), getName(clazz));
for (var overload : clazz.getDeclaredMethods()) { for (var overload : clazz.getDeclaredMethods()) {
var init = overload.getAnnotation(NativeInit.class); var init = overload.getAnnotation(NativeInit.class);
if (init == null || init.value() != InitType.PROTOTYPE) continue; if (init == null || init.value() != InitType.PROTOTYPE) continue;
@ -137,11 +145,7 @@ public class NativeWrapperProvider implements WrappersProvider {
* @param clazz The class for which a constructor should be generated * @param clazz The class for which a constructor should be generated
*/ */
public static FunctionValue makeConstructor(Environment ctx, Class<?> clazz) { public static FunctionValue makeConstructor(Environment ctx, Class<?> clazz) {
var name = clazz.getName(); FunctionValue func = new OverloadFunction(getName(clazz));
var classNat = clazz.getAnnotation(Native.class);
if (classNat != null && !classNat.value().trim().equals("")) name = classNat.value().trim();
FunctionValue func = new OverloadFunction(name);
for (var overload : clazz.getDeclaredConstructors()) { for (var overload : clazz.getDeclaredConstructors()) {
var nat = overload.getAnnotation(Native.class); var nat = overload.getAnnotation(Native.class);
@ -256,7 +260,27 @@ public class NativeWrapperProvider implements WrappersProvider {
constructors.put(clazz, value); constructors.put(clazz, value);
} }
private void initError() {
var proto = new ObjectValue();
proto.defineProperty(null, "message", new NativeFunction("message", (ctx, thisArg, args) -> {
if (thisArg instanceof Throwable) return ((Throwable)thisArg).getMessage();
else return null;
}));
proto.defineProperty(null, "name", new NativeFunction("name", (ctx, thisArg, args) -> getName(thisArg.getClass())));
var constr = makeConstructor(null, Throwable.class);
proto.defineProperty(null, "constructor", constr, true, false, false);
constr.defineProperty(null, "prototype", proto, true, false, false);
proto.setPrototype(null, getProto(Object.class));
constr.setPrototype(null, getConstr(Object.class));
setProto(Throwable.class, proto);
setConstr(Throwable.class, constr);
}
public NativeWrapperProvider(Environment env) { public NativeWrapperProvider(Environment env) {
this.env = env; this.env = env;
initError();
} }
} }

View File

@ -8,6 +8,7 @@ import java.util.List;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.NativeWrapper;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.ConvertException; import me.topchetoeu.jscript.exceptions.ConvertException;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
@ -92,7 +93,14 @@ public class OverloadFunction extends FunctionValue {
throw new InterruptException(); throw new InterruptException();
} }
else { else {
throw EngineException.ofError(e.getTargetException().getMessage()).add(name, loc); var target = e.getTargetException();
var targetClass = target.getClass();
var err = new NativeWrapper(e.getTargetException());
err.defineProperty(ctx, "message", target.getMessage());
err.defineProperty(ctx, "name", NativeWrapperProvider.getName(targetClass));
throw new EngineException(err).add(name, loc);
} }
} }
catch (ReflectiveOperationException e) { catch (ReflectiveOperationException e) {

View File

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

View File

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

View File

@ -4,16 +4,14 @@ import java.util.Iterator;
import java.util.Stack; import java.util.Stack;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.NativeFunction;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.interop.InitType;
import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.NativeConstructor; import me.topchetoeu.jscript.interop.NativeConstructor;
import me.topchetoeu.jscript.interop.NativeGetter; import me.topchetoeu.jscript.interop.NativeGetter;
import me.topchetoeu.jscript.interop.NativeInit;
import me.topchetoeu.jscript.interop.NativeSetter; import me.topchetoeu.jscript.interop.NativeSetter;
@Native("Array") public class ArrayLib { @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) { @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) { @Native(thisArg = true) public static ObjectValue keys(Context ctx, ArrayValue thisArg) {
return Values.fromJavaIterable(ctx, () -> new Iterator<Object>() { return Values.toJSIterator(ctx, () -> new Iterator<Object>() {
private int i = 0; private int i = 0;
@Override @Override
@ -43,7 +41,7 @@ import me.topchetoeu.jscript.interop.NativeSetter;
}); });
} }
@Native(thisArg = true) public static ObjectValue entries(Context ctx, ArrayValue thisArg) { @Native(thisArg = true) public static ObjectValue entries(Context ctx, ArrayValue thisArg) {
return Values.fromJavaIterable(ctx, () -> new Iterator<Object>() { return Values.toJSIterator(ctx, () -> new Iterator<Object>() {
private int i = 0; private int i = 0;
@Override @Override
@ -94,8 +92,11 @@ import me.topchetoeu.jscript.interop.NativeSetter;
} }
@Native(thisArg = true) public static ArrayValue sort(Context ctx, ArrayValue arr, FunctionValue cmp) { @Native(thisArg = true) public static ArrayValue sort(Context ctx, ArrayValue arr, FunctionValue cmp) {
var defaultCmp = new NativeFunction("", (_ctx, thisArg, args) -> {
return Values.toString(ctx, args[0]).compareTo(Values.toString(ctx, args[1]));
});
arr.sort((a, b) -> { 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;
if (res > 0) return 1; if (res > 0) return 1;
return 0; return 0;
@ -369,8 +370,4 @@ import me.topchetoeu.jscript.interop.NativeSetter;
return res; return res;
} }
@NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) {
target.defineProperty(null, env.symbol("Symbol.typeName"), "Array");
}
} }

View File

@ -1,7 +1,6 @@
package me.topchetoeu.jscript.lib; package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.engine.Context; 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.CodeFrame;
import me.topchetoeu.jscript.engine.frame.Runners; import me.topchetoeu.jscript.engine.frame.Runners;
import me.topchetoeu.jscript.engine.values.CodeFunction; 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) { private void next(Context ctx, Object inducedValue, Object inducedError) {
Object res = null; Object res = null;
StackData.pushFrame(ctx, frame); ctx.pushFrame(frame);
ctx.pushEnv(frame.function.environment); ctx.pushEnv(frame.function.environment);
awaiting = false; awaiting = false;
@ -40,7 +39,7 @@ import me.topchetoeu.jscript.interop.Native;
} }
} }
StackData.popFrame(ctx, frame); ctx.popFrame(frame);
if (awaiting) { if (awaiting) {
PromiseLib.then(ctx, frame.pop(), new NativeFunction(this::fulfill), new NativeFunction(this::reject)); PromiseLib.then(ctx, frame.pop(), new NativeFunction(this::fulfill), new NativeFunction(this::reject));

View File

@ -3,7 +3,6 @@ package me.topchetoeu.jscript.lib;
import java.util.Map; import java.util.Map;
import me.topchetoeu.jscript.engine.Context; 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.CodeFrame;
import me.topchetoeu.jscript.engine.frame.Runners; import me.topchetoeu.jscript.engine.frame.Runners;
import me.topchetoeu.jscript.engine.values.NativeFunction; import me.topchetoeu.jscript.engine.values.NativeFunction;
@ -29,7 +28,7 @@ import me.topchetoeu.jscript.interop.Native;
} }
Object res = null; Object res = null;
StackData.pushFrame(ctx, frame); ctx.pushFrame(frame);
state = 0; state = 0;
while (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) { if (state == 1) {
PromiseLib.then(ctx, frame.pop(), new NativeFunction(this::fulfill), new NativeFunction(this::reject)); PromiseLib.then(ctx, frame.pop(), new NativeFunction(this::fulfill), new NativeFunction(this::reject));

View File

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

View File

@ -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.Context;
import me.topchetoeu.jscript.engine.Environment; 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.ArrayValue;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto;
import me.topchetoeu.jscript.interop.InitType; import me.topchetoeu.jscript.interop.InitType;
import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.NativeConstructor; import me.topchetoeu.jscript.interop.NativeConstructor;
@ -51,8 +51,8 @@ import me.topchetoeu.jscript.interop.NativeInit;
var target = new ObjectValue(); var target = new ObjectValue();
if (thisArg instanceof ObjectValue) target = (ObjectValue)thisArg; if (thisArg instanceof ObjectValue) target = (ObjectValue)thisArg;
target.defineProperty(ctx, "stack", ArrayValue.of(ctx, StackData.stackTrace(ctx))); target.setPrototype(PlaceholderProto.ERROR);
target.defineProperty(ctx, "name", "Error"); target.defineProperty(ctx, "stack", ArrayValue.of(ctx, ctx.stackTrace()));
if (message == null) target.defineProperty(ctx, "message", ""); if (message == null) target.defineProperty(ctx, "message", "");
else target.defineProperty(ctx, "message", Values.toString(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) { @NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) {
target.defineProperty(null, env.symbol("Symbol.typeName"), "Error");
target.defineProperty(null, "name", "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.Location;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.CodeFunction; import me.topchetoeu.jscript.engine.values.CodeFunction;
import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.NativeFunction; import me.topchetoeu.jscript.engine.values.NativeFunction;
import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.interop.InitType;
import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.NativeInit;
@Native("Function") public class FunctionLib { @Native("Function") public class FunctionLib {
@Native(thisArg = true) public static Object location(Context ctx, FunctionValue func) { @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) { @Native(thisArg = true) public static String toString(Context ctx, Object func) {
return "function (...) { ... }"; return func.toString();
} }
@Native public static FunctionValue async(FunctionValue func) { @Native public static FunctionValue async(FunctionValue func) {
@ -55,8 +51,4 @@ import me.topchetoeu.jscript.interop.NativeInit;
@Native public static FunctionValue generator(FunctionValue func) { @Native public static FunctionValue generator(FunctionValue func) {
return new GeneratorFunctionLib(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; package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.engine.Context; 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.CodeFrame;
import me.topchetoeu.jscript.engine.frame.Runners; import me.topchetoeu.jscript.engine.frame.Runners;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.ObjectValue;
@ -25,7 +24,7 @@ import me.topchetoeu.jscript.interop.Native;
} }
Object res = null; Object res = null;
StackData.pushFrame(ctx, frame); ctx.pushFrame(frame);
yielding = false; yielding = false;
while (!yielding) { while (!yielding) {
@ -43,7 +42,7 @@ import me.topchetoeu.jscript.interop.Native;
} }
} }
StackData.popFrame(ctx, frame); ctx.popFrame(frame);
if (done) frame = null; if (done) frame = null;
else res = frame.pop(); else res = frame.pop();

View File

@ -119,6 +119,8 @@ public class Internals {
glob.define(null, "Math", false, wp.getNamespace(MathLib.class)); glob.define(null, "Math", false, wp.getNamespace(MathLib.class));
glob.define(null, "JSON", false, wp.getNamespace(JSONLib.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, "Date", false, wp.getConstr(DateLib.class));
glob.define(null, "Object", false, wp.getConstr(ObjectLib.class)); glob.define(null, "Object", false, wp.getConstr(ObjectLib.class));

View File

@ -35,15 +35,15 @@ import me.topchetoeu.jscript.interop.NativeGetter;
var res = map.entrySet().stream().map(v -> { var res = map.entrySet().stream().map(v -> {
return new ArrayValue(ctx, v.getKey(), v.getValue()); return new ArrayValue(ctx, v.getKey(), v.getValue());
}).collect(Collectors.toList()); }).collect(Collectors.toList());
return Values.fromJavaIterator(ctx, res.iterator()); return Values.toJSIterator(ctx, res.iterator());
} }
@Native public ObjectValue keys(Context ctx) { @Native public ObjectValue keys(Context ctx) {
var res = new ArrayList<>(map.keySet()); var res = new ArrayList<>(map.keySet());
return Values.fromJavaIterator(ctx, res.iterator()); return Values.toJSIterator(ctx, res.iterator());
} }
@Native public ObjectValue values(Context ctx) { @Native public ObjectValue values(Context ctx) {
var res = new ArrayList<>(map.values()); var res = new ArrayList<>(map.values());
return Values.fromJavaIterator(ctx, res.iterator()); return Values.toJSIterator(ctx, res.iterator());
} }
@Native public Object get(Object key) { @Native public Object get(Object key) {
@ -68,7 +68,7 @@ import me.topchetoeu.jscript.interop.NativeGetter;
} }
@Native public MapLib(Context ctx, Object iterable) { @Native public MapLib(Context ctx, Object iterable) {
for (var el : Values.toJavaIterable(ctx, iterable)) { for (var el : Values.fromJSIterator(ctx, iterable)) {
try { try {
set(Values.getMember(ctx, el, 0), Values.getMember(ctx, el, 1)); set(Values.getMember(ctx, el, 0), Values.getMember(ctx, el, 1));
} }

View File

@ -1,13 +1,10 @@
package me.topchetoeu.jscript.lib; package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.interop.InitType;
import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.NativeConstructor; import me.topchetoeu.jscript.interop.NativeConstructor;
import me.topchetoeu.jscript.interop.NativeInit;
@Native("Number") public class NumberLib { @Native("Number") public class NumberLib {
@Native public static final double EPSILON = java.lang.Math.ulp(1.0); @Native public static final double EPSILON = java.lang.Math.ulp(1.0);
@ -52,8 +49,4 @@ import me.topchetoeu.jscript.interop.NativeInit;
public NumberLib(double val) { public NumberLib(double val) {
this.value = val; this.value = val;
} }
@NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) {
target.defineProperty(null, env.symbol("Symbol.typeName"), "Number");
}
} }

View File

@ -1,17 +1,14 @@
package me.topchetoeu.jscript.lib; package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Symbol; import me.topchetoeu.jscript.engine.values.Symbol;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.interop.InitType;
import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.NativeConstructor; import me.topchetoeu.jscript.interop.NativeConstructor;
import me.topchetoeu.jscript.interop.NativeInit;
@Native("Object") public class ObjectLib { @Native("Object") public class ObjectLib {
@Native public static ObjectValue assign(Context ctx, ObjectValue dst, Object... src) { @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) { @Native public static ObjectValue fromEntries(Context ctx, Object iterable) {
var res = new ObjectValue(); var res = new ObjectValue();
for (var el : Values.toJavaIterable(ctx, iterable)) { for (var el : Values.fromJSIterator(ctx, iterable)) {
if (el instanceof ArrayValue) { if (el instanceof ArrayValue) {
res.defineProperty(ctx, ((ArrayValue)el).get(0), ((ArrayValue)el).get(1)); 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 if (arg instanceof Symbol) return SymbolPolyfill.constructor(ctx, thisArg, arg);
else return 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 java.util.Map;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.NativeFunction; import me.topchetoeu.jscript.engine.values.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.ObjectValue;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.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.Native;
import me.topchetoeu.jscript.interop.NativeInit;
@Native("Promise") public class PromiseLib { @Native("Promise") public class PromiseLib {
public static interface PromiseRunner {
Object run();
}
private static class Handle { private static class Handle {
public final Context ctx; public final Context ctx;
public final FunctionValue fulfilled; public final FunctionValue fulfilled;
@ -171,9 +170,7 @@ import me.topchetoeu.jscript.interop.NativeInit;
} }
var fulfillHandle = new NativeFunction(null, (_ctx, th, a) -> { var fulfillHandle = new NativeFunction(null, (_ctx, th, a) -> {
try { try { res.fulfill(ctx, Values.convert(ctx, fulfill.call(ctx, null, a[0]), Object.class)); }
res.fulfill(ctx, Values.convert(ctx, fulfill.call(ctx, null, a[0]), Object.class));
}
catch (EngineException err) { res.reject(ctx, err.value); } catch (EngineException err) { res.reject(ctx, err.value); }
return null; return null;
}); });
@ -234,7 +231,7 @@ import me.topchetoeu.jscript.interop.NativeInit;
private boolean handled = false; private boolean handled = false;
private Object val; 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 (this.state != STATE_PENDING) return;
if (val instanceof PromiseLib) ((PromiseLib)val).handle(ctx, 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; }) new NativeFunction(null, (e, th, a) -> { this.reject(ctx, a[0]); return null; })
); );
else { else {
Object next; Object then;
try { next = Values.getMember(ctx, val, "next"); } try { then = Values.getMember(ctx, val, "then"); }
catch (IllegalArgumentException e) { next = null; } catch (IllegalArgumentException e) { then = null; }
try { 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.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; }) 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.val = val;
this.state = STATE_FULFILLED; this.state = STATE_FULFILLED;
for (var handle : handles) handle.fulfilled.call(handle.ctx, null, val); ctx.engine.pushMsg(true, ctx, new NativeFunction((_ctx, _thisArg, _args) -> {
for (var handle : handles) {
handle.fulfilled.call(handle.ctx, null, val);
}
handles = null; handles = null;
return null;
}), null);
} }
} }
catch (EngineException err) { 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 (this.state != STATE_PENDING) return;
if (val instanceof PromiseLib) ((PromiseLib)val).handle(ctx, 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; }) new NativeFunction(null, (e, th, a) -> { this.reject(ctx, a[0]); return null; })
); );
else { else {
Object next; Object then;
try { next = Values.getMember(ctx, val, "next"); } try { then = Values.getMember(ctx, val, "then"); }
catch (IllegalArgumentException e) { next = null; } catch (IllegalArgumentException e) { then = null; }
try { 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; }),
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,20 +287,15 @@ import me.topchetoeu.jscript.interop.NativeInit;
this.val = val; this.val = val;
this.state = STATE_REJECTED; 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) -> { ctx.engine.pushMsg(true, ctx, new NativeFunction((_ctx, _thisArg, _args) -> {
for (var handle : handles) handle.rejected.call(handle.ctx, null, val);
if (!handled) { if (!handled) {
Values.printError(new EngineException(val).setCtx(ctx.environment(), ctx.engine), "(in promise)"); Values.printError(new EngineException(val).setCtx(ctx.environment(), ctx.engine), "(in promise)");
throw new InterruptException();
} }
handles = null;
return null; return null;
}), null); }), null);
} }
handles = null;
}
} }
catch (EngineException err) { catch (EngineException err) {
this.reject(ctx, err.value); this.reject(ctx, err.value);
@ -353,7 +349,18 @@ import me.topchetoeu.jscript.interop.NativeInit;
this(STATE_PENDING, null); this(STATE_PENDING, null);
} }
@NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) { public static PromiseLib await(Context ctx, PromiseRunner runner) {
target.defineProperty(null, env.symbol("Symbol.typeName"), "Promise"); 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.Context;
import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto;
import me.topchetoeu.jscript.interop.InitType; import me.topchetoeu.jscript.interop.InitType;
import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.NativeConstructor; import me.topchetoeu.jscript.interop.NativeConstructor;
@ -11,11 +12,11 @@ import me.topchetoeu.jscript.interop.NativeInit;
@Native("RangeError") public class RangeErrorLib extends ErrorLib { @Native("RangeError") public class RangeErrorLib extends ErrorLib {
@NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) { @NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) {
var target = ErrorLib.constructor(ctx, thisArg, message); var target = ErrorLib.constructor(ctx, thisArg, message);
target.setPrototype(PlaceholderProto.SYNTAX_ERROR);
target.defineProperty(ctx, "name", "RangeError"); target.defineProperty(ctx, "name", "RangeError");
return target; return target;
} }
@NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue 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"); 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) { @Native("@@Symbol.matchAll") public Object matchAll(Context ctx, String target) {
var pattern = new RegExpLib(this.source, this.flags() + "g"); 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 Object val = null;
private boolean updated = false; private boolean updated = false;

View File

@ -22,15 +22,15 @@ import me.topchetoeu.jscript.interop.NativeGetter;
@Native public ObjectValue entries(Context ctx) { @Native public ObjectValue entries(Context ctx) {
var res = set.stream().map(v -> new ArrayValue(ctx, v, v)).collect(Collectors.toList()); 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) { @Native public ObjectValue keys(Context ctx) {
var res = new ArrayList<>(set); var res = new ArrayList<>(set);
return Values.fromJavaIterator(ctx, res.iterator()); return Values.toJSIterator(ctx, res.iterator());
} }
@Native public ObjectValue values(Context ctx) { @Native public ObjectValue values(Context ctx) {
var res = new ArrayList<>(set); var res = new ArrayList<>(set);
return Values.fromJavaIterator(ctx, res.iterator()); return Values.toJSIterator(ctx, res.iterator());
} }
@Native public Object add(Object key) { @Native public Object add(Object key) {
@ -58,6 +58,6 @@ import me.topchetoeu.jscript.interop.NativeGetter;
} }
@Native public SetLib(Context ctx, Object iterable) { @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 java.util.regex.Pattern;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.values.ArrayValue; import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.FunctionValue; import me.topchetoeu.jscript.engine.values.FunctionValue;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.interop.InitType;
import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.NativeConstructor; import me.topchetoeu.jscript.interop.NativeConstructor;
import me.topchetoeu.jscript.interop.NativeGetter; import me.topchetoeu.jscript.interop.NativeGetter;
import me.topchetoeu.jscript.interop.NativeInit;
// TODO: implement index wrapping properly // TODO: implement index wrapping properly
@Native("String") public class StringLib { @Native("String") public class StringLib {
@ -263,8 +260,4 @@ import me.topchetoeu.jscript.interop.NativeInit;
public StringLib(String val) { public StringLib(String val) {
this.value = val; this.value = val;
} }
@NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) {
target.defineProperty(null, env.symbol("Symbol.typeName"), "String");
}
} }

View File

@ -4,16 +4,13 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.Symbol; import me.topchetoeu.jscript.engine.values.Symbol;
import me.topchetoeu.jscript.engine.values.Values; import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException; import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.interop.InitType;
import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.NativeConstructor; import me.topchetoeu.jscript.interop.NativeConstructor;
import me.topchetoeu.jscript.interop.NativeGetter; import me.topchetoeu.jscript.interop.NativeGetter;
import me.topchetoeu.jscript.interop.NativeInit;
@Native("Symbol") public class SymbolLib { @Native("Symbol") public class SymbolLib {
private static final Map<String, Symbol> symbols = new HashMap<>(); private static final Map<String, Symbol> symbols = new HashMap<>();
@ -63,8 +60,4 @@ import me.topchetoeu.jscript.interop.NativeInit;
public SymbolLib(Symbol val) { public SymbolLib(Symbol val) {
this.value = val; this.value = val;
} }
@NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) {
target.defineProperty(null, env.symbol("Symbol.typeName"), "Symbol");
}
} }

View File

@ -3,6 +3,7 @@ package me.topchetoeu.jscript.lib;
import me.topchetoeu.jscript.engine.Context; import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.values.ObjectValue; 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.InitType;
import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.NativeConstructor; import me.topchetoeu.jscript.interop.NativeConstructor;
@ -11,11 +12,10 @@ import me.topchetoeu.jscript.interop.NativeInit;
@Native("SyntaxError") public class SyntaxErrorLib extends ErrorLib { @Native("SyntaxError") public class SyntaxErrorLib extends ErrorLib {
@NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) { @NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) {
var target = ErrorLib.constructor(ctx, thisArg, message); var target = ErrorLib.constructor(ctx, thisArg, message);
target.defineProperty(ctx, "name", "SyntaxError"); target.setPrototype(PlaceholderProto.SYNTAX_ERROR);
return target; return target;
} }
@NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue 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"); 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.Context;
import me.topchetoeu.jscript.engine.Environment; import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.values.ObjectValue; import me.topchetoeu.jscript.engine.values.ObjectValue;
import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto;
import me.topchetoeu.jscript.interop.InitType; import me.topchetoeu.jscript.interop.InitType;
import me.topchetoeu.jscript.interop.Native; import me.topchetoeu.jscript.interop.Native;
import me.topchetoeu.jscript.interop.NativeConstructor; import me.topchetoeu.jscript.interop.NativeConstructor;
@ -11,11 +12,10 @@ import me.topchetoeu.jscript.interop.NativeInit;
@Native("TypeError") public class TypeErrorLib extends ErrorLib { @Native("TypeError") public class TypeErrorLib extends ErrorLib {
@NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) { @NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) {
var target = ErrorLib.constructor(ctx, thisArg, message); var target = ErrorLib.constructor(ctx, thisArg, message);
target.defineProperty(ctx, "name", "TypeError"); target.setPrototype(PlaceholderProto.SYNTAX_ERROR);
return target; return target;
} }
@NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue 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"); target.defineProperty(null, "name", "TypeError");
} }
} }

View File

@ -11,7 +11,6 @@ import java.util.TreeSet;
import me.topchetoeu.jscript.Filename; import me.topchetoeu.jscript.Filename;
import me.topchetoeu.jscript.Location; import me.topchetoeu.jscript.Location;
import me.topchetoeu.jscript.compilation.*; import me.topchetoeu.jscript.compilation.*;
import me.topchetoeu.jscript.compilation.Instruction.Type;
import me.topchetoeu.jscript.compilation.VariableDeclareStatement.Pair; import me.topchetoeu.jscript.compilation.VariableDeclareStatement.Pair;
import me.topchetoeu.jscript.compilation.control.*; import me.topchetoeu.jscript.compilation.control.*;
import me.topchetoeu.jscript.compilation.control.SwitchStatement.SwitchCase; import me.topchetoeu.jscript.compilation.control.SwitchStatement.SwitchCase;
@ -1896,10 +1895,7 @@ public class Parsing {
res.add(Instruction.throwSyntax(e)); res.add(Instruction.throwSyntax(e));
} }
if (res.size() != 0 && res.get(res.size() - 1).type == Type.DISCARD) { res.add(Instruction.ret());
res.set(res.size() - 1, Instruction.ret());
}
else res.add(Instruction.ret());
return new CodeFunction(environment, "", subscope.localsCount(), 0, new ValueVariable[0], new FunctionBody(res.array(), subscope.captures(), subscope.locals())); 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 "State [pr=%s;trg=%s;wildN=%s;wild=%s]".formatted(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));
}
}