Merge pull request #12 from TopchetoEU/TopchetoEU/cleanup
Major codebase cleanup
This commit is contained in:
commit
18d22a1282
1
build.js
1
build.js
@ -77,6 +77,7 @@ async function downloadTypescript(outFile) {
|
||||
console.log('Minifying typescript...');
|
||||
|
||||
const minified = minify((await fs.readFile('tmp/typescript-es5.js')).toString());
|
||||
// const minified = { code: (await fs.readFile('tmp/typescript-es5.js')).toString() };
|
||||
if (minified.error) throw minified.error;
|
||||
|
||||
// Patch unsupported regex syntax
|
||||
|
4
src/assets/js/lib.d.ts
vendored
4
src/assets/js/lib.d.ts
vendored
@ -566,7 +566,7 @@ declare class Map<KeyT, ValueT> {
|
||||
|
||||
public get size(): number;
|
||||
|
||||
public forEach(func: (key: KeyT, val: ValueT, map: Map<KeyT, ValueT>) => void, thisArg?: any): void;
|
||||
public forEach(func: (val: ValueT, key: KeyT, map: Map<KeyT, ValueT>) => void, thisArg?: any): void;
|
||||
|
||||
public constructor();
|
||||
}
|
||||
@ -585,7 +585,7 @@ declare class Set<T> {
|
||||
|
||||
public get size(): number;
|
||||
|
||||
public forEach(func: (key: T, set: Set<T>) => void, thisArg?: any): void;
|
||||
public forEach(func: (key: T, value: T, set: Set<T>) => void, thisArg?: any): void;
|
||||
|
||||
public constructor();
|
||||
}
|
||||
|
@ -8,42 +8,32 @@ import java.nio.file.Path;
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.Engine;
|
||||
import me.topchetoeu.jscript.engine.Environment;
|
||||
import me.topchetoeu.jscript.engine.debug.DebugContext;
|
||||
import me.topchetoeu.jscript.engine.debug.DebugServer;
|
||||
import me.topchetoeu.jscript.engine.debug.SimpleDebugger;
|
||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.engine.values.NativeFunction;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.events.Observer;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.exceptions.InterruptException;
|
||||
import me.topchetoeu.jscript.exceptions.SyntaxException;
|
||||
import me.topchetoeu.jscript.filesystem.Filesystem;
|
||||
import me.topchetoeu.jscript.filesystem.MemoryFilesystem;
|
||||
import me.topchetoeu.jscript.filesystem.Mode;
|
||||
import me.topchetoeu.jscript.filesystem.PhysicalFilesystem;
|
||||
import me.topchetoeu.jscript.filesystem.RootFilesystem;
|
||||
import me.topchetoeu.jscript.lib.EnvironmentLib;
|
||||
import me.topchetoeu.jscript.lib.Internals;
|
||||
import me.topchetoeu.jscript.modules.ModuleRepo;
|
||||
import me.topchetoeu.jscript.permissions.PermissionsManager;
|
||||
import me.topchetoeu.jscript.permissions.PermissionsProvider;
|
||||
|
||||
public class Main {
|
||||
public static class Printer implements Observer<Object> {
|
||||
public void next(Object data) {
|
||||
Values.printValue(null, data);
|
||||
System.out.println();
|
||||
}
|
||||
|
||||
public void error(RuntimeException err) {
|
||||
Values.printError(err, null);
|
||||
}
|
||||
|
||||
public void finish() {
|
||||
engineTask.interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
static Thread engineTask, debugTask;
|
||||
static Engine engine = new Engine(true);
|
||||
static Engine engine = new Engine();
|
||||
static DebugServer debugServer = new DebugServer();
|
||||
static Environment environment = new Environment(null, null, null);
|
||||
static Environment environment = new Environment();
|
||||
|
||||
static int j = 0;
|
||||
static boolean exited = false;
|
||||
@ -70,7 +60,7 @@ public class Main {
|
||||
}
|
||||
for (var i = 0; ; i++) {
|
||||
try {
|
||||
var raw = Reading.read();
|
||||
var raw = Reading.readline();
|
||||
|
||||
if (raw == null) break;
|
||||
var res = engine.pushMsg(
|
||||
@ -104,54 +94,66 @@ public class Main {
|
||||
private static void initEnv() {
|
||||
environment = Internals.apply(environment);
|
||||
|
||||
environment.global.define(false, new NativeFunction("exit", (_ctx, th, args) -> {
|
||||
environment.global.define(false, new NativeFunction("exit", args -> {
|
||||
exited = true;
|
||||
throw new InterruptException();
|
||||
}));
|
||||
environment.global.define(false, new NativeFunction("go", (_ctx, th, args) -> {
|
||||
environment.global.define(false, new NativeFunction("go", args -> {
|
||||
try {
|
||||
var f = Path.of("do.js");
|
||||
var func = _ctx.compile(new Filename("do", "do/" + j++ + ".js"), new String(Files.readAllBytes(f)));
|
||||
return func.call(_ctx);
|
||||
var func = args.ctx.compile(new Filename("do", "do/" + j++ + ".js"), new String(Files.readAllBytes(f)));
|
||||
return func.call(args.ctx);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new EngineException("Couldn't open do.js");
|
||||
}
|
||||
}));
|
||||
|
||||
environment.filesystem.protocols.put("temp", new MemoryFilesystem(Mode.READ_WRITE));
|
||||
environment.filesystem.protocols.put("file", new PhysicalFilesystem("."));
|
||||
environment.modules.repos.put("file", ModuleRepo.ofFilesystem(environment.filesystem));
|
||||
var fs = new RootFilesystem(PermissionsProvider.get(environment));
|
||||
fs.protocols.put("temp", new MemoryFilesystem(Mode.READ_WRITE));
|
||||
fs.protocols.put("file", new PhysicalFilesystem("."));
|
||||
|
||||
environment.add(PermissionsProvider.ENV_KEY, PermissionsManager.ALL_PERMS);
|
||||
environment.add(Filesystem.ENV_KEY, fs);
|
||||
environment.add(ModuleRepo.ENV_KEY, ModuleRepo.ofFilesystem(fs));
|
||||
}
|
||||
private static void initEngine() {
|
||||
debugServer.targets.put("target", (ws, req) -> new SimpleDebugger(ws, engine));
|
||||
var ctx = new DebugContext();
|
||||
engine.add(DebugContext.ENV_KEY, ctx);
|
||||
|
||||
debugServer.targets.put("target", (ws, req) -> new SimpleDebugger(ws).attach(ctx));
|
||||
engineTask = engine.start();
|
||||
debugTask = debugServer.start(new InetSocketAddress("127.0.0.1", 9229), true);
|
||||
}
|
||||
private static void initTypescript() {
|
||||
var tsEnv = Internals.apply(new Environment());
|
||||
var bsEnv = Internals.apply(new Environment());
|
||||
|
||||
try {
|
||||
var tsEnv = Internals.apply(new Environment(null, null, null));
|
||||
tsEnv.stackVisible = false;
|
||||
tsEnv.global.define(null, "module", false, new ObjectValue());
|
||||
var bsEnv = Internals.apply(new Environment(null, null, null));
|
||||
bsEnv.stackVisible = false;
|
||||
|
||||
engine.pushMsg(
|
||||
false, tsEnv,
|
||||
new Filename("jscript", "ts.js"),
|
||||
Reading.resourceToString("js/ts.js"), null
|
||||
Reading.resourceToString("assets/js/ts.js"), null
|
||||
).await();
|
||||
System.out.println("Loaded typescript!");
|
||||
|
||||
var typescript = tsEnv.global.get(new Context(engine, bsEnv), "ts");
|
||||
var libs = new ArrayValue(null, Reading.resourceToString("assets/js/lib.d.ts"));
|
||||
|
||||
engine.pushMsg(
|
||||
false, bsEnv,
|
||||
new Filename("jscript", "bootstrap.js"), Reading.resourceToString("js/bootstrap.js"), null,
|
||||
tsEnv.global.get(new Context(engine, bsEnv), "ts"), environment, new ArrayValue(null, Reading.resourceToString("js/lib.d.ts"))
|
||||
new Filename("jscript", "bootstrap.js"), Reading.resourceToString("assets/js/bootstrap.js"), null,
|
||||
typescript, new EnvironmentLib(environment), libs
|
||||
).await();
|
||||
}
|
||||
catch (EngineException e) {
|
||||
Values.printError(e, "(while initializing TS)");
|
||||
}
|
||||
|
||||
bsEnv.add(Environment.HIDE_STACK, true);
|
||||
tsEnv.add(Environment.HIDE_STACK, true);
|
||||
}
|
||||
|
||||
public static void main(String args[]) {
|
||||
|
@ -1,6 +0,0 @@
|
||||
package me.topchetoeu.jscript;
|
||||
|
||||
public interface MessageReceiver {
|
||||
void sendMessage(String msg);
|
||||
void sendError(String msg);
|
||||
}
|
@ -10,7 +10,7 @@ import me.topchetoeu.jscript.exceptions.UncheckedException;
|
||||
public class Reading {
|
||||
private static final BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
|
||||
|
||||
public static synchronized String read() throws IOException {
|
||||
public static synchronized String readline() throws IOException {
|
||||
return reader.readLine();
|
||||
}
|
||||
|
||||
@ -19,7 +19,7 @@ public class Reading {
|
||||
catch (Throwable e) { throw new UncheckedException(e); }
|
||||
}
|
||||
public static InputStream resourceToStream(String name) {
|
||||
return Reading.class.getResourceAsStream("/assets/" + name);
|
||||
return Reading.class.getResourceAsStream("/" + name);
|
||||
}
|
||||
public static String resourceToString(String name) {
|
||||
return streamToString(resourceToStream(name));
|
||||
|
5
src/me/topchetoeu/jscript/ResultRunnable.java
Normal file
5
src/me/topchetoeu/jscript/ResultRunnable.java
Normal file
@ -0,0 +1,5 @@
|
||||
package me.topchetoeu.jscript;
|
||||
|
||||
public interface ResultRunnable<T> {
|
||||
T run();
|
||||
}
|
@ -2,44 +2,71 @@ package me.topchetoeu.jscript.engine;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Stack;
|
||||
import java.util.TreeSet;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
import me.topchetoeu.jscript.Filename;
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.engine.debug.DebugContext;
|
||||
import me.topchetoeu.jscript.engine.frame.CodeFrame;
|
||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.engine.values.Symbol;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.lib.EnvironmentLib;
|
||||
import me.topchetoeu.jscript.mapping.SourceMap;
|
||||
|
||||
public class Context {
|
||||
private final Stack<Environment> env = new Stack<>();
|
||||
private final ArrayList<CodeFrame> frames = new ArrayList<>();
|
||||
public class Context implements Extensions {
|
||||
public final Context parent;
|
||||
public final Environment environment;
|
||||
public final CodeFrame frame;
|
||||
public final Engine engine;
|
||||
public final int stackSize;
|
||||
|
||||
public Environment environment() {
|
||||
return env.empty() ? null : env.peek();
|
||||
@Override public <T> void add(Symbol key, T obj) {
|
||||
if (environment != null) environment.add(key, obj);
|
||||
else if (engine != null) engine.add(key, obj);
|
||||
}
|
||||
@Override public <T> T get(Symbol key) {
|
||||
if (environment != null && environment.has(key)) return environment.get(key);
|
||||
else if (engine != null && engine.has(key)) return engine.get(key);
|
||||
return null;
|
||||
}
|
||||
@Override public boolean has(Symbol key) {
|
||||
return
|
||||
environment != null && environment.has(key) ||
|
||||
engine != null && engine.has(key);
|
||||
}
|
||||
@Override public boolean remove(Symbol key) {
|
||||
var res = false;
|
||||
|
||||
private Context pushEnv(Environment env) {
|
||||
this.env.push(env);
|
||||
return this;
|
||||
if (environment != null) res |= environment.remove(key);
|
||||
else if (engine != null) res |= engine.remove(key);
|
||||
|
||||
return res;
|
||||
}
|
||||
private void popEnv() {
|
||||
if (!env.empty()) this.env.pop();
|
||||
@Override public Iterable<Symbol> keys() {
|
||||
if (engine == null && environment == null) return List.of();
|
||||
if (engine == null) return environment.keys();
|
||||
if (environment == null) return engine.keys();
|
||||
|
||||
return () -> Stream.concat(
|
||||
StreamSupport.stream(engine.keys().spliterator(), false),
|
||||
StreamSupport.stream(environment.keys().spliterator(), false)
|
||||
).distinct().iterator();
|
||||
}
|
||||
|
||||
public FunctionValue compile(Filename filename, String raw) {
|
||||
var env = environment();
|
||||
var result = env.compile.call(this, null, raw, filename.toString(), env);
|
||||
var env = environment;
|
||||
var result = Environment.compileFunc(this).call(this, null, raw, filename.toString(), new EnvironmentLib(env));
|
||||
|
||||
var function = (FunctionValue)Values.getMember(this, result, "function");
|
||||
if (!engine.debugging) return function;
|
||||
if (!DebugContext.enabled(this)) return function;
|
||||
|
||||
var rawMapChain = ((ArrayValue)Values.getMember(this, result, "mapChain")).toArray();
|
||||
var breakpoints = new TreeSet<>(
|
||||
@ -62,39 +89,41 @@ public class Context {
|
||||
breakpoints = newBreakpoints;
|
||||
}
|
||||
|
||||
engine.onSource(filename, raw, breakpoints, map);
|
||||
DebugContext.get(this).onSource(filename, raw, breakpoints, map);
|
||||
|
||||
return function;
|
||||
}
|
||||
|
||||
|
||||
public void pushFrame(CodeFrame frame) {
|
||||
frames.add(frame);
|
||||
if (frames.size() > engine.maxStackFrames) throw EngineException.ofRange("Stack overflow!");
|
||||
pushEnv(frame.function.environment);
|
||||
engine.onFramePush(this, frame);
|
||||
}
|
||||
public boolean popFrame(CodeFrame frame) {
|
||||
if (frames.size() == 0) return false;
|
||||
if (frames.get(frames.size() - 1) != frame) return false;
|
||||
frames.remove(frames.size() - 1);
|
||||
popEnv();
|
||||
engine.onFramePop(this, frame);
|
||||
return true;
|
||||
}
|
||||
public CodeFrame peekFrame() {
|
||||
if (frames.size() == 0) return null;
|
||||
return frames.get(frames.size() - 1);
|
||||
public Context pushFrame(CodeFrame frame) {
|
||||
var res = new Context(this, frame.function.environment, frame, engine, stackSize + 1);
|
||||
return res;
|
||||
}
|
||||
|
||||
public List<CodeFrame> frames() {
|
||||
return Collections.unmodifiableList(frames);
|
||||
public Iterable<CodeFrame> frames() {
|
||||
var self = this;
|
||||
return () -> new Iterator<CodeFrame>() {
|
||||
private Context curr = self;
|
||||
|
||||
private void update() {
|
||||
while (curr != null && curr.frame == null) curr = curr.parent;
|
||||
}
|
||||
|
||||
@Override public boolean hasNext() {
|
||||
update();
|
||||
return curr != null;
|
||||
}
|
||||
@Override public CodeFrame next() {
|
||||
update();
|
||||
var res = curr.frame;
|
||||
curr = curr.parent;
|
||||
return res;
|
||||
}
|
||||
};
|
||||
}
|
||||
public List<String> stackTrace() {
|
||||
var res = new ArrayList<String>();
|
||||
|
||||
for (var i = frames.size() - 1; i >= 0; i--) {
|
||||
var el = frames.get(i);
|
||||
for (var el : frames()) {
|
||||
var name = el.function.name;
|
||||
Location loc = null;
|
||||
|
||||
@ -114,11 +143,22 @@ public class Context {
|
||||
return res;
|
||||
}
|
||||
|
||||
public Context(Engine engine) {
|
||||
private Context(Context parent, Environment environment, CodeFrame frame, Engine engine, int stackSize) {
|
||||
this.parent = parent;
|
||||
this.environment = environment;
|
||||
this.frame = frame;
|
||||
this.engine = engine;
|
||||
this.stackSize = stackSize;
|
||||
|
||||
if (hasNotNull(Environment.MAX_STACK_COUNT) && stackSize > (int)get(Environment.MAX_STACK_COUNT)) {
|
||||
throw EngineException.ofRange("Stack overflow!");
|
||||
}
|
||||
}
|
||||
|
||||
public Context(Engine engine) {
|
||||
this(null, null, null, engine, 0);
|
||||
}
|
||||
public Context(Engine engine, Environment env) {
|
||||
this(engine);
|
||||
if (env != null) this.pushEnv(env);
|
||||
this(null, env, null, engine, 0);
|
||||
}
|
||||
}
|
||||
|
@ -1,56 +0,0 @@
|
||||
package me.topchetoeu.jscript.engine;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public class Data {
|
||||
private HashMap<DataKey<Object>, Object> data = new HashMap<>();
|
||||
|
||||
public Data copy() {
|
||||
return new Data().addAll(this);
|
||||
}
|
||||
|
||||
public Data addAll(Map<DataKey<?>, ?> data) {
|
||||
for (var el : data.entrySet()) {
|
||||
get((DataKey<Object>)el.getKey(), (Object)el.getValue());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
public Data addAll(Data data) {
|
||||
for (var el : data.data.entrySet()) {
|
||||
get((DataKey<Object>)el.getKey(), (Object)el.getValue());
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public <T> T remove(DataKey<T> key) {
|
||||
return (T)data.remove(key);
|
||||
}
|
||||
public <T> Data set(DataKey<T> key, T val) {
|
||||
data.put((DataKey<Object>)key, (Object)val);
|
||||
return this;
|
||||
}
|
||||
public <T> T get(DataKey<T> key, T val) {
|
||||
if (data.containsKey(key)) return (T)data.get((DataKey<Object>)key);
|
||||
set(key, val);
|
||||
return val;
|
||||
}
|
||||
public <T> T get(DataKey<T> key) {
|
||||
if (data.containsKey(key)) return (T)data.get((DataKey<Object>)key);
|
||||
return null;
|
||||
}
|
||||
public boolean has(DataKey<?> key) { return data.containsKey(key); }
|
||||
|
||||
public int increase(DataKey<Integer> key, int n, int start) {
|
||||
int res;
|
||||
set(key, res = get(key, start) + n);
|
||||
return res;
|
||||
}
|
||||
public int increase(DataKey<Integer> key, int n) {
|
||||
return increase(key, n, 0);
|
||||
}
|
||||
public int increase(DataKey<Integer> key) {
|
||||
return increase(key, 1, 0);
|
||||
}
|
||||
}
|
@ -1,3 +0,0 @@
|
||||
package me.topchetoeu.jscript.engine;
|
||||
|
||||
public class DataKey<T> { }
|
@ -1,175 +1,57 @@
|
||||
package me.topchetoeu.jscript.engine;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.PriorityBlockingQueue;
|
||||
|
||||
import me.topchetoeu.jscript.Filename;
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.FunctionBody;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.engine.debug.DebugController;
|
||||
import me.topchetoeu.jscript.engine.frame.CodeFrame;
|
||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.engine.values.Symbol;
|
||||
import me.topchetoeu.jscript.events.Awaitable;
|
||||
import me.topchetoeu.jscript.events.DataNotifier;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.exceptions.InterruptException;
|
||||
import me.topchetoeu.jscript.mapping.SourceMap;
|
||||
|
||||
public class Engine implements DebugController {
|
||||
private class UncompiledFunction extends FunctionValue {
|
||||
public final Filename filename;
|
||||
public final String raw;
|
||||
private FunctionValue compiled = null;
|
||||
|
||||
@Override
|
||||
public Object call(Context ctx, Object thisArg, Object ...args) {
|
||||
if (compiled == null) compiled = ctx.compile(filename, raw);
|
||||
return compiled.call(ctx, thisArg, args);
|
||||
}
|
||||
|
||||
public UncompiledFunction(Filename filename, String raw) {
|
||||
super(filename + "", 0);
|
||||
this.filename = filename;
|
||||
this.raw = raw;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Task implements Comparable<Task> {
|
||||
public final FunctionValue func;
|
||||
public final Object thisArg;
|
||||
public final Object[] args;
|
||||
public final DataNotifier<Object> notifier = new DataNotifier<>();
|
||||
public final Context ctx;
|
||||
public final boolean micro;
|
||||
|
||||
public Task(Context ctx, FunctionValue func, Object thisArg, Object[] args, boolean micro) {
|
||||
this.ctx = ctx;
|
||||
this.func = func;
|
||||
this.thisArg = thisArg;
|
||||
this.args = args;
|
||||
this.micro = micro;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Task other) {
|
||||
return Integer.compare(this.micro ? 0 : 1, other.micro ? 0 : 1);
|
||||
}
|
||||
}
|
||||
|
||||
private static int nextId = 0;
|
||||
public class Engine extends EventLoop implements Extensions {
|
||||
public static final HashMap<Long, FunctionBody> functions = new HashMap<>();
|
||||
|
||||
public final int id = ++nextId;
|
||||
public final boolean debugging;
|
||||
public int maxStackFrames = 10000;
|
||||
private final Environment env = new Environment();
|
||||
|
||||
private final HashMap<Filename, String> sources = new HashMap<>();
|
||||
private final HashMap<Filename, TreeSet<Location>> bpts = new HashMap<>();
|
||||
private final HashMap<Filename, SourceMap> maps = new HashMap<>();
|
||||
|
||||
public Location mapToCompiled(Location location) {
|
||||
var map = maps.get(location.filename());
|
||||
if (map == null) return location;
|
||||
return map.toCompiled(location);
|
||||
@Override
|
||||
public <T> void add(Symbol key, T obj) {
|
||||
this.env.add(key, obj);
|
||||
}
|
||||
public Location mapToOriginal(Location location) {
|
||||
var map = maps.get(location.filename());
|
||||
if (map == null) return location;
|
||||
return map.toOriginal(location);
|
||||
@Override
|
||||
public <T> T get(Symbol key) {
|
||||
return this.env.get(key);
|
||||
}
|
||||
@Override
|
||||
public boolean has(Symbol key) {
|
||||
return this.env.has(key);
|
||||
}
|
||||
@Override
|
||||
public boolean remove(Symbol key) {
|
||||
return this.env.remove(key);
|
||||
}
|
||||
@Override
|
||||
public Iterable<Symbol> keys() {
|
||||
return env.keys();
|
||||
}
|
||||
|
||||
private DebugController debugger;
|
||||
private Thread thread;
|
||||
private PriorityBlockingQueue<Task> tasks = new PriorityBlockingQueue<>();
|
||||
|
||||
public synchronized boolean attachDebugger(DebugController debugger) {
|
||||
if (!debugging || this.debugger != null) return false;
|
||||
|
||||
for (var source : sources.entrySet()) debugger.onSource(
|
||||
source.getKey(), source.getValue(),
|
||||
bpts.get(source.getKey()),
|
||||
maps.get(source.getKey())
|
||||
);
|
||||
|
||||
this.debugger = debugger;
|
||||
return true;
|
||||
}
|
||||
public synchronized boolean detachDebugger() {
|
||||
if (!debugging || this.debugger == null) return false;
|
||||
this.debugger = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void runTask(Task task) {
|
||||
try {
|
||||
task.notifier.next(task.func.call(task.ctx, task.thisArg, task.args));
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
if (e instanceof InterruptException) throw e;
|
||||
task.notifier.error(e);
|
||||
}
|
||||
}
|
||||
public void run(boolean untilEmpty) {
|
||||
while (!untilEmpty || !tasks.isEmpty()) {
|
||||
try {
|
||||
runTask(tasks.take());
|
||||
}
|
||||
catch (InterruptedException | InterruptException e) {
|
||||
for (var msg : tasks) msg.notifier.error(new InterruptException(e));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Thread start() {
|
||||
if (this.thread == null) {
|
||||
this.thread = new Thread(() -> run(false), "JavaScript Runner #" + id);
|
||||
this.thread.start();
|
||||
}
|
||||
return this.thread;
|
||||
}
|
||||
public void stop() {
|
||||
thread.interrupt();
|
||||
thread = null;
|
||||
}
|
||||
public boolean inExecThread() {
|
||||
return Thread.currentThread() == thread;
|
||||
}
|
||||
public synchronized boolean isRunning() {
|
||||
return this.thread != null;
|
||||
public Engine copy() {
|
||||
var res = new Engine();
|
||||
res.env.addAll(env);
|
||||
return res;
|
||||
}
|
||||
|
||||
public Awaitable<Object> pushMsg(boolean micro, Environment env, FunctionValue func, Object thisArg, Object ...args) {
|
||||
var msg = new Task(new Context(this, env), func, thisArg, args, micro);
|
||||
tasks.add(msg);
|
||||
return msg.notifier;
|
||||
return pushMsg(() -> {
|
||||
return func.call(new Context(this, env), thisArg, args);
|
||||
}, micro);
|
||||
}
|
||||
public Awaitable<Object> pushMsg(boolean micro, Environment env, Filename filename, String raw, Object thisArg, Object ...args) {
|
||||
return pushMsg(micro, env, new UncompiledFunction(filename, raw), thisArg, args);
|
||||
return pushMsg(() -> {
|
||||
var ctx = new Context(this, env);
|
||||
return ctx.compile(filename, raw).call(new Context(this, env), thisArg, args);
|
||||
}, micro);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFramePush(Context ctx, CodeFrame frame) {
|
||||
if (debugging && debugger != null) debugger.onFramePush(ctx, frame);
|
||||
}
|
||||
@Override public void onFramePop(Context ctx, CodeFrame frame) {
|
||||
if (debugging && debugger != null) debugger.onFramePop(ctx, frame);
|
||||
}
|
||||
@Override public boolean onInstruction(Context ctx, CodeFrame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught) {
|
||||
if (debugging && debugger != null) return debugger.onInstruction(ctx, frame, instruction, returnVal, error, caught);
|
||||
else return false;
|
||||
}
|
||||
@Override public void onSource(Filename filename, String source, TreeSet<Location> breakpoints, SourceMap map) {
|
||||
if (!debugging) return;
|
||||
if (debugger != null) debugger.onSource(filename, source, breakpoints, map);
|
||||
sources.put(filename, source);
|
||||
bpts.put(filename, breakpoints);
|
||||
maps.put(filename, map);
|
||||
}
|
||||
|
||||
public Engine(boolean debugging) {
|
||||
this.debugging = debugging;
|
||||
public Engine() {
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import java.util.stream.Collectors;
|
||||
|
||||
import me.topchetoeu.jscript.Filename;
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.engine.debug.DebugContext;
|
||||
import me.topchetoeu.jscript.engine.scope.GlobalScope;
|
||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||
@ -13,117 +14,108 @@ import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.Symbol;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.filesystem.RootFilesystem;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
import me.topchetoeu.jscript.interop.NativeSetter;
|
||||
import me.topchetoeu.jscript.interop.NativeWrapperProvider;
|
||||
import me.topchetoeu.jscript.modules.RootModuleRepo;
|
||||
import me.topchetoeu.jscript.parsing.Parsing;
|
||||
import me.topchetoeu.jscript.permissions.Permission;
|
||||
import me.topchetoeu.jscript.permissions.PermissionsProvider;
|
||||
|
||||
// TODO: Remove hardcoded extensions form environment
|
||||
public class Environment implements PermissionsProvider {
|
||||
private HashMap<String, ObjectValue> prototypes = new HashMap<>();
|
||||
@SuppressWarnings("unchecked")
|
||||
public class Environment implements Extensions {
|
||||
|
||||
public final Data data = new Data();
|
||||
public static final HashMap<String, Symbol> symbols = new HashMap<>();
|
||||
|
||||
public static final Symbol WRAPPERS = Symbol.get("Environment.wrappers");
|
||||
public static final Symbol COMPILE_FUNC = Symbol.get("Environment.compile");
|
||||
|
||||
public static final Symbol REGEX_CONSTR = Symbol.get("Environment.regexConstructor");
|
||||
public static final Symbol STACK = Symbol.get("Environment.stack");
|
||||
public static final Symbol MAX_STACK_COUNT = Symbol.get("Environment.maxStackCount");
|
||||
public static final Symbol HIDE_STACK = Symbol.get("Environment.hideStack");
|
||||
|
||||
public static final Symbol OBJECT_PROTO = Symbol.get("Environment.objectPrototype");
|
||||
public static final Symbol FUNCTION_PROTO = Symbol.get("Environment.functionPrototype");
|
||||
public static final Symbol ARRAY_PROTO = Symbol.get("Environment.arrayPrototype");
|
||||
public static final Symbol BOOL_PROTO = Symbol.get("Environment.boolPrototype");
|
||||
public static final Symbol NUMBER_PROTO = Symbol.get("Environment.numberPrototype");
|
||||
public static final Symbol STRING_PROTO = Symbol.get("Environment.stringPrototype");
|
||||
public static final Symbol SYMBOL_PROTO = Symbol.get("Environment.symbolPrototype");
|
||||
public static final Symbol ERROR_PROTO = Symbol.get("Environment.errorPrototype");
|
||||
public static final Symbol SYNTAX_ERR_PROTO = Symbol.get("Environment.syntaxErrorPrototype");
|
||||
public static final Symbol TYPE_ERR_PROTO = Symbol.get("Environment.typeErrorPrototype");
|
||||
public static final Symbol RANGE_ERR_PROTO = Symbol.get("Environment.rangeErrorPrototype");
|
||||
|
||||
private HashMap<Symbol, Object> data = new HashMap<>();
|
||||
|
||||
public GlobalScope global;
|
||||
public WrappersProvider wrappers;
|
||||
|
||||
public PermissionsProvider permissions = null;
|
||||
public final RootFilesystem filesystem = new RootFilesystem(this);
|
||||
public final RootModuleRepo modules = new RootModuleRepo();
|
||||
public String moduleCwd = "/";
|
||||
|
||||
private static int nextId = 0;
|
||||
|
||||
@Native public boolean stackVisible = true;
|
||||
@Native public int id = ++nextId;
|
||||
|
||||
@Native public FunctionValue compile = new NativeFunction("compile", (ctx, thisArg, args) -> {
|
||||
var source = Values.toString(ctx, args[0]);
|
||||
var filename = Values.toString(ctx, args[1]);
|
||||
var isDebug = Values.toBoolean(args[2]);
|
||||
|
||||
var env = Values.wrapper(args[2], Environment.class);
|
||||
var res = new ObjectValue();
|
||||
|
||||
var target = Parsing.compile(env, Filename.parse(filename), source);
|
||||
Engine.functions.putAll(target.functions);
|
||||
Engine.functions.remove(0l);
|
||||
|
||||
res.defineProperty(ctx, "function", target.func(env));
|
||||
res.defineProperty(ctx, "mapChain", new ArrayValue());
|
||||
|
||||
if (isDebug) {
|
||||
res.defineProperty(ctx, "breakpoints", ArrayValue.of(ctx, target.breakpoints.stream().map(Location::toString).collect(Collectors.toList())));
|
||||
@Override public <T> void add(Symbol key, T obj) {
|
||||
data.put(key, obj);
|
||||
}
|
||||
@Override public <T> T get(Symbol key) {
|
||||
return (T)data.get(key);
|
||||
}
|
||||
@Override public boolean remove(Symbol key) {
|
||||
if (data.containsKey(key)) {
|
||||
data.remove(key);
|
||||
return true;
|
||||
}
|
||||
|
||||
return res;
|
||||
});
|
||||
@Native public FunctionValue regexConstructor = new NativeFunction("RegExp", (ctx, thisArg, args) -> {
|
||||
throw EngineException.ofError("Regular expressions not supported.").setCtx(ctx.environment(), ctx.engine);
|
||||
});
|
||||
|
||||
@Native public ObjectValue proto(String name) {
|
||||
return prototypes.get(name);
|
||||
return false;
|
||||
}
|
||||
@Native public void setProto(String name, ObjectValue val) {
|
||||
prototypes.put(name, val);
|
||||
@Override public boolean has(Symbol key) {
|
||||
return data.containsKey(key);
|
||||
}
|
||||
@Override public Iterable<Symbol> keys() {
|
||||
return data.keySet();
|
||||
}
|
||||
|
||||
@Native public Symbol symbol(String name) {
|
||||
return getSymbol(name);
|
||||
public static FunctionValue compileFunc(Extensions ext) {
|
||||
return ext.init(COMPILE_FUNC, new NativeFunction("compile", args -> {
|
||||
var source = args.getString(0);
|
||||
var filename = args.getString(1);
|
||||
var env = Values.wrapper(args.convert(2, ObjectValue.class).getMember(args.ctx, Symbol.get("env")), Environment.class);
|
||||
var isDebug = DebugContext.enabled(args.ctx);
|
||||
var res = new ObjectValue();
|
||||
|
||||
var target = Parsing.compile(env, Filename.parse(filename), source);
|
||||
Engine.functions.putAll(target.functions);
|
||||
Engine.functions.remove(0l);
|
||||
|
||||
res.defineProperty(args.ctx, "function", target.func(env));
|
||||
res.defineProperty(args.ctx, "mapChain", new ArrayValue());
|
||||
|
||||
if (isDebug) res.defineProperty(
|
||||
args.ctx, "breakpoints",
|
||||
ArrayValue.of(args.ctx, target.breakpoints.stream().map(Location::toString).collect(Collectors.toList()))
|
||||
);
|
||||
|
||||
return res;
|
||||
}));
|
||||
}
|
||||
public static FunctionValue regexConstructor(Extensions ext) {
|
||||
return ext.init(COMPILE_FUNC, new NativeFunction("RegExp", args -> {
|
||||
throw EngineException.ofError("Regular expressions not supported.").setCtx(args.ctx.environment, args.ctx.engine);
|
||||
}));
|
||||
}
|
||||
|
||||
@NativeGetter("global") public ObjectValue getGlobal() {
|
||||
return global.obj;
|
||||
}
|
||||
@NativeSetter("global") public void setGlobal(ObjectValue val) {
|
||||
global = new GlobalScope(val);
|
||||
}
|
||||
public Environment copy() {
|
||||
var res = new Environment(null, global);
|
||||
|
||||
@Native public Environment fork() {
|
||||
var res = new Environment(compile, null, global);
|
||||
res.wrappers = wrappers.fork(res);
|
||||
res.regexConstructor = regexConstructor;
|
||||
res.prototypes = new HashMap<>(prototypes);
|
||||
return res;
|
||||
}
|
||||
@Native public Environment child() {
|
||||
var res = fork();
|
||||
res.global = res.global.globalChild();
|
||||
res.permissions = this.permissions;
|
||||
res.filesystem.protocols.putAll(this.filesystem.protocols);
|
||||
res.modules.repos.putAll(this.modules.repos);
|
||||
return res;
|
||||
}
|
||||
res.global = global;
|
||||
res.data.putAll(data);
|
||||
|
||||
@Override public boolean hasPermission(Permission perm, char delim) {
|
||||
return permissions == null || permissions.hasPermission(perm, delim);
|
||||
return res;
|
||||
}
|
||||
@Override public boolean hasPermission(Permission perm) {
|
||||
return permissions == null || permissions.hasPermission(perm);
|
||||
public Environment child() {
|
||||
var res = copy();
|
||||
res.global = res.global.globalChild();
|
||||
return res;
|
||||
}
|
||||
|
||||
public Context context(Engine engine) {
|
||||
return new Context(engine, this);
|
||||
}
|
||||
|
||||
public static Symbol getSymbol(String name) {
|
||||
if (symbols.containsKey(name)) return symbols.get(name);
|
||||
else {
|
||||
var res = new Symbol(name);
|
||||
symbols.put(name, res);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
public Environment(FunctionValue compile, WrappersProvider nativeConverter, GlobalScope global) {
|
||||
if (compile != null) this.compile = compile;
|
||||
public Environment(WrappersProvider nativeConverter, GlobalScope global) {
|
||||
if (nativeConverter == null) nativeConverter = new NativeWrapperProvider(this);
|
||||
if (global == null) global = new GlobalScope();
|
||||
|
||||
@ -131,6 +123,6 @@ public class Environment implements PermissionsProvider {
|
||||
this.global = global;
|
||||
}
|
||||
public Environment() {
|
||||
this(null, null, null);
|
||||
this(null, null);
|
||||
}
|
||||
}
|
||||
|
81
src/me/topchetoeu/jscript/engine/EventLoop.java
Normal file
81
src/me/topchetoeu/jscript/engine/EventLoop.java
Normal file
@ -0,0 +1,81 @@
|
||||
package me.topchetoeu.jscript.engine;
|
||||
|
||||
import java.util.concurrent.PriorityBlockingQueue;
|
||||
|
||||
import me.topchetoeu.jscript.ResultRunnable;
|
||||
import me.topchetoeu.jscript.events.Awaitable;
|
||||
import me.topchetoeu.jscript.events.DataNotifier;
|
||||
import me.topchetoeu.jscript.exceptions.InterruptException;
|
||||
|
||||
public class EventLoop {
|
||||
private static class Task implements Comparable<Task> {
|
||||
public final ResultRunnable<?> runnable;
|
||||
public final DataNotifier<Object> notifier = new DataNotifier<>();
|
||||
public final boolean micro;
|
||||
|
||||
public Task(ResultRunnable<?> runnable, boolean micro) {
|
||||
this.runnable = runnable;
|
||||
this.micro = micro;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Task other) {
|
||||
return Integer.compare(this.micro ? 0 : 1, other.micro ? 0 : 1);
|
||||
}
|
||||
}
|
||||
|
||||
private PriorityBlockingQueue<Task> tasks = new PriorityBlockingQueue<>();
|
||||
private Thread thread;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> Awaitable<T> pushMsg(ResultRunnable<T> runnable, boolean micro) {
|
||||
var msg = new Task(runnable, micro);
|
||||
tasks.add(msg);
|
||||
return (Awaitable<T>)msg.notifier;
|
||||
}
|
||||
public Awaitable<Object> pushMsg(Runnable runnable, boolean micro) {
|
||||
return pushMsg(() -> { runnable.run(); return null; }, micro);
|
||||
}
|
||||
|
||||
public void run(boolean untilEmpty) {
|
||||
while (!untilEmpty || !tasks.isEmpty()) {
|
||||
try {
|
||||
var task = tasks.take();
|
||||
|
||||
try {
|
||||
task.notifier.next(task.runnable.run());
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
if (e instanceof InterruptException) throw e;
|
||||
task.notifier.error(e);
|
||||
}
|
||||
}
|
||||
catch (InterruptedException | InterruptException e) {
|
||||
for (var msg : tasks) msg.notifier.error(new InterruptException(e));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Thread thread() {
|
||||
return thread;
|
||||
}
|
||||
public Thread start() {
|
||||
if (thread == null) {
|
||||
thread = new Thread(() -> run(false), "Event loop #" + hashCode());
|
||||
thread.start();
|
||||
}
|
||||
return thread;
|
||||
}
|
||||
public void stop() {
|
||||
if (thread != null) thread.interrupt();
|
||||
thread = null;
|
||||
}
|
||||
|
||||
public boolean inLoopThread() {
|
||||
return Thread.currentThread() == thread;
|
||||
}
|
||||
public boolean isRunning() {
|
||||
return this.thread != null;
|
||||
}
|
||||
}
|
39
src/me/topchetoeu/jscript/engine/ExtensionStack.java
Normal file
39
src/me/topchetoeu/jscript/engine/ExtensionStack.java
Normal file
@ -0,0 +1,39 @@
|
||||
package me.topchetoeu.jscript.engine;
|
||||
|
||||
import me.topchetoeu.jscript.engine.values.Symbol;
|
||||
|
||||
public abstract class ExtensionStack implements Extensions {
|
||||
protected abstract Extensions[] extensionStack();
|
||||
|
||||
@Override public <T> void add(Symbol key, T obj) {
|
||||
for (var el : extensionStack()) {
|
||||
if (el != null) {
|
||||
el.add(key, obj);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@Override public <T> T get(Symbol key) {
|
||||
for (var el : extensionStack()) {
|
||||
if (el != null && el.has(key)) return el.get(key);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@Override public boolean has(Symbol key) {
|
||||
for (var el : extensionStack()) {
|
||||
if (el != null && el.has(key)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
@Override public boolean remove(Symbol key) {
|
||||
var anyRemoved = false;
|
||||
|
||||
for (var el : extensionStack()) {
|
||||
if (el != null) anyRemoved &= el.remove(key);
|
||||
}
|
||||
|
||||
return anyRemoved;
|
||||
}
|
||||
}
|
34
src/me/topchetoeu/jscript/engine/Extensions.java
Normal file
34
src/me/topchetoeu/jscript/engine/Extensions.java
Normal file
@ -0,0 +1,34 @@
|
||||
package me.topchetoeu.jscript.engine;
|
||||
|
||||
import me.topchetoeu.jscript.engine.values.Symbol;
|
||||
|
||||
public interface Extensions {
|
||||
<T> T get(Symbol key);
|
||||
<T> void add(Symbol key, T obj);
|
||||
Iterable<Symbol> keys();
|
||||
|
||||
boolean has(Symbol key);
|
||||
boolean remove(Symbol key);
|
||||
|
||||
default boolean hasNotNull(Symbol key) {
|
||||
return has(key) && get(key) != null;
|
||||
}
|
||||
|
||||
default <T> T get(Symbol key, T defaultVal) {
|
||||
if (has(key)) return get(key);
|
||||
else return defaultVal;
|
||||
}
|
||||
|
||||
default <T> T init(Symbol key, T val) {
|
||||
if (has(key)) return get(key);
|
||||
else {
|
||||
add(key, val);
|
||||
return val;
|
||||
}
|
||||
}
|
||||
default void addAll(Extensions source) {
|
||||
for (var key : source.keys()) {
|
||||
add(key, source.get(key));
|
||||
}
|
||||
}
|
||||
}
|
100
src/me/topchetoeu/jscript/engine/debug/DebugContext.java
Normal file
100
src/me/topchetoeu/jscript/engine/debug/DebugContext.java
Normal file
@ -0,0 +1,100 @@
|
||||
package me.topchetoeu.jscript.engine.debug;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import me.topchetoeu.jscript.Filename;
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.Extensions;
|
||||
import me.topchetoeu.jscript.engine.frame.CodeFrame;
|
||||
import me.topchetoeu.jscript.engine.values.Symbol;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.mapping.SourceMap;
|
||||
|
||||
public class DebugContext implements DebugController {
|
||||
public static final Symbol ENV_KEY = Symbol.get("Engine.debug");
|
||||
public static final Symbol IGNORE = Symbol.get("Engine.ignoreDebug");
|
||||
|
||||
private HashMap<Filename, String> sources;
|
||||
private HashMap<Filename, TreeSet<Location>> bpts;
|
||||
private HashMap<Filename, SourceMap> maps;
|
||||
private DebugController debugger;
|
||||
|
||||
public boolean attachDebugger(DebugController debugger) {
|
||||
if (this.debugger != null) return false;
|
||||
|
||||
if (sources != null) {
|
||||
for (var source : sources.entrySet()) debugger.onSource(
|
||||
source.getKey(), source.getValue(),
|
||||
bpts.get(source.getKey()),
|
||||
maps.get(source.getKey())
|
||||
);
|
||||
}
|
||||
|
||||
this.debugger = debugger;
|
||||
return true;
|
||||
}
|
||||
public boolean detachDebugger() {
|
||||
this.debugger = null;
|
||||
return true;
|
||||
}
|
||||
|
||||
public DebugController debugger() {
|
||||
if (debugger == null) return DebugController.empty();
|
||||
else return debugger;
|
||||
}
|
||||
|
||||
@Override public void onFramePop(Context ctx, CodeFrame frame) {
|
||||
if (debugger != null) debugger.onFramePop(ctx, frame);
|
||||
}
|
||||
@Override public void onFramePush(Context ctx, CodeFrame frame) {
|
||||
if (debugger != null) debugger.onFramePush(ctx, frame);
|
||||
}
|
||||
@Override public boolean onInstruction(Context ctx, CodeFrame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught) {
|
||||
if (debugger != null) return debugger.onInstruction(ctx, frame, instruction, returnVal, error, caught);
|
||||
else return false;
|
||||
}
|
||||
@Override public void onSource(Filename filename, String source, TreeSet<Location> breakpoints, SourceMap map) {
|
||||
if (debugger != null) debugger.onSource(filename, source, breakpoints, map);
|
||||
if (sources != null) sources.put(filename, source);
|
||||
if (bpts != null) bpts.put(filename, breakpoints);
|
||||
if (maps != null) maps.put(filename, map);
|
||||
}
|
||||
|
||||
public Location mapToCompiled(Location location) {
|
||||
if (maps == null) return location;
|
||||
|
||||
var map = maps.get(location.filename());
|
||||
if (map == null) return location;
|
||||
return map.toCompiled(location);
|
||||
}
|
||||
public Location mapToOriginal(Location location) {
|
||||
if (maps == null) return location;
|
||||
|
||||
var map = maps.get(location.filename());
|
||||
if (map == null) return location;
|
||||
return map.toOriginal(location);
|
||||
}
|
||||
|
||||
private DebugContext(boolean enabled) {
|
||||
if (enabled) {
|
||||
sources = new HashMap<>();
|
||||
bpts = new HashMap<>();
|
||||
maps = new HashMap<>();
|
||||
}
|
||||
}
|
||||
|
||||
public DebugContext() {
|
||||
this(true);
|
||||
}
|
||||
|
||||
public static boolean enabled(Extensions exts) {
|
||||
return exts.hasNotNull(ENV_KEY) && !exts.has(IGNORE);
|
||||
}
|
||||
public static DebugContext get(Extensions exts) {
|
||||
if (enabled(exts)) return exts.get(ENV_KEY);
|
||||
else return new DebugContext(false);
|
||||
}
|
||||
}
|
@ -27,7 +27,7 @@ public interface DebugController {
|
||||
* @param frame The frame in which execution is occuring
|
||||
* @param instruction The instruction which was or will be executed
|
||||
* @param loc The most recent location the code frame has been at
|
||||
* @param returnVal The return value of the instruction, Runners.NO_RETURN if none
|
||||
* @param returnVal The return value of the instruction, Values.NO_RETURN if none
|
||||
* @param error The error that the instruction threw, null if none
|
||||
* @param caught Whether or not the error has been caught
|
||||
* @return Whether or not the frame should restart
|
||||
@ -48,4 +48,15 @@ public interface DebugController {
|
||||
* @param frame The code frame which was popped out
|
||||
*/
|
||||
void onFramePop(Context ctx, CodeFrame frame);
|
||||
|
||||
public static DebugController empty() {
|
||||
return new DebugController () {
|
||||
@Override public void onFramePop(Context ctx, CodeFrame frame) { }
|
||||
@Override public void onFramePush(Context ctx, CodeFrame frame) { }
|
||||
@Override public boolean onInstruction(Context ctx, CodeFrame frame, Instruction instruction, Object returnVal, EngineException error, boolean caught) {
|
||||
return false;
|
||||
}
|
||||
@Override public void onSource(Filename filename, String source, TreeSet<Location> breakpoints, SourceMap map) { }
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -25,9 +25,6 @@ public interface DebugHandler {
|
||||
void getProperties(V8Message msg);
|
||||
void releaseObjectGroup(V8Message msg);
|
||||
void releaseObject(V8Message msg);
|
||||
/**
|
||||
* This method might not execute the actual code for well-known requests
|
||||
*/
|
||||
void callFunctionOn(V8Message msg);
|
||||
|
||||
void runtimeEnable(V8Message msg);
|
||||
|
@ -50,8 +50,6 @@ public class DebugServer {
|
||||
private void handle(WebSocket ws, Debugger debugger) {
|
||||
WebSocketMessage raw;
|
||||
|
||||
debugger.connect();
|
||||
|
||||
while ((raw = ws.receive()) != null) {
|
||||
if (raw.type != Type.Text) {
|
||||
ws.send(new V8Error("Expected a text message."));
|
||||
@ -72,8 +70,9 @@ public class DebugServer {
|
||||
switch (msg.name) {
|
||||
case "Debugger.enable":
|
||||
connNotifier.next();
|
||||
debugger.enable(msg); continue;
|
||||
case "Debugger.disable": debugger.disable(msg); continue;
|
||||
debugger.enable(msg);
|
||||
continue;
|
||||
case "Debugger.disable": debugger.close(); continue;
|
||||
|
||||
case "Debugger.setBreakpointByUrl": debugger.setBreakpointByUrl(msg); continue;
|
||||
case "Debugger.removeBreakpoint": debugger.removeBreakpoint(msg); continue;
|
||||
@ -116,7 +115,7 @@ public class DebugServer {
|
||||
}
|
||||
}
|
||||
|
||||
debugger.disconnect();
|
||||
debugger.close();
|
||||
}
|
||||
private void onWsConnect(HttpRequest req, Socket socket, DebuggerProvider debuggerProvider) {
|
||||
var key = req.headers.get("sec-websocket-key");
|
||||
@ -151,7 +150,7 @@ public class DebugServer {
|
||||
catch (RuntimeException e) {
|
||||
ws.send(new V8Error(e.getMessage()));
|
||||
}
|
||||
finally { ws.close(); debugger.disconnect(); }
|
||||
finally { ws.close(); debugger.close(); }
|
||||
}, "Debug Handler");
|
||||
}
|
||||
|
||||
@ -232,9 +231,9 @@ public class DebugServer {
|
||||
|
||||
public DebugServer() {
|
||||
try {
|
||||
this.favicon = Reading.resourceToStream("debugger/favicon.png").readAllBytes();
|
||||
this.protocol = Reading.resourceToStream("debugger/protocol.json").readAllBytes();
|
||||
this.index = Reading.resourceToString("debugger/index.html")
|
||||
this.favicon = Reading.resourceToStream("assets/debugger/favicon.png").readAllBytes();
|
||||
this.protocol = Reading.resourceToStream("assets/debugger/protocol.json").readAllBytes();
|
||||
this.index = Reading.resourceToString("assets/debugger/index.html")
|
||||
.replace("${NAME}", Metadata.name())
|
||||
.replace("${VERSION}", Metadata.version())
|
||||
.replace("${AUTHOR}", Metadata.author())
|
||||
|
@ -1,6 +1,5 @@
|
||||
package me.topchetoeu.jscript.engine.debug;
|
||||
|
||||
public interface Debugger extends DebugHandler, DebugController {
|
||||
void connect();
|
||||
void disconnect();
|
||||
void close();
|
||||
}
|
||||
|
@ -15,8 +15,8 @@ import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.compilation.Instruction.Type;
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.Engine;
|
||||
import me.topchetoeu.jscript.engine.Environment;
|
||||
import me.topchetoeu.jscript.engine.frame.CodeFrame;
|
||||
import me.topchetoeu.jscript.engine.frame.Runners;
|
||||
import me.topchetoeu.jscript.engine.scope.GlobalScope;
|
||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.engine.values.CodeFunction;
|
||||
@ -109,34 +109,48 @@ public class SimpleDebugger implements Debugger {
|
||||
public ObjectValue local, capture, global, valstack;
|
||||
public JSONMap serialized;
|
||||
public Location location;
|
||||
public boolean debugData = false;
|
||||
|
||||
public void updateLoc(Location loc) {
|
||||
if (loc == null) return;
|
||||
this.location = loc;
|
||||
}
|
||||
|
||||
public Frame(Context ctx, CodeFrame frame, int id) {
|
||||
public Frame(CodeFrame frame, int id) {
|
||||
this.frame = frame;
|
||||
this.func = frame.function;
|
||||
this.id = id;
|
||||
|
||||
this.global = frame.function.environment.global.obj;
|
||||
this.local = frame.getLocalScope(ctx, true);
|
||||
this.capture = frame.getCaptureScope(ctx, true);
|
||||
this.local.setPrototype(ctx, capture);
|
||||
this.capture.setPrototype(ctx, global);
|
||||
this.valstack = frame.getValStackScope(ctx);
|
||||
debugData = true;
|
||||
this.local = frame.getLocalScope(true);
|
||||
this.capture = frame.getCaptureScope(true);
|
||||
this.local.setPrototype(frame.ctx, capture);
|
||||
this.capture.setPrototype(frame.ctx, global);
|
||||
this.valstack = frame.getValStackScope();
|
||||
|
||||
this.serialized = new JSONMap()
|
||||
.set("callFrameId", id + "")
|
||||
.set("functionName", func.name)
|
||||
.set("scopeChain", new JSONList()
|
||||
.add(new JSONMap().set("type", "local").set("name", "Local Scope").set("object", serializeObj(ctx, local)))
|
||||
.add(new JSONMap().set("type", "closure").set("name", "Closure").set("object", serializeObj(ctx, capture)))
|
||||
.add(new JSONMap().set("type", "global").set("name", "Global Scope").set("object", serializeObj(ctx, global)))
|
||||
.add(new JSONMap().set("type", "other").set("name", "Value Stack").set("object", serializeObj(ctx, valstack)))
|
||||
.add(new JSONMap()
|
||||
.set("type", "local")
|
||||
.set("name", "Local Scope")
|
||||
.set("object", serializeObj(frame.ctx, local))
|
||||
)
|
||||
.add(new JSONMap()
|
||||
.set("type", "closure")
|
||||
.set("name", "Closure")
|
||||
.set("object", serializeObj(frame.ctx, capture))
|
||||
)
|
||||
.add(new JSONMap()
|
||||
.set("type", "global")
|
||||
.set("name", "Global Scope")
|
||||
.set("object", serializeObj(frame.ctx, global))
|
||||
)
|
||||
.add(new JSONMap()
|
||||
.set("type", "other")
|
||||
.set("name", "Value Stack")
|
||||
.set("object", serializeObj(frame.ctx, valstack))
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -173,7 +187,6 @@ public class SimpleDebugger implements Debugger {
|
||||
public State state = State.RESUMED;
|
||||
|
||||
public final WebSocket ws;
|
||||
public final Engine target;
|
||||
|
||||
private ObjectValue emptyObject = new ObjectValue();
|
||||
|
||||
@ -232,26 +245,29 @@ public class SimpleDebugger implements Debugger {
|
||||
return nextId++;
|
||||
}
|
||||
|
||||
private synchronized void updateFrames(Context ctx) {
|
||||
var frame = ctx.peekFrame();
|
||||
if (frame == null) return;
|
||||
|
||||
private synchronized Frame getFrame(CodeFrame frame) {
|
||||
if (!codeFrameToFrame.containsKey(frame)) {
|
||||
var id = nextId();
|
||||
var fr = new Frame(ctx, frame, id);
|
||||
var fr = new Frame(frame, id);
|
||||
|
||||
idToFrame.put(id, fr);
|
||||
codeFrameToFrame.put(frame, fr);
|
||||
}
|
||||
|
||||
currFrame = codeFrameToFrame.get(frame);
|
||||
return fr;
|
||||
}
|
||||
else return codeFrameToFrame.get(frame);
|
||||
}
|
||||
private synchronized void updateFrames(Context ctx) {
|
||||
var frame = ctx.frame;
|
||||
if (frame == null) return;
|
||||
|
||||
currFrame = getFrame(frame);
|
||||
}
|
||||
private JSONList serializeFrames(Context ctx) {
|
||||
var res = new JSONList();
|
||||
var frames = ctx.frames();
|
||||
|
||||
for (var i = frames.size() - 1; i >= 0; i--) {
|
||||
var frame = codeFrameToFrame.get(frames.get(i));
|
||||
for (var el : ctx.frames()) {
|
||||
var frame = getFrame(el);
|
||||
if (frame.location == null) continue;
|
||||
frame.serialized.set("location", serializeLocation(frame.location));
|
||||
if (frame.location != null) res.add(frame.serialized);
|
||||
@ -302,6 +318,8 @@ public class SimpleDebugger implements Debugger {
|
||||
}
|
||||
private JSONMap serializeObj(Context ctx, Object val, boolean byValue) {
|
||||
val = Values.normalize(null, val);
|
||||
ctx = new Context(ctx.engine.copy(), ctx.environment);
|
||||
ctx.engine.add(DebugContext.IGNORE, true);
|
||||
|
||||
if (val == Values.NULL) {
|
||||
return new JSONMap()
|
||||
@ -341,7 +359,7 @@ public class SimpleDebugger implements Debugger {
|
||||
try {
|
||||
defaultToString =
|
||||
Values.getMember(ctx, obj, "toString") ==
|
||||
Values.getMember(ctx, ctx.environment().proto("object"), "toString");
|
||||
Values.getMember(ctx, ctx.get(Environment.OBJECT_PROTO), "toString");
|
||||
}
|
||||
catch (Exception e) { }
|
||||
|
||||
@ -477,13 +495,13 @@ public class SimpleDebugger implements Debugger {
|
||||
|
||||
private RunResult run(Frame codeFrame, String code) {
|
||||
if (codeFrame == null) return new RunResult(null, code, new EngineException("Invalid code frame!"));
|
||||
var engine = new Engine(false);
|
||||
var env = codeFrame.func.environment.fork();
|
||||
var engine = new Engine();
|
||||
var env = codeFrame.func.environment.copy();
|
||||
|
||||
env.global = new GlobalScope(codeFrame.local);
|
||||
|
||||
var ctx = new Context(engine, env);
|
||||
var awaiter = engine.pushMsg(false, ctx.environment(), new Filename("jscript", "eval"), code, codeFrame.frame.thisArg, codeFrame.frame.args);
|
||||
var awaiter = engine.pushMsg(false, ctx.environment, new Filename("jscript", "eval"), code, codeFrame.frame.thisArg, codeFrame.frame.args);
|
||||
|
||||
engine.run(true);
|
||||
|
||||
@ -495,7 +513,7 @@ public class SimpleDebugger implements Debugger {
|
||||
var res = new ArrayValue();
|
||||
var passed = new HashSet<String>();
|
||||
var tildas = "~";
|
||||
if (target == null) target = ctx.environment().getGlobal();
|
||||
if (target == null) target = ctx.environment.global;
|
||||
|
||||
for (var proto = target; proto != null && proto != Values.NULL; proto = Values.getPrototype(ctx, proto)) {
|
||||
for (var el : Values.getMembers(ctx, proto, true, true)) {
|
||||
@ -574,8 +592,36 @@ public class SimpleDebugger implements Debugger {
|
||||
updateNotifier.next();
|
||||
}
|
||||
@Override public synchronized void disable(V8Message msg) {
|
||||
enabled = false;
|
||||
close();
|
||||
ws.send(msg.respond());
|
||||
}
|
||||
public synchronized void close() {
|
||||
enabled = false;
|
||||
execptionType = CatchType.NONE;
|
||||
state = State.RESUMED;
|
||||
|
||||
idToBptCand.clear();
|
||||
|
||||
idToBreakpoint.clear();
|
||||
locToBreakpoint.clear();
|
||||
tmpBreakpts.clear();
|
||||
|
||||
filenameToId.clear();
|
||||
idToSource.clear();
|
||||
pendingSources.clear();
|
||||
|
||||
idToFrame.clear();
|
||||
codeFrameToFrame.clear();
|
||||
|
||||
idToObject.clear();
|
||||
objectToId.clear();
|
||||
objectGroups.clear();
|
||||
|
||||
pendingPause = false;
|
||||
|
||||
stepOutFrame = currFrame = null;
|
||||
stepOutPtr = 0;
|
||||
|
||||
updateNotifier.next();
|
||||
}
|
||||
|
||||
@ -866,18 +912,16 @@ public class SimpleDebugger implements Debugger {
|
||||
Frame frame;
|
||||
|
||||
synchronized (this) {
|
||||
frame = codeFrameToFrame.get(cf);
|
||||
frame = getFrame(cf);
|
||||
|
||||
if (!frame.debugData) return false;
|
||||
|
||||
if (instruction.location != null) frame.updateLoc(ctx.engine.mapToCompiled(instruction.location));
|
||||
if (instruction.location != null) frame.updateLoc(DebugContext.get(ctx).mapToCompiled(instruction.location));
|
||||
loc = frame.location;
|
||||
isBreakpointable = loc != null && (instruction.breakpoint.shouldStepIn());
|
||||
|
||||
if (error != null && (execptionType == CatchType.ALL || execptionType == CatchType.UNCAUGHT && !caught)) {
|
||||
pauseException(ctx);
|
||||
}
|
||||
else if (loc != null && (state == State.STEPPING_IN || state == State.STEPPING_OVER) && returnVal != Runners.NO_RETURN && stepOutFrame == frame) {
|
||||
else if (loc != null && (state == State.STEPPING_IN || state == State.STEPPING_OVER) && returnVal != Values.NO_RETURN && stepOutFrame == frame) {
|
||||
pauseDebug(ctx, null);
|
||||
}
|
||||
else if (isBreakpointable && locToBreakpoint.containsKey(loc)) {
|
||||
@ -906,7 +950,7 @@ public class SimpleDebugger implements Debugger {
|
||||
case STEPPING_IN:
|
||||
case STEPPING_OVER:
|
||||
if (stepOutFrame.frame == frame.frame) {
|
||||
if (returnVal != Runners.NO_RETURN || error != null) {
|
||||
if (returnVal != Values.NO_RETURN || error != null) {
|
||||
state = State.STEPPING_OUT;
|
||||
continue;
|
||||
}
|
||||
@ -943,7 +987,7 @@ public class SimpleDebugger implements Debugger {
|
||||
try { idToFrame.remove(codeFrameToFrame.remove(frame).id); }
|
||||
catch (NullPointerException e) { }
|
||||
|
||||
if (ctx.frames().size() == 0) {
|
||||
if (ctx.stackSize == 0) {
|
||||
if (state == State.PAUSED_EXCEPTION || state == State.PAUSED_NORMAL) resume(State.RESUMED);
|
||||
}
|
||||
else if (stepOutFrame != null && stepOutFrame.frame == frame && state == State.STEPPING_OUT) {
|
||||
@ -952,19 +996,12 @@ public class SimpleDebugger implements Debugger {
|
||||
}
|
||||
}
|
||||
|
||||
@Override public synchronized void connect() {
|
||||
if (!target.attachDebugger(this)) {
|
||||
ws.send(new V8Error("A debugger is already attached to this engine."));
|
||||
}
|
||||
}
|
||||
@Override public synchronized void disconnect() {
|
||||
target.detachDebugger();
|
||||
enabled = false;
|
||||
updateNotifier.next();
|
||||
public SimpleDebugger attach(DebugContext ctx) {
|
||||
ctx.attachDebugger(this);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SimpleDebugger(WebSocket ws, Engine target) {
|
||||
public SimpleDebugger(WebSocket ws) {
|
||||
this.ws = ws;
|
||||
this.target = target;
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import java.util.Stack;
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.debug.DebugContext;
|
||||
import me.topchetoeu.jscript.engine.scope.LocalScope;
|
||||
import me.topchetoeu.jscript.engine.scope.ValueVariable;
|
||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||
@ -37,8 +38,10 @@ public class CodeFrame {
|
||||
return ptr >= start && ptr < end;
|
||||
}
|
||||
|
||||
public void setCause(EngineException target) {
|
||||
if (error != null) target.setCause(error);
|
||||
}
|
||||
public TryCtx _catch(EngineException e) {
|
||||
if (error != null) e.setCause(error);
|
||||
return new TryCtx(TryState.CATCH, e, result, restoreStackPtr, start, end, -1, finallyStart);
|
||||
}
|
||||
public TryCtx _finally(PendingResult res) {
|
||||
@ -93,13 +96,14 @@ public class CodeFrame {
|
||||
public final Object[] args;
|
||||
public final Stack<TryCtx> tryStack = new Stack<>();
|
||||
public final CodeFunction function;
|
||||
public final Context ctx;
|
||||
public Object[] stack = new Object[32];
|
||||
public int stackPtr = 0;
|
||||
public int codePtr = 0;
|
||||
public boolean jumpFlag = false, popTryFlag = false;
|
||||
private Location prevLoc = null;
|
||||
|
||||
public ObjectValue getLocalScope(Context ctx, boolean props) {
|
||||
public ObjectValue getLocalScope(boolean props) {
|
||||
var names = new String[scope.locals.length];
|
||||
|
||||
for (int i = 0; i < scope.locals.length; i++) {
|
||||
@ -114,7 +118,7 @@ public class CodeFrame {
|
||||
|
||||
return new ScopeValue(scope.locals, names);
|
||||
}
|
||||
public ObjectValue getCaptureScope(Context ctx, boolean props) {
|
||||
public ObjectValue getCaptureScope(boolean props) {
|
||||
var names = new String[scope.captures.length];
|
||||
|
||||
for (int i = 0; i < scope.captures.length; i++) {
|
||||
@ -125,7 +129,7 @@ public class CodeFrame {
|
||||
|
||||
return new ScopeValue(scope.captures, names);
|
||||
}
|
||||
public ObjectValue getValStackScope(Context ctx) {
|
||||
public ObjectValue getValStackScope() {
|
||||
return new ObjectValue() {
|
||||
@Override
|
||||
protected Object getField(Context ctx, Object key) {
|
||||
@ -177,7 +181,7 @@ public class CodeFrame {
|
||||
|
||||
return res;
|
||||
}
|
||||
public void push(Context ctx, Object val) {
|
||||
public void push(Object val) {
|
||||
if (stack.length <= stackPtr) {
|
||||
var newStack = new Object[stack.length * 2];
|
||||
System.arraycopy(stack, 0, newStack, 0, stack.length);
|
||||
@ -186,20 +190,19 @@ public class CodeFrame {
|
||||
stack[stackPtr++] = Values.normalize(ctx, val);
|
||||
}
|
||||
|
||||
public Object next(Context ctx, Object value, Object returnValue, EngineException error) {
|
||||
if (value != Runners.NO_RETURN) push(ctx, value);
|
||||
public Object next(Object value, Object returnValue, EngineException error) {
|
||||
if (value != Values.NO_RETURN) push(value);
|
||||
|
||||
Instruction instr = null;
|
||||
if (codePtr >= 0 && codePtr < function.body.length) instr = function.body[codePtr];
|
||||
|
||||
if (returnValue == Runners.NO_RETURN && error == null) {
|
||||
if (returnValue == Values.NO_RETURN && error == null) {
|
||||
try {
|
||||
if (Thread.currentThread().isInterrupted()) throw new InterruptException();
|
||||
|
||||
if (instr == null) returnValue = null;
|
||||
else {
|
||||
// System.out.println(instr + "@" + instr.location);
|
||||
ctx.engine.onInstruction(ctx, this, instr, Runners.NO_RETURN, null, false);
|
||||
DebugContext.get(ctx).onInstruction(ctx, this, instr, Values.NO_RETURN, null, false);
|
||||
|
||||
if (instr.location != null) prevLoc = instr.location;
|
||||
|
||||
@ -220,10 +223,11 @@ public class CodeFrame {
|
||||
TryCtx newCtx = null;
|
||||
|
||||
if (error != null) {
|
||||
tryCtx.setCause(error);
|
||||
if (tryCtx.hasCatch()) newCtx = tryCtx._catch(error);
|
||||
else if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofThrow(error, instr));
|
||||
}
|
||||
else if (returnValue != Runners.NO_RETURN) {
|
||||
else if (returnValue != Values.NO_RETURN) {
|
||||
if (tryCtx.hasFinally()) newCtx = tryCtx._finally(PendingResult.ofReturn(returnValue, instr));
|
||||
}
|
||||
else if (jumpFlag && !tryCtx.inBounds(codePtr)) {
|
||||
@ -250,7 +254,7 @@ public class CodeFrame {
|
||||
tryStack.push(newCtx);
|
||||
}
|
||||
error = null;
|
||||
returnValue = Runners.NO_RETURN;
|
||||
returnValue = Values.NO_RETURN;
|
||||
break;
|
||||
}
|
||||
else {
|
||||
@ -268,15 +272,17 @@ public class CodeFrame {
|
||||
tryStack.pop();
|
||||
codePtr = tryCtx.end;
|
||||
if (tryCtx.result.instruction != null) instr = tryCtx.result.instruction;
|
||||
if (tryCtx.result.isJump) {
|
||||
codePtr = tryCtx.result.ptr;
|
||||
jumpFlag = true;
|
||||
if (!jumpFlag && returnValue == Values.NO_RETURN && error == null) {
|
||||
if (tryCtx.result.isJump) {
|
||||
codePtr = tryCtx.result.ptr;
|
||||
jumpFlag = true;
|
||||
}
|
||||
if (tryCtx.result.isReturn) returnValue = tryCtx.result.value;
|
||||
if (error == null && tryCtx.result.isThrow) {
|
||||
error = tryCtx.result.error;
|
||||
}
|
||||
}
|
||||
if (tryCtx.result.isReturn) returnValue = tryCtx.result.value;
|
||||
if (tryCtx.result.isThrow) {
|
||||
error = tryCtx.result.error;
|
||||
}
|
||||
if (error != null) error.setCause(tryCtx.error);
|
||||
if (error != null) tryCtx.setCause(error);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@ -291,15 +297,22 @@ public class CodeFrame {
|
||||
}
|
||||
}
|
||||
|
||||
ctx.engine.onInstruction(ctx, this, instr, null, error, caught);
|
||||
DebugContext.get(ctx).onInstruction(ctx, this, instr, null, error, caught);
|
||||
throw error;
|
||||
}
|
||||
if (returnValue != Runners.NO_RETURN) {
|
||||
ctx.engine.onInstruction(ctx, this, instr, returnValue, null, false);
|
||||
if (returnValue != Values.NO_RETURN) {
|
||||
DebugContext.get(ctx).onInstruction(ctx, this, instr, returnValue, null, false);
|
||||
return returnValue;
|
||||
}
|
||||
|
||||
return Runners.NO_RETURN;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
|
||||
public void onPush() {
|
||||
DebugContext.get(ctx).onFramePush(ctx, this);
|
||||
}
|
||||
public void onPop() {
|
||||
DebugContext.get(ctx.parent).onFramePop(ctx.parent, this);
|
||||
}
|
||||
|
||||
public CodeFrame(Context ctx, Object thisArg, Object[] args, CodeFunction func) {
|
||||
@ -314,5 +327,6 @@ public class CodeFrame {
|
||||
|
||||
this.thisArg = thisArg;
|
||||
this.function = func;
|
||||
this.ctx = ctx.pushFrame(this);
|
||||
}
|
||||
}
|
||||
|
@ -5,18 +5,18 @@ import java.util.Collections;
|
||||
import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.Engine;
|
||||
import me.topchetoeu.jscript.engine.Environment;
|
||||
import me.topchetoeu.jscript.engine.Operation;
|
||||
import me.topchetoeu.jscript.engine.scope.ValueVariable;
|
||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.engine.values.CodeFunction;
|
||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.Symbol;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
|
||||
public class Runners {
|
||||
public static final Object NO_RETURN = new Object();
|
||||
|
||||
public static Object execReturn(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
return frame.pop();
|
||||
}
|
||||
@ -32,26 +32,26 @@ public class Runners {
|
||||
var func = frame.pop();
|
||||
var thisArg = frame.pop();
|
||||
|
||||
frame.push(ctx, Values.call(ctx, func, thisArg, callArgs));
|
||||
frame.push(Values.call(ctx, func, thisArg, callArgs));
|
||||
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execCallNew(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
var callArgs = frame.take(instr.get(0));
|
||||
var funcObj = frame.pop();
|
||||
|
||||
frame.push(ctx, Values.callNew(ctx, funcObj, callArgs));
|
||||
frame.push(Values.callNew(ctx, funcObj, callArgs));
|
||||
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object execMakeVar(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
var name = (String)instr.get(0);
|
||||
ctx.environment().global.define(name);
|
||||
ctx.environment.global.define(name);
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execDefProp(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
var setter = frame.pop();
|
||||
@ -59,14 +59,14 @@ public class Runners {
|
||||
var name = frame.pop();
|
||||
var obj = frame.pop();
|
||||
|
||||
if (getter != null && !Values.isFunction(getter)) throw EngineException.ofType("Getter must be a function or undefined.");
|
||||
if (setter != null && !Values.isFunction(setter)) throw EngineException.ofType("Setter must be a function or undefined.");
|
||||
if (!Values.isObject(obj)) throw EngineException.ofType("Property apply target must be an object.");
|
||||
if (getter != null && !(getter instanceof FunctionValue)) throw EngineException.ofType("Getter must be a function or undefined.");
|
||||
if (setter != null && !(setter instanceof FunctionValue)) throw EngineException.ofType("Setter must be a function or undefined.");
|
||||
if (!(obj instanceof ObjectValue)) throw EngineException.ofType("Property apply target must be an object.");
|
||||
Values.object(obj).defineProperty(ctx, name, Values.function(getter), Values.function(setter), false, false);
|
||||
|
||||
frame.push(ctx, obj);
|
||||
frame.push(obj);
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execInstanceof(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
var type = frame.pop();
|
||||
@ -74,14 +74,14 @@ public class Runners {
|
||||
|
||||
if (!Values.isPrimitive(type)) {
|
||||
var proto = Values.getMember(ctx, type, "prototype");
|
||||
frame.push(ctx, Values.isInstanceOf(ctx, obj, proto));
|
||||
frame.push(Values.isInstanceOf(ctx, obj, proto));
|
||||
}
|
||||
else {
|
||||
frame.push(ctx, false);
|
||||
frame.push(false);
|
||||
}
|
||||
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execKeys(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
var val = frame.pop();
|
||||
@ -89,17 +89,17 @@ public class Runners {
|
||||
var members = Values.getMembers(ctx, val, false, false);
|
||||
Collections.reverse(members);
|
||||
|
||||
frame.push(ctx, null);
|
||||
frame.push(null);
|
||||
|
||||
for (var el : members) {
|
||||
if (el instanceof Symbol) continue;
|
||||
var obj = new ObjectValue();
|
||||
obj.defineProperty(ctx, "value", el);
|
||||
frame.push(ctx, obj);
|
||||
frame.push(obj);
|
||||
}
|
||||
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object execTryStart(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
@ -111,58 +111,58 @@ public class Runners {
|
||||
int end = (int)instr.get(2) + start;
|
||||
frame.addTry(start, end, catchStart, finallyStart);
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execTryEnd(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
frame.popTryFlag = true;
|
||||
return NO_RETURN;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object execDup(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
int count = instr.get(0);
|
||||
|
||||
for (var i = 0; i < count; i++) {
|
||||
frame.push(ctx, frame.peek(count - 1));
|
||||
frame.push(frame.peek(count - 1));
|
||||
}
|
||||
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execLoadUndefined(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
frame.push(ctx, null);
|
||||
frame.push(null);
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execLoadValue(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
frame.push(ctx, instr.get(0));
|
||||
frame.push(instr.get(0));
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execLoadVar(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
var i = instr.get(0);
|
||||
|
||||
if (i instanceof String) frame.push(ctx, ctx.environment().global.get(ctx, (String)i));
|
||||
else frame.push(ctx, frame.scope.get((int)i).get(ctx));
|
||||
if (i instanceof String) frame.push(ctx.environment.global.get(ctx, (String)i));
|
||||
else frame.push(frame.scope.get((int)i).get(ctx));
|
||||
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execLoadObj(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
frame.push(ctx, new ObjectValue());
|
||||
frame.push(new ObjectValue());
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execLoadGlob(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
frame.push(ctx, ctx.environment().global.obj);
|
||||
frame.push(ctx.environment.global.obj);
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execLoadArr(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
var res = new ArrayValue();
|
||||
res.setSize(instr.get(0));
|
||||
frame.push(ctx, res);
|
||||
frame.push(res);
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execLoadFunc(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
long id = (Long)instr.get(0);
|
||||
@ -172,40 +172,45 @@ public class Runners {
|
||||
captures[i - 1] = frame.scope.get(instr.get(i));
|
||||
}
|
||||
|
||||
var func = new CodeFunction(ctx.environment(), "", Engine.functions.get(id), captures);
|
||||
var func = new CodeFunction(ctx.environment, "", Engine.functions.get(id), captures);
|
||||
|
||||
frame.push(ctx, func);
|
||||
frame.push(func);
|
||||
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execLoadMember(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
var key = frame.pop();
|
||||
var obj = frame.pop();
|
||||
|
||||
try {
|
||||
frame.push(ctx, Values.getMember(ctx, obj, key));
|
||||
frame.push(Values.getMember(ctx, obj, key));
|
||||
}
|
||||
catch (IllegalArgumentException e) {
|
||||
throw EngineException.ofType(e.getMessage());
|
||||
}
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execLoadKeyMember(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
frame.push(ctx, instr.get(0));
|
||||
frame.push(instr.get(0));
|
||||
return execLoadMember(ctx, instr, frame);
|
||||
}
|
||||
public static Object execLoadRegEx(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
frame.push(ctx, ctx.environment().regexConstructor.call(ctx, null, instr.get(0), instr.get(1)));
|
||||
if (ctx.hasNotNull(Environment.REGEX_CONSTR)) {
|
||||
frame.push(Values.callNew(ctx, ctx.get(Environment.REGEX_CONSTR), instr.get(0), instr.get(1)));
|
||||
}
|
||||
else {
|
||||
throw EngineException.ofSyntax("Regex is not supported.");
|
||||
}
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object execDiscard(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
frame.pop();
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execStoreMember(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
var val = frame.pop();
|
||||
@ -213,30 +218,30 @@ public class Runners {
|
||||
var obj = frame.pop();
|
||||
|
||||
if (!Values.setMember(ctx, obj, key, val)) throw EngineException.ofSyntax("Can't set member '" + key + "'.");
|
||||
if ((boolean)instr.get(0)) frame.push(ctx, val);
|
||||
if ((boolean)instr.get(0)) frame.push(val);
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execStoreVar(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
var val = (boolean)instr.get(1) ? frame.peek() : frame.pop();
|
||||
var i = instr.get(0);
|
||||
|
||||
if (i instanceof String) ctx.environment().global.set(ctx, (String)i, val);
|
||||
if (i instanceof String) ctx.environment.global.set(ctx, (String)i, val);
|
||||
else frame.scope.get((int)i).set(ctx, val);
|
||||
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execStoreSelfFunc(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
frame.scope.locals[(int)instr.get(0)].set(ctx, frame.function);
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object execJmp(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
frame.codePtr += (int)instr.get(0);
|
||||
frame.jumpFlag = true;
|
||||
return NO_RETURN;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execJmpIf(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
if (Values.toBoolean(frame.pop())) {
|
||||
@ -244,7 +249,7 @@ public class Runners {
|
||||
frame.jumpFlag = true;
|
||||
}
|
||||
else frame.codePtr ++;
|
||||
return NO_RETURN;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execJmpIfNot(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
if (Values.not(frame.pop())) {
|
||||
@ -252,37 +257,37 @@ public class Runners {
|
||||
frame.jumpFlag = true;
|
||||
}
|
||||
else frame.codePtr ++;
|
||||
return NO_RETURN;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object execIn(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
var obj = frame.pop();
|
||||
var index = frame.pop();
|
||||
|
||||
frame.push(ctx, Values.hasMember(ctx, obj, index, false));
|
||||
frame.push(Values.hasMember(ctx, obj, index, false));
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execTypeof(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
String name = instr.get(0);
|
||||
Object obj;
|
||||
|
||||
if (name != null) {
|
||||
if (ctx.environment().global.has(ctx, name)) {
|
||||
obj = ctx.environment().global.get(ctx, name);
|
||||
if (ctx.environment.global.has(ctx, name)) {
|
||||
obj = ctx.environment.global.get(ctx, name);
|
||||
}
|
||||
else obj = null;
|
||||
}
|
||||
else obj = frame.pop();
|
||||
|
||||
frame.push(ctx, Values.type(obj));
|
||||
frame.push(Values.type(obj));
|
||||
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
public static Object execNop(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object execDelete(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
@ -291,7 +296,7 @@ public class Runners {
|
||||
|
||||
if (!Values.deleteMember(ctx, val, key)) throw EngineException.ofSyntax("Can't delete member '" + key + "'.");
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object execOperation(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
@ -300,9 +305,9 @@ public class Runners {
|
||||
|
||||
for (var i = op.operands - 1; i >= 0; i--) args[i] = frame.pop();
|
||||
|
||||
frame.push(ctx, Values.operation(ctx, op, args));
|
||||
frame.push(Values.operation(ctx, op, args));
|
||||
frame.codePtr++;
|
||||
return NO_RETURN;
|
||||
return Values.NO_RETURN;
|
||||
}
|
||||
|
||||
public static Object exec(Context ctx, Instruction instr, CodeFrame frame) {
|
||||
|
@ -35,8 +35,8 @@ public class GlobalScope implements ScopeRecord {
|
||||
}
|
||||
public void define(String name, Variable val) {
|
||||
obj.defineProperty(null, name,
|
||||
new NativeFunction("get " + name, (ctx, th, a) -> val.get(ctx)),
|
||||
new NativeFunction("set " + name, (ctx, th, args) -> { val.set(ctx, args.length > 0 ? args[0] : null); return null; }),
|
||||
new NativeFunction("get " + name, args -> val.get(args.ctx)),
|
||||
new NativeFunction("set " + name, args -> { val.set(args.ctx, args.get(0)); return null; }),
|
||||
true, true
|
||||
);
|
||||
}
|
||||
|
@ -84,6 +84,8 @@ public class ArrayValue extends ObjectValue implements Iterable<Object> {
|
||||
}
|
||||
public void copyTo(Context ctx, ArrayValue arr, int sourceStart, int destStart, int count) {
|
||||
// Iterate in reverse to reallocate at most once
|
||||
if (destStart + count > arr.size) arr.size = destStart + count;
|
||||
|
||||
for (var i = count - 1; i >= 0; i--) {
|
||||
if (i + sourceStart < 0 || i + sourceStart >= size) arr.remove(i + destStart);
|
||||
if (values[i + sourceStart] == UNDEFINED) arr.set(ctx, i + destStart, null);
|
||||
|
@ -6,7 +6,6 @@ import me.topchetoeu.jscript.compilation.Instruction;
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.Environment;
|
||||
import me.topchetoeu.jscript.engine.frame.CodeFrame;
|
||||
import me.topchetoeu.jscript.engine.frame.Runners;
|
||||
import me.topchetoeu.jscript.engine.scope.ValueVariable;
|
||||
|
||||
public class CodeFunction extends FunctionValue {
|
||||
@ -32,16 +31,17 @@ public class CodeFunction extends FunctionValue {
|
||||
@Override
|
||||
public Object call(Context ctx, Object thisArg, Object ...args) {
|
||||
var frame = new CodeFrame(ctx, thisArg, args, this);
|
||||
try {
|
||||
ctx.pushFrame(frame);
|
||||
|
||||
frame.onPush();
|
||||
|
||||
try {
|
||||
while (true) {
|
||||
var res = frame.next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, null);
|
||||
if (res != Runners.NO_RETURN) return res;
|
||||
var res = frame.next(Values.NO_RETURN, Values.NO_RETURN, null);
|
||||
if (res != Values.NO_RETURN) return res;
|
||||
}
|
||||
}
|
||||
finally {
|
||||
ctx.popFrame(frame);
|
||||
frame.onPop();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,17 +1,18 @@
|
||||
package me.topchetoeu.jscript.engine.values;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.interop.Arguments;
|
||||
|
||||
public class NativeFunction extends FunctionValue {
|
||||
public static interface NativeFunctionRunner {
|
||||
Object run(Context ctx, Object thisArg, Object[] args);
|
||||
Object run(Arguments args);
|
||||
}
|
||||
|
||||
public final NativeFunctionRunner action;
|
||||
|
||||
@Override
|
||||
public Object call(Context ctx, Object thisArg, Object ...args) {
|
||||
return action.run(ctx, thisArg, args);
|
||||
return action.run(new Arguments(ctx, thisArg, args));
|
||||
}
|
||||
|
||||
public NativeFunction(String name, NativeFunctionRunner action) {
|
||||
|
@ -8,7 +8,7 @@ public class NativeWrapper extends ObjectValue {
|
||||
|
||||
@Override
|
||||
public ObjectValue getPrototype(Context ctx) {
|
||||
if (prototype == NATIVE_PROTO) return ctx.environment().wrappers.getProto(wrapped.getClass());
|
||||
if (prototype == NATIVE_PROTO) return ctx.environment.wrappers.getProto(wrapped.getClass());
|
||||
else return super.getPrototype(ctx);
|
||||
}
|
||||
|
||||
|
@ -7,6 +7,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.Environment;
|
||||
|
||||
public class ObjectValue {
|
||||
public static enum PlaceholderProto {
|
||||
@ -146,13 +147,13 @@ public class ObjectValue {
|
||||
|
||||
public ObjectValue getPrototype(Context ctx) {
|
||||
try {
|
||||
if (prototype == OBJ_PROTO) return ctx.environment().proto("object");
|
||||
if (prototype == ARR_PROTO) return ctx.environment().proto("array");
|
||||
if (prototype == FUNC_PROTO) return ctx.environment().proto("function");
|
||||
if (prototype == ERR_PROTO) return ctx.environment().proto("error");
|
||||
if (prototype == RANGE_ERR_PROTO) return ctx.environment().proto("rangeErr");
|
||||
if (prototype == SYNTAX_ERR_PROTO) return ctx.environment().proto("syntaxErr");
|
||||
if (prototype == TYPE_ERR_PROTO) return ctx.environment().proto("typeErr");
|
||||
if (prototype == OBJ_PROTO) return ctx.get(Environment.OBJECT_PROTO);
|
||||
if (prototype == ARR_PROTO) return ctx.get(Environment.ARRAY_PROTO);
|
||||
if (prototype == FUNC_PROTO) return ctx.get(Environment.FUNCTION_PROTO);
|
||||
if (prototype == ERR_PROTO) return ctx.get(Environment.ERROR_PROTO);
|
||||
if (prototype == RANGE_ERR_PROTO) return ctx.get(Environment.RANGE_ERR_PROTO);
|
||||
if (prototype == SYNTAX_ERR_PROTO) return ctx.get(Environment.SYNTAX_ERR_PROTO);
|
||||
if (prototype == TYPE_ERR_PROTO) return ctx.get(Environment.TYPE_ERR_PROTO);
|
||||
}
|
||||
catch (NullPointerException e) { return null; }
|
||||
|
||||
@ -166,17 +167,17 @@ public class ObjectValue {
|
||||
prototype = null;
|
||||
return true;
|
||||
}
|
||||
else if (Values.isObject(val)) {
|
||||
else if (val instanceof ObjectValue) {
|
||||
var obj = Values.object(val);
|
||||
|
||||
if (ctx != null && ctx.environment() != null) {
|
||||
if (obj == ctx.environment().proto("object")) prototype = OBJ_PROTO;
|
||||
else if (obj == ctx.environment().proto("array")) prototype = ARR_PROTO;
|
||||
else if (obj == ctx.environment().proto("function")) prototype = FUNC_PROTO;
|
||||
else if (obj == ctx.environment().proto("error")) prototype = ERR_PROTO;
|
||||
else if (obj == ctx.environment().proto("syntaxErr")) prototype = SYNTAX_ERR_PROTO;
|
||||
else if (obj == ctx.environment().proto("typeErr")) prototype = TYPE_ERR_PROTO;
|
||||
else if (obj == ctx.environment().proto("rangeErr")) prototype = RANGE_ERR_PROTO;
|
||||
if (ctx != null) {
|
||||
if (obj == ctx.get(Environment.OBJECT_PROTO)) prototype = OBJ_PROTO;
|
||||
else if (obj == ctx.get(Environment.ARRAY_PROTO)) prototype = ARR_PROTO;
|
||||
else if (obj == ctx.get(Environment.FUNCTION_PROTO)) prototype = FUNC_PROTO;
|
||||
else if (obj == ctx.get(Environment.ERROR_PROTO)) prototype = ERR_PROTO;
|
||||
else if (obj == ctx.get(Environment.SYNTAX_ERR_PROTO)) prototype = SYNTAX_ERR_PROTO;
|
||||
else if (obj == ctx.get(Environment.TYPE_ERR_PROTO)) prototype = TYPE_ERR_PROTO;
|
||||
else if (obj == ctx.get(Environment.RANGE_ERR_PROTO)) prototype = RANGE_ERR_PROTO;
|
||||
else prototype = obj;
|
||||
}
|
||||
else prototype = obj;
|
||||
|
@ -1,6 +1,10 @@
|
||||
package me.topchetoeu.jscript.engine.values;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
public final class Symbol {
|
||||
private static final HashMap<String, Symbol> registry = new HashMap<>();
|
||||
|
||||
public final String value;
|
||||
|
||||
public Symbol(String value) {
|
||||
@ -12,4 +16,13 @@ public final class Symbol {
|
||||
if (value == null) return "Symbol";
|
||||
else return "@@" + value;
|
||||
}
|
||||
|
||||
public static Symbol get(String name) {
|
||||
if (registry.containsKey(name)) return registry.get(name);
|
||||
else {
|
||||
var res = new Symbol(name);
|
||||
registry.put(name, res);
|
||||
return res;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.Environment;
|
||||
import me.topchetoeu.jscript.engine.Operation;
|
||||
import me.topchetoeu.jscript.engine.frame.ConvertHint;
|
||||
import me.topchetoeu.jscript.exceptions.ConvertException;
|
||||
@ -39,10 +40,8 @@ public class Values {
|
||||
}
|
||||
|
||||
public static final Object NULL = new Object();
|
||||
public static final Object NO_RETURN = new Object();
|
||||
|
||||
public static boolean isObject(Object val) { return val instanceof ObjectValue; }
|
||||
public static boolean isFunction(Object val) { return val instanceof FunctionValue; }
|
||||
public static boolean isArray(Object val) { return val instanceof ArrayValue; }
|
||||
public static boolean isWrapper(Object val) { return val instanceof NativeWrapper; }
|
||||
public static boolean isWrapper(Object val, Class<?> clazz) {
|
||||
if (!isWrapper(val)) return false;
|
||||
@ -89,8 +88,8 @@ public class Values {
|
||||
private static Object tryCallConvertFunc(Context ctx, Object obj, String name) {
|
||||
var func = getMember(ctx, obj, name);
|
||||
|
||||
if (func != null) {
|
||||
var res = ((FunctionValue)func).call(ctx, obj);
|
||||
if (func instanceof FunctionValue) {
|
||||
var res = Values.call(ctx, func, obj);
|
||||
if (isPrimitive(res)) return res;
|
||||
}
|
||||
|
||||
@ -284,7 +283,7 @@ public class Values {
|
||||
obj = normalize(ctx, obj); key = normalize(ctx, key);
|
||||
if (obj == null) throw new IllegalArgumentException("Tried to access member of undefined.");
|
||||
if (obj == NULL) throw new IllegalArgumentException("Tried to access member of null.");
|
||||
if (isObject(obj)) return object(obj).getMember(ctx, key);
|
||||
if (obj instanceof ObjectValue) return object(obj).getMember(ctx, key);
|
||||
|
||||
if (obj instanceof String && key instanceof Number) {
|
||||
var i = number(key);
|
||||
@ -310,7 +309,7 @@ public class Values {
|
||||
if (obj == null) throw EngineException.ofType("Tried to access member of undefined.");
|
||||
if (obj == NULL) throw EngineException.ofType("Tried to access member of null.");
|
||||
if (key != null && "__proto__".equals(key)) return setPrototype(ctx, obj, val);
|
||||
if (isObject(obj)) return object(obj).setMember(ctx, key, val, false);
|
||||
if (obj instanceof ObjectValue) return object(obj).setMember(ctx, key, val, false);
|
||||
|
||||
var proto = getPrototype(ctx, obj);
|
||||
return proto.setMember(ctx, key, val, obj, true);
|
||||
@ -320,7 +319,7 @@ public class Values {
|
||||
obj = normalize(ctx, obj); key = normalize(ctx, key);
|
||||
|
||||
if ("__proto__".equals(key)) return true;
|
||||
if (isObject(obj)) return object(obj).hasMember(ctx, key, own);
|
||||
if (obj instanceof ObjectValue) return object(obj).hasMember(ctx, key, own);
|
||||
|
||||
if (obj instanceof String && key instanceof Number) {
|
||||
var i = number(key);
|
||||
@ -337,30 +336,30 @@ public class Values {
|
||||
if (obj == null || obj == NULL) return false;
|
||||
obj = normalize(ctx, obj); key = normalize(ctx, key);
|
||||
|
||||
if (isObject(obj)) return object(obj).deleteMember(ctx, key);
|
||||
if (obj instanceof ObjectValue) return object(obj).deleteMember(ctx, key);
|
||||
else return false;
|
||||
}
|
||||
public static ObjectValue getPrototype(Context ctx, Object obj) {
|
||||
if (obj == null || obj == NULL) return null;
|
||||
obj = normalize(ctx, obj);
|
||||
if (isObject(obj)) return object(obj).getPrototype(ctx);
|
||||
if (obj instanceof ObjectValue) return object(obj).getPrototype(ctx);
|
||||
if (ctx == null) return null;
|
||||
|
||||
if (obj instanceof String) return ctx.environment().proto("string");
|
||||
else if (obj instanceof Number) return ctx.environment().proto("number");
|
||||
else if (obj instanceof Boolean) return ctx.environment().proto("bool");
|
||||
else if (obj instanceof Symbol) return ctx.environment().proto("symbol");
|
||||
if (obj instanceof String) return ctx.get(Environment.STRING_PROTO);
|
||||
else if (obj instanceof Number) return ctx.get(Environment.NUMBER_PROTO);
|
||||
else if (obj instanceof Boolean) return ctx.get(Environment.BOOL_PROTO);
|
||||
else if (obj instanceof Symbol) return ctx.get(Environment.SYMBOL_PROTO);
|
||||
|
||||
return null;
|
||||
}
|
||||
public static boolean setPrototype(Context ctx, Object obj, Object proto) {
|
||||
obj = normalize(ctx, obj);
|
||||
return isObject(obj) && object(obj).setPrototype(ctx, proto);
|
||||
return obj instanceof ObjectValue && object(obj).setPrototype(ctx, proto);
|
||||
}
|
||||
public static List<Object> getMembers(Context ctx, Object obj, boolean own, boolean includeNonEnumerable) {
|
||||
List<Object> res = new ArrayList<>();
|
||||
|
||||
if (isObject(obj)) res = object(obj).keys(includeNonEnumerable);
|
||||
if (obj instanceof ObjectValue) res = object(obj).keys(includeNonEnumerable);
|
||||
if (obj instanceof String) {
|
||||
for (var i = 0; i < ((String)obj).length(); i++) res.add((double)i);
|
||||
}
|
||||
@ -396,7 +395,7 @@ public class Values {
|
||||
}
|
||||
|
||||
public static Object call(Context ctx, Object func, Object thisArg, Object ...args) {
|
||||
if (!isFunction(func)) throw EngineException.ofType("Tried to call a non-function value.");
|
||||
if (!(func instanceof FunctionValue)) throw EngineException.ofType("Tried to call a non-function value.");
|
||||
return function(func).call(ctx, thisArg, args);
|
||||
}
|
||||
public static Object callNew(Context ctx, Object func, Object ...args) {
|
||||
@ -476,7 +475,7 @@ public class Values {
|
||||
|
||||
if (val instanceof Class) {
|
||||
if (ctx == null) return null;
|
||||
else return ctx.environment().wrappers.getConstr((Class<?>)val);
|
||||
else return ctx.environment.wrappers.getConstr((Class<?>)val);
|
||||
}
|
||||
|
||||
return new NativeWrapper(val);
|
||||
@ -543,6 +542,9 @@ public class Values {
|
||||
|
||||
if (obj == null) return null;
|
||||
if (clazz.isInstance(obj)) return (T)obj;
|
||||
if (clazz.isAssignableFrom(NativeWrapper.class)) {
|
||||
return (T)new NativeWrapper(obj);
|
||||
}
|
||||
|
||||
throw new ConvertException(type(obj), clazz.getSimpleName());
|
||||
}
|
||||
@ -550,16 +552,16 @@ public class Values {
|
||||
public static Iterable<Object> fromJSIterator(Context ctx, Object obj) {
|
||||
return () -> {
|
||||
try {
|
||||
var symbol = ctx.environment().symbol("Symbol.iterator");
|
||||
var symbol = Symbol.get("Symbol.iterator");
|
||||
|
||||
var iteratorFunc = getMember(ctx, obj, symbol);
|
||||
if (!isFunction(iteratorFunc)) return Collections.emptyIterator();
|
||||
if (!(iteratorFunc instanceof FunctionValue)) return Collections.emptyIterator();
|
||||
var iterator = iteratorFunc instanceof FunctionValue ?
|
||||
((FunctionValue)iteratorFunc).call(ctx, obj, obj) :
|
||||
iteratorFunc;
|
||||
var nextFunc = getMember(ctx, call(ctx, iteratorFunc, obj), "next");
|
||||
|
||||
if (!isFunction(nextFunc)) return Collections.emptyIterator();
|
||||
if (!(nextFunc instanceof FunctionValue)) return Collections.emptyIterator();
|
||||
|
||||
return new Iterator<Object>() {
|
||||
private Object value = null;
|
||||
@ -604,16 +606,16 @@ public class Values {
|
||||
var res = new ObjectValue();
|
||||
|
||||
try {
|
||||
var key = getMember(ctx, getMember(ctx, ctx.environment().proto("symbol"), "constructor"), "iterator");
|
||||
res.defineProperty(ctx, key, new NativeFunction("", (_ctx, thisArg, args) -> thisArg));
|
||||
var key = getMember(ctx, getMember(ctx, ctx.get(Environment.SYMBOL_PROTO), "constructor"), "iterator");
|
||||
res.defineProperty(ctx, key, new NativeFunction("", args -> args.self));
|
||||
}
|
||||
catch (IllegalArgumentException | NullPointerException e) { }
|
||||
|
||||
res.defineProperty(ctx, "next", new NativeFunction("", (_ctx, _th, _args) -> {
|
||||
res.defineProperty(ctx, "next", new NativeFunction("", args -> {
|
||||
if (!it.hasNext()) return new ObjectValue(ctx, Map.of("done", true));
|
||||
else {
|
||||
var obj = new ObjectValue();
|
||||
obj.defineProperty(_ctx, "value", it.next());
|
||||
obj.defineProperty(args.ctx, "value", it.next());
|
||||
return obj;
|
||||
}
|
||||
}));
|
||||
@ -629,17 +631,17 @@ public class Values {
|
||||
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));
|
||||
var key = getMemberPath(ctx, ctx.get(Environment.SYMBOL_PROTO), "constructor", "asyncIterator");
|
||||
res.defineProperty(ctx, key, new NativeFunction("", args -> args.self));
|
||||
}
|
||||
catch (IllegalArgumentException | NullPointerException e) { }
|
||||
|
||||
res.defineProperty(ctx, "next", new NativeFunction("", (_ctx, _th, _args) -> {
|
||||
return PromiseLib.await(ctx, () -> {
|
||||
res.defineProperty(ctx, "next", new NativeFunction("", args -> {
|
||||
return PromiseLib.await(args.ctx, () -> {
|
||||
if (!it.hasNext()) return new ObjectValue(ctx, Map.of("done", true));
|
||||
else {
|
||||
var obj = new ObjectValue();
|
||||
obj.defineProperty(_ctx, "value", it.next());
|
||||
obj.defineProperty(args.ctx, "value", it.next());
|
||||
return obj;
|
||||
}
|
||||
});
|
||||
|
@ -1,25 +1,27 @@
|
||||
package me.topchetoeu.jscript.events;
|
||||
|
||||
import me.topchetoeu.jscript.exceptions.InterruptException;
|
||||
|
||||
public interface Awaitable<T> {
|
||||
T await() throws FinishedException;
|
||||
public static interface ResultHandler<T> {
|
||||
public void onResult(T data);
|
||||
}
|
||||
public static interface ErrorHandler {
|
||||
public void onError(RuntimeException error);
|
||||
}
|
||||
|
||||
default Observable<T> toObservable() {
|
||||
return sub -> {
|
||||
var thread = new Thread(() -> {
|
||||
try {
|
||||
sub.next(await());
|
||||
sub.finish();
|
||||
}
|
||||
catch (InterruptException | FinishedException e) { sub.finish(); }
|
||||
catch (RuntimeException e) {
|
||||
sub.error(e);
|
||||
}
|
||||
}, "Awaiter");
|
||||
thread.start();
|
||||
T await();
|
||||
|
||||
return () -> thread.interrupt();
|
||||
};
|
||||
default void handle(ResultHandler<T> onResult, ErrorHandler onError) {
|
||||
var thread = new Thread(() -> {
|
||||
try {
|
||||
onResult.onResult(await());
|
||||
}
|
||||
catch (RuntimeException e) {
|
||||
onError.onError(e);
|
||||
}
|
||||
}, "Awaiter");
|
||||
thread.start();
|
||||
}
|
||||
default void handle(ResultHandler<T> onResult) {
|
||||
handle(onResult, err -> {});
|
||||
}
|
||||
}
|
||||
|
@ -1,49 +0,0 @@
|
||||
package me.topchetoeu.jscript.events;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
||||
public class Event<T> implements Observer<T>, Observable<T> {
|
||||
private HashSet<Observer<T>> handlers = new HashSet<>();
|
||||
|
||||
public Handle on(Observer<T> handler) {
|
||||
if (handlers == null) {
|
||||
handler.finish();
|
||||
return () -> {};
|
||||
}
|
||||
|
||||
handlers.add(handler);
|
||||
return () -> {
|
||||
if (handlers == null) return;
|
||||
handlers.remove(handler);
|
||||
};
|
||||
}
|
||||
|
||||
public boolean isFinished() {
|
||||
return handlers == null;
|
||||
}
|
||||
|
||||
public void next(T value) {
|
||||
if (handlers == null) throw new IllegalStateException("Cannot use a finished event.");
|
||||
for (var handler : handlers) {
|
||||
handler.next(value);
|
||||
}
|
||||
}
|
||||
public void error(RuntimeException value) {
|
||||
if (handlers == null) throw new IllegalStateException("Cannot use a finished event.");
|
||||
for (var handler : handlers) {
|
||||
handler.error(value);
|
||||
}
|
||||
|
||||
handlers.clear();
|
||||
handlers = null;
|
||||
}
|
||||
public void finish() {
|
||||
if (handlers == null) throw new IllegalStateException("Cannot use a finished event.");
|
||||
for (var handler : handlers) {
|
||||
handler.finish();
|
||||
}
|
||||
|
||||
handlers.clear();
|
||||
handlers = null;
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package me.topchetoeu.jscript.events;
|
||||
|
||||
public class FinishedException extends RuntimeException {
|
||||
public FinishedException() {
|
||||
super("The observable has ended.");
|
||||
}
|
||||
}
|
@ -1,5 +0,0 @@
|
||||
package me.topchetoeu.jscript.events;
|
||||
|
||||
public interface Handle {
|
||||
void free();
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
package me.topchetoeu.jscript.events;
|
||||
|
||||
public interface Observable<T> {
|
||||
Handle on(Observer<T> val);
|
||||
|
||||
default Handle once(Observer<T> observer) {
|
||||
// Java is fucking retarded
|
||||
var unhandler = new Handle[1];
|
||||
var shouldUnsub = new boolean[1];
|
||||
|
||||
unhandler[0] = on(new Observer<>() {
|
||||
public void next(T data) {
|
||||
observer.next(data);
|
||||
if (unhandler[0] == null) shouldUnsub[0] = true;
|
||||
else unhandler[0].free();
|
||||
}
|
||||
public void error(RuntimeException err) {
|
||||
observer.error(err);
|
||||
if (unhandler[0] == null) shouldUnsub[0] = true;
|
||||
else unhandler[0].free();
|
||||
}
|
||||
public void finish() {
|
||||
observer.finish();
|
||||
if (unhandler[0] == null) shouldUnsub[0] = true;
|
||||
else unhandler[0].free();
|
||||
}
|
||||
});
|
||||
|
||||
if (shouldUnsub[0]) {
|
||||
unhandler[0].free();
|
||||
return () -> {};
|
||||
}
|
||||
else return unhandler[0];
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
default Awaitable<T> toAwaitable() {
|
||||
return () -> {
|
||||
var notifier = new Notifier();
|
||||
var valRef = new Object[1];
|
||||
var isErrRef = new boolean[1];
|
||||
|
||||
once(new Observer<>() {
|
||||
public void next(T data) {
|
||||
valRef[0] = data;
|
||||
notifier.next();
|
||||
}
|
||||
public void error(RuntimeException err) {
|
||||
isErrRef[0] = true;
|
||||
valRef[0] = err;
|
||||
notifier.next();
|
||||
}
|
||||
public void finish() {
|
||||
isErrRef[0] = true;
|
||||
valRef[0] = new FinishedException();
|
||||
notifier.next();
|
||||
}
|
||||
});
|
||||
|
||||
notifier.await();
|
||||
|
||||
if (isErrRef[0]) throw (RuntimeException)valRef[0];
|
||||
else return (T)valRef[0];
|
||||
};
|
||||
}
|
||||
default Observable<T> encapsulate() {
|
||||
return val -> on(val);
|
||||
}
|
||||
|
||||
default <T2> Observable<T2> pipe(Pipe<T, T2> pipe) {
|
||||
return sub -> on(pipe.apply(sub));
|
||||
}
|
||||
default WarmObservable<T> warmUp() {
|
||||
return new WarmObservable<>(this);
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package me.topchetoeu.jscript.events;
|
||||
|
||||
public interface Observer<T> {
|
||||
public void next(T data);
|
||||
public default void error(RuntimeException err) {}
|
||||
public default void finish() { }
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
package me.topchetoeu.jscript.events;
|
||||
|
||||
public interface Pipe<T, T2> {
|
||||
Observer<T> apply(Observer<T2> obs);
|
||||
// void next(T val, Observer<T2> target);
|
||||
// default void error(RuntimeException err, Observer<T2> target) {
|
||||
// target.error(err);
|
||||
// }
|
||||
// default void finish(Observer<T2> target) {
|
||||
// target.finish();
|
||||
// }
|
||||
|
||||
public static interface MapFunc<T1, T2> {
|
||||
T2 map(T1 val);
|
||||
}
|
||||
|
||||
public static <T1, T2> Pipe<T1, T2> map(MapFunc<T1, T2> func) {
|
||||
return o -> val -> o.next(func.map(val));
|
||||
}
|
||||
public static <T> Pipe<T, T> filter(MapFunc<T, Boolean> func) {
|
||||
return o -> val -> {
|
||||
if (func.map(val)) o.next(val);
|
||||
};
|
||||
}
|
||||
public static <T> Pipe<T, T> skip(int n) {
|
||||
var i = new int[1];
|
||||
|
||||
return target -> val -> {
|
||||
if (i[0] >= n) target.next(val);
|
||||
else i[0]++;
|
||||
};
|
||||
}
|
||||
public static <T> Pipe<T, T> limit(int n) {
|
||||
return target -> new Observer<T>() {
|
||||
private int i;
|
||||
|
||||
public void next(T val) {
|
||||
if (i >= n) target.finish();
|
||||
else {
|
||||
target.next(val);
|
||||
i++;
|
||||
}
|
||||
}
|
||||
public void error(RuntimeException err) {
|
||||
if (i < n) target.error(err);
|
||||
}
|
||||
public void finish() {
|
||||
if (i < n) target.finish();
|
||||
}
|
||||
};
|
||||
}
|
||||
public static <T> Pipe<T, T> first() {
|
||||
return limit(1);
|
||||
}
|
||||
|
||||
public static <T> Pipe<Observable<T>, T> merge() {
|
||||
return target -> val -> val.on(target);
|
||||
}
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
package me.topchetoeu.jscript.events;
|
||||
|
||||
import java.util.HashSet;
|
||||
|
||||
public class WarmObservable<T> implements Observable<T>, Handle {
|
||||
private HashSet<Observer<T>> observers = new HashSet<>();
|
||||
private Handle handle;
|
||||
|
||||
@Override
|
||||
public Handle on(Observer<T> val) {
|
||||
if (observers == null) return () -> {};
|
||||
observers.add(val);
|
||||
return () -> observers.remove(val);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void free() {
|
||||
if (observers == null) return;
|
||||
handle.free();
|
||||
handle = null;
|
||||
observers = null;
|
||||
}
|
||||
|
||||
public WarmObservable(Observable<T> observable) {
|
||||
observable.on(new Observer<>() {
|
||||
public void next(T data) {
|
||||
for (var obs : observers) obs.next(data);
|
||||
}
|
||||
public void error(RuntimeException err) {
|
||||
for (var obs : observers) obs.error(err);
|
||||
handle = null;
|
||||
observers = null;
|
||||
}
|
||||
public void finish() {
|
||||
for (var obs : observers) obs.finish();
|
||||
handle = null;
|
||||
observers = null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public WarmObservable<T> warmUp() {
|
||||
return this;
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.Engine;
|
||||
import me.topchetoeu.jscript.engine.Environment;
|
||||
import me.topchetoeu.jscript.engine.debug.DebugContext;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto;
|
||||
@ -18,13 +19,13 @@ public class EngineException extends RuntimeException {
|
||||
public final Context ctx;
|
||||
|
||||
public boolean visible() {
|
||||
return ctx == null || ctx.environment() == null || ctx.environment().stackVisible;
|
||||
return ctx == null || !ctx.get(Environment.HIDE_STACK, false);
|
||||
}
|
||||
public String toString() {
|
||||
var res = "";
|
||||
var loc = location;
|
||||
|
||||
if (loc != null && ctx != null && ctx.engine != null) loc = ctx.engine.mapToCompiled(loc);
|
||||
if (loc != null && ctx != null && ctx.engine != null) loc = DebugContext.get(ctx).mapToCompiled(loc);
|
||||
|
||||
if (loc != null) res += "at " + loc.toString() + " ";
|
||||
if (function != null && !function.equals("")) res += "in " + function + " ";
|
||||
@ -37,7 +38,7 @@ public class EngineException extends RuntimeException {
|
||||
if (function.equals("")) function = null;
|
||||
|
||||
if (ctx == null) this.ctx = null;
|
||||
else this.ctx = new Context(ctx.engine, ctx.environment());
|
||||
else this.ctx = new Context(ctx.engine, ctx.environment);
|
||||
this.location = location;
|
||||
this.function = function;
|
||||
}
|
||||
@ -52,7 +53,7 @@ public class EngineException extends RuntimeException {
|
||||
public EngineException add(Context ctx, String name, Location location) {
|
||||
var el = new StackElement(ctx, location, name);
|
||||
if (el.function == null && el.location == null) return this;
|
||||
setCtx(ctx.environment(), ctx.engine);
|
||||
setCtx(ctx.environment, ctx.engine);
|
||||
stackTrace.add(el);
|
||||
return this;
|
||||
}
|
||||
@ -65,6 +66,11 @@ public class EngineException extends RuntimeException {
|
||||
if (this.engine == null) this.engine = engine;
|
||||
return this;
|
||||
}
|
||||
public EngineException setCtx(Context ctx) {
|
||||
if (this.env == null) this.env = ctx.environment;
|
||||
if (this.engine == null) this.engine = ctx.engine;
|
||||
return this;
|
||||
}
|
||||
|
||||
public String toString(Context ctx) {
|
||||
var ss = new StringBuilder();
|
||||
|
@ -1,8 +1,17 @@
|
||||
package me.topchetoeu.jscript.filesystem;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Extensions;
|
||||
import me.topchetoeu.jscript.engine.values.Symbol;
|
||||
|
||||
public interface Filesystem {
|
||||
public static final Symbol ENV_KEY = Symbol.get("Environment.fs");
|
||||
|
||||
String normalize(String... path);
|
||||
File open(String path, Mode mode) throws FilesystemException;
|
||||
void create(String path, EntryType type) throws FilesystemException;
|
||||
FileStat stat(String path) throws FilesystemException;
|
||||
|
||||
public static Filesystem get(Extensions exts) {
|
||||
return exts.get(ENV_KEY);
|
||||
}
|
||||
}
|
121
src/me/topchetoeu/jscript/interop/Arguments.java
Normal file
121
src/me/topchetoeu/jscript/interop/Arguments.java
Normal file
@ -0,0 +1,121 @@
|
||||
package me.topchetoeu.jscript.interop;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.values.NativeWrapper;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
|
||||
public class Arguments {
|
||||
public final Object self;
|
||||
public final Object[] args;
|
||||
public final Context ctx;
|
||||
|
||||
public int n() {
|
||||
return args.length;
|
||||
}
|
||||
|
||||
public boolean has(int i) {
|
||||
return i == -1 || i >= 0 && i < args.length;
|
||||
}
|
||||
|
||||
public <T> T self(Class<T> type) {
|
||||
return convert(-1, type);
|
||||
}
|
||||
public <T> T convert(int i, Class<T> type) {
|
||||
return Values.convert(ctx, get(i), type);
|
||||
}
|
||||
public Object get(int i, boolean unwrap) {
|
||||
Object res = null;
|
||||
|
||||
if (i == -1) res = self;
|
||||
if (i >= 0 && i < args.length) res = args[i];
|
||||
if (unwrap && res instanceof NativeWrapper) res = ((NativeWrapper)res).wrapped;
|
||||
|
||||
return res;
|
||||
}
|
||||
public Object get(int i) {
|
||||
return get(i, false);
|
||||
}
|
||||
public Object getOrDefault(int i, Object def) {
|
||||
if (i < 0 || i >= args.length) return def;
|
||||
else return get(i);
|
||||
}
|
||||
|
||||
public Arguments slice(int start) {
|
||||
var res = new Object[Math.max(0, args.length - start)];
|
||||
for (int j = start; j < args.length; j++) res[j - start] = get(j);
|
||||
return new Arguments(ctx, args, res);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T[] convert(Class<T> type) {
|
||||
var res = Array.newInstance(type, args.length);
|
||||
for (int i = 0; i < args.length; i++) Array.set(res, i, convert(i, type));
|
||||
return (T[])res;
|
||||
}
|
||||
public int[] convertInt() {
|
||||
var res = new int[args.length];
|
||||
for (int i = 0; i < args.length; i++) res[i] = convert(i, Integer.class);
|
||||
return res;
|
||||
}
|
||||
public long[] convertLong() {
|
||||
var res = new long[Math.max(0, args.length)];
|
||||
for (int i = 0; i < args.length; i++) res[i] = convert(i, Long.class);
|
||||
return res;
|
||||
}
|
||||
public short[] sliceShort() {
|
||||
var res = new short[Math.max(0, args.length)];
|
||||
for (int i = 0; i < args.length; i++) res[i] = convert(i, Short.class);
|
||||
return res;
|
||||
}
|
||||
public float[] sliceFloat() {
|
||||
var res = new float[Math.max(0, args.length)];
|
||||
for (int i = 0; i < args.length; i++) res[i] = convert(i, Float.class);
|
||||
return res;
|
||||
}
|
||||
public double[] sliceDouble() {
|
||||
var res = new double[Math.max(0, args.length)];
|
||||
for (int i = 0; i < args.length; i++) res[i] = convert(i, Double.class);
|
||||
return res;
|
||||
}
|
||||
public byte[] sliceByte() {
|
||||
var res = new byte[Math.max(0, args.length)];
|
||||
for (int i = 0; i < args.length; i++) res[i] = convert(i, Byte.class);
|
||||
return res;
|
||||
}
|
||||
public char[] sliceChar() {
|
||||
var res = new char[Math.max(0, args.length)];
|
||||
for (int i = 0; i < args.length; i++) res[i] = convert(i, Character.class);
|
||||
return res;
|
||||
}
|
||||
public boolean[] sliceBool() {
|
||||
var res = new boolean[Math.max(0, args.length)];
|
||||
for (int i = 0; i < args.length; i++) res[i] = convert(i, Boolean.class);
|
||||
return res;
|
||||
}
|
||||
|
||||
public String getString(int i) { return Values.toString(ctx, get(i)); }
|
||||
public boolean getBoolean(int i) { return Values.toBoolean(get(i)); }
|
||||
public int getInt(int i) { return (int)Values.toNumber(ctx, get(i)); }
|
||||
public long getLong(int i) { return (long)Values.toNumber(ctx, get(i)); }
|
||||
public double getDouble(int i) { return Values.toNumber(ctx, get(i)); }
|
||||
public float getFloat(int i) { return (float)Values.toNumber(ctx, get(i)); }
|
||||
|
||||
public int getInt(int i, int def) {
|
||||
var res = get(i);
|
||||
if (res == null) return def;
|
||||
else return (int)Values.toNumber(ctx, res);
|
||||
}
|
||||
public String getString(int i, String def) {
|
||||
var res = get(i);
|
||||
if (res == null) return def;
|
||||
else return Values.toString(ctx, res);
|
||||
}
|
||||
|
||||
public Arguments(Context ctx, Object thisArg, Object... args) {
|
||||
this.ctx = ctx;
|
||||
this.args = args;
|
||||
this.self = thisArg;
|
||||
}
|
||||
}
|
@ -7,7 +7,8 @@ import java.lang.annotation.Target;
|
||||
|
||||
@Target({ ElementType.METHOD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface NativeSetter {
|
||||
public @interface Expose {
|
||||
public String value() default "";
|
||||
public boolean thisArg() default false;
|
||||
public ExposeType type() default ExposeType.METHOD;
|
||||
public ExposeTarget target() default ExposeTarget.MEMBER;
|
||||
}
|
@ -7,7 +7,4 @@ import java.lang.annotation.Target;
|
||||
|
||||
@Target({ ElementType.METHOD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface NativeConstructor {
|
||||
public boolean thisArg() default false;
|
||||
}
|
||||
|
||||
public @interface ExposeConstructor { }
|
@ -5,9 +5,9 @@ import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target({ ElementType.METHOD })
|
||||
@Target({ ElementType.METHOD, ElementType.FIELD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface NativeGetter {
|
||||
public @interface ExposeField {
|
||||
public String value() default "";
|
||||
public boolean thisArg() default false;
|
||||
public ExposeTarget target() default ExposeTarget.MEMBER;
|
||||
}
|
28
src/me/topchetoeu/jscript/interop/ExposeTarget.java
Normal file
28
src/me/topchetoeu/jscript/interop/ExposeTarget.java
Normal file
@ -0,0 +1,28 @@
|
||||
package me.topchetoeu.jscript.interop;
|
||||
|
||||
public enum ExposeTarget {
|
||||
STATIC(true, true, false),
|
||||
MEMBER(false, false, true),
|
||||
NAMESPACE(false, true, false),
|
||||
CONSTRUCTOR(true, false, false),
|
||||
PROTOTYPE(false, false, true),
|
||||
ALL(true, true, true);
|
||||
|
||||
public final boolean constructor;
|
||||
public final boolean namespace;
|
||||
public final boolean prototype;
|
||||
|
||||
public boolean shouldApply(ExposeTarget other) {
|
||||
if (other.constructor && !constructor) return false;
|
||||
if (other.namespace && !namespace) return false;
|
||||
if (other.prototype && !prototype) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private ExposeTarget(boolean constructor, boolean namespace, boolean prototype) {
|
||||
this.constructor = constructor;
|
||||
this.namespace = namespace;
|
||||
this.prototype = prototype;
|
||||
}
|
||||
}
|
8
src/me/topchetoeu/jscript/interop/ExposeType.java
Normal file
8
src/me/topchetoeu/jscript/interop/ExposeType.java
Normal file
@ -0,0 +1,8 @@
|
||||
package me.topchetoeu.jscript.interop;
|
||||
|
||||
public enum ExposeType {
|
||||
INIT,
|
||||
METHOD,
|
||||
GETTER,
|
||||
SETTER,
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package me.topchetoeu.jscript.interop;
|
||||
|
||||
public enum InitType {
|
||||
CONSTRUCTOR,
|
||||
PROTOTYPE,
|
||||
NAMESPACE,
|
||||
}
|
@ -1,13 +0,0 @@
|
||||
package me.topchetoeu.jscript.interop;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target({ ElementType.METHOD, ElementType.FIELD, ElementType.CONSTRUCTOR, ElementType.TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Native {
|
||||
public String value() default "";
|
||||
public boolean thisArg() default false;
|
||||
}
|
@ -1,15 +1,25 @@
|
||||
package me.topchetoeu.jscript.interop;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.Environment;
|
||||
import me.topchetoeu.jscript.engine.WrappersProvider;
|
||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.engine.values.NativeFunction;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.Symbol;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.exceptions.UncheckedException;
|
||||
import me.topchetoeu.jscript.exceptions.InterruptException;
|
||||
|
||||
public class NativeWrapperProvider implements WrappersProvider {
|
||||
private final HashMap<Class<?>, FunctionValue> constructors = new HashMap<>();
|
||||
@ -17,125 +27,211 @@ public class NativeWrapperProvider implements WrappersProvider {
|
||||
private final HashMap<Class<?>, ObjectValue> namespaces = new HashMap<>();
|
||||
private final Environment env;
|
||||
|
||||
private static void applyMethods(Environment env, boolean member, ObjectValue target, Class<?> clazz) {
|
||||
for (var method : clazz.getDeclaredMethods()) {
|
||||
var nat = method.getAnnotation(Native.class);
|
||||
var get = method.getAnnotation(NativeGetter.class);
|
||||
var set = method.getAnnotation(NativeSetter.class);
|
||||
var memberMatch = !Modifier.isStatic(method.getModifiers()) == member;
|
||||
|
||||
if (nat != null) {
|
||||
if (nat.thisArg() && !member || !nat.thisArg() && !memberMatch) continue;
|
||||
|
||||
Object name = nat.value();
|
||||
if (((String)name).startsWith("@@")) name = env.symbol(((String)name).substring(2));
|
||||
else if (name.equals("")) name = method.getName();
|
||||
|
||||
var val = target.values.get(name);
|
||||
|
||||
if (!(val instanceof OverloadFunction)) target.defineProperty(null, name, val = new OverloadFunction(name.toString()), true, true, false);
|
||||
|
||||
((OverloadFunction)val).add(Overload.fromMethod(method, nat.thisArg()));
|
||||
private static Object call(Context ctx, String name, Method method, Object thisArg, Object... args) {
|
||||
try {
|
||||
var realArgs = new Object[method.getParameterCount()];
|
||||
System.arraycopy(args, 0, realArgs, 0, realArgs.length);
|
||||
if (Modifier.isStatic(method.getModifiers())) thisArg = null;
|
||||
return Values.normalize(ctx, method.invoke(Values.convert(ctx, thisArg, method.getDeclaringClass()), realArgs));
|
||||
}
|
||||
catch (InvocationTargetException e) {
|
||||
if (e.getTargetException() instanceof EngineException) {
|
||||
throw ((EngineException)e.getTargetException()).add(ctx, name, Location.INTERNAL);
|
||||
}
|
||||
else {
|
||||
if (get != null) {
|
||||
if (get.thisArg() && !member || !get.thisArg() && !memberMatch) continue;
|
||||
|
||||
Object name = get.value();
|
||||
if (((String)name).startsWith("@@")) name = env.symbol(((String)name).substring(2));
|
||||
else if (name.equals("")) name = method.getName();
|
||||
|
||||
var prop = target.properties.get(name);
|
||||
OverloadFunction getter = null;
|
||||
var setter = prop == null ? null : prop.setter;
|
||||
|
||||
if (prop != null && prop.getter instanceof OverloadFunction) getter = (OverloadFunction)prop.getter;
|
||||
else getter = new OverloadFunction("get " + name);
|
||||
|
||||
getter.add(Overload.fromMethod(method, get.thisArg()));
|
||||
target.defineProperty(null, name, getter, setter, true, false);
|
||||
}
|
||||
if (set != null) {
|
||||
if (set.thisArg() && !member || !set.thisArg() && !memberMatch) continue;
|
||||
|
||||
Object name = set.value();
|
||||
if (((String)name).startsWith("@@")) name = env.symbol(((String)name).substring(2));
|
||||
else if (name.equals("")) name = method.getName();
|
||||
|
||||
var prop = target.properties.get(name);
|
||||
var getter = prop == null ? null : prop.getter;
|
||||
OverloadFunction setter = null;
|
||||
|
||||
if (prop != null && prop.setter instanceof OverloadFunction) setter = (OverloadFunction)prop.setter;
|
||||
else setter = new OverloadFunction("set " + name);
|
||||
|
||||
setter.add(Overload.fromMethod(method, set.thisArg()));
|
||||
target.defineProperty(null, name, getter, setter, true, false);
|
||||
}
|
||||
else if (e.getTargetException() instanceof NullPointerException) {
|
||||
e.printStackTrace();
|
||||
throw EngineException.ofType("Unexpected value of 'undefined'.").add(ctx, name, Location.INTERNAL);
|
||||
}
|
||||
else if (e.getTargetException() instanceof InterruptException || e.getTargetException() instanceof InterruptedException) {
|
||||
throw new InterruptException();
|
||||
}
|
||||
else throw new EngineException(e.getTargetException()).add(ctx, name, Location.INTERNAL);
|
||||
}
|
||||
catch (ReflectiveOperationException e) {
|
||||
throw EngineException.ofError(e.getMessage()).add(ctx, name, Location.INTERNAL);
|
||||
}
|
||||
}
|
||||
private static void applyFields(Environment env, boolean member, ObjectValue target, Class<?> clazz) {
|
||||
for (var field : clazz.getDeclaredFields()) {
|
||||
if (!Modifier.isStatic(field.getModifiers()) != member) continue;
|
||||
var nat = field.getAnnotation(Native.class);
|
||||
private static FunctionValue create(String name, Method method) {
|
||||
return new NativeFunction(name, args -> call(args.ctx, name, method, args.self, args));
|
||||
}
|
||||
private static void checkSignature(Method method, boolean forceStatic, Class<?> ...params) {
|
||||
if (forceStatic && !Modifier.isStatic(method.getModifiers())) throw new IllegalArgumentException(String.format(
|
||||
"Method %s must be static.",
|
||||
method.getDeclaringClass().getName() + "." + method.getName()
|
||||
));
|
||||
|
||||
if (nat != null) {
|
||||
Object name = nat.value();
|
||||
if (((String)name).startsWith("@@")) name = env.symbol(((String)name).substring(2));
|
||||
else if (name.equals("")) name = field.getName();
|
||||
var actual = method.getParameterTypes();
|
||||
|
||||
var getter = OverloadFunction.of("get " + name, Overload.getterFromField(field));
|
||||
var setter = OverloadFunction.of("set " + name, Overload.setterFromField(field));
|
||||
target.defineProperty(null, name, getter, setter, true, false);
|
||||
boolean failed = actual.length > params.length;
|
||||
|
||||
if (!failed) for (var i = 0; i < actual.length; i++) {
|
||||
if (!actual[i].isAssignableFrom(params[i])) {
|
||||
failed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (failed) throw new IllegalArgumentException(String.format(
|
||||
"Method %s was expected to have a signature of '%s', found '%s' instead.",
|
||||
method.getDeclaringClass().getName() + "." + method.getName(),
|
||||
String.join(", ", Arrays.stream(params).map(v -> v.getName()).collect(Collectors.toList())),
|
||||
String.join(", ", Arrays.stream(actual).map(v -> v.getName()).collect(Collectors.toList()))
|
||||
));
|
||||
}
|
||||
private static void applyClasses(Environment env, boolean member, ObjectValue target, Class<?> clazz) {
|
||||
for (var cl : clazz.getDeclaredClasses()) {
|
||||
if (!Modifier.isStatic(cl.getModifiers()) != member) continue;
|
||||
var nat = cl.getAnnotation(Native.class);
|
||||
|
||||
if (nat != null) {
|
||||
Object name = nat.value();
|
||||
if (((String)name).startsWith("@@")) name = env.symbol(((String)name).substring(2));
|
||||
else if (name.equals("")) name = cl.getName();
|
||||
|
||||
var getter = new NativeFunction("get " + name, (ctx, thisArg, args) -> cl);
|
||||
|
||||
target.defineProperty(null, name, getter, null, true, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String getName(Class<?> clazz) {
|
||||
var classNat = clazz.getAnnotation(Native.class);
|
||||
private static String getName(Class<?> clazz) {
|
||||
var classNat = clazz.getAnnotation(WrapperName.class);
|
||||
if (classNat != null && !classNat.value().trim().equals("")) return classNat.value().trim();
|
||||
else return clazz.getSimpleName();
|
||||
}
|
||||
|
||||
private static void checkUnderscore(Member member) {
|
||||
if (!member.getName().startsWith("__")) {
|
||||
System.out.println(String.format("WARNING: The name of the exposed member '%s.%s' doesn't start with '__'.",
|
||||
member.getDeclaringClass().getName(),
|
||||
member.getName()
|
||||
));
|
||||
}
|
||||
}
|
||||
private static String getName(Member member, String overrideName) {
|
||||
if (overrideName == null) overrideName = "";
|
||||
if (overrideName.isBlank()) {
|
||||
var res = member.getName();
|
||||
if (res.startsWith("__")) res = res.substring(2);
|
||||
return res;
|
||||
}
|
||||
else return overrideName.trim();
|
||||
}
|
||||
private static Object getKey(String name) {
|
||||
if (name.startsWith("@@")) return Symbol.get(name.substring(2));
|
||||
else return name;
|
||||
}
|
||||
|
||||
private static void apply(ObjectValue obj, Environment env, ExposeTarget target, Class<?> clazz) {
|
||||
var getters = new HashMap<Object, FunctionValue>();
|
||||
var setters = new HashMap<Object, FunctionValue>();
|
||||
var props = new HashSet<Object>();
|
||||
var nonProps = new HashSet<Object>();
|
||||
|
||||
for (var method : clazz.getDeclaredMethods()) {
|
||||
for (var annotation : method.getAnnotationsByType(Expose.class)) {
|
||||
if (!annotation.target().shouldApply(target)) continue;
|
||||
|
||||
checkUnderscore(method);
|
||||
var name = getName(method, annotation.value());
|
||||
var key = getKey(name);
|
||||
var repeat = false;
|
||||
|
||||
switch (annotation.type()) {
|
||||
case INIT:
|
||||
checkSignature(method, true,
|
||||
target == ExposeTarget.CONSTRUCTOR ? FunctionValue.class : ObjectValue.class,
|
||||
Environment.class
|
||||
);
|
||||
call(null, null, method, obj, null, env);
|
||||
break;
|
||||
case METHOD:
|
||||
if (props.contains(key) || nonProps.contains(key)) repeat = true;
|
||||
else {
|
||||
checkSignature(method, false, Arguments.class);
|
||||
obj.defineProperty(null, key, create(name, method), true, true, false);
|
||||
nonProps.add(key);
|
||||
}
|
||||
break;
|
||||
case GETTER:
|
||||
if (nonProps.contains(key) || getters.containsKey(key)) repeat = true;
|
||||
else {
|
||||
checkSignature(method, false, Arguments.class);
|
||||
getters.put(key, create(name, method));
|
||||
props.add(key);
|
||||
}
|
||||
break;
|
||||
case SETTER:
|
||||
if (nonProps.contains(key) || setters.containsKey(key)) repeat = true;
|
||||
else {
|
||||
checkSignature(method, false, Arguments.class);
|
||||
setters.put(key, create(name, method));
|
||||
props.add(key);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (repeat)
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"A member '%s' in the wrapper for '%s' of type '%s' is already present.",
|
||||
name, clazz.getName(), target.toString()
|
||||
));
|
||||
}
|
||||
for (var annotation : method.getAnnotationsByType(ExposeField.class)) {
|
||||
if (!annotation.target().shouldApply(target)) continue;
|
||||
|
||||
checkUnderscore(method);
|
||||
var name = getName(method, annotation.value());
|
||||
var key = getKey(name);
|
||||
var repeat = false;
|
||||
|
||||
if (props.contains(key) || nonProps.contains(key)) repeat = true;
|
||||
else {
|
||||
checkSignature(method, true, Environment.class);
|
||||
obj.defineProperty(null, key, call(new Context(null, env), name, method, null, env), true, true, false);
|
||||
nonProps.add(key);
|
||||
}
|
||||
|
||||
if (repeat)
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"A member '%s' in the wrapper for '%s' of type '%s' is already present.",
|
||||
name, clazz.getName(), target.toString()
|
||||
));
|
||||
}
|
||||
}
|
||||
for (var field : clazz.getDeclaredFields()) {
|
||||
for (var annotation : field.getAnnotationsByType(ExposeField.class)) {
|
||||
if (!annotation.target().shouldApply(target)) continue;
|
||||
|
||||
checkUnderscore(field);
|
||||
var name = getName(field, annotation.value());
|
||||
var key = getKey(name);
|
||||
var repeat = false;
|
||||
|
||||
if (props.contains(key) || nonProps.contains(key)) repeat = true;
|
||||
else {
|
||||
try {
|
||||
obj.defineProperty(null, key, Values.normalize(new Context(null, env), field.get(null)), true, true, false);
|
||||
nonProps.add(key);
|
||||
}
|
||||
catch (IllegalArgumentException | IllegalAccessException e) { }
|
||||
}
|
||||
|
||||
if (repeat)
|
||||
throw new IllegalArgumentException(String.format(
|
||||
"A member '%s' in the wrapper for '%s' of type '%s' is already present.",
|
||||
name, clazz.getName(), target.toString()
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
for (var key : props) obj.defineProperty(null, key, getters.get(key), setters.get(key), true, false);
|
||||
}
|
||||
|
||||
private static Method getConstructor(Environment env, Class<?> clazz) {
|
||||
for (var method : clazz.getDeclaredMethods()) {
|
||||
if (!method.isAnnotationPresent(ExposeConstructor.class)) continue;
|
||||
checkSignature(method, true, Arguments.class);
|
||||
return method;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a prototype for the given class.
|
||||
* The returned object will have appropriate wrappers for all instance members.
|
||||
* All accessors and methods will expect the this argument to be a native wrapper of the given class type.
|
||||
* @param clazz The class for which a prototype should be generated
|
||||
*/
|
||||
public static ObjectValue makeProto(Environment ctx, Class<?> clazz) {
|
||||
public static ObjectValue makeProto(Environment env, Class<?> clazz) {
|
||||
var res = new ObjectValue();
|
||||
|
||||
res.defineProperty(null, ctx.symbol("Symbol.typeName"), getName(clazz));
|
||||
|
||||
for (var overload : clazz.getDeclaredMethods()) {
|
||||
var init = overload.getAnnotation(NativeInit.class);
|
||||
if (init == null || init.value() != InitType.PROTOTYPE) continue;
|
||||
try { overload.invoke(null, ctx, res); }
|
||||
catch (Throwable e) { throw new UncheckedException(e); }
|
||||
}
|
||||
|
||||
applyMethods(ctx, true, res, clazz);
|
||||
applyFields(ctx, true, res, clazz);
|
||||
applyClasses(ctx, true, res, clazz);
|
||||
|
||||
res.defineProperty(null, Symbol.get("Symbol.typeName"), getName(clazz));
|
||||
apply(res, env, ExposeTarget.PROTOTYPE, clazz);
|
||||
return res;
|
||||
}
|
||||
/**
|
||||
@ -145,36 +241,17 @@ public class NativeWrapperProvider implements WrappersProvider {
|
||||
* @param clazz The class for which a constructor should be generated
|
||||
*/
|
||||
public static FunctionValue makeConstructor(Environment ctx, Class<?> clazz) {
|
||||
FunctionValue func = new OverloadFunction(getName(clazz));
|
||||
var constr = getConstructor(ctx, clazz);
|
||||
|
||||
for (var overload : clazz.getDeclaredConstructors()) {
|
||||
var nat = overload.getAnnotation(Native.class);
|
||||
if (nat == null) continue;
|
||||
((OverloadFunction)func).add(Overload.fromConstructor(overload, nat.thisArg()));
|
||||
}
|
||||
for (var overload : clazz.getDeclaredMethods()) {
|
||||
var constr = overload.getAnnotation(NativeConstructor.class);
|
||||
if (constr == null) continue;
|
||||
((OverloadFunction)func).add(Overload.fromMethod(overload, constr.thisArg()));
|
||||
}
|
||||
for (var overload : clazz.getDeclaredMethods()) {
|
||||
var init = overload.getAnnotation(NativeInit.class);
|
||||
if (init == null || init.value() != InitType.CONSTRUCTOR) continue;
|
||||
try { overload.invoke(null, ctx, func); }
|
||||
catch (Throwable e) { throw new UncheckedException(e); }
|
||||
}
|
||||
FunctionValue res = constr == null ?
|
||||
new NativeFunction(getName(clazz), args -> { throw EngineException.ofError("This constructor is not invokable."); }) :
|
||||
create(getName(clazz), constr);
|
||||
|
||||
if (((OverloadFunction)func).overloads.size() == 0) {
|
||||
func = new NativeFunction(getName(clazz), (a, b, c) -> { throw EngineException.ofError("This constructor is not invokable."); });
|
||||
}
|
||||
res.special = true;
|
||||
|
||||
applyMethods(ctx, false, func, clazz);
|
||||
applyFields(ctx, false, func, clazz);
|
||||
applyClasses(ctx, false, func, clazz);
|
||||
apply(res, ctx, ExposeTarget.CONSTRUCTOR, clazz);
|
||||
|
||||
func.special = true;
|
||||
|
||||
return func;
|
||||
return res;
|
||||
}
|
||||
/**
|
||||
* Generates a namespace for the given class.
|
||||
@ -183,19 +260,9 @@ public class NativeWrapperProvider implements WrappersProvider {
|
||||
* @param clazz The class for which a constructor should be generated
|
||||
*/
|
||||
public static ObjectValue makeNamespace(Environment ctx, Class<?> clazz) {
|
||||
ObjectValue res = new ObjectValue();
|
||||
|
||||
for (var overload : clazz.getDeclaredMethods()) {
|
||||
var init = overload.getAnnotation(NativeInit.class);
|
||||
if (init == null || init.value() != InitType.NAMESPACE) continue;
|
||||
try { overload.invoke(null, ctx, res); }
|
||||
catch (Throwable e) { throw new UncheckedException(e); }
|
||||
}
|
||||
|
||||
applyMethods(ctx, false, res, clazz);
|
||||
applyFields(ctx, false, res, clazz);
|
||||
applyClasses(ctx, false, res, clazz);
|
||||
|
||||
var res = new ObjectValue();
|
||||
res.defineProperty(null, Symbol.get("Symbol.typeName"), getName(clazz));
|
||||
apply(res, ctx, ExposeTarget.NAMESPACE, clazz);
|
||||
return res;
|
||||
}
|
||||
|
||||
@ -248,8 +315,7 @@ public class NativeWrapperProvider implements WrappersProvider {
|
||||
return constructors.get(clazz);
|
||||
}
|
||||
|
||||
@Override
|
||||
public WrappersProvider fork(Environment env) {
|
||||
@Override public WrappersProvider fork(Environment env) {
|
||||
return new NativeWrapperProvider(env);
|
||||
}
|
||||
|
||||
@ -262,12 +328,12 @@ public class NativeWrapperProvider implements WrappersProvider {
|
||||
|
||||
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();
|
||||
proto.defineProperty(null, "message", new NativeFunction("message", args -> {
|
||||
if (args.self instanceof Throwable) return ((Throwable)args.self).getMessage();
|
||||
else return null;
|
||||
}));
|
||||
proto.defineProperty(null, "name", new NativeFunction("name", (ctx, thisArg, args) -> getName(thisArg.getClass())));
|
||||
proto.defineProperty(null, "toString", new NativeFunction("toString", (ctx, thisArg, args) -> thisArg.toString()));
|
||||
proto.defineProperty(null, "name", new NativeFunction("name", args -> getName(args.self.getClass())));
|
||||
proto.defineProperty(null, "toString", new NativeFunction("toString", args -> args.self.toString()));
|
||||
|
||||
var constr = makeConstructor(null, Throwable.class);
|
||||
proto.defineProperty(null, "constructor", constr, true, false, false);
|
||||
|
@ -7,6 +7,6 @@ import java.lang.annotation.Target;
|
||||
|
||||
@Target({ ElementType.METHOD })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface NativeInit {
|
||||
InitType value();
|
||||
public @interface OnWrapperInit {
|
||||
|
||||
}
|
@ -1,70 +0,0 @@
|
||||
package me.topchetoeu.jscript.interop;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
|
||||
public class Overload {
|
||||
public static interface OverloadRunner {
|
||||
Object run(Context ctx, Object thisArg, Object[] args) throws ReflectiveOperationException, IllegalArgumentException;
|
||||
}
|
||||
|
||||
public final OverloadRunner runner;
|
||||
public final boolean variadic;
|
||||
public final boolean passThis;
|
||||
public final Class<?> thisArg;
|
||||
public final Class<?>[] params;
|
||||
|
||||
public static Overload fromMethod(Method method, boolean passThis) {
|
||||
return new Overload(
|
||||
(ctx, th, args) -> method.invoke(th, args),
|
||||
method.isVarArgs(), passThis,
|
||||
Modifier.isStatic(method.getModifiers()) ? null : method.getDeclaringClass(),
|
||||
method.getParameterTypes()
|
||||
);
|
||||
}
|
||||
public static Overload fromConstructor(Constructor<?> method, boolean passThis) {
|
||||
return new Overload(
|
||||
(ctx, th, args) -> method.newInstance(args),
|
||||
method.isVarArgs(), passThis,
|
||||
null,
|
||||
method.getParameterTypes()
|
||||
);
|
||||
}
|
||||
public static Overload getterFromField(Field field) {
|
||||
return new Overload(
|
||||
(ctx, th, args) -> field.get(th), false, false,
|
||||
Modifier.isStatic(field.getModifiers()) ? null : field.getDeclaringClass(),
|
||||
new Class[0]
|
||||
);
|
||||
}
|
||||
public static Overload setterFromField(Field field) {
|
||||
if (Modifier.isFinal(field.getModifiers())) return null;
|
||||
return new Overload(
|
||||
(ctx, th, args) -> {
|
||||
field.set(th, args[0]); return null;
|
||||
}, false, false,
|
||||
Modifier.isStatic(field.getModifiers()) ? null : field.getDeclaringClass(),
|
||||
new Class[] { field.getType() }
|
||||
);
|
||||
}
|
||||
|
||||
public static Overload getter(Class<?> thisArg, OverloadRunner runner, boolean passThis) {
|
||||
return new Overload(
|
||||
(ctx, th, args) -> runner.run(ctx, th, args), false, passThis,
|
||||
thisArg,
|
||||
new Class[0]
|
||||
);
|
||||
}
|
||||
|
||||
public Overload(OverloadRunner runner, boolean variadic, boolean passThis, Class<?> thisArg, Class<?> args[]) {
|
||||
this.runner = runner;
|
||||
this.variadic = variadic;
|
||||
this.passThis = passThis;
|
||||
this.thisArg = thisArg;
|
||||
this.params = args;
|
||||
}
|
||||
}
|
@ -1,127 +0,0 @@
|
||||
package me.topchetoeu.jscript.interop;
|
||||
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.engine.values.NativeWrapper;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.exceptions.ConvertException;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.exceptions.InterruptException;
|
||||
|
||||
public class OverloadFunction extends FunctionValue {
|
||||
public final List<Overload> overloads = new ArrayList<>();
|
||||
|
||||
public Object call(Context ctx, Object thisArg, Object ...args) {
|
||||
loop: for (var overload : overloads) {
|
||||
Object[] newArgs = new Object[overload.params.length];
|
||||
|
||||
boolean consumesEngine = overload.params.length > 0 && overload.params[0] == Context.class;
|
||||
int start = (consumesEngine ? 1 : 0) + (overload.passThis ? 1 : 0);
|
||||
int end = overload.params.length - (overload.variadic ? 1 : 0);
|
||||
|
||||
for (var i = start; i < end; i++) {
|
||||
Object val;
|
||||
|
||||
if (i - start >= args.length) val = null;
|
||||
else val = args[i - start];
|
||||
|
||||
try {
|
||||
newArgs[i] = Values.convert(ctx, val, overload.params[i]);
|
||||
}
|
||||
catch (ConvertException e) {
|
||||
if (overloads.size() > 1) continue loop;
|
||||
else throw EngineException.ofType(String.format("Argument %d can't be converted from %s to %s", i - start, e.source, e.target));
|
||||
}
|
||||
}
|
||||
|
||||
if (overload.variadic) {
|
||||
var type = overload.params[overload.params.length - 1].getComponentType();
|
||||
var n = Math.max(args.length - end + start, 0);
|
||||
Object varArg = Array.newInstance(type, n);
|
||||
|
||||
for (var i = 0; i < n; i++) {
|
||||
try {
|
||||
Array.set(varArg, i, Values.convert(ctx, args[i + end - start], type));
|
||||
}
|
||||
catch (ConvertException e) {
|
||||
if (overloads.size() > 1) continue loop;
|
||||
else throw EngineException.ofType(String.format("Element in variadic argument can't be converted from %s to %s", e.source, e.target));
|
||||
}
|
||||
}
|
||||
|
||||
newArgs[newArgs.length - 1] = varArg;
|
||||
}
|
||||
|
||||
var thisArgType = overload.passThis ? overload.params[consumesEngine ? 1 : 0] : overload.thisArg;
|
||||
Object _this;
|
||||
|
||||
try {
|
||||
_this = thisArgType == null ? null : Values.convert(ctx, thisArg, thisArgType);
|
||||
}
|
||||
catch (ConvertException e) {
|
||||
if (overloads.size() > 1) continue loop;
|
||||
else throw EngineException.ofType(String.format("This argument can't be converted from %s to %s", e.source, e.target));
|
||||
}
|
||||
|
||||
if (consumesEngine) newArgs[0] = ctx;
|
||||
if (overload.passThis) {
|
||||
newArgs[consumesEngine ? 1 : 0] = _this;
|
||||
_this = null;
|
||||
}
|
||||
|
||||
try {
|
||||
return Values.normalize(ctx, overload.runner.run(ctx, _this, newArgs));
|
||||
}
|
||||
catch (InstantiationException e) { throw EngineException.ofError("The class may not be instantiated."); }
|
||||
catch (IllegalAccessException | IllegalArgumentException e) { continue; }
|
||||
catch (InvocationTargetException e) {
|
||||
var loc = Location.INTERNAL;
|
||||
if (e.getTargetException() instanceof EngineException) {
|
||||
throw ((EngineException)e.getTargetException()).add(ctx, name, loc);
|
||||
}
|
||||
else if (e.getTargetException() instanceof NullPointerException) {
|
||||
e.printStackTrace();
|
||||
throw EngineException.ofType("Unexpected value of 'undefined'.").add(ctx, name, loc);
|
||||
}
|
||||
else if (e.getTargetException() instanceof InterruptException || e.getTargetException() instanceof InterruptedException) {
|
||||
throw new InterruptException();
|
||||
}
|
||||
else {
|
||||
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(ctx, name, loc);
|
||||
}
|
||||
}
|
||||
catch (ReflectiveOperationException e) {
|
||||
throw EngineException.ofError(e.getMessage()).add(ctx, name, Location.INTERNAL);
|
||||
}
|
||||
}
|
||||
|
||||
throw EngineException.ofType("No overload found for native method.");
|
||||
}
|
||||
|
||||
public OverloadFunction add(Overload overload) {
|
||||
this.overloads.add(overload);
|
||||
return this;
|
||||
}
|
||||
|
||||
public OverloadFunction(String name) {
|
||||
super(name, 0);
|
||||
}
|
||||
|
||||
public static OverloadFunction of(String name, Overload overload) {
|
||||
if (overload == null) return null;
|
||||
else return new OverloadFunction(name).add(overload);
|
||||
}
|
||||
}
|
12
src/me/topchetoeu/jscript/interop/WrapperName.java
Normal file
12
src/me/topchetoeu/jscript/interop/WrapperName.java
Normal file
@ -0,0 +1,12 @@
|
||||
package me.topchetoeu.jscript.interop;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target({ ElementType.TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface WrapperName {
|
||||
String value();
|
||||
}
|
@ -3,35 +3,48 @@ package me.topchetoeu.jscript.lib;
|
||||
import java.util.Iterator;
|
||||
import java.util.Stack;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.engine.values.NativeFunction;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeConstructor;
|
||||
import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
import me.topchetoeu.jscript.interop.NativeSetter;
|
||||
import me.topchetoeu.jscript.interop.Arguments;
|
||||
import me.topchetoeu.jscript.interop.Expose;
|
||||
import me.topchetoeu.jscript.interop.ExposeConstructor;
|
||||
import me.topchetoeu.jscript.interop.ExposeTarget;
|
||||
import me.topchetoeu.jscript.interop.ExposeType;
|
||||
import me.topchetoeu.jscript.interop.WrapperName;
|
||||
|
||||
@Native("Array") public class ArrayLib {
|
||||
@NativeGetter(thisArg = true) public static int length(Context ctx, ArrayValue thisArg) {
|
||||
return thisArg.size();
|
||||
}
|
||||
@NativeSetter(thisArg = true) public static void length(Context ctx, ArrayValue thisArg, int len) {
|
||||
thisArg.setSize(len);
|
||||
@WrapperName("Array")
|
||||
public class ArrayLib {
|
||||
private static int normalizeI(int len, int i, boolean clamp) {
|
||||
if (i < 0) i += len;
|
||||
if (clamp) {
|
||||
if (i < 0) i = 0;
|
||||
if (i > len) i = len;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
@Native(thisArg = true) public static ObjectValue values(Context ctx, ArrayValue thisArg) {
|
||||
return Values.toJSIterator(ctx, thisArg);
|
||||
@Expose(value = "length", type = ExposeType.GETTER)
|
||||
public static int __getLength(Arguments args) {
|
||||
return args.self(ArrayValue.class).size();
|
||||
}
|
||||
@Native(thisArg = true) public static ObjectValue keys(Context ctx, ArrayValue thisArg) {
|
||||
return Values.toJSIterator(ctx, () -> new Iterator<Object>() {
|
||||
@Expose(value = "length", type = ExposeType.SETTER)
|
||||
public static void __setLength(Arguments args) {
|
||||
args.self(ArrayValue.class).setSize(args.getInt(0));
|
||||
}
|
||||
|
||||
@Expose public static ObjectValue __values(Arguments args) {
|
||||
return __iterator(args);
|
||||
}
|
||||
@Expose public static ObjectValue __keys(Arguments args) {
|
||||
return Values.toJSIterator(args.ctx, () -> new Iterator<Object>() {
|
||||
private int i = 0;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return i < thisArg.size();
|
||||
return i < args.self(ArrayValue.class).size();
|
||||
}
|
||||
@Override
|
||||
public Object next() {
|
||||
@ -40,63 +53,67 @@ import me.topchetoeu.jscript.interop.NativeSetter;
|
||||
}
|
||||
});
|
||||
}
|
||||
@Native(thisArg = true) public static ObjectValue entries(Context ctx, ArrayValue thisArg) {
|
||||
return Values.toJSIterator(ctx, () -> new Iterator<Object>() {
|
||||
@Expose public static ObjectValue __entries(Arguments args) {
|
||||
return Values.toJSIterator(args.ctx, () -> new Iterator<Object>() {
|
||||
private int i = 0;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return i < thisArg.size();
|
||||
return i < args.self(ArrayValue.class).size();
|
||||
}
|
||||
@Override
|
||||
public Object next() {
|
||||
if (!hasNext()) return null;
|
||||
return new ArrayValue(ctx, i, thisArg.get(i++));
|
||||
return new ArrayValue(args.ctx, i, args.self(ArrayValue.class).get(i++));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Native(value = "@@Symbol.iterator", thisArg = true)
|
||||
public static ObjectValue iterator(Context ctx, ArrayValue thisArg) {
|
||||
return values(ctx, thisArg);
|
||||
@Expose(value = "@@Symbol.iterator")
|
||||
public static ObjectValue __iterator(Arguments args) {
|
||||
return Values.toJSIterator(args.ctx, args.self(ArrayValue.class));
|
||||
}
|
||||
@Native(value = "@@Symbol.asyncIterator", thisArg = true)
|
||||
public static ObjectValue asyncIterator(Context ctx, ArrayValue thisArg) {
|
||||
return values(ctx, thisArg);
|
||||
@Expose(value = "@@Symbol.asyncIterator")
|
||||
public static ObjectValue __asyncIterator(Arguments args) {
|
||||
return Values.toJSAsyncIterator(args.ctx, args.self(ArrayValue.class).iterator());
|
||||
}
|
||||
|
||||
@Native(thisArg = true) public static ArrayValue concat(Context ctx, ArrayValue thisArg, Object ...others) {
|
||||
@Expose public static ArrayValue __concat(Arguments args) {
|
||||
// TODO: Fully implement with non-array spreadable objects
|
||||
var size = thisArg.size();
|
||||
var arrs = args.slice(-1);
|
||||
var size = 0;
|
||||
|
||||
for (int i = 0; i < others.length; i++) {
|
||||
if (others[i] instanceof ArrayValue) size += ((ArrayValue)others[i]).size();
|
||||
for (int i = 0; i < arrs.n(); i++) {
|
||||
if (arrs.get(i) instanceof ArrayValue) size += arrs.convert(i, ArrayValue.class).size();
|
||||
else i++;
|
||||
}
|
||||
|
||||
var res = new ArrayValue(size);
|
||||
thisArg.copyTo(ctx, res, 0, 0, thisArg.size());
|
||||
|
||||
for (int i = 0, j = thisArg.size(); i < others.length; i++) {
|
||||
if (others[i] instanceof ArrayValue) {
|
||||
int n = ((ArrayValue)others[i]).size();
|
||||
((ArrayValue)others[i]).copyTo(ctx, res, 0, j, n);
|
||||
for (int i = 0, j = 0; i < arrs.n(); i++) {
|
||||
if (arrs.get(i) instanceof ArrayValue) {
|
||||
var arrEl = arrs.convert(i, ArrayValue.class);
|
||||
int n = arrEl.size();
|
||||
arrEl.copyTo(args.ctx, res, 0, j, n);
|
||||
j += n;
|
||||
}
|
||||
else {
|
||||
res.set(ctx, j++, others[i]);
|
||||
res.set(args.ctx, j++, arrs.get(i));
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@Expose public static ArrayValue __sort(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
var cmp = args.convert(0, FunctionValue.class);
|
||||
|
||||
@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]));
|
||||
var defaultCmp = new NativeFunction("", _args -> {
|
||||
return _args.getString(0).compareTo(_args.getString(1));
|
||||
});
|
||||
|
||||
arr.sort((a, b) -> {
|
||||
var res = Values.toNumber(ctx, (cmp == null ? defaultCmp : cmp).call(ctx, null, a, b));
|
||||
var res = Values.toNumber(args.ctx, (cmp == null ? defaultCmp : cmp).call(args.ctx, null, a, b));
|
||||
if (res < 0) return -1;
|
||||
if (res > 0) return 1;
|
||||
return 0;
|
||||
@ -104,100 +121,121 @@ import me.topchetoeu.jscript.interop.NativeSetter;
|
||||
return arr;
|
||||
}
|
||||
|
||||
private static int normalizeI(int len, int i, boolean clamp) {
|
||||
if (i < 0) i += len;
|
||||
if (clamp) {
|
||||
if (i < 0) i = 0;
|
||||
if (i >= len) i = len;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
@Expose public static ArrayValue __fill(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
var val = args.get(0);
|
||||
var start = normalizeI(arr.size(), args.getInt(1, 0), true);
|
||||
var end = normalizeI(arr.size(), args.getInt(2, arr.size()), true);
|
||||
|
||||
@Native(thisArg = true) public static ArrayValue fill(Context ctx, ArrayValue arr, Object val, int start, int end) {
|
||||
start = normalizeI(arr.size(), start, true);
|
||||
end = normalizeI(arr.size(), end, true);
|
||||
|
||||
for (; start < end; start++) {
|
||||
arr.set(ctx, start, val);
|
||||
}
|
||||
for (; start < end; start++) arr.set(args.ctx, start, val);
|
||||
|
||||
return arr;
|
||||
}
|
||||
@Native(thisArg = true) public static ArrayValue fill(Context ctx, ArrayValue arr, Object val, int start) {
|
||||
return fill(ctx, arr, val, start, arr.size());
|
||||
}
|
||||
@Native(thisArg = true) public static ArrayValue fill(Context ctx, ArrayValue arr, Object val) {
|
||||
return fill(ctx, arr, val, 0, arr.size());
|
||||
}
|
||||
@Expose public static boolean __every(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
|
||||
@Native(thisArg = true) public static boolean every(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) {
|
||||
for (var i = 0; i < arr.size(); i++) {
|
||||
if (!Values.toBoolean(func.call(ctx, thisArg, arr.get(i), i, arr))) return false;
|
||||
if (arr.has(i) && !Values.toBoolean(Values.call(
|
||||
args.ctx, args.get(0), args.get(1),
|
||||
arr.get(i), i, arr
|
||||
))) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@Native(thisArg = true) public static boolean some(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) {
|
||||
@Expose public static boolean __some(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
|
||||
for (var i = 0; i < arr.size(); i++) {
|
||||
if (Values.toBoolean(func.call(ctx, thisArg, arr.get(i), i, arr))) return true;
|
||||
if (arr.has(i) && Values.toBoolean(Values.call(
|
||||
args.ctx, args.get(0), args.get(1),
|
||||
arr.get(i), i, arr
|
||||
))) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Native(thisArg = true) public static ArrayValue filter(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) {
|
||||
@Expose public static ArrayValue __filter(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
var res = new ArrayValue(arr.size());
|
||||
|
||||
for (int i = 0, j = 0; i < arr.size(); i++) {
|
||||
if (arr.has(i) && Values.toBoolean(func.call(ctx, thisArg, arr.get(i), i, arr))) res.set(ctx, j++, arr.get(i));
|
||||
if (arr.has(i) && Values.toBoolean(Values.call(
|
||||
args.ctx, args.get(0), args.get(1),
|
||||
arr.get(i), i, arr
|
||||
))) res.set(args.ctx, j++, arr.get(i));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@Native(thisArg = true) public static ArrayValue map(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) {
|
||||
@Expose public static ArrayValue __map(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
var res = new ArrayValue(arr.size());
|
||||
for (int i = 0, j = 0; i < arr.size(); i++) {
|
||||
if (arr.has(i)) res.set(ctx, j++, func.call(ctx, thisArg, arr.get(i), i, arr));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
@Native(thisArg = true) public static void forEach(Context ctx, ArrayValue arr, FunctionValue func, Object thisArg) {
|
||||
res.setSize(arr.size());
|
||||
|
||||
for (int i = 0; i < arr.size(); i++) {
|
||||
if (arr.has(i)) func.call(ctx, thisArg, arr.get(i), i, arr);
|
||||
if (arr.has(i)) res.set(args.ctx, i, Values.call(args.ctx, args.get(0), args.get(1), arr.get(i), i, arr));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
@Expose public static void __forEach(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
var func = args.convert(0, FunctionValue.class);
|
||||
var thisArg = args.get(1);
|
||||
|
||||
for (int i = 0; i < arr.size(); i++) {
|
||||
if (arr.has(i)) func.call(args.ctx, thisArg, arr.get(i), i, arr);
|
||||
}
|
||||
}
|
||||
|
||||
@Native(thisArg = true) public static Object reduce(Context ctx, ArrayValue arr, FunctionValue func, Object... args) {
|
||||
@Expose public static Object __reduce(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
var func = args.convert(0, FunctionValue.class);
|
||||
var res = args.get(1);
|
||||
var i = 0;
|
||||
var res = arr.get(0);
|
||||
|
||||
if (args.length > 0) res = args[0];
|
||||
else for (; !arr.has(i) && i < arr.size(); i++) res = arr.get(i);
|
||||
if (args.n() < 2) {
|
||||
for (; i < arr.size(); i++) {
|
||||
if (arr.has(i)){
|
||||
res = arr.get(i++);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (; i < arr.size(); i++) {
|
||||
if (arr.has(i)) {
|
||||
res = func.call(ctx, null, res, arr.get(i), i, arr);
|
||||
res = func.call(args.ctx, null, res, arr.get(i), i, arr);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@Native(thisArg = true) public static Object reduceRight(Context ctx, ArrayValue arr, FunctionValue func, Object... args) {
|
||||
@Expose public static Object __reduceRight(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
var func = args.convert(0, FunctionValue.class);
|
||||
var res = args.get(1);
|
||||
var i = arr.size();
|
||||
var res = arr.get(0);
|
||||
|
||||
if (args.length > 0) res = args[0];
|
||||
else while (!arr.has(i--) && i >= 0) res = arr.get(i);
|
||||
if (args.n() < 2) {
|
||||
while (!arr.has(i--) && i >= 0) {
|
||||
res = arr.get(i);
|
||||
}
|
||||
}
|
||||
else i--;
|
||||
|
||||
for (; i >= 0; i--) {
|
||||
if (arr.has(i)) {
|
||||
res = func.call(ctx, null, res, arr.get(i), i, arr);
|
||||
res = func.call(args.ctx, null, res, arr.get(i), i, arr);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@Native(thisArg = true) public static ArrayValue flat(Context ctx, ArrayValue arr, int depth) {
|
||||
@Expose public static ArrayValue __flat(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
var depth = args.getInt(0, 1);
|
||||
var res = new ArrayValue(arr.size());
|
||||
var stack = new Stack<Object>();
|
||||
var depths = new Stack<Integer>();
|
||||
@ -209,127 +247,169 @@ import me.topchetoeu.jscript.interop.NativeSetter;
|
||||
var el = stack.pop();
|
||||
int d = depths.pop();
|
||||
|
||||
if (d <= depth && el instanceof ArrayValue) {
|
||||
for (int i = ((ArrayValue)el).size() - 1; i >= 0; i--) {
|
||||
stack.push(((ArrayValue)el).get(i));
|
||||
if ((d == -1 || d < depth) && el instanceof ArrayValue) {
|
||||
var arrEl = (ArrayValue)el;
|
||||
for (int i = arrEl.size() - 1; i >= 0; i--) {
|
||||
if (!arrEl.has(i)) continue;
|
||||
stack.push(arrEl.get(i));
|
||||
depths.push(d + 1);
|
||||
}
|
||||
}
|
||||
else res.set(ctx, depth, arr);
|
||||
else res.set(args.ctx, res.size(), el);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@Native(thisArg = true) public static ArrayValue flatMap(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) {
|
||||
return flat(ctx, map(ctx, arr, cmp, thisArg), 1);
|
||||
@Expose public static ArrayValue __flatMap(Arguments args) {
|
||||
return __flat(new Arguments(args.ctx, __map(args), 1));
|
||||
}
|
||||
|
||||
@Native(thisArg = true) public static Object find(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) {
|
||||
@Expose public static Object __find(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
|
||||
for (int i = 0; i < arr.size(); i++) {
|
||||
if (arr.has(i) && Values.toBoolean(cmp.call(ctx, thisArg, arr.get(i), i, arr))) return arr.get(i);
|
||||
if (arr.has(i) && Values.toBoolean(Values.call(
|
||||
args.ctx, args.get(0), args.get(1),
|
||||
arr.get(i), i, args.self
|
||||
))) return arr.get(i);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
@Native(thisArg = true) public static Object findLast(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) {
|
||||
@Expose public static Object __findLast(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
|
||||
for (var i = arr.size() - 1; i >= 0; i--) {
|
||||
if (arr.has(i) && Values.toBoolean(cmp.call(ctx, thisArg, arr.get(i), i, arr))) return arr.get(i);
|
||||
if (arr.has(i) && Values.toBoolean(Values.call(
|
||||
args.ctx, args.get(0), args.get(1),
|
||||
arr.get(i), i, args.self
|
||||
))) return arr.get(i);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Native(thisArg = true) public static int findIndex(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) {
|
||||
@Expose public static int __findIndex(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
|
||||
for (int i = 0; i < arr.size(); i++) {
|
||||
if (arr.has(i) && Values.toBoolean(cmp.call(ctx, thisArg, arr.get(i), i, arr))) return i;
|
||||
if (arr.has(i) && Values.toBoolean(Values.call(
|
||||
args.ctx, args.get(0), args.get(1),
|
||||
arr.get(i), i, args.self
|
||||
))) return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
@Native(thisArg = true) public static int findLastIndex(Context ctx, ArrayValue arr, FunctionValue cmp, Object thisArg) {
|
||||
@Expose public static int __findLastIndex(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
|
||||
for (var i = arr.size() - 1; i >= 0; i--) {
|
||||
if (arr.has(i) && Values.toBoolean(cmp.call(ctx, thisArg, arr.get(i), i, arr))) return i;
|
||||
if (arr.has(i) && Values.toBoolean(Values.call(
|
||||
args.ctx, args.get(0), args.get(1),
|
||||
arr.get(i), i, args.self
|
||||
))) return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Native(thisArg = true) public static int indexOf(Context ctx, ArrayValue arr, Object val, int start) {
|
||||
start = normalizeI(arr.size(), start, true);
|
||||
@Expose public static int __indexOf(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
var val = args.get(0);
|
||||
var start = normalizeI(arr.size(), args.getInt(1), true);
|
||||
|
||||
for (int i = start; i < arr.size(); i++) {
|
||||
if (Values.strictEquals(ctx, arr.get(i), val)) return i;
|
||||
if (Values.strictEquals(args.ctx, arr.get(i), val)) return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
@Native(thisArg = true) public static int lastIndexOf(Context ctx, ArrayValue arr, Object val, int start) {
|
||||
start = normalizeI(arr.size(), start, true);
|
||||
@Expose public static int __lastIndexOf(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
var val = args.get(0);
|
||||
var start = normalizeI(arr.size(), args.getInt(1), true);
|
||||
|
||||
for (int i = arr.size(); i >= start; i--) {
|
||||
if (Values.strictEquals(ctx, arr.get(i), val)) return i;
|
||||
if (Values.strictEquals(args.ctx, arr.get(i), val)) return i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
@Native(thisArg = true) public static boolean includes(Context ctx, ArrayValue arr, Object el, int start) {
|
||||
return indexOf(ctx, arr, el, start) >= 0;
|
||||
@Expose public static boolean __includes(Arguments args) {
|
||||
return __indexOf(args) >= 0;
|
||||
}
|
||||
|
||||
@Native(thisArg = true) public static Object pop(Context ctx, ArrayValue arr) {
|
||||
@Expose public static Object __pop(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
if (arr.size() == 0) return null;
|
||||
|
||||
var val = arr.get(arr.size() - 1);
|
||||
arr.shrink(1);
|
||||
return val;
|
||||
}
|
||||
@Native(thisArg = true) public static int push(Context ctx, ArrayValue arr, Object ...values) {
|
||||
arr.copyFrom(ctx, values, 0, arr.size(), values.length);
|
||||
@Expose public static int __push(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
var values = args.args;
|
||||
|
||||
arr.copyFrom(args.ctx, values, 0, arr.size(), values.length);
|
||||
return arr.size();
|
||||
}
|
||||
|
||||
@Native(thisArg = true) public static Object shift(Context ctx, ArrayValue arr) {
|
||||
@Expose public static Object __shift(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
|
||||
if (arr.size() == 0) return null;
|
||||
var val = arr.get(0);
|
||||
|
||||
arr.move(1, 0, arr.size());
|
||||
arr.shrink(1);
|
||||
return val;
|
||||
}
|
||||
@Native(thisArg = true) public static int unshift(Context ctx, ArrayValue arr, Object ...values) {
|
||||
@Expose public static int __unshift(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
var values = args.slice(0).args;
|
||||
|
||||
arr.move(0, values.length, arr.size());
|
||||
arr.copyFrom(ctx, values, 0, 0, values.length);
|
||||
arr.copyFrom(args.ctx, values, 0, 0, values.length);
|
||||
return arr.size();
|
||||
}
|
||||
|
||||
@Native(thisArg = true) public static ArrayValue slice(Context ctx, ArrayValue arr, int start, Object _end) {
|
||||
start = normalizeI(arr.size(), start, true);
|
||||
int end = normalizeI(arr.size(), (int)(_end == null ? arr.size() : Values.toNumber(ctx, _end)), true);
|
||||
@Expose public static ArrayValue __slice(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
var start = normalizeI(arr.size(), args.getInt(0), true);
|
||||
var end = normalizeI(arr.size(), args.getInt(1, arr.size()), true);
|
||||
|
||||
var res = new ArrayValue(end - start);
|
||||
arr.copyTo(ctx, res, start, 0, end - start);
|
||||
arr.copyTo(args.ctx, res, start, 0, end - start);
|
||||
return res;
|
||||
}
|
||||
|
||||
@Native(thisArg = true) public static ArrayValue splice(Context ctx, ArrayValue arr, int start, Object _deleteCount, Object ...items) {
|
||||
start = normalizeI(arr.size(), start, true);
|
||||
int deleteCount = _deleteCount == null ? arr.size() - 1 : (int)Values.toNumber(ctx, _deleteCount);
|
||||
deleteCount = normalizeI(arr.size(), deleteCount, true);
|
||||
@Expose public static ArrayValue __splice(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
var start = normalizeI(arr.size(), args.getInt(0), true);
|
||||
var deleteCount = normalizeI(arr.size(), args.getInt(1, arr.size()), true);
|
||||
var items = args.slice(2).args;
|
||||
|
||||
if (start + deleteCount >= arr.size()) deleteCount = arr.size() - start;
|
||||
|
||||
var size = arr.size() - deleteCount + items.length;
|
||||
var res = new ArrayValue(deleteCount);
|
||||
arr.copyTo(ctx, res, start, 0, deleteCount);
|
||||
arr.copyTo(args.ctx, res, start, 0, deleteCount);
|
||||
arr.move(start + deleteCount, start + items.length, arr.size() - start - deleteCount);
|
||||
arr.copyFrom(ctx, items, 0, start, items.length);
|
||||
arr.copyFrom(args.ctx, items, 0, start, items.length);
|
||||
arr.setSize(size);
|
||||
|
||||
return res;
|
||||
}
|
||||
@Native(thisArg = true) public static String toString(Context ctx, ArrayValue arr) {
|
||||
return join(ctx, arr, ",");
|
||||
@Expose public static String __toString(Arguments args) {
|
||||
return __join(new Arguments(args.ctx, args.self, ","));
|
||||
}
|
||||
|
||||
@Native(thisArg = true) public static String join(Context ctx, ArrayValue arr, String sep) {
|
||||
@Expose public static String __join(Arguments args) {
|
||||
var arr = args.self(ArrayValue.class);
|
||||
var sep = args.getString(0, ", ");
|
||||
var res = new StringBuilder();
|
||||
var comma = false;
|
||||
|
||||
@ -342,30 +422,33 @@ import me.topchetoeu.jscript.interop.NativeSetter;
|
||||
var el = arr.get(i);
|
||||
if (el == null || el == Values.NULL) continue;
|
||||
|
||||
res.append(Values.toString(ctx, el));
|
||||
res.append(Values.toString(args.ctx, el));
|
||||
}
|
||||
|
||||
return res.toString();
|
||||
}
|
||||
|
||||
@Native public static boolean isArray(Context ctx, Object val) { return val instanceof ArrayValue; }
|
||||
@Native public static ArrayValue of(Context ctx, Object... args) {
|
||||
var res = new ArrayValue(args.length);
|
||||
res.copyFrom(ctx, args, 0, 0, args.length);
|
||||
return res;
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static boolean __isArray(Arguments args) {
|
||||
return args.get(0) instanceof ArrayValue;
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static ArrayValue __of(Arguments args) {
|
||||
return new ArrayValue(args.ctx, args.slice(0).args);
|
||||
}
|
||||
|
||||
@NativeConstructor public static ArrayValue constructor(Context ctx, Object... args) {
|
||||
@ExposeConstructor public static ArrayValue __constructor(Arguments args) {
|
||||
ArrayValue res;
|
||||
|
||||
if (args.length == 1 && args[0] instanceof Number) {
|
||||
int len = ((Number)args[0]).intValue();
|
||||
if (args.n() == 1 && args.get(0) instanceof Number) {
|
||||
var len = args.getInt(0);
|
||||
res = new ArrayValue(len);
|
||||
res.setSize(len);
|
||||
}
|
||||
else {
|
||||
res = new ArrayValue(args.length);
|
||||
res.copyFrom(ctx, args, 0, 0, args.length);
|
||||
var val = args.args;
|
||||
res = new ArrayValue(val.length);
|
||||
res.copyFrom(args.ctx, val, 0, 0, val.length);
|
||||
}
|
||||
|
||||
return res;
|
||||
|
@ -2,61 +2,65 @@ package me.topchetoeu.jscript.lib;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.frame.CodeFrame;
|
||||
import me.topchetoeu.jscript.engine.frame.Runners;
|
||||
import me.topchetoeu.jscript.engine.values.CodeFunction;
|
||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.engine.values.NativeFunction;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.Arguments;
|
||||
import me.topchetoeu.jscript.interop.WrapperName;
|
||||
import me.topchetoeu.jscript.lib.PromiseLib.Handle;
|
||||
|
||||
@Native("AsyncFunction") public class AsyncFunctionLib extends FunctionValue {
|
||||
@WrapperName("AsyncFunction")
|
||||
public class AsyncFunctionLib extends FunctionValue {
|
||||
public final FunctionValue factory;
|
||||
|
||||
public static class AsyncHelper {
|
||||
private static class AsyncHelper {
|
||||
public PromiseLib promise = new PromiseLib();
|
||||
public CodeFrame frame;
|
||||
|
||||
private boolean awaiting = false;
|
||||
|
||||
private void next(Context ctx, Object inducedValue, Object inducedError) {
|
||||
private void next(Context ctx, Object inducedValue, EngineException inducedError) {
|
||||
Object res = null;
|
||||
ctx.pushFrame(frame);
|
||||
|
||||
frame.onPush();
|
||||
awaiting = false;
|
||||
while (!awaiting) {
|
||||
try {
|
||||
res = frame.next(ctx, inducedValue, Runners.NO_RETURN, inducedError == Runners.NO_RETURN ? null : new EngineException(inducedError));
|
||||
inducedValue = inducedError = Runners.NO_RETURN;
|
||||
if (res != Runners.NO_RETURN) {
|
||||
res = frame.next(inducedValue, Values.NO_RETURN, inducedError);
|
||||
inducedValue = Values.NO_RETURN;
|
||||
inducedError = null;
|
||||
|
||||
if (res != Values.NO_RETURN) {
|
||||
promise.fulfill(ctx, res);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (EngineException e) {
|
||||
promise.reject(ctx, e.value);
|
||||
promise.reject(ctx, e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ctx.popFrame(frame);
|
||||
frame.onPop();
|
||||
|
||||
if (awaiting) {
|
||||
PromiseLib.then(ctx, frame.pop(), new NativeFunction(this::fulfill), new NativeFunction(this::reject));
|
||||
PromiseLib.handle(ctx, frame.pop(), new Handle() {
|
||||
@Override
|
||||
public void onFulfil(Object val) {
|
||||
next(ctx, val, null);
|
||||
}
|
||||
@Override
|
||||
public void onReject(EngineException err) {
|
||||
next(ctx, Values.NO_RETURN, err);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public Object fulfill(Context ctx, Object thisArg, Object ...args) {
|
||||
next(ctx, args.length > 0 ? args[0] : null, Runners.NO_RETURN);
|
||||
return null;
|
||||
}
|
||||
public Object reject(Context ctx, Object thisArg, Object ...args) {
|
||||
next(ctx, Runners.NO_RETURN, args.length > 0 ? args[0] : null);
|
||||
return null;
|
||||
}
|
||||
|
||||
public Object await(Context ctx, Object thisArg, Object[] args) {
|
||||
public Object await(Arguments args) {
|
||||
this.awaiting = true;
|
||||
return args.length > 0 ? args[0] : null;
|
||||
return args.get(0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -66,7 +70,7 @@ import me.topchetoeu.jscript.interop.Native;
|
||||
var func = factory.call(ctx, thisArg, new NativeFunction("await", handler::await));
|
||||
if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function.");
|
||||
handler.frame = new CodeFrame(ctx, thisArg, args, (CodeFunction)func);
|
||||
handler.next(ctx, Runners.NO_RETURN, Runners.NO_RETURN);
|
||||
handler.next(ctx, Values.NO_RETURN, null);
|
||||
return handler.promise;
|
||||
}
|
||||
|
||||
|
@ -6,9 +6,10 @@ import me.topchetoeu.jscript.engine.values.CodeFunction;
|
||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.engine.values.NativeFunction;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.WrapperName;
|
||||
|
||||
@Native("AsyncGeneratorFunction") public class AsyncGeneratorFunctionLib extends FunctionValue {
|
||||
@WrapperName("AsyncGeneratorFunction")
|
||||
public class AsyncGeneratorFunctionLib extends FunctionValue {
|
||||
public final FunctionValue factory;
|
||||
|
||||
@Override
|
||||
|
@ -4,38 +4,42 @@ import java.util.Map;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.frame.CodeFrame;
|
||||
import me.topchetoeu.jscript.engine.frame.Runners;
|
||||
import me.topchetoeu.jscript.engine.values.NativeFunction;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.Arguments;
|
||||
import me.topchetoeu.jscript.interop.Expose;
|
||||
import me.topchetoeu.jscript.interop.WrapperName;
|
||||
import me.topchetoeu.jscript.lib.PromiseLib.Handle;
|
||||
|
||||
@Native("AsyncGenerator") public class AsyncGeneratorLib {
|
||||
@Native("@@Symbol.typeName") public final String name = "AsyncGenerator";
|
||||
@WrapperName("AsyncGenerator")
|
||||
public class AsyncGeneratorLib {
|
||||
private int state = 0;
|
||||
private boolean done = false;
|
||||
private PromiseLib currPromise;
|
||||
public CodeFrame frame;
|
||||
|
||||
private void next(Context ctx, Object inducedValue, Object inducedReturn, Object inducedError) {
|
||||
private void next(Context ctx, Object inducedValue, Object inducedReturn, EngineException inducedError) {
|
||||
if (done) {
|
||||
if (inducedError != Runners.NO_RETURN) throw new EngineException(inducedError);
|
||||
if (inducedError != null) throw inducedError;
|
||||
currPromise.fulfill(ctx, new ObjectValue(ctx, Map.of(
|
||||
"done", true,
|
||||
"value", inducedReturn == Runners.NO_RETURN ? null : inducedReturn
|
||||
"value", inducedReturn == Values.NO_RETURN ? null : inducedReturn
|
||||
)));
|
||||
return;
|
||||
}
|
||||
|
||||
Object res = null;
|
||||
ctx.pushFrame(frame);
|
||||
state = 0;
|
||||
|
||||
frame.onPush();
|
||||
while (state == 0) {
|
||||
try {
|
||||
res = frame.next(ctx, inducedValue, inducedReturn, inducedError == Runners.NO_RETURN ? null : new EngineException(inducedError));
|
||||
inducedValue = inducedReturn = inducedError = Runners.NO_RETURN;
|
||||
if (res != Runners.NO_RETURN) {
|
||||
res = frame.next(inducedValue, inducedReturn, inducedError);
|
||||
inducedValue = inducedReturn = Values.NO_RETURN;
|
||||
inducedError = null;
|
||||
|
||||
if (res != Values.NO_RETURN) {
|
||||
var obj = new ObjectValue();
|
||||
obj.defineProperty(ctx, "done", true);
|
||||
obj.defineProperty(ctx, "value", res);
|
||||
@ -44,15 +48,21 @@ import me.topchetoeu.jscript.interop.Native;
|
||||
}
|
||||
}
|
||||
catch (EngineException e) {
|
||||
currPromise.reject(ctx, e.value);
|
||||
currPromise.reject(ctx, e);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ctx.popFrame(frame);
|
||||
frame.onPop();
|
||||
|
||||
if (state == 1) {
|
||||
PromiseLib.then(ctx, frame.pop(), new NativeFunction(this::fulfill), new NativeFunction(this::reject));
|
||||
PromiseLib.handle(ctx, frame.pop(), new Handle() {
|
||||
@Override public void onFulfil(Object val) {
|
||||
next(ctx, val, Values.NO_RETURN, null);
|
||||
}
|
||||
@Override public void onReject(EngineException err) {
|
||||
next(ctx, Values.NO_RETURN, Values.NO_RETURN, err);
|
||||
}
|
||||
});
|
||||
}
|
||||
else if (state == 2) {
|
||||
var obj = new ObjectValue();
|
||||
@ -69,42 +79,29 @@ import me.topchetoeu.jscript.interop.Native;
|
||||
return "Generator [running]";
|
||||
}
|
||||
|
||||
public Object fulfill(Context ctx, Object thisArg, Object ...args) {
|
||||
next(ctx, args.length > 0 ? args[0] : null, Runners.NO_RETURN, Runners.NO_RETURN);
|
||||
return null;
|
||||
}
|
||||
public Object reject(Context ctx, Object thisArg, Object ...args) {
|
||||
next(ctx, Runners.NO_RETURN, args.length > 0 ? args[0] : null, Runners.NO_RETURN);
|
||||
return null;
|
||||
}
|
||||
|
||||
@Native
|
||||
public PromiseLib next(Context ctx, Object ...args) {
|
||||
this.currPromise = new PromiseLib();
|
||||
if (args.length == 0) next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, Runners.NO_RETURN);
|
||||
else next(ctx, args[0], Runners.NO_RETURN, Runners.NO_RETURN);
|
||||
return this.currPromise;
|
||||
}
|
||||
@Native("throw")
|
||||
public PromiseLib _throw(Context ctx, Object error) {
|
||||
this.currPromise = new PromiseLib();
|
||||
next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, error);
|
||||
return this.currPromise;
|
||||
}
|
||||
@Native("return")
|
||||
public PromiseLib _return(Context ctx, Object value) {
|
||||
this.currPromise = new PromiseLib();
|
||||
next(ctx, Runners.NO_RETURN, value, Runners.NO_RETURN);
|
||||
return this.currPromise;
|
||||
}
|
||||
|
||||
|
||||
public Object await(Context ctx, Object thisArg, Object[] args) {
|
||||
public Object await(Arguments args) {
|
||||
this.state = 1;
|
||||
return args.length > 0 ? args[0] : null;
|
||||
return args.get(0);
|
||||
}
|
||||
public Object yield(Context ctx, Object thisArg, Object[] args) {
|
||||
public Object yield(Arguments args) {
|
||||
this.state = 2;
|
||||
return args.length > 0 ? args[0] : null;
|
||||
return args.get(0);
|
||||
}
|
||||
|
||||
@Expose public PromiseLib __next(Arguments args) {
|
||||
this.currPromise = new PromiseLib();
|
||||
if (args.has(0)) next(args.ctx, args.get(0), Values.NO_RETURN, null);
|
||||
else next(args.ctx, Values.NO_RETURN, Values.NO_RETURN, null);
|
||||
return this.currPromise;
|
||||
}
|
||||
@Expose public PromiseLib __return(Arguments args) {
|
||||
this.currPromise = new PromiseLib();
|
||||
next(args.ctx, Values.NO_RETURN, args.get(0), null);
|
||||
return this.currPromise;
|
||||
}
|
||||
@Expose public PromiseLib __throw(Arguments args) {
|
||||
this.currPromise = new PromiseLib();
|
||||
next(args.ctx, Values.NO_RETURN, Values.NO_RETURN, new EngineException(args.get(0)).setCtx(args.ctx));
|
||||
return this.currPromise;
|
||||
}
|
||||
}
|
@ -1,30 +1,37 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeConstructor;
|
||||
import me.topchetoeu.jscript.interop.Arguments;
|
||||
import me.topchetoeu.jscript.interop.Expose;
|
||||
import me.topchetoeu.jscript.interop.ExposeConstructor;
|
||||
import me.topchetoeu.jscript.interop.WrapperName;
|
||||
|
||||
@Native("Boolean") public class BooleanLib {
|
||||
@WrapperName("Boolean")
|
||||
public class BooleanLib {
|
||||
public static final BooleanLib TRUE = new BooleanLib(true);
|
||||
public static final BooleanLib FALSE = new BooleanLib(false);
|
||||
|
||||
public final boolean value;
|
||||
|
||||
@NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object val) {
|
||||
val = Values.toBoolean(val);
|
||||
if (thisArg instanceof ObjectValue) return (boolean)val ? TRUE : FALSE;
|
||||
else return val;
|
||||
}
|
||||
@Native(thisArg = true) public static String toString(Context ctx, Object thisArg) {
|
||||
return Values.toBoolean(thisArg) ? "true" : "false";
|
||||
}
|
||||
@Native(thisArg = true) public static boolean valueOf(Context ctx, Object thisArg) {
|
||||
return Values.toBoolean(thisArg);
|
||||
@Override public String toString() {
|
||||
return value + "";
|
||||
}
|
||||
|
||||
public BooleanLib(boolean val) {
|
||||
this.value = val;
|
||||
}
|
||||
|
||||
@ExposeConstructor public static Object __constructor(Arguments args) {
|
||||
var val = args.getBoolean(0);
|
||||
if (args.self instanceof ObjectValue) return val ? TRUE : FALSE;
|
||||
else return val;
|
||||
}
|
||||
@Expose public static String __toString(Arguments args) {
|
||||
return args.self(Boolean.class) ? "true" : "false";
|
||||
}
|
||||
@Expose public static boolean __valueOf(Arguments args) {
|
||||
if (Values.isWrapper(args.self, BooleanLib.class)) return Values.wrapper(args.self, BooleanLib.class).value;
|
||||
return args.self(Boolean.class);
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,17 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.Arguments;
|
||||
import me.topchetoeu.jscript.interop.Expose;
|
||||
import me.topchetoeu.jscript.interop.ExposeConstructor;
|
||||
import me.topchetoeu.jscript.interop.ExposeTarget;
|
||||
import me.topchetoeu.jscript.interop.WrapperName;
|
||||
|
||||
@Native("Date") public class DateLib {
|
||||
@WrapperName("Date")
|
||||
public class DateLib {
|
||||
private Calendar normal;
|
||||
private Calendar utc;
|
||||
|
||||
@ -22,244 +27,221 @@ import me.topchetoeu.jscript.interop.Native;
|
||||
normal = utc = null;
|
||||
}
|
||||
|
||||
@Native
|
||||
public static double now() {
|
||||
return new DateLib().getTime();
|
||||
}
|
||||
|
||||
@Native
|
||||
public double getYear() {
|
||||
@Expose public double __getYear() {
|
||||
if (normal == null) return Double.NaN;
|
||||
return normal.get(Calendar.YEAR) - 1900;
|
||||
}
|
||||
@Native
|
||||
public double setYear(Context ctx, double real) {
|
||||
@Expose public double __setYeard(Arguments args) {
|
||||
var real = args.getDouble(0);
|
||||
if (real >= 0 && real <= 99) real = real + 1900;
|
||||
if (Double.isNaN(real)) invalidate();
|
||||
else normal.set(Calendar.YEAR, (int)real);
|
||||
updateUTC();
|
||||
return getTime();
|
||||
return __getTime();
|
||||
}
|
||||
|
||||
@Native
|
||||
public double getFullYear() {
|
||||
@Expose public double __getFullYear() {
|
||||
if (normal == null) return Double.NaN;
|
||||
return normal.get(Calendar.YEAR);
|
||||
}
|
||||
@Native
|
||||
public double getMonth() {
|
||||
@Expose public double __getMonth() {
|
||||
if (normal == null) return Double.NaN;
|
||||
return normal.get(Calendar.MONTH);
|
||||
}
|
||||
@Native
|
||||
public double getDate() {
|
||||
@Expose public double __getDate() {
|
||||
if (normal == null) return Double.NaN;
|
||||
return normal.get(Calendar.DAY_OF_MONTH);
|
||||
}
|
||||
@Native
|
||||
public double getDay() {
|
||||
@Expose public double __getDay() {
|
||||
if (normal == null) return Double.NaN;
|
||||
return normal.get(Calendar.DAY_OF_WEEK);
|
||||
}
|
||||
@Native
|
||||
public double getHours() {
|
||||
@Expose public double __getHours() {
|
||||
if (normal == null) return Double.NaN;
|
||||
return normal.get(Calendar.HOUR_OF_DAY);
|
||||
}
|
||||
@Native
|
||||
public double getMinutes() {
|
||||
@Expose public double __getMinutes() {
|
||||
if (normal == null) return Double.NaN;
|
||||
return normal.get(Calendar.MINUTE);
|
||||
}
|
||||
@Native
|
||||
public double getSeconds() {
|
||||
@Expose public double __getSeconds() {
|
||||
if (normal == null) return Double.NaN;
|
||||
return normal.get(Calendar.SECOND);
|
||||
}
|
||||
@Native
|
||||
public double getMilliseconds() {
|
||||
@Expose public double __getMilliseconds() {
|
||||
if (normal == null) return Double.NaN;
|
||||
return normal.get(Calendar.MILLISECOND);
|
||||
}
|
||||
|
||||
@Native
|
||||
public double getUTCFullYear() {
|
||||
@Expose public double __getUTCFullYear() {
|
||||
if (utc == null) return Double.NaN;
|
||||
return utc.get(Calendar.YEAR);
|
||||
}
|
||||
@Native
|
||||
public double getUTCMonth() {
|
||||
@Expose public double __getUTCMonth() {
|
||||
if (utc == null) return Double.NaN;
|
||||
return utc.get(Calendar.MONTH);
|
||||
}
|
||||
@Native
|
||||
public double getUTCDate() {
|
||||
@Expose public double __getUTCDate() {
|
||||
if (utc == null) return Double.NaN;
|
||||
return utc.get(Calendar.DAY_OF_MONTH);
|
||||
}
|
||||
@Native
|
||||
public double getUTCDay() {
|
||||
@Expose public double __getUTCDay() {
|
||||
if (utc == null) return Double.NaN;
|
||||
return utc.get(Calendar.DAY_OF_WEEK);
|
||||
}
|
||||
@Native
|
||||
public double getUTCHours() {
|
||||
@Expose public double __getUTCHours() {
|
||||
if (utc == null) return Double.NaN;
|
||||
return utc.get(Calendar.HOUR_OF_DAY);
|
||||
}
|
||||
@Native
|
||||
public double getUTCMinutes() {
|
||||
@Expose public double __getUTCMinutes() {
|
||||
if (utc == null) return Double.NaN;
|
||||
return utc.get(Calendar.MINUTE);
|
||||
}
|
||||
@Native
|
||||
public double getUTCSeconds() {
|
||||
@Expose public double __getUTCSeconds() {
|
||||
if (utc == null) return Double.NaN;
|
||||
return utc.get(Calendar.SECOND);
|
||||
}
|
||||
@Native
|
||||
public double getUTCMilliseconds() {
|
||||
@Expose public double __getUTCMilliseconds() {
|
||||
if (utc == null) return Double.NaN;
|
||||
return utc.get(Calendar.MILLISECOND);
|
||||
}
|
||||
|
||||
@Native
|
||||
public double setFullYear(Context ctx, double real) {
|
||||
@Expose public double __setFullYear(Arguments args) {
|
||||
var real = args.getDouble(0);
|
||||
if (Double.isNaN(real)) invalidate();
|
||||
else normal.set(Calendar.YEAR, (int)real);
|
||||
updateUTC();
|
||||
return getTime();
|
||||
return __getTime();
|
||||
}
|
||||
@Native
|
||||
public double setMonth(Context ctx, double real) {
|
||||
@Expose public double __setMonthd(Arguments args) {
|
||||
var real = args.getDouble(0);
|
||||
if (Double.isNaN(real)) invalidate();
|
||||
else normal.set(Calendar.MONTH, (int)real);
|
||||
updateUTC();
|
||||
return getTime();
|
||||
return __getTime();
|
||||
}
|
||||
@Native
|
||||
public double setDate(Context ctx, double real) {
|
||||
@Expose public double __setDated(Arguments args) {
|
||||
var real = args.getDouble(0);
|
||||
if (Double.isNaN(real)) invalidate();
|
||||
else normal.set(Calendar.DAY_OF_MONTH, (int)real);
|
||||
updateUTC();
|
||||
return getTime();
|
||||
return __getTime();
|
||||
}
|
||||
@Native
|
||||
public double setDay(Context ctx, double real) {
|
||||
@Expose public double __setDayd(Arguments args) {
|
||||
var real = args.getDouble(0);
|
||||
if (Double.isNaN(real)) invalidate();
|
||||
else normal.set(Calendar.DAY_OF_WEEK, (int)real);
|
||||
updateUTC();
|
||||
return getTime();
|
||||
return __getTime();
|
||||
}
|
||||
@Native
|
||||
public double setHours(Context ctx, double real) {
|
||||
@Expose public double __setHoursd(Arguments args) {
|
||||
var real = args.getDouble(0);
|
||||
if (Double.isNaN(real)) invalidate();
|
||||
else normal.set(Calendar.HOUR_OF_DAY, (int)real);
|
||||
updateUTC();
|
||||
return getTime();
|
||||
return __getTime();
|
||||
}
|
||||
@Native
|
||||
public double setMinutes(Context ctx, double real) {
|
||||
@Expose public double __setMinutesd(Arguments args) {
|
||||
var real = args.getDouble(0);
|
||||
if (Double.isNaN(real)) invalidate();
|
||||
else normal.set(Calendar.MINUTE, (int)real);
|
||||
updateUTC();
|
||||
return getTime();
|
||||
return __getTime();
|
||||
}
|
||||
@Native
|
||||
public double setSeconds(Context ctx, double real) {
|
||||
@Expose public double __setSecondsd(Arguments args) {
|
||||
var real = args.getDouble(0);
|
||||
if (Double.isNaN(real)) invalidate();
|
||||
else normal.set(Calendar.SECOND, (int)real);
|
||||
updateUTC();
|
||||
return getTime();
|
||||
return __getTime();
|
||||
}
|
||||
@Native
|
||||
public double setMilliseconds(Context ctx, double real) {
|
||||
@Expose public double __setMillisecondsd(Arguments args) {
|
||||
var real = args.getDouble(0);
|
||||
if (Double.isNaN(real)) invalidate();
|
||||
else normal.set(Calendar.MILLISECOND, (int)real);
|
||||
updateUTC();
|
||||
return getTime();
|
||||
return __getTime();
|
||||
}
|
||||
|
||||
@Native
|
||||
public double setUTCFullYear(Context ctx, double real) {
|
||||
@Expose public double __setUTCFullYeard(Arguments args) {
|
||||
var real = args.getDouble(0);
|
||||
if (Double.isNaN(real)) invalidate();
|
||||
else utc.set(Calendar.YEAR, (int)real);
|
||||
updateNormal();
|
||||
return getTime();
|
||||
return __getTime();
|
||||
}
|
||||
@Native
|
||||
public double setUTCMonth(Context ctx, double real) {
|
||||
@Expose public double __setUTCMonthd(Arguments args) {
|
||||
var real = args.getDouble(0);
|
||||
if (Double.isNaN(real)) invalidate();
|
||||
else utc.set(Calendar.MONTH, (int)real);
|
||||
updateNormal();
|
||||
return getTime();
|
||||
return __getTime();
|
||||
}
|
||||
@Native
|
||||
public double setUTCDate(Context ctx, double real) {
|
||||
@Expose public double __setUTCDated(Arguments args) {
|
||||
var real = args.getDouble(0);
|
||||
if (Double.isNaN(real)) invalidate();
|
||||
else utc.set(Calendar.DAY_OF_MONTH, (int)real);
|
||||
updateNormal();
|
||||
return getTime();
|
||||
return __getTime();
|
||||
}
|
||||
@Native
|
||||
public double setUTCDay(Context ctx, double real) {
|
||||
@Expose public double __setUTCDayd(Arguments args) {
|
||||
var real = args.getDouble(0);
|
||||
if (Double.isNaN(real)) invalidate();
|
||||
else utc.set(Calendar.DAY_OF_WEEK, (int)real);
|
||||
updateNormal();
|
||||
return getTime();
|
||||
return __getTime();
|
||||
}
|
||||
@Native
|
||||
public double setUTCHours(Context ctx, double real) {
|
||||
@Expose public double __setUTCHoursd(Arguments args) {
|
||||
var real = args.getDouble(0);
|
||||
if (Double.isNaN(real)) invalidate();
|
||||
else utc.set(Calendar.HOUR_OF_DAY, (int)real);
|
||||
updateNormal();
|
||||
return getTime();
|
||||
return __getTime();
|
||||
}
|
||||
@Native
|
||||
public double setUTCMinutes(Context ctx, double real) {
|
||||
@Expose public double __setUTCMinutesd(Arguments args) {
|
||||
var real = args.getDouble(0);
|
||||
if (Double.isNaN(real)) invalidate();
|
||||
else utc.set(Calendar.MINUTE, (int)real);
|
||||
updateNormal();
|
||||
return getTime();
|
||||
return __getTime();
|
||||
}
|
||||
@Native
|
||||
public double setUTCSeconds(Context ctx, double real) {
|
||||
@Expose public double __setUTCSecondsd(Arguments args) {
|
||||
var real = args.getDouble(0);
|
||||
if (Double.isNaN(real)) invalidate();
|
||||
else utc.set(Calendar.SECOND, (int)real);
|
||||
updateNormal();
|
||||
return getTime();
|
||||
return __getTime();
|
||||
}
|
||||
@Native
|
||||
public double setUTCMilliseconds(Context ctx, double real) {
|
||||
@Expose public double __setUTCMillisecondsd(Arguments args) {
|
||||
var real = args.getDouble(0);
|
||||
if (Double.isNaN(real)) invalidate();
|
||||
else utc.set(Calendar.MILLISECOND, (int)real);
|
||||
updateNormal();
|
||||
return getTime();
|
||||
return __getTime();
|
||||
}
|
||||
|
||||
@Native
|
||||
public double getTime() {
|
||||
@Expose public double __getTime() {
|
||||
if (utc == null) return Double.NaN;
|
||||
return utc.getTimeInMillis();
|
||||
}
|
||||
@Native
|
||||
public double getTimezoneOffset() {
|
||||
@Expose public double __getTimezoneOffset() {
|
||||
if (normal == null) return Double.NaN;
|
||||
return normal.getTimeZone().getRawOffset() / 60000;
|
||||
}
|
||||
|
||||
@Native
|
||||
public double valueOf() {
|
||||
@Expose public double __valueOf() {
|
||||
if (normal == null) return Double.NaN;
|
||||
else return normal.getTimeInMillis();
|
||||
}
|
||||
|
||||
@Native
|
||||
public String toString() {
|
||||
@Expose public String __toString() {
|
||||
return normal.getTime().toString();
|
||||
}
|
||||
|
||||
@Native
|
||||
@Override public String toString() {
|
||||
return __toString();
|
||||
}
|
||||
|
||||
public DateLib(long timestamp) {
|
||||
normal = Calendar.getInstance();
|
||||
utc = Calendar.getInstance();
|
||||
@ -268,8 +250,17 @@ import me.topchetoeu.jscript.interop.Native;
|
||||
utc.setTimeInMillis(timestamp);
|
||||
}
|
||||
|
||||
@Native
|
||||
public DateLib() {
|
||||
this(new java.util.Date().getTime());
|
||||
this(new Date().getTime());
|
||||
}
|
||||
|
||||
@ExposeConstructor public static DateLib init(Arguments args) {
|
||||
if (args.has(0)) return new DateLib(args.getLong(0));
|
||||
else return new DateLib();
|
||||
}
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __now() {
|
||||
return new DateLib().__getTime();
|
||||
}
|
||||
}
|
||||
|
@ -1,20 +1,89 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import me.topchetoeu.jscript.Buffer;
|
||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.interop.Arguments;
|
||||
import me.topchetoeu.jscript.interop.Expose;
|
||||
import me.topchetoeu.jscript.interop.ExposeTarget;
|
||||
import me.topchetoeu.jscript.interop.WrapperName;
|
||||
import me.topchetoeu.jscript.parsing.Parsing;
|
||||
|
||||
@Native("Encoding")
|
||||
@WrapperName("Encoding")
|
||||
public class EncodingLib {
|
||||
@Native public static ArrayValue encode(String value) {
|
||||
private static final String HEX = "0123456789ABCDEF";
|
||||
|
||||
public static String encodeUriAny(String str, String keepAlphabet) {
|
||||
if (str == null) str = "undefined";
|
||||
|
||||
var bytes = str.getBytes();
|
||||
var sb = new StringBuilder(bytes.length);
|
||||
|
||||
for (byte c : bytes) {
|
||||
if (Parsing.isAlphanumeric((char)c) || Parsing.isAny((char)c, keepAlphabet)) sb.append((char)c);
|
||||
else {
|
||||
sb.append('%');
|
||||
sb.append(HEX.charAt(c / 16));
|
||||
sb.append(HEX.charAt(c % 16));
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
}
|
||||
public static String decodeUriAny(String str, String keepAlphabet) {
|
||||
if (str == null) str = "undefined";
|
||||
|
||||
var res = new Buffer();
|
||||
var bytes = str.getBytes();
|
||||
|
||||
for (var i = 0; i < bytes.length; i++) {
|
||||
var c = bytes[i];
|
||||
if (c == '%') {
|
||||
if (i >= bytes.length - 2) throw EngineException.ofError("URIError", "URI malformed.");
|
||||
var b = Parsing.fromHex((char)bytes[i + 1]) * 16 | Parsing.fromHex((char)bytes[i + 2]);
|
||||
if (!Parsing.isAny((char)b, keepAlphabet)) {
|
||||
i += 2;
|
||||
res.append((byte)b);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
res.append(c);
|
||||
}
|
||||
|
||||
return new String(res.data());
|
||||
}
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static ArrayValue __encode(Arguments args) {
|
||||
var res = new ArrayValue();
|
||||
for (var el : value.getBytes()) res.set(null, res.size(), (int)el);
|
||||
for (var el : args.getString(0).getBytes()) res.set(null, res.size(), (int)el);
|
||||
return res;
|
||||
}
|
||||
@Native public static String decode(Context ctx, ArrayValue raw) {
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static String __decode(Arguments args) {
|
||||
var raw = args.convert(0, ArrayList.class);
|
||||
var res = new byte[raw.size()];
|
||||
for (var i = 0; i < raw.size(); i++) res[i] = (byte)Values.toNumber(ctx, raw.get(i));
|
||||
for (var i = 0; i < raw.size(); i++) res[i] = (byte)Values.toNumber(args.ctx, raw.get(i));
|
||||
return new String(res);
|
||||
}
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static String __encodeURIComponent(Arguments args) {
|
||||
return EncodingLib.encodeUriAny(args.getString(0), ".-_!~*'()");
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static String __decodeURIComponent(Arguments args) {
|
||||
return decodeUriAny(args.getString(0), "");
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static String __encodeURI(Arguments args) {
|
||||
return encodeUriAny(args.getString(0), ";,/?:@&=+$#.-_!~*'()");
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static String __decodeURI(Arguments args) {
|
||||
return decodeUriAny(args.getString(0), ",/?:@&=+$#.");
|
||||
}
|
||||
}
|
||||
|
39
src/me/topchetoeu/jscript/lib/EnvironmentLib.java
Normal file
39
src/me/topchetoeu/jscript/lib/EnvironmentLib.java
Normal file
@ -0,0 +1,39 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Environment;
|
||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.interop.Arguments;
|
||||
import me.topchetoeu.jscript.interop.Expose;
|
||||
import me.topchetoeu.jscript.interop.ExposeType;
|
||||
import me.topchetoeu.jscript.interop.WrapperName;
|
||||
|
||||
@WrapperName("Environment")
|
||||
public class EnvironmentLib {
|
||||
private Environment env;
|
||||
|
||||
@Expose(value = "@@env", type = ExposeType.GETTER)
|
||||
public Environment __env() { return env; }
|
||||
|
||||
@Expose(type = ExposeType.GETTER)
|
||||
public int __id(Arguments args) {
|
||||
return env.hashCode();
|
||||
}
|
||||
@Expose(type = ExposeType.GETTER)
|
||||
public ObjectValue __global(Arguments args) {
|
||||
return env.global.obj;
|
||||
}
|
||||
|
||||
@Expose(type = ExposeType.GETTER)
|
||||
public FunctionValue __compile() {
|
||||
return Environment.compileFunc(env);
|
||||
}
|
||||
@Expose(type = ExposeType.SETTER)
|
||||
public void __compile(Arguments args) {
|
||||
env.add(Environment.COMPILE_FUNC, args.convert(0, FunctionValue.class));
|
||||
}
|
||||
|
||||
public EnvironmentLib(Environment env) {
|
||||
this.env = env;
|
||||
}
|
||||
}
|
@ -1,18 +1,19 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.Environment;
|
||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto;
|
||||
import me.topchetoeu.jscript.interop.InitType;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeConstructor;
|
||||
import me.topchetoeu.jscript.interop.NativeInit;
|
||||
import me.topchetoeu.jscript.exceptions.ConvertException;
|
||||
import me.topchetoeu.jscript.interop.WrapperName;
|
||||
import me.topchetoeu.jscript.interop.Arguments;
|
||||
import me.topchetoeu.jscript.interop.Expose;
|
||||
import me.topchetoeu.jscript.interop.ExposeConstructor;
|
||||
import me.topchetoeu.jscript.interop.ExposeField;
|
||||
|
||||
@Native("Error") public class ErrorLib {
|
||||
private static String toString(Context ctx, boolean rethrown, Object cause, Object name, Object message, ArrayValue stack) {
|
||||
@WrapperName("Error")
|
||||
public class ErrorLib {
|
||||
private static String toString(Context ctx, Object name, Object message) {
|
||||
if (name == null) name = "";
|
||||
else name = Values.toString(ctx, name).trim();
|
||||
if (message == null) message = "";
|
||||
@ -23,43 +24,31 @@ import me.topchetoeu.jscript.interop.NativeInit;
|
||||
if (!message.equals("") && !name.equals("")) res.append(": ");
|
||||
if (!message.equals("")) res.append(message);
|
||||
|
||||
if (cause instanceof ObjectValue) {
|
||||
if (rethrown) res.append("\n (rethrown)");
|
||||
else res.append("\nCaused by ").append(toString(ctx, cause));
|
||||
}
|
||||
|
||||
return res.toString();
|
||||
}
|
||||
|
||||
@Native(thisArg = true) public static String toString(Context ctx, Object thisArg) {
|
||||
if (thisArg instanceof ObjectValue) {
|
||||
var stack = Values.getMember(ctx, thisArg, "stack");
|
||||
if (!(stack instanceof ArrayValue)) stack = null;
|
||||
var cause = Values.getMember(ctx, thisArg, ctx.environment().symbol("Symbol.cause"));
|
||||
return toString(ctx,
|
||||
thisArg == cause,
|
||||
cause,
|
||||
Values.getMember(ctx, thisArg, "name"),
|
||||
Values.getMember(ctx, thisArg, "message"),
|
||||
(ArrayValue)stack
|
||||
);
|
||||
}
|
||||
@ExposeField public static final String __name = "Error";
|
||||
|
||||
@Expose public static String __toString(Arguments args) {
|
||||
if (args.self instanceof ObjectValue) return toString(args.ctx,
|
||||
Values.getMember(args.ctx, args.self, "name"),
|
||||
Values.getMember(args.ctx, args.self, "message")
|
||||
);
|
||||
else return "[Invalid error]";
|
||||
}
|
||||
|
||||
@NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) {
|
||||
@ExposeConstructor public static ObjectValue __constructor(Arguments args) {
|
||||
var target = new ObjectValue();
|
||||
if (thisArg instanceof ObjectValue) target = (ObjectValue)thisArg;
|
||||
var message = args.getString(0, "");
|
||||
|
||||
try {
|
||||
target = args.self(ObjectValue.class);
|
||||
}
|
||||
catch (ConvertException e) {}
|
||||
|
||||
target.setPrototype(PlaceholderProto.ERROR);
|
||||
target.defineProperty(ctx, "stack", ArrayValue.of(ctx, ctx.stackTrace()));
|
||||
if (message == null) target.defineProperty(ctx, "message", "");
|
||||
else target.defineProperty(ctx, "message", Values.toString(ctx, message));
|
||||
target.defineProperty(args.ctx, "message", Values.toString(args.ctx, message));
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
@NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) {
|
||||
target.defineProperty(null, "name", "Error");
|
||||
}
|
||||
}
|
||||
|
@ -1,27 +1,27 @@
|
||||
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;
|
||||
import me.topchetoeu.jscript.interop.Arguments;
|
||||
import me.topchetoeu.jscript.interop.Expose;
|
||||
import me.topchetoeu.jscript.interop.WrapperName;
|
||||
|
||||
@Native("File")
|
||||
@WrapperName("File")
|
||||
public class FileLib {
|
||||
public final File file;
|
||||
|
||||
@NativeGetter public PromiseLib pointer(Context ctx) {
|
||||
return PromiseLib.await(ctx, () -> {
|
||||
@Expose public PromiseLib __pointer(Arguments args) {
|
||||
return PromiseLib.await(args.ctx, () -> {
|
||||
try {
|
||||
return file.seek(0, 1);
|
||||
}
|
||||
catch (FilesystemException e) { throw e.toEngineException(); }
|
||||
});
|
||||
}
|
||||
@NativeGetter public PromiseLib length(Context ctx) {
|
||||
return PromiseLib.await(ctx, () -> {
|
||||
@Expose public PromiseLib __length(Arguments args) {
|
||||
return PromiseLib.await(args.ctx, () -> {
|
||||
try {
|
||||
long curr = file.seek(0, 1);
|
||||
long res = file.seek(0, 2);
|
||||
@ -32,25 +32,27 @@ public class FileLib {
|
||||
});
|
||||
}
|
||||
|
||||
@Native public PromiseLib read(Context ctx, int n) {
|
||||
return PromiseLib.await(ctx, () -> {
|
||||
@Expose public PromiseLib __read(Arguments args) {
|
||||
return PromiseLib.await(args.ctx, () -> {
|
||||
var n = args.getInt(0);
|
||||
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]);
|
||||
for (var i = resI - 1; i >= 0; i--) res.set(args.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, () -> {
|
||||
@Expose public PromiseLib __write(Arguments args) {
|
||||
return PromiseLib.await(args.ctx, () -> {
|
||||
var val = args.convert(0, ArrayValue.class);
|
||||
try {
|
||||
var res = new byte[val.size()];
|
||||
|
||||
for (var i = 0; i < val.size(); i++) res[i] = (byte)Values.toNumber(ctx, val.get(i));
|
||||
for (var i = 0; i < val.size(); i++) res[i] = (byte)Values.toNumber(args.ctx, val.get(i));
|
||||
file.write(res);
|
||||
|
||||
return null;
|
||||
@ -58,14 +60,17 @@ public class FileLib {
|
||||
catch (FilesystemException e) { throw e.toEngineException(); }
|
||||
});
|
||||
}
|
||||
@Native public PromiseLib close(Context ctx) {
|
||||
return PromiseLib.await(ctx, () -> {
|
||||
@Expose public PromiseLib __close(Arguments args) {
|
||||
return PromiseLib.await(args.ctx, () -> {
|
||||
file.close();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
@Native public PromiseLib seek(Context ctx, long ptr, int whence) {
|
||||
return PromiseLib.await(ctx, () -> {
|
||||
@Expose public PromiseLib __seek(Arguments args) {
|
||||
return PromiseLib.await(args.ctx, () -> {
|
||||
var ptr = args.getLong(0);
|
||||
var whence = args.getInt(1);
|
||||
|
||||
try {
|
||||
return file.seek(ptr, whence);
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ 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;
|
||||
@ -16,47 +15,54 @@ 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;
|
||||
import me.topchetoeu.jscript.interop.Arguments;
|
||||
import me.topchetoeu.jscript.interop.Expose;
|
||||
import me.topchetoeu.jscript.interop.ExposeField;
|
||||
import me.topchetoeu.jscript.interop.ExposeTarget;
|
||||
import me.topchetoeu.jscript.interop.WrapperName;
|
||||
|
||||
@Native("Filesystem")
|
||||
@WrapperName("Filesystem")
|
||||
public class FilesystemLib {
|
||||
@Native public static final int SEEK_SET = 0;
|
||||
@Native public static final int SEEK_CUR = 1;
|
||||
@Native public static final int SEEK_END = 2;
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final int __SEEK_SET = 0;
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final int __SEEK_CUR = 1;
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final int __SEEK_END = 2;
|
||||
|
||||
private static Filesystem fs(Context ctx) {
|
||||
var env = ctx.environment();
|
||||
if (env != null) {
|
||||
var fs = ctx.environment().filesystem;
|
||||
if (fs != null) return fs;
|
||||
}
|
||||
var fs = Filesystem.get(ctx);
|
||||
if (fs != null) return fs;
|
||||
throw EngineException.ofError("Current environment doesn't have a file system.");
|
||||
}
|
||||
|
||||
@Native public static String normalize(Context ctx, String... paths) {
|
||||
return fs(ctx).normalize(paths);
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static String __normalize(Arguments args) {
|
||||
return fs(args.ctx).normalize(args.convert(String.class));
|
||||
}
|
||||
|
||||
@Native public static PromiseLib open(Context ctx, String _path, String mode) {
|
||||
var path = fs(ctx).normalize(_path);
|
||||
var _mode = Mode.parse(mode);
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static PromiseLib __open(Arguments args) {
|
||||
return PromiseLib.await(args.ctx, () -> {
|
||||
var fs = fs(args.ctx);
|
||||
var path = fs.normalize(args.getString(0));
|
||||
var _mode = Mode.parse(args.getString(1));
|
||||
|
||||
return PromiseLib.await(ctx, () -> {
|
||||
try {
|
||||
if (fs(ctx).stat(path).type != EntryType.FILE) {
|
||||
if (fs.stat(path).type != EntryType.FILE) {
|
||||
throw new FilesystemException(path, FSCode.NOT_FILE);
|
||||
}
|
||||
|
||||
var file = fs(ctx).open(path, _mode);
|
||||
var file = fs.open(path, _mode);
|
||||
return new FileLib(file);
|
||||
}
|
||||
catch (FilesystemException e) { throw e.toEngineException(); }
|
||||
});
|
||||
}
|
||||
@Native public static ObjectValue ls(Context ctx, String _path) throws IOException {
|
||||
var path = fs(ctx).normalize(_path);
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static ObjectValue __ls(Arguments args) {
|
||||
|
||||
return Values.toJSAsyncIterator(ctx, new Iterator<>() {
|
||||
return Values.toJSAsyncIterator(args.ctx, new Iterator<>() {
|
||||
private boolean failed, done;
|
||||
private File file;
|
||||
private String nextLine;
|
||||
@ -65,11 +71,14 @@ public class FilesystemLib {
|
||||
if (done) return;
|
||||
if (!failed) {
|
||||
if (file == null) {
|
||||
if (fs(ctx).stat(path).type != EntryType.FOLDER) {
|
||||
var fs = fs(args.ctx);
|
||||
var path = fs.normalize(args.getString(0));
|
||||
|
||||
if (fs.stat(path).type != EntryType.FOLDER) {
|
||||
throw new FilesystemException(path, FSCode.NOT_FOLDER);
|
||||
}
|
||||
|
||||
file = fs(ctx).open(path, Mode.READ);
|
||||
file = fs.open(path, Mode.READ);
|
||||
}
|
||||
|
||||
if (nextLine == null) {
|
||||
@ -106,29 +115,36 @@ public class FilesystemLib {
|
||||
}
|
||||
});
|
||||
}
|
||||
@Native public static PromiseLib mkdir(Context ctx, String _path) throws IOException {
|
||||
return PromiseLib.await(ctx, () -> {
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static PromiseLib __mkdir(Arguments args) throws IOException {
|
||||
return PromiseLib.await(args.ctx, () -> {
|
||||
try {
|
||||
fs(ctx).create(Filename.parse(_path).toString(), EntryType.FOLDER);
|
||||
fs(args.ctx).create(args.getString(0), 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, () -> {
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static PromiseLib __mkfile(Arguments args) throws IOException {
|
||||
return PromiseLib.await(args.ctx, () -> {
|
||||
try {
|
||||
fs(ctx).create(path, EntryType.FILE);
|
||||
fs(args.ctx).create(args.getString(0), 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, () -> {
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static PromiseLib __rm(Arguments args) throws IOException {
|
||||
return PromiseLib.await(args.ctx, () -> {
|
||||
try {
|
||||
if (!recursive) fs(ctx).create(path, EntryType.NONE);
|
||||
var fs = fs(args.ctx);
|
||||
var path = fs.normalize(args.getString(0));
|
||||
var recursive = args.getBoolean(1);
|
||||
|
||||
if (!recursive) fs.create(path, EntryType.NONE);
|
||||
else {
|
||||
var stack = new Stack<String>();
|
||||
stack.push(path);
|
||||
@ -137,13 +153,13 @@ public class FilesystemLib {
|
||||
var currPath = stack.pop();
|
||||
FileStat stat;
|
||||
|
||||
try { stat = fs(ctx).stat(currPath); }
|
||||
try { stat = fs.stat(currPath); }
|
||||
catch (FilesystemException e) { continue; }
|
||||
|
||||
if (stat.type == EntryType.FOLDER) {
|
||||
for (var el : fs(ctx).open(currPath, Mode.READ).readToString().split("\n")) stack.push(el);
|
||||
for (var el : fs.open(currPath, Mode.READ).readToString().split("\n")) stack.push(el);
|
||||
}
|
||||
else fs(ctx).create(currPath, EntryType.NONE);
|
||||
else fs.create(currPath, EntryType.NONE);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@ -151,22 +167,26 @@ public class FilesystemLib {
|
||||
catch (FilesystemException e) { throw e.toEngineException(); }
|
||||
});
|
||||
}
|
||||
@Native public static PromiseLib stat(Context ctx, String path) throws IOException {
|
||||
return PromiseLib.await(ctx, () -> {
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static PromiseLib __stat(Arguments args) throws IOException {
|
||||
return PromiseLib.await(args.ctx, () -> {
|
||||
try {
|
||||
var stat = fs(ctx).stat(path);
|
||||
var fs = fs(args.ctx);
|
||||
var path = fs.normalize(args.getString(0));
|
||||
var stat = fs.stat(path);
|
||||
var res = new ObjectValue();
|
||||
|
||||
res.defineProperty(ctx, "type", stat.type.name);
|
||||
res.defineProperty(ctx, "mode", stat.mode.name);
|
||||
res.defineProperty(args.ctx, "type", stat.type.name);
|
||||
res.defineProperty(args.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; }
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static PromiseLib __exists(Arguments args) throws IOException {
|
||||
return PromiseLib.await(args.ctx, () -> {
|
||||
try { fs(args.ctx).stat(args.getString(0)); return true; }
|
||||
catch (FilesystemException e) { return false; }
|
||||
});
|
||||
}
|
||||
|
@ -1,54 +1,59 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import me.topchetoeu.jscript.Location;
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.engine.values.CodeFunction;
|
||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.engine.values.NativeFunction;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.Arguments;
|
||||
import me.topchetoeu.jscript.interop.Expose;
|
||||
import me.topchetoeu.jscript.interop.ExposeTarget;
|
||||
import me.topchetoeu.jscript.interop.WrapperName;
|
||||
|
||||
@Native("Function") public class FunctionLib {
|
||||
@Native(thisArg = true) public static Object location(Context ctx, FunctionValue func) {
|
||||
if (func instanceof CodeFunction) return ((CodeFunction)func).loc().toString();
|
||||
@WrapperName("Function")
|
||||
public class FunctionLib {
|
||||
@Expose public static Object __location(Arguments args) {
|
||||
if (args.self instanceof CodeFunction) return ((CodeFunction)args.self).loc().toString();
|
||||
else return Location.INTERNAL.toString();
|
||||
}
|
||||
@Native(thisArg = true) public static Object apply(Context ctx, FunctionValue func, Object thisArg, ArrayValue args) {
|
||||
return func.call(ctx, thisArg, args.toArray());
|
||||
@Expose public static Object __apply(Arguments args) {
|
||||
return args.self(FunctionValue.class).call(args.ctx, args.get(0), args.convert(1, ArrayValue.class).toArray());
|
||||
}
|
||||
@Native(thisArg = true) public static Object call(Context ctx, FunctionValue func, Object thisArg, Object... args) {
|
||||
if (!(func instanceof FunctionValue)) throw EngineException.ofError("Expected this to be a function.");
|
||||
|
||||
return func.call(ctx, thisArg, args);
|
||||
@Expose public static Object __call(Arguments args) {
|
||||
return args.self(FunctionValue.class).call(args.ctx, args.get(0), args.slice(1).args);
|
||||
}
|
||||
@Native(thisArg = true) public static FunctionValue bind(FunctionValue func, Object thisArg, Object... args) {
|
||||
if (!(func instanceof FunctionValue)) throw EngineException.ofError("Expected this to be a function.");
|
||||
@Expose public static FunctionValue __bind(Arguments args) {
|
||||
var self = args.self(FunctionValue.class);
|
||||
var thisArg = args.get(0);
|
||||
var bindArgs = args.slice(1).args;
|
||||
|
||||
return new NativeFunction(func.name + " (bound)", (callCtx, _0, callArgs) -> {
|
||||
return new NativeFunction(self.name + " (bound)", callArgs -> {
|
||||
Object[] resArgs;
|
||||
|
||||
if (args.length == 0) resArgs = callArgs;
|
||||
if (args.n() == 0) resArgs = bindArgs;
|
||||
else {
|
||||
resArgs = new Object[args.length + callArgs.length];
|
||||
System.arraycopy(args, 0, resArgs, 0, args.length);
|
||||
System.arraycopy(callArgs, 0, resArgs, args.length, callArgs.length);
|
||||
resArgs = new Object[bindArgs.length + callArgs.n()];
|
||||
System.arraycopy(bindArgs, 0, resArgs, 0, bindArgs.length);
|
||||
System.arraycopy(callArgs.args, 0, resArgs, bindArgs.length, callArgs.n());
|
||||
}
|
||||
|
||||
return func.call(callCtx, thisArg, resArgs);
|
||||
return self.call(callArgs.ctx, thisArg, resArgs);
|
||||
});
|
||||
}
|
||||
@Native(thisArg = true) public static String toString(Context ctx, Object func) {
|
||||
return func.toString();
|
||||
@Expose public static String __toString(Arguments args) {
|
||||
return args.self.toString();
|
||||
}
|
||||
|
||||
@Native public static FunctionValue async(FunctionValue func) {
|
||||
return new AsyncFunctionLib(func);
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static FunctionValue __async(Arguments args) {
|
||||
return new AsyncFunctionLib(args.convert(0, FunctionValue.class));
|
||||
}
|
||||
@Native public static FunctionValue asyncGenerator(FunctionValue func) {
|
||||
return new AsyncGeneratorFunctionLib(func);
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static FunctionValue __asyncGenerator(Arguments args) {
|
||||
return new AsyncGeneratorFunctionLib(args.convert(0, FunctionValue.class));
|
||||
}
|
||||
@Native public static FunctionValue generator(FunctionValue func) {
|
||||
return new GeneratorFunctionLib(func);
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static FunctionValue __generator(Arguments args) {
|
||||
return new GeneratorFunctionLib(args.convert(0, FunctionValue.class));
|
||||
}
|
||||
}
|
||||
|
@ -6,13 +6,13 @@ import me.topchetoeu.jscript.engine.values.CodeFunction;
|
||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.engine.values.NativeFunction;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.WrapperName;
|
||||
|
||||
@Native("GeneratorFunction") public class GeneratorFunctionLib extends FunctionValue {
|
||||
@WrapperName("GeneratorFunction")
|
||||
public class GeneratorFunctionLib extends FunctionValue {
|
||||
public final FunctionValue factory;
|
||||
|
||||
@Override
|
||||
public Object call(Context ctx, Object thisArg, Object ...args) {
|
||||
@Override public Object call(Context ctx, Object thisArg, Object ...args) {
|
||||
var handler = new GeneratorLib();
|
||||
var func = factory.call(ctx, thisArg, new NativeFunction("yield", handler::yield));
|
||||
if (!(func instanceof CodeFunction)) throw EngineException.ofType("Return value of argument must be a js function.");
|
||||
|
@ -2,36 +2,38 @@ package me.topchetoeu.jscript.lib;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.frame.CodeFrame;
|
||||
import me.topchetoeu.jscript.engine.frame.Runners;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.Arguments;
|
||||
import me.topchetoeu.jscript.interop.Expose;
|
||||
import me.topchetoeu.jscript.interop.WrapperName;
|
||||
|
||||
@Native("Generator") public class GeneratorLib {
|
||||
@WrapperName("Generator")
|
||||
public class GeneratorLib {
|
||||
private boolean yielding = true;
|
||||
private boolean done = false;
|
||||
public CodeFrame frame;
|
||||
|
||||
@Native("@@Symbol.typeName") public final String name = "Generator";
|
||||
|
||||
private ObjectValue next(Context ctx, Object inducedValue, Object inducedReturn, Object inducedError) {
|
||||
private ObjectValue next(Context ctx, Object inducedValue, Object inducedReturn, EngineException inducedError) {
|
||||
if (done) {
|
||||
if (inducedError != Runners.NO_RETURN) throw new EngineException(inducedError);
|
||||
if (inducedError != Values.NO_RETURN) throw inducedError;
|
||||
var res = new ObjectValue();
|
||||
res.defineProperty(ctx, "done", true);
|
||||
res.defineProperty(ctx, "value", inducedReturn == Runners.NO_RETURN ? null : inducedReturn);
|
||||
res.defineProperty(ctx, "value", inducedReturn == Values.NO_RETURN ? null : inducedReturn);
|
||||
return res;
|
||||
}
|
||||
|
||||
Object res = null;
|
||||
ctx.pushFrame(frame);
|
||||
yielding = false;
|
||||
|
||||
frame.onPush();
|
||||
while (!yielding) {
|
||||
try {
|
||||
res = frame.next(ctx, inducedValue, inducedReturn, inducedError == Runners.NO_RETURN ? null : new EngineException(inducedError));
|
||||
inducedReturn = inducedError = Runners.NO_RETURN;
|
||||
if (res != Runners.NO_RETURN) {
|
||||
res = frame.next(inducedValue, inducedReturn, inducedError);
|
||||
inducedReturn = Values.NO_RETURN;
|
||||
inducedError = null;
|
||||
if (res != Values.NO_RETURN) {
|
||||
done = true;
|
||||
break;
|
||||
}
|
||||
@ -41,8 +43,8 @@ import me.topchetoeu.jscript.interop.Native;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
frame.onPop();
|
||||
|
||||
ctx.popFrame(frame);
|
||||
if (done) frame = null;
|
||||
else res = frame.pop();
|
||||
|
||||
@ -52,29 +54,25 @@ import me.topchetoeu.jscript.interop.Native;
|
||||
return obj;
|
||||
}
|
||||
|
||||
@Native
|
||||
public ObjectValue next(Context ctx, Object ...args) {
|
||||
if (args.length == 0) return next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, Runners.NO_RETURN);
|
||||
else return next(ctx, args[0], Runners.NO_RETURN, Runners.NO_RETURN);
|
||||
@Expose public ObjectValue __next(Arguments args) {
|
||||
if (args.n() == 0) return next(args.ctx, Values.NO_RETURN, Values.NO_RETURN, null);
|
||||
else return next(args.ctx, args.get(0), Values.NO_RETURN, null);
|
||||
}
|
||||
@Native("throw")
|
||||
public ObjectValue _throw(Context ctx, Object error) {
|
||||
return next(ctx, Runners.NO_RETURN, Runners.NO_RETURN, error);
|
||||
@Expose public ObjectValue __throw(Arguments args) {
|
||||
return next(args.ctx, Values.NO_RETURN, Values.NO_RETURN, new EngineException(args.get(0)).setCtx(args.ctx));
|
||||
}
|
||||
@Native("return")
|
||||
public ObjectValue _return(Context ctx, Object value) {
|
||||
return next(ctx, Runners.NO_RETURN, value, Runners.NO_RETURN);
|
||||
@Expose public ObjectValue __return(Arguments args) {
|
||||
return next(args.ctx, Values.NO_RETURN, args.get(0), null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
@Override public String toString() {
|
||||
if (done) return "Generator [closed]";
|
||||
if (yielding) return "Generator [suspended]";
|
||||
return "Generator [running]";
|
||||
}
|
||||
|
||||
public Object yield(Context ctx, Object thisArg, Object[] args) {
|
||||
public Object yield(Arguments args) {
|
||||
this.yielding = true;
|
||||
return args.length > 0 ? args[0] : null;
|
||||
return args.get(0);
|
||||
}
|
||||
}
|
@ -3,43 +3,50 @@ package me.topchetoeu.jscript.lib;
|
||||
import java.io.IOException;
|
||||
import java.util.HashMap;
|
||||
|
||||
import me.topchetoeu.jscript.Buffer;
|
||||
import me.topchetoeu.jscript.Reading;
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.DataKey;
|
||||
import me.topchetoeu.jscript.engine.Environment;
|
||||
import me.topchetoeu.jscript.engine.scope.GlobalScope;
|
||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.engine.values.Symbol;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
import me.topchetoeu.jscript.parsing.Parsing;
|
||||
import me.topchetoeu.jscript.interop.Arguments;
|
||||
import me.topchetoeu.jscript.interop.Expose;
|
||||
import me.topchetoeu.jscript.interop.ExposeField;
|
||||
import me.topchetoeu.jscript.interop.ExposeTarget;
|
||||
import me.topchetoeu.jscript.modules.ModuleRepo;
|
||||
|
||||
public class Internals {
|
||||
private static final DataKey<HashMap<Integer, Thread>> THREADS = new DataKey<>();
|
||||
private static final DataKey<Integer> I = new DataKey<>();
|
||||
private static final Symbol THREADS = new Symbol("Internals.threads");
|
||||
private static final Symbol I = new Symbol("Internals.i");
|
||||
|
||||
@Native public static Object require(Context ctx, String name) {
|
||||
var env = ctx.environment();
|
||||
var res = env.modules.getModule(ctx, env.moduleCwd, name);
|
||||
res.load(ctx);
|
||||
return res.value();
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static Object __require(Arguments args) {
|
||||
var repo = ModuleRepo.get(args.ctx);
|
||||
|
||||
if (repo != null) {
|
||||
var res = repo.getModule(args.ctx, ModuleRepo.cwd(args.ctx), args.getString(0));
|
||||
res.load(args.ctx);
|
||||
return res.value();
|
||||
}
|
||||
|
||||
else throw EngineException.ofError("Modules are not supported.");
|
||||
}
|
||||
|
||||
@Native public static Object log(Context ctx, Object ...args) {
|
||||
for (var arg : args) {
|
||||
Values.printValue(ctx, arg);
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static Object __log(Arguments args) {
|
||||
for (var arg : args.args) {
|
||||
Values.printValue(args.ctx, arg);
|
||||
System.out.print(" ");
|
||||
}
|
||||
System.out.println();
|
||||
|
||||
if (args.length == 0) return null;
|
||||
else return args[0];
|
||||
return args.get(0);
|
||||
}
|
||||
@Native public static String readline(Context ctx) {
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static String __readline() {
|
||||
try {
|
||||
return Reading.read();
|
||||
return Reading.readline();
|
||||
}
|
||||
catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
@ -47,26 +54,35 @@ public class Internals {
|
||||
}
|
||||
}
|
||||
|
||||
@Native public static int setTimeout(Context ctx, FunctionValue func, int delay, Object ...args) {
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static Thread __setTimeout(Arguments args) {
|
||||
var func = args.convert(0, FunctionValue.class);
|
||||
var delay = args.getDouble(1);
|
||||
var arguments = args.slice(2).args;
|
||||
|
||||
var thread = new Thread(() -> {
|
||||
var ms = (long)delay;
|
||||
var ns = (int)((delay - ms) * 10000000);
|
||||
|
||||
try {
|
||||
Thread.sleep(ms, ns);
|
||||
}
|
||||
try { Thread.sleep(ms, ns); }
|
||||
catch (InterruptedException e) { return; }
|
||||
|
||||
ctx.engine.pushMsg(false, ctx.environment(), func, null, args);
|
||||
args.ctx.engine.pushMsg(false, args.ctx.environment, func, null, arguments);
|
||||
});
|
||||
thread.start();
|
||||
|
||||
int i = ctx.environment().data.increase(I, 1, 0);
|
||||
var threads = ctx.environment().data.get(THREADS, new HashMap<>());
|
||||
threads.put(++i, thread);
|
||||
return i;
|
||||
thread.start();
|
||||
var i = args.ctx.init(I, 1);
|
||||
args.ctx.add(I, i + 1);
|
||||
args.ctx.init(THREADS, new HashMap<Integer, Thread>()).put(i, thread);
|
||||
|
||||
return thread;
|
||||
}
|
||||
@Native public static int setInterval(Context ctx, FunctionValue func, int delay, Object ...args) {
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static Thread __setInterval(Arguments args) {
|
||||
var func = args.convert(0, FunctionValue.class);
|
||||
var delay = args.getDouble(1);
|
||||
var arguments = args.slice(2).args;
|
||||
|
||||
var thread = new Thread(() -> {
|
||||
var ms = (long)delay;
|
||||
var ns = (int)((delay - ms) * 10000000);
|
||||
@ -77,103 +93,76 @@ public class Internals {
|
||||
}
|
||||
catch (InterruptedException e) { return; }
|
||||
|
||||
ctx.engine.pushMsg(false, ctx.environment(), func, null, args);
|
||||
args.ctx.engine.pushMsg(false, args.ctx.environment, func, null, arguments);
|
||||
}
|
||||
});
|
||||
thread.start();
|
||||
var i = args.ctx.init(I, 1);
|
||||
args.ctx.add(I, i + 1);
|
||||
args.ctx.init(THREADS, new HashMap<Integer, Thread>()).put(i, thread);
|
||||
|
||||
int i = ctx.environment().data.increase(I, 1, 0);
|
||||
var threads = ctx.environment().data.get(THREADS, new HashMap<>());
|
||||
threads.put(++i, thread);
|
||||
return i;
|
||||
return thread;
|
||||
}
|
||||
|
||||
@Native public static void clearTimeout(Context ctx, int i) {
|
||||
var threads = ctx.environment().data.get(THREADS, new HashMap<>());
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static void __clearTimeout(Arguments args) {
|
||||
var i = args.getInt(0);
|
||||
HashMap<Integer, Thread> map = args.ctx.get(THREADS);
|
||||
if (map == null) return;
|
||||
|
||||
var thread = threads.remove(i);
|
||||
if (thread != null) thread.interrupt();
|
||||
var thread = map.get(i);
|
||||
if (thread == null) return;
|
||||
|
||||
thread.interrupt();
|
||||
map.remove(i);
|
||||
}
|
||||
@Native public static void clearInterval(Context ctx, int i) {
|
||||
clearTimeout(ctx, i);
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static void __clearInterval(Arguments args) {
|
||||
__clearTimeout(args);
|
||||
}
|
||||
|
||||
@Native public static double parseInt(Context ctx, String val) {
|
||||
return NumberLib.parseInt(ctx, val);
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __parseInt(Arguments args) {
|
||||
return NumberLib.__parseInt(args);
|
||||
}
|
||||
@Native public static double parseFloat(Context ctx, String val) {
|
||||
return NumberLib.parseFloat(ctx, val);
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __parseFloat(Arguments args) {
|
||||
return NumberLib.__parseFloat(args);
|
||||
}
|
||||
|
||||
@Native public static boolean isNaN(Context ctx, double val) {
|
||||
return NumberLib.isNaN(ctx, val);
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static boolean __isNaN(Arguments args) {
|
||||
return NumberLib.__isNaN(args);
|
||||
}
|
||||
@Native public static boolean isFinite(Context ctx, double val) {
|
||||
return NumberLib.isFinite(ctx, val);
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static boolean __isFinite(Arguments args) {
|
||||
return NumberLib.__isFinite(args);
|
||||
}
|
||||
@Native public static boolean isInfinite(Context ctx, double val) {
|
||||
return NumberLib.isInfinite(ctx, val);
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static boolean __isInfinite(Arguments args) {
|
||||
return NumberLib.__isInfinite(args);
|
||||
}
|
||||
|
||||
@NativeGetter public static double NaN(Context ctx) {
|
||||
return Double.NaN;
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static double __NaN = Double.NaN;
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static double __Infinity = Double.POSITIVE_INFINITY;
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static String __encodeURIComponent(Arguments args) {
|
||||
return EncodingLib.__encodeURIComponent(args);
|
||||
}
|
||||
@NativeGetter public static double Infinity(Context ctx) {
|
||||
return Double.POSITIVE_INFINITY;
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static String __decodeURIComponent(Arguments args) {
|
||||
return EncodingLib.__decodeURIComponent(args);
|
||||
}
|
||||
private static final String HEX = "0123456789ABCDEF";
|
||||
|
||||
private static String encodeUriAny(String str, String keepAlphabet) {
|
||||
if (str == null) str = "undefined";
|
||||
|
||||
var bytes = str.getBytes();
|
||||
var sb = new StringBuilder(bytes.length);
|
||||
|
||||
for (byte c : bytes) {
|
||||
if (Parsing.isAlphanumeric((char)c) || Parsing.isAny((char)c, keepAlphabet)) sb.append((char)c);
|
||||
else {
|
||||
sb.append('%');
|
||||
sb.append(HEX.charAt(c / 16));
|
||||
sb.append(HEX.charAt(c % 16));
|
||||
}
|
||||
}
|
||||
|
||||
return sb.toString();
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static String __encodeURI(Arguments args) {
|
||||
return EncodingLib.__encodeURI(args);
|
||||
}
|
||||
private static String decodeUriAny(String str, String keepAlphabet) {
|
||||
if (str == null) str = "undefined";
|
||||
|
||||
var res = new Buffer();
|
||||
var bytes = str.getBytes();
|
||||
|
||||
for (var i = 0; i < bytes.length; i++) {
|
||||
var c = bytes[i];
|
||||
if (c == '%') {
|
||||
if (i >= bytes.length - 2) throw EngineException.ofError("URIError", "URI malformed.");
|
||||
var b = Parsing.fromHex((char)bytes[i + 1]) * 16 | Parsing.fromHex((char)bytes[i + 2]);
|
||||
if (!Parsing.isAny((char)b, keepAlphabet)) {
|
||||
i += 2;
|
||||
res.append((byte)b);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
res.append(c);
|
||||
}
|
||||
|
||||
return new String(res.data());
|
||||
}
|
||||
|
||||
@Native public static String encodeURIComponent(String str) {
|
||||
return encodeUriAny(str, ".-_!~*'()");
|
||||
}
|
||||
@Native public static String decodeURIComponent(String str) {
|
||||
return decodeUriAny(str, "");
|
||||
}
|
||||
@Native public static String encodeURI(String str) {
|
||||
return encodeUriAny(str, ";,/?:@&=+$#.-_!~*'()");
|
||||
}
|
||||
@Native public static String decodeURI(String str) {
|
||||
return decodeUriAny(str, ",/?:@&=+$#.");
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static String __decodeURI(Arguments args) {
|
||||
return EncodingLib.__decodeURI(args);
|
||||
}
|
||||
|
||||
public static Environment apply(Environment env) {
|
||||
@ -205,22 +194,22 @@ public class Internals {
|
||||
glob.define(false, wp.getConstr(TypeErrorLib.class));
|
||||
glob.define(false, wp.getConstr(RangeErrorLib.class));
|
||||
|
||||
env.setProto("object", wp.getProto(ObjectLib.class));
|
||||
env.setProto("function", wp.getProto(FunctionLib.class));
|
||||
env.setProto("array", wp.getProto(ArrayLib.class));
|
||||
env.add(Environment.OBJECT_PROTO, wp.getProto(ObjectLib.class));
|
||||
env.add(Environment.FUNCTION_PROTO, wp.getProto(FunctionLib.class));
|
||||
env.add(Environment.ARRAY_PROTO, wp.getProto(ArrayLib.class));
|
||||
|
||||
env.setProto("bool", wp.getProto(BooleanLib.class));
|
||||
env.setProto("number", wp.getProto(NumberLib.class));
|
||||
env.setProto("string", wp.getProto(StringLib.class));
|
||||
env.setProto("symbol", wp.getProto(SymbolLib.class));
|
||||
env.add(Environment.BOOL_PROTO, wp.getProto(BooleanLib.class));
|
||||
env.add(Environment.NUMBER_PROTO, wp.getProto(NumberLib.class));
|
||||
env.add(Environment.STRING_PROTO, wp.getProto(StringLib.class));
|
||||
env.add(Environment.SYMBOL_PROTO, wp.getProto(SymbolLib.class));
|
||||
|
||||
env.setProto("error", wp.getProto(ErrorLib.class));
|
||||
env.setProto("syntaxErr", wp.getProto(SyntaxErrorLib.class));
|
||||
env.setProto("typeErr", wp.getProto(TypeErrorLib.class));
|
||||
env.setProto("rangeErr", wp.getProto(RangeErrorLib.class));
|
||||
env.add(Environment.ERROR_PROTO, wp.getProto(ErrorLib.class));
|
||||
env.add(Environment.SYNTAX_ERR_PROTO, wp.getProto(SyntaxErrorLib.class));
|
||||
env.add(Environment.TYPE_ERR_PROTO, wp.getProto(TypeErrorLib.class));
|
||||
env.add(Environment.RANGE_ERR_PROTO, wp.getProto(RangeErrorLib.class));
|
||||
|
||||
wp.getProto(ObjectLib.class).setPrototype(null, null);
|
||||
env.regexConstructor = wp.getConstr(RegExpLib.class);
|
||||
env.add(Environment.REGEX_CONSTR, wp.getConstr(RegExpLib.class));
|
||||
|
||||
return env;
|
||||
}
|
||||
|
@ -1,21 +1,24 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.exceptions.SyntaxException;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.Arguments;
|
||||
import me.topchetoeu.jscript.interop.Expose;
|
||||
import me.topchetoeu.jscript.interop.ExposeTarget;
|
||||
import me.topchetoeu.jscript.interop.WrapperName;
|
||||
import me.topchetoeu.jscript.json.JSON;
|
||||
|
||||
@Native("JSON") public class JSONLib {
|
||||
@Native
|
||||
public static Object parse(Context ctx, String val) {
|
||||
@WrapperName("JSON")
|
||||
public class JSONLib {
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static Object __parse(Arguments args) {
|
||||
try {
|
||||
return JSON.toJs(JSON.parse(null, val));
|
||||
return JSON.toJs(JSON.parse(null, args.getString(0)));
|
||||
}
|
||||
catch (SyntaxException e) { throw EngineException.ofSyntax(e.msg); }
|
||||
}
|
||||
@Native
|
||||
public static String stringify(Context ctx, Object val) {
|
||||
return me.topchetoeu.jscript.json.JSON.stringify(JSON.fromJs(ctx, val));
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static String __stringify(Arguments args) {
|
||||
return JSON.stringify(JSON.fromJs(args.ctx, args.get(0)));
|
||||
}
|
||||
}
|
||||
|
@ -6,24 +6,28 @@ import java.util.stream.Collectors;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
import me.topchetoeu.jscript.interop.Arguments;
|
||||
import me.topchetoeu.jscript.interop.Expose;
|
||||
import me.topchetoeu.jscript.interop.ExposeConstructor;
|
||||
import me.topchetoeu.jscript.interop.ExposeType;
|
||||
import me.topchetoeu.jscript.interop.WrapperName;
|
||||
|
||||
@Native("Map") public class MapLib {
|
||||
@WrapperName("Map")
|
||||
public class MapLib {
|
||||
private LinkedHashMap<Object, Object> map = new LinkedHashMap<>();
|
||||
|
||||
@Native("@@Symbol.typeName") public final String name = "Map";
|
||||
@Native("@@Symbol.iterator") public ObjectValue iterator(Context ctx) {
|
||||
return this.entries(ctx);
|
||||
@Expose("@@Symbol.iterator")
|
||||
public ObjectValue __iterator(Arguments args) {
|
||||
return this.__entries(args);
|
||||
}
|
||||
|
||||
@Native public void clear() {
|
||||
@Expose public void __clear() {
|
||||
map.clear();
|
||||
}
|
||||
@Native public boolean delete(Object key) {
|
||||
@Expose public boolean __delete(Arguments args) {
|
||||
var key = args.get(0);
|
||||
if (map.containsKey(key)) {
|
||||
map.remove(key);
|
||||
return true;
|
||||
@ -31,48 +35,53 @@ import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Native public ObjectValue entries(Context ctx) {
|
||||
return ArrayValue.of(ctx, map
|
||||
@Expose public ObjectValue __entries(Arguments args) {
|
||||
return Values.toJSIterator(args.ctx, map
|
||||
.entrySet()
|
||||
.stream()
|
||||
.map(v -> new ArrayValue(ctx, v.getKey(), v.getValue()))
|
||||
.map(v -> new ArrayValue(args.ctx, v.getKey(), v.getValue()))
|
||||
.collect(Collectors.toList())
|
||||
);
|
||||
}
|
||||
@Native public ObjectValue keys(Context ctx) {
|
||||
return ArrayValue.of(ctx, map.keySet());
|
||||
@Expose public ObjectValue __keys(Arguments args) {
|
||||
return Values.toJSIterator(args.ctx, map.keySet());
|
||||
}
|
||||
@Native public ObjectValue values(Context ctx) {
|
||||
return ArrayValue.of(ctx, map.values());
|
||||
@Expose public ObjectValue __values(Arguments args) {
|
||||
return Values.toJSIterator(args.ctx, map.values());
|
||||
}
|
||||
|
||||
@Native public Object get(Object key) {
|
||||
return map.get(key);
|
||||
@Expose public Object __get(Arguments args) {
|
||||
return map.get(args.get(0));
|
||||
}
|
||||
@Native public MapLib set(Object key, Object val) {
|
||||
map.put(key, val);
|
||||
@Expose public MapLib __set(Arguments args) {
|
||||
map.put(args.get(0), args.get(1));
|
||||
return this;
|
||||
}
|
||||
@Native public boolean has(Object key) {
|
||||
return map.containsKey(key);
|
||||
@Expose public boolean __has(Arguments args) {
|
||||
return map.containsKey(args.get(0));
|
||||
}
|
||||
|
||||
@NativeGetter public int size() {
|
||||
@Expose(type = ExposeType.GETTER)
|
||||
public int __size() {
|
||||
return map.size();
|
||||
}
|
||||
|
||||
@Native public void forEach(Context ctx, FunctionValue func, Object thisArg) {
|
||||
@Expose public void __forEach(Arguments args) {
|
||||
var keys = new ArrayList<>(map.keySet());
|
||||
|
||||
for (var el : keys) func.call(ctx, thisArg, map.get(el), el,this);
|
||||
for (var el : keys) Values.call(args.ctx, args.get(0), args.get(1), map.get(el), el, args.self);
|
||||
}
|
||||
|
||||
@Native public MapLib(Context ctx, Object iterable) {
|
||||
public MapLib(Context ctx, Object iterable) {
|
||||
for (var el : Values.fromJSIterator(ctx, iterable)) {
|
||||
try {
|
||||
set(Values.getMember(ctx, el, 0), Values.getMember(ctx, el, 1));
|
||||
map.put(Values.getMember(ctx, el, 0), Values.getMember(ctx, el, 1));
|
||||
}
|
||||
catch (IllegalArgumentException e) { }
|
||||
}
|
||||
}
|
||||
|
||||
@ExposeConstructor public static MapLib __constructor(Arguments args) {
|
||||
return new MapLib(args.ctx, args.get(0));
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +1,47 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.Arguments;
|
||||
import me.topchetoeu.jscript.interop.Expose;
|
||||
import me.topchetoeu.jscript.interop.ExposeField;
|
||||
import me.topchetoeu.jscript.interop.ExposeTarget;
|
||||
import me.topchetoeu.jscript.interop.WrapperName;
|
||||
|
||||
@Native("Math") public class MathLib {
|
||||
@Native public static final double E = Math.E;
|
||||
@Native public static final double PI = Math.PI;
|
||||
@Native public static final double SQRT2 = Math.sqrt(2);
|
||||
@Native public static final double SQRT1_2 = Math.sqrt(.5);
|
||||
@Native public static final double LN2 = Math.log(2);
|
||||
@Native public static final double LN10 = Math.log(10);
|
||||
@Native public static final double LOG2E = Math.log(Math.E) / LN2;
|
||||
@Native public static final double LOG10E = Math.log10(Math.E);
|
||||
@WrapperName("Math")
|
||||
public class MathLib {
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final double __E = Math.E;
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final double __PI = Math.PI;
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final double __SQRT2 = Math.sqrt(2);
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final double __SQRT1_2 = Math.sqrt(.5);
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final double __LN2 = Math.log(2);
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final double __LN10 = Math.log(10);
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final double __LOG2E = Math.log(Math.E) / __LN2;
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final double __LOG10E = Math.log10(Math.E);
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __asin(Arguments args) {
|
||||
return Math.asin(args.getDouble(0));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __acos(Arguments args) {
|
||||
return Math.acos(args.getDouble(0));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __atan(Arguments args) {
|
||||
return Math.atan(args.getDouble(0));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __atan2(Arguments args) {
|
||||
var x = args.getDouble(1);
|
||||
var y = args.getDouble(0);
|
||||
|
||||
@Native public static double asin(double x) { return Math.asin(x); }
|
||||
@Native public static double acos(double x) { return Math.acos(x); }
|
||||
@Native public static double atan(double x) { return Math.atan(x); }
|
||||
@Native public static double atan2(double y, double x) {
|
||||
if (x == 0) {
|
||||
if (y == 0) return Double.NaN;
|
||||
return Math.signum(y) * Math.PI / 2;
|
||||
@ -29,71 +55,157 @@ import me.topchetoeu.jscript.interop.Native;
|
||||
|
||||
}
|
||||
|
||||
@Native public static double asinh(double x) { return Math.log(x + Math.sqrt(x * x + 1)); }
|
||||
@Native public static double acosh(double x) { return Math.log(x + Math.sqrt(x * x - 1)); }
|
||||
@Native public static double atanh(double x) {
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __asinh(Arguments args) {
|
||||
var x = args.getDouble(0);
|
||||
return Math.log(x + Math.sqrt(x * x + 1));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __acosh(Arguments args) {
|
||||
var x = args.getDouble(0);
|
||||
return Math.log(x + Math.sqrt(x * x - 1));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __atanh(Arguments args) {
|
||||
var x = args.getDouble(0);
|
||||
|
||||
if (x <= -1 || x >= 1) return Double.NaN;
|
||||
return .5 * Math.log((1 + x) / (1 - x));
|
||||
}
|
||||
|
||||
@Native public static double sin(double x) { return Math.sin(x); }
|
||||
@Native public static double cos(double x) { return Math.cos(x); }
|
||||
@Native public static double tan(double x) { return Math.tan(x); }
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __sin(Arguments args) {
|
||||
return Math.sin(args.getDouble(0));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __cos(Arguments args) {
|
||||
return Math.cos(args.getDouble(0));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __tan(Arguments args) {
|
||||
return Math.tan(args.getDouble(0));
|
||||
}
|
||||
|
||||
@Native public static double sinh(double x) { return Math.sinh(x); }
|
||||
@Native public static double cosh(double x) { return Math.cosh(x); }
|
||||
@Native public static double tanh(double x) { return Math.tanh(x); }
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __sinh(Arguments args) {
|
||||
return Math.sinh(args.getDouble(0));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __cosh(Arguments args) {
|
||||
return Math.cosh(args.getDouble(0));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __tanh(Arguments args) {
|
||||
return Math.tanh(args.getDouble(0));
|
||||
}
|
||||
|
||||
@Native public static double sqrt(double x) { return Math.sqrt(x); }
|
||||
@Native public static double cbrt(double x) { return Math.cbrt(x); }
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __sqrt(Arguments args) {
|
||||
return Math.sqrt(args.getDouble(0));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __cbrt(Arguments args) {
|
||||
return Math.cbrt(args.getDouble(0));
|
||||
}
|
||||
|
||||
@Native public static double hypot(double ...vals) {
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __hypot(Arguments args) {
|
||||
var res = 0.;
|
||||
for (var el : vals) {
|
||||
var val = el;
|
||||
for (var i = 0; i < args.n(); i++) {
|
||||
var val = args.getDouble(i);
|
||||
res += val * val;
|
||||
}
|
||||
return Math.sqrt(res);
|
||||
}
|
||||
@Native public static int imul(double a, double b) { return (int)a * (int)b; }
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static int __imul(Arguments args) { return args.getInt(0) * args.getInt(1); }
|
||||
|
||||
@Native public static double exp(double x) { return Math.exp(x); }
|
||||
@Native public static double expm1(double x) { return Math.expm1(x); }
|
||||
@Native public static double pow(double x, double y) { return Math.pow(x, y); }
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __exp(Arguments args) {
|
||||
return Math.exp(args.getDouble(0));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __expm1(Arguments args) {
|
||||
return Math.expm1(args.getDouble(0));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __pow(Arguments args) { return Math.pow(args.getDouble(0), args.getDouble(1)); }
|
||||
|
||||
@Native public static double log(double x) { return Math.log(x); }
|
||||
@Native public static double log10(double x) { return Math.log10(x); }
|
||||
@Native public static double log1p(double x) { return Math.log1p(x); }
|
||||
@Native public static double log2(double x) { return Math.log(x) / LN2; }
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __log(Arguments args) {
|
||||
return Math.log(args.getDouble(0));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __log10(Arguments args) {
|
||||
return Math.log10(args.getDouble(0));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __log1p(Arguments args) {
|
||||
return Math.log1p(args.getDouble(0));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __log2(Arguments args) {
|
||||
return Math.log(args.getDouble(0)) / __LN2;
|
||||
}
|
||||
|
||||
@Native public static double ceil(double x) { return Math.ceil(x); }
|
||||
@Native public static double floor(double x) { return Math.floor(x); }
|
||||
@Native public static double round(double x) { return Math.round(x); }
|
||||
@Native public static float fround(double x) { return (float)x; }
|
||||
@Native public static double trunc(double x) { return Math.floor(Math.abs(x)) * Math.signum(x); }
|
||||
@Native public static double abs(double x) { return Math.abs(x); }
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __ceil(Arguments args) {
|
||||
return Math.ceil(args.getDouble(0));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __floor(Arguments args) {
|
||||
return Math.floor(args.getDouble(0));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __round(Arguments args) {
|
||||
return Math.round(args.getDouble(0));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static float __fround(Arguments args) {
|
||||
return (float)args.getDouble(0);
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __trunc(Arguments args) {
|
||||
var x = args.getDouble(0);
|
||||
return Math.floor(Math.abs(x)) * Math.signum(x);
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __abs(Arguments args) {
|
||||
return Math.abs(args.getDouble(0));
|
||||
}
|
||||
|
||||
@Native public static double max(double ...vals) {
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __max(Arguments args) {
|
||||
var res = Double.NEGATIVE_INFINITY;
|
||||
|
||||
for (var el : vals) {
|
||||
for (var i = 0; i < args.n(); i++) {
|
||||
var el = args.getDouble(i);
|
||||
if (el > res) res = el;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@Native public static double min(double ...vals) {
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __min(Arguments args) {
|
||||
var res = Double.POSITIVE_INFINITY;
|
||||
|
||||
for (var el : vals) {
|
||||
for (var i = 0; i < args.n(); i++) {
|
||||
var el = args.getDouble(i);
|
||||
if (el < res) res = el;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@Native public static double sign(double x) { return Math.signum(x); }
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __sign(Arguments args) {
|
||||
return Math.signum(args.getDouble(0));
|
||||
}
|
||||
|
||||
@Native public static double random() { return Math.random(); }
|
||||
@Native public static int clz32(double x) { return Integer.numberOfLeadingZeros((int)x); }
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __random() { return Math.random(); }
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static int __clz32(Arguments args) {
|
||||
return Integer.numberOfLeadingZeros(args.getInt(0));
|
||||
}
|
||||
}
|
||||
|
@ -1,52 +1,92 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeConstructor;
|
||||
import me.topchetoeu.jscript.interop.Arguments;
|
||||
import me.topchetoeu.jscript.interop.Expose;
|
||||
import me.topchetoeu.jscript.interop.ExposeConstructor;
|
||||
import me.topchetoeu.jscript.interop.ExposeField;
|
||||
import me.topchetoeu.jscript.interop.ExposeTarget;
|
||||
import me.topchetoeu.jscript.interop.WrapperName;
|
||||
|
||||
@Native("Number") public class NumberLib {
|
||||
@Native public static final double EPSILON = java.lang.Math.ulp(1.0);
|
||||
@Native public static final double MAX_SAFE_INTEGER = 9007199254740991.;
|
||||
@Native public static final double MIN_SAFE_INTEGER = -MAX_SAFE_INTEGER;
|
||||
@WrapperName("Number")
|
||||
public class NumberLib {
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final double __EPSILON = Math.ulp(1.0);
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final double __MAX_SAFE_INTEGER = 9007199254740991.;
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final double __MIN_SAFE_INTEGER = -__MAX_SAFE_INTEGER;
|
||||
// lmao big number go brrr
|
||||
@Native public static final double MAX_VALUE = 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.;
|
||||
@Native public static final double MIN_VALUE = -MAX_VALUE;
|
||||
@Native public static final double NaN = 0. / 0;
|
||||
@Native public static final double NEGATIVE_INFINITY = -1. / 0;
|
||||
@Native public static final double POSITIVE_INFINITY = 1. / 0;
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final double __MAX_VALUE = 179769313486231570814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368.;
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final double __MIN_VALUE = -__MAX_VALUE;
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final double __NaN = 0. / 0;
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final double __NEGATIVE_INFINITY = -1. / 0;
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final double __POSITIVE_INFINITY = 1. / 0;
|
||||
|
||||
public final double value;
|
||||
|
||||
@Native public static boolean isFinite(Context ctx, double val) { return Double.isFinite(val); }
|
||||
@Native public static boolean isInfinite(Context ctx, double val) { return Double.isInfinite(val); }
|
||||
@Native public static boolean isNaN(Context ctx, double val) { return Double.isNaN(val); }
|
||||
@Native public static boolean isSafeInteger(Context ctx, double val) {
|
||||
return val > MIN_SAFE_INTEGER && val < MAX_SAFE_INTEGER;
|
||||
}
|
||||
|
||||
@Native public static double parseFloat(Context ctx, String val) {
|
||||
return Values.toNumber(ctx, val);
|
||||
}
|
||||
@Native public static double parseInt(Context ctx, String val) {
|
||||
return (long)Values.toNumber(ctx, val);
|
||||
}
|
||||
|
||||
@NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object val) {
|
||||
val = Values.toNumber(ctx, val);
|
||||
if (thisArg instanceof ObjectValue) return new NumberLib((double)val);
|
||||
else return val;
|
||||
}
|
||||
@Native(thisArg = true) public static String toString(Context ctx, Object thisArg) {
|
||||
return Values.toString(ctx, Values.toNumber(ctx, thisArg));
|
||||
}
|
||||
@Native(thisArg = true) public static double valueOf(Context ctx, Object thisArg) {
|
||||
if (thisArg instanceof NumberLib) return ((NumberLib)thisArg).value;
|
||||
else return Values.toNumber(ctx, thisArg);
|
||||
}
|
||||
@Override public String toString() { return value + ""; }
|
||||
|
||||
public NumberLib(double val) {
|
||||
this.value = val;
|
||||
}
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static boolean __isFinite(Arguments args) { return Double.isFinite(args.getDouble(0)); }
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static boolean __isInfinite(Arguments args) { return Double.isInfinite(args.getDouble(0)); }
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static boolean __isNaN(Arguments args) { return Double.isNaN(args.getDouble(0)); }
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static boolean __isSafeInteger(Arguments args) {
|
||||
return args.getDouble(0) > __MIN_SAFE_INTEGER && args.getDouble(0) < __MAX_SAFE_INTEGER;
|
||||
}
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __parseFloat(Arguments args) {
|
||||
return args.getDouble(0);
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static double __parseInt(Arguments args) {
|
||||
var radix = args.getInt(1, 10);
|
||||
|
||||
if (radix < 2 || radix > 36) return Double.NaN;
|
||||
else {
|
||||
long res = 0;
|
||||
|
||||
for (var c : args.getString(0).toCharArray()) {
|
||||
var digit = 0;
|
||||
|
||||
if (c >= '0' && c <= '9') digit = c - '0';
|
||||
else if (c >= 'a' && c <= 'z') digit = c - 'a' + 10;
|
||||
else if (c >= 'A' && c <= 'Z') digit = c - 'A' + 10;
|
||||
else break;
|
||||
|
||||
if (digit > radix) break;
|
||||
|
||||
res *= radix;
|
||||
res += digit;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
@ExposeConstructor public static Object __constructor(Arguments args) {
|
||||
if (args.self instanceof ObjectValue) return new NumberLib(args.getDouble(0));
|
||||
else return args.getDouble(0);
|
||||
}
|
||||
@Expose public static String __toString(Arguments args) {
|
||||
return Values.toString(args.ctx, args.self);
|
||||
}
|
||||
@Expose public static double __valueOf(Arguments args) {
|
||||
if (Values.isWrapper(args.self, NumberLib.class)) return Values.wrapper(args.self, NumberLib.class).value;
|
||||
else return Values.toNumber(args.ctx, args.self);
|
||||
}
|
||||
}
|
||||
|
@ -1,212 +1,273 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.Symbol;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeConstructor;
|
||||
import me.topchetoeu.jscript.interop.Arguments;
|
||||
import me.topchetoeu.jscript.interop.Expose;
|
||||
import me.topchetoeu.jscript.interop.ExposeConstructor;
|
||||
import me.topchetoeu.jscript.interop.ExposeTarget;
|
||||
import me.topchetoeu.jscript.interop.WrapperName;
|
||||
|
||||
@Native("Object") public class ObjectLib {
|
||||
@Native public static ObjectValue assign(Context ctx, ObjectValue dst, Object... src) {
|
||||
for (var obj : src) {
|
||||
for (var key : Values.getMembers(ctx, obj, true, true)) {
|
||||
Values.setMember(ctx, dst, key, Values.getMember(ctx, obj, key));
|
||||
@WrapperName("Object")
|
||||
public class ObjectLib {
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static Object __assign(Arguments args) {
|
||||
for (var obj : args.slice(1).args) {
|
||||
for (var key : Values.getMembers(args.ctx, obj, true, true)) {
|
||||
Values.setMember(args.ctx, args.get(0), key, Values.getMember(args.ctx, obj, key));
|
||||
}
|
||||
}
|
||||
return dst;
|
||||
return args.get(0);
|
||||
}
|
||||
@Native public static ObjectValue create(Context ctx, ObjectValue proto, ObjectValue props) {
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static ObjectValue __create(Arguments args) {
|
||||
var obj = new ObjectValue();
|
||||
obj.setPrototype(ctx, proto);
|
||||
return defineProperties(ctx, obj, props);
|
||||
obj.setPrototype(args.ctx, args.get(0));
|
||||
|
||||
if (args.n() >= 1) {
|
||||
var newArgs = new Object[args.n()];
|
||||
System.arraycopy(args.args, 1, args, 1, args.n() - 1);
|
||||
newArgs[0] = obj;
|
||||
|
||||
__defineProperties(new Arguments(args.ctx, null, newArgs));
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
@Native public static ObjectValue defineProperty(Context ctx, ObjectValue obj, Object key, ObjectValue attrib) {
|
||||
var hasVal = attrib.hasMember(ctx, "value", false);
|
||||
var hasGet = attrib.hasMember(ctx, "get", false);
|
||||
var hasSet = attrib.hasMember(ctx, "set", false);
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static ObjectValue __defineProperty(Arguments args) {
|
||||
var obj = args.convert(0, ObjectValue.class);
|
||||
var key = args.get(1);
|
||||
var attrib = args.convert(2, ObjectValue.class);
|
||||
|
||||
var hasVal = Values.hasMember(args.ctx, attrib, "value", false);
|
||||
var hasGet = Values.hasMember(args.ctx, attrib, "get", false);
|
||||
var hasSet = Values.hasMember(args.ctx, attrib, "set", false);
|
||||
|
||||
if (hasVal) {
|
||||
if (hasGet || hasSet) throw EngineException.ofType("Cannot specify a value and accessors for a property.");
|
||||
if (!obj.defineProperty(
|
||||
ctx, key,
|
||||
attrib.getMember(ctx, "value"),
|
||||
Values.toBoolean(attrib.getMember(ctx, "writable")),
|
||||
Values.toBoolean(attrib.getMember(ctx, "configurable")),
|
||||
Values.toBoolean(attrib.getMember(ctx, "enumerable"))
|
||||
args.ctx, key,
|
||||
attrib.getMember(args.ctx, "value"),
|
||||
Values.toBoolean(Values.getMember(args.ctx, attrib, "writable")),
|
||||
Values.toBoolean(Values.getMember(args.ctx, attrib, "configurable")),
|
||||
Values.toBoolean(Values.getMember(args.ctx, attrib, "enumerable"))
|
||||
)) throw EngineException.ofType("Can't define property '" + key + "'.");
|
||||
}
|
||||
else {
|
||||
var get = attrib.getMember(ctx, "get");
|
||||
var set = attrib.getMember(ctx, "set");
|
||||
var get = Values.getMember(args.ctx, attrib, "get");
|
||||
var set = Values.getMember(args.ctx, attrib, "set");
|
||||
if (get != null && !(get instanceof FunctionValue)) throw EngineException.ofType("Get accessor must be a function.");
|
||||
if (set != null && !(set instanceof FunctionValue)) throw EngineException.ofType("Set accessor must be a function.");
|
||||
|
||||
if (!obj.defineProperty(
|
||||
ctx, key,
|
||||
args.ctx, key,
|
||||
(FunctionValue)get, (FunctionValue)set,
|
||||
Values.toBoolean(attrib.getMember(ctx, "configurable")),
|
||||
Values.toBoolean(attrib.getMember(ctx, "enumerable"))
|
||||
Values.toBoolean(Values.getMember(args.ctx, attrib, "configurable")),
|
||||
Values.toBoolean(Values.getMember(args.ctx, attrib, "enumerable"))
|
||||
)) throw EngineException.ofType("Can't define property '" + key + "'.");
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
@Native public static ObjectValue defineProperties(Context ctx, ObjectValue obj, ObjectValue attrib) {
|
||||
for (var key : Values.getMembers(null, obj, false, false)) {
|
||||
obj.defineProperty(ctx, key, attrib.getMember(ctx, key));
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static ObjectValue __defineProperties(Arguments args) {
|
||||
var obj = args.convert(0, ObjectValue.class);
|
||||
var attrib = args.get(1);
|
||||
|
||||
for (var key : Values.getMembers(null, attrib, false, false)) {
|
||||
__defineProperty(new Arguments(args.ctx, null, obj, key, Values.getMember(args.ctx, attrib, key)));
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
@Native public static ArrayValue keys(Context ctx, Object obj, Object all) {
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static ArrayValue __keys(Arguments args) {
|
||||
var obj = args.get(0);
|
||||
var all = args.getBoolean(1);
|
||||
var res = new ArrayValue();
|
||||
var _all = Values.toBoolean(all);
|
||||
|
||||
for (var key : Values.getMembers(ctx, obj, true, false)) {
|
||||
if (_all || !(key instanceof Symbol)) res.set(ctx, res.size(), key);
|
||||
for (var key : Values.getMembers(args.ctx, obj, true, false)) {
|
||||
if (all || !(key instanceof Symbol)) res.set(args.ctx, res.size(), key);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@Native public static ArrayValue entries(Context ctx, Object obj, Object all) {
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static ArrayValue __entries(Arguments args) {
|
||||
var res = new ArrayValue();
|
||||
var _all = Values.toBoolean(all);
|
||||
var obj = args.get(0);
|
||||
var all = args.getBoolean(1);
|
||||
|
||||
for (var key : Values.getMembers(ctx, obj, true, false)) {
|
||||
if (_all || !(key instanceof Symbol)) res.set(ctx, res.size(), new ArrayValue(ctx, key, Values.getMember(ctx, obj, key)));
|
||||
for (var key : Values.getMembers(args.ctx, obj, true, false)) {
|
||||
if (all || !(key instanceof Symbol)) res.set(args.ctx, res.size(), new ArrayValue(args.ctx, key, Values.getMember(args.ctx, obj, key)));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@Native public static ArrayValue values(Context ctx, Object obj, Object all) {
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static ArrayValue __values(Arguments args) {
|
||||
var res = new ArrayValue();
|
||||
var _all = Values.toBoolean(all);
|
||||
var obj = args.get(0);
|
||||
var all = args.getBoolean(1);
|
||||
|
||||
for (var key : Values.getMembers(ctx, obj, true, false)) {
|
||||
if (_all || key instanceof String) res.set(ctx, res.size(), Values.getMember(ctx, obj, key));
|
||||
for (var key : Values.getMembers(args.ctx, obj, true, false)) {
|
||||
if (all || !(key instanceof Symbol)) res.set(args.ctx, res.size(), Values.getMember(args.ctx, obj, key));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@Native public static ObjectValue getOwnPropertyDescriptor(Context ctx, Object obj, Object key) {
|
||||
return Values.getMemberDescriptor(ctx, obj, key);
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static ObjectValue __getOwnPropertyDescriptor(Arguments args) {
|
||||
return Values.getMemberDescriptor(args.ctx, args.get(0), args.get(1));
|
||||
}
|
||||
@Native public static ObjectValue getOwnPropertyDescriptors(Context ctx, Object obj) {
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static ObjectValue __getOwnPropertyDescriptors(Arguments args) {
|
||||
var res = new ObjectValue();
|
||||
for (var key : Values.getMembers(ctx, obj, true, true)) {
|
||||
res.defineProperty(ctx, key, getOwnPropertyDescriptor(ctx, obj, key));
|
||||
var obj = args.get(0);
|
||||
for (var key : Values.getMembers(args.ctx, obj, true, true)) {
|
||||
res.defineProperty(args.ctx, key, Values.getMemberDescriptor(args.ctx, obj, key));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@Native public static ArrayValue getOwnPropertyNames(Context ctx, Object obj, Object all) {
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static ArrayValue __getOwnPropertyNames(Arguments args) {
|
||||
var res = new ArrayValue();
|
||||
var _all = Values.toBoolean(all);
|
||||
var obj = args.get(0);
|
||||
var all = args.getBoolean(1);
|
||||
|
||||
for (var key : Values.getMembers(ctx, obj, true, true)) {
|
||||
if (_all || !(key instanceof Symbol)) res.set(ctx, res.size(), key);
|
||||
for (var key : Values.getMembers(args.ctx, obj, true, true)) {
|
||||
if (all || !(key instanceof Symbol)) res.set(args.ctx, res.size(), key);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@Native public static ArrayValue getOwnPropertySymbols(Context ctx, Object obj) {
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static ArrayValue __getOwnPropertySymbols(Arguments args) {
|
||||
var obj = args.get(0);
|
||||
var res = new ArrayValue();
|
||||
|
||||
for (var key : Values.getMembers(ctx, obj, true, true)) {
|
||||
if (key instanceof Symbol) res.set(ctx, res.size(), key);
|
||||
for (var key : Values.getMembers(args.ctx, obj, true, true)) {
|
||||
if (key instanceof Symbol) res.set(args.ctx, res.size(), key);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@Native public static boolean hasOwn(Context ctx, Object obj, Object key) {
|
||||
return Values.hasMember(ctx, obj, key, true);
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static boolean __hasOwn(Arguments args) {
|
||||
return Values.hasMember(args.ctx, args.get(0), args.get(1), true);
|
||||
}
|
||||
|
||||
@Native public static ObjectValue getPrototypeOf(Context ctx, Object obj) {
|
||||
return Values.getPrototype(ctx, obj);
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static ObjectValue __getPrototypeOf(Arguments args) {
|
||||
return Values.getPrototype(args.ctx, args.get(0));
|
||||
}
|
||||
@Native public static Object setPrototypeOf(Context ctx, Object obj, Object proto) {
|
||||
Values.setPrototype(ctx, obj, proto);
|
||||
return obj;
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static Object __setPrototypeOf(Arguments args) {
|
||||
Values.setPrototype(args.ctx, args.get(0), args.get(1));
|
||||
return args.get(0);
|
||||
}
|
||||
|
||||
@Native public static ObjectValue fromEntries(Context ctx, Object iterable) {
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static ObjectValue __fromEntries(Arguments args) {
|
||||
var res = new ObjectValue();
|
||||
|
||||
for (var el : Values.fromJSIterator(ctx, iterable)) {
|
||||
for (var el : Values.fromJSIterator(args.ctx, args.get(0))) {
|
||||
if (el instanceof ArrayValue) {
|
||||
res.defineProperty(ctx, ((ArrayValue)el).get(0), ((ArrayValue)el).get(1));
|
||||
res.defineProperty(args.ctx, ((ArrayValue)el).get(0), ((ArrayValue)el).get(1));
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@Native public static Object preventExtensions(Context ctx, Object obj) {
|
||||
if (obj instanceof ObjectValue) ((ObjectValue)obj).preventExtensions();
|
||||
return obj;
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static Object __preventExtensions(Arguments args) {
|
||||
if (args.get(0) instanceof ObjectValue) args.convert(0, ObjectValue.class).preventExtensions();
|
||||
return args.get(0);
|
||||
}
|
||||
@Native public static Object seal(Context ctx, Object obj) {
|
||||
if (obj instanceof ObjectValue) ((ObjectValue)obj).seal();
|
||||
return obj;
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static Object __seal(Arguments args) {
|
||||
if (args.get(0) instanceof ObjectValue) args.convert(0, ObjectValue.class).seal();
|
||||
return args.get(0);
|
||||
}
|
||||
@Native public static Object freeze(Context ctx, Object obj) {
|
||||
if (obj instanceof ObjectValue) ((ObjectValue)obj).freeze();
|
||||
return obj;
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static Object __freeze(Arguments args) {
|
||||
if (args.get(0) instanceof ObjectValue) args.convert(0, ObjectValue.class).freeze();
|
||||
return args.get(0);
|
||||
}
|
||||
|
||||
@Native public static boolean isExtensible(Context ctx, Object obj) {
|
||||
return obj instanceof ObjectValue && ((ObjectValue)obj).extensible();
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static boolean __isExtensible(Arguments args) {
|
||||
var obj = args.get(0);
|
||||
if (!(obj instanceof ObjectValue)) return false;
|
||||
return ((ObjectValue)obj).extensible();
|
||||
}
|
||||
@Native public static boolean isSealed(Context ctx, Object obj) {
|
||||
if (obj instanceof ObjectValue && ((ObjectValue)obj).extensible()) {
|
||||
var _obj = (ObjectValue)obj;
|
||||
for (var key : _obj.keys(true)) {
|
||||
if (_obj.memberConfigurable(key)) return false;
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static boolean __isSealed(Arguments args) {
|
||||
var obj = args.get(0);
|
||||
|
||||
if (!(obj instanceof ObjectValue)) return true;
|
||||
var _obj = (ObjectValue)obj;
|
||||
|
||||
if (_obj.extensible()) return false;
|
||||
|
||||
for (var key : _obj.keys(true)) {
|
||||
if (_obj.memberConfigurable(key)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
@Native public static boolean isFrozen(Context ctx, Object obj) {
|
||||
if (obj instanceof ObjectValue && ((ObjectValue)obj).extensible()) {
|
||||
var _obj = (ObjectValue)obj;
|
||||
for (var key : _obj.keys(true)) {
|
||||
if (_obj.memberConfigurable(key)) return false;
|
||||
if (_obj.memberWritable(key)) return false;
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static boolean __isFrozen(Arguments args) {
|
||||
var obj = args.get(0);
|
||||
|
||||
if (!(obj instanceof ObjectValue)) return true;
|
||||
var _obj = (ObjectValue)obj;
|
||||
|
||||
if (_obj.extensible()) return false;
|
||||
|
||||
for (var key : _obj.keys(true)) {
|
||||
if (_obj.memberConfigurable(key)) return false;
|
||||
if (_obj.memberWritable(key)) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Native(thisArg = true) public static Object valueOf(Context ctx, Object thisArg) {
|
||||
return thisArg;
|
||||
@Expose
|
||||
public static Object __valueOf(Arguments args) {
|
||||
return args.self;
|
||||
}
|
||||
@Native(thisArg = true) public static String toString(Context ctx, Object thisArg) {
|
||||
var name = Values.getMember(ctx, thisArg, ctx.environment().symbol("Symbol.typeName"));
|
||||
@Expose
|
||||
public static String __toString(Arguments args) {
|
||||
var name = Values.getMember(args.ctx, args.self, Symbol.get("Symbol.typeName"));
|
||||
if (name == null) name = "Unknown";
|
||||
else name = Values.toString(ctx, name);
|
||||
else name = Values.toString(args.ctx, name);
|
||||
|
||||
return "[object " + name + "]";
|
||||
}
|
||||
@Native(thisArg = true) public static boolean hasOwnProperty(Context ctx, Object thisArg, Object key) {
|
||||
return ObjectLib.hasOwn(ctx, thisArg, Values.convert(ctx, key, String.class));
|
||||
@Expose
|
||||
public static boolean __hasOwnProperty(Arguments args) {
|
||||
return Values.hasMember(args.ctx, args.self, args.get(0), true);
|
||||
}
|
||||
|
||||
@NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object arg) {
|
||||
@ExposeConstructor
|
||||
public static Object __constructor(Arguments args) {
|
||||
var arg = args.get(0);
|
||||
if (arg == null || arg == Values.NULL) return new ObjectValue();
|
||||
else if (arg instanceof Boolean) return BooleanLib.constructor(ctx, thisArg, arg);
|
||||
else if (arg instanceof Number) return NumberLib.constructor(ctx, thisArg, arg);
|
||||
else if (arg instanceof String) return StringLib.constructor(ctx, thisArg, arg);
|
||||
// else if (arg instanceof Symbol) return SymbolPolyfill.constructor(ctx, thisArg, arg);
|
||||
else if (arg instanceof Boolean) return new BooleanLib((boolean)arg);
|
||||
else if (arg instanceof Number) return new NumberLib(((Number)arg).doubleValue());
|
||||
else if (arg instanceof String) return new StringLib((String)arg);
|
||||
else if (arg instanceof Symbol) return new SymbolLib((Symbol)arg);
|
||||
else return arg;
|
||||
}
|
||||
}
|
||||
|
@ -2,355 +2,113 @@ package me.topchetoeu.jscript.lib;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.EventLoop;
|
||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.engine.values.NativeFunction;
|
||||
import me.topchetoeu.jscript.engine.values.NativeWrapper;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.Arguments;
|
||||
import me.topchetoeu.jscript.interop.Expose;
|
||||
import me.topchetoeu.jscript.interop.ExposeConstructor;
|
||||
import me.topchetoeu.jscript.interop.ExposeTarget;
|
||||
import me.topchetoeu.jscript.interop.WrapperName;
|
||||
import me.topchetoeu.jscript.ResultRunnable;
|
||||
|
||||
@Native("Promise") public class PromiseLib {
|
||||
public static interface PromiseRunner {
|
||||
Object run();
|
||||
}
|
||||
private static class Handle {
|
||||
public final Context ctx;
|
||||
public final FunctionValue fulfilled;
|
||||
public final FunctionValue rejected;
|
||||
@WrapperName("Promise")
|
||||
public class PromiseLib {
|
||||
public static interface Handle {
|
||||
void onFulfil(Object val);
|
||||
void onReject(EngineException err);
|
||||
|
||||
public Handle(Context ctx, FunctionValue fulfilled, FunctionValue rejected) {
|
||||
this.ctx = ctx;
|
||||
this.fulfilled = fulfilled;
|
||||
this.rejected = rejected;
|
||||
default Handle defer(EventLoop loop) {
|
||||
var self = this;
|
||||
return new Handle() {
|
||||
@Override public void onFulfil(Object val) {
|
||||
loop.pushMsg(() -> self.onFulfil(val), true);
|
||||
}
|
||||
@Override public void onReject(EngineException val) {
|
||||
loop.pushMsg(() -> self.onReject(val), true);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@Native("resolve")
|
||||
public static PromiseLib ofResolved(Context ctx, Object val) {
|
||||
var res = new PromiseLib();
|
||||
res.fulfill(ctx, val);
|
||||
return res;
|
||||
}
|
||||
@Native("reject")
|
||||
public static PromiseLib ofRejected(Context ctx, Object val) {
|
||||
var res = new PromiseLib();
|
||||
res.reject(ctx, val);
|
||||
return res;
|
||||
}
|
||||
|
||||
@Native public static PromiseLib any(Context ctx, Object _promises) {
|
||||
if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array.");
|
||||
var promises = Values.array(_promises);
|
||||
if (promises.size() == 0) return ofResolved(ctx, new ArrayValue());
|
||||
var n = new int[] { promises.size() };
|
||||
var res = new PromiseLib();
|
||||
|
||||
var errors = new ArrayValue();
|
||||
|
||||
for (var i = 0; i < promises.size(); i++) {
|
||||
var index = i;
|
||||
var val = promises.get(i);
|
||||
then(ctx, val,
|
||||
new NativeFunction(null, (e, th, args) -> { res.fulfill(e, args[0]); return null; }),
|
||||
new NativeFunction(null, (e, th, args) -> {
|
||||
errors.set(ctx, index, args[0]);
|
||||
n[0]--;
|
||||
if (n[0] <= 0) res.reject(e, errors);
|
||||
return null;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@Native public static PromiseLib race(Context ctx, Object _promises) {
|
||||
if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array.");
|
||||
var promises = Values.array(_promises);
|
||||
if (promises.size() == 0) return ofResolved(ctx, new ArrayValue());
|
||||
var res = new PromiseLib();
|
||||
|
||||
for (var i = 0; i < promises.size(); i++) {
|
||||
var val = promises.get(i);
|
||||
then(ctx, val,
|
||||
new NativeFunction(null, (e, th, args) -> { res.fulfill(e, args[0]); return null; }),
|
||||
new NativeFunction(null, (e, th, args) -> { res.reject(e, args[0]); return null; })
|
||||
);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@Native public static PromiseLib all(Context ctx, Object _promises) {
|
||||
if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array.");
|
||||
var promises = Values.array(_promises);
|
||||
if (promises.size() == 0) return ofResolved(ctx, new ArrayValue());
|
||||
var n = new int[] { promises.size() };
|
||||
var res = new PromiseLib();
|
||||
|
||||
var result = new ArrayValue();
|
||||
|
||||
for (var i = 0; i < promises.size(); i++) {
|
||||
var index = i;
|
||||
var val = promises.get(i);
|
||||
then(ctx, val,
|
||||
new NativeFunction(null, (e, th, args) -> {
|
||||
result.set(ctx, index, args[0]);
|
||||
n[0]--;
|
||||
if (n[0] <= 0) res.fulfill(e, result);
|
||||
return null;
|
||||
}),
|
||||
new NativeFunction(null, (e, th, args) -> { res.reject(e, args[0]); return null; })
|
||||
);
|
||||
}
|
||||
|
||||
if (n[0] <= 0) res.fulfill(ctx, result);
|
||||
|
||||
return res;
|
||||
}
|
||||
@Native public static PromiseLib allSettled(Context ctx, Object _promises) {
|
||||
if (!Values.isArray(_promises)) throw EngineException.ofType("Expected argument for any to be an array.");
|
||||
var promises = Values.array(_promises);
|
||||
if (promises.size() == 0) return ofResolved(ctx, new ArrayValue());
|
||||
var n = new int[] { promises.size() };
|
||||
var res = new PromiseLib();
|
||||
|
||||
var result = new ArrayValue();
|
||||
|
||||
for (var i = 0; i < promises.size(); i++) {
|
||||
var index = i;
|
||||
var val = promises.get(i);
|
||||
then(ctx, val,
|
||||
new NativeFunction(null, (e, th, args) -> {
|
||||
result.set(ctx, index, new ObjectValue(ctx, Map.of(
|
||||
"status", "fulfilled",
|
||||
"value", args[0]
|
||||
)));
|
||||
n[0]--;
|
||||
if (n[0] <= 0) res.fulfill(e, result);
|
||||
return null;
|
||||
}),
|
||||
new NativeFunction(null, (e, th, args) -> {
|
||||
result.set(ctx, index, new ObjectValue(ctx, Map.of(
|
||||
"status", "rejected",
|
||||
"reason", args[0]
|
||||
)));
|
||||
n[0]--;
|
||||
if (n[0] <= 0) res.fulfill(e, result);
|
||||
return null;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
if (n[0] <= 0) res.fulfill(ctx, result);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
/**
|
||||
* Thread safe - you can call this from anywhere
|
||||
* HOWEVER, it's strongly recommended to use this only in javascript
|
||||
*/
|
||||
@Native(thisArg=true) public static Object then(Context ctx, Object _thisArg, Object _onFulfill, Object _onReject) {
|
||||
var onFulfill = _onFulfill instanceof FunctionValue ? ((FunctionValue)_onFulfill) : null;
|
||||
var onReject = _onReject instanceof FunctionValue ? ((FunctionValue)_onReject) : null;
|
||||
|
||||
var res = new PromiseLib();
|
||||
|
||||
var fulfill = onFulfill == null ? new NativeFunction((_ctx, _0, _args) -> _args.length > 0 ? _args[0] : null) : (FunctionValue)onFulfill;
|
||||
var reject = onReject == null ? new NativeFunction((_ctx, _0, _args) -> {
|
||||
throw new EngineException(_args.length > 0 ? _args[0] : null);
|
||||
}) : (FunctionValue)onReject;
|
||||
|
||||
var thisArg = _thisArg instanceof NativeWrapper && ((NativeWrapper)_thisArg).wrapped instanceof PromiseLib ?
|
||||
((NativeWrapper)_thisArg).wrapped :
|
||||
_thisArg;
|
||||
|
||||
var fulfillHandle = new NativeFunction(null, (_ctx, th, a) -> {
|
||||
try { res.fulfill(ctx, Values.convert(ctx, fulfill.call(ctx, null, a[0]), Object.class)); }
|
||||
catch (EngineException err) { res.reject(ctx, err.value); }
|
||||
return null;
|
||||
});
|
||||
var rejectHandle = new NativeFunction(null, (_ctx, th, a) -> {
|
||||
try { res.fulfill(ctx, reject.call(ctx, null, a[0])); }
|
||||
catch (EngineException err) { res.reject(ctx, err.value); }
|
||||
if (thisArg instanceof PromiseLib) ((PromiseLib)thisArg).handled = true;
|
||||
return null;
|
||||
});
|
||||
|
||||
if (thisArg instanceof PromiseLib) ((PromiseLib)thisArg).handle(ctx, fulfillHandle, rejectHandle);
|
||||
else {
|
||||
Object next;
|
||||
try { next = Values.getMember(ctx, thisArg, "then"); }
|
||||
catch (IllegalArgumentException e) { next = null; }
|
||||
|
||||
try {
|
||||
if (next instanceof FunctionValue) ((FunctionValue)next).call(ctx, thisArg, fulfillHandle, rejectHandle);
|
||||
else res.fulfill(ctx, fulfill.call(ctx, null, thisArg));
|
||||
}
|
||||
catch (EngineException err) {
|
||||
res.reject(ctx, fulfill.call(ctx, null, err.value));
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
/**
|
||||
* Thread safe - you can call this from anywhere
|
||||
* HOWEVER, it's strongly recommended to use this only in javascript
|
||||
*/
|
||||
@Native(value="catch", thisArg=true) public static Object _catch(Context ctx, Object thisArg, Object _onReject) {
|
||||
return then(ctx, thisArg, null, _onReject);
|
||||
}
|
||||
/**
|
||||
* Thread safe - you can call this from anywhere
|
||||
* HOWEVER, it's strongly recommended to use this only in javascript
|
||||
*/
|
||||
@Native(value="finally", thisArg=true) public static Object _finally(Context ctx, Object thisArg, Object _handle) {
|
||||
return then(ctx, thisArg,
|
||||
new NativeFunction(null, (e, th, _args) -> {
|
||||
if (_handle instanceof FunctionValue) ((FunctionValue)_handle).call(ctx);
|
||||
return _args.length > 0 ? _args[0] : null;
|
||||
}),
|
||||
new NativeFunction(null, (e, th, _args) -> {
|
||||
if (_handle instanceof FunctionValue) ((FunctionValue)_handle).call(ctx);
|
||||
throw new EngineException(_args.length > 0 ? _args[0] : null);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private List<Handle> handles = new ArrayList<>();
|
||||
|
||||
private static final int STATE_PENDING = 0;
|
||||
private static final int STATE_FULFILLED = 1;
|
||||
private static final int STATE_REJECTED = 2;
|
||||
|
||||
private List<Handle> handles = new ArrayList<>();
|
||||
|
||||
private int state = STATE_PENDING;
|
||||
private boolean handled = false;
|
||||
private Object val;
|
||||
|
||||
private void resolveSynchronized(Context ctx, Object val, int newState) {
|
||||
ctx.engine.pushMsg(() -> {
|
||||
this.val = val;
|
||||
this.state = newState;
|
||||
|
||||
for (var handle : handles) {
|
||||
if (newState == STATE_FULFILLED) handle.onFulfil(val);
|
||||
if (newState == STATE_REJECTED) {
|
||||
handle.onReject((EngineException)val);
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (state == STATE_REJECTED && !handled) {
|
||||
Values.printError(((EngineException)val).setCtx(ctx.environment, ctx.engine), "(in promise)");
|
||||
}
|
||||
|
||||
handles = null;
|
||||
}, true);
|
||||
|
||||
}
|
||||
private synchronized void resolve(Context ctx, Object val, int newState) {
|
||||
if (this.state != STATE_PENDING || newState == STATE_PENDING) return;
|
||||
|
||||
handle(ctx, val, new Handle() {
|
||||
@Override public void onFulfil(Object val) {
|
||||
resolveSynchronized(ctx, val, newState);
|
||||
}
|
||||
@Override public void onReject(EngineException err) {
|
||||
resolveSynchronized(ctx, val, STATE_REJECTED);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public synchronized void fulfill(Context ctx, Object val) {
|
||||
if (this.state != STATE_PENDING) return;
|
||||
|
||||
if (val instanceof PromiseLib) ((PromiseLib)val).handle(ctx,
|
||||
new NativeFunction(null, (e, th, a) -> { this.fulfill(ctx, a[0]); return null; }),
|
||||
new NativeFunction(null, (e, th, a) -> { this.reject(ctx, a[0]); return null; })
|
||||
);
|
||||
else {
|
||||
Object then;
|
||||
try { then = Values.getMember(ctx, val, "then"); }
|
||||
catch (IllegalArgumentException e) { then = null; }
|
||||
|
||||
try {
|
||||
if (then instanceof FunctionValue) ((FunctionValue)then).call(ctx, val,
|
||||
new NativeFunction((e, _thisArg, a) -> { this.fulfill(ctx, a.length > 0 ? a[0] : null); return null; }),
|
||||
new NativeFunction((e, _thisArg, a) -> { this.reject(ctx, a.length > 0 ? a[0] : null); return null; })
|
||||
);
|
||||
else {
|
||||
this.val = val;
|
||||
this.state = STATE_FULFILLED;
|
||||
|
||||
ctx.engine.pushMsg(true, ctx.environment(), new NativeFunction((_ctx, _thisArg, _args) -> {
|
||||
for (var handle : handles) {
|
||||
handle.fulfilled.call(handle.ctx, null, val);
|
||||
}
|
||||
handles = null;
|
||||
return null;
|
||||
}), null);
|
||||
}
|
||||
}
|
||||
catch (EngineException err) {
|
||||
this.reject(ctx, err.value);
|
||||
}
|
||||
}
|
||||
resolve(ctx, val, STATE_FULFILLED);
|
||||
}
|
||||
public synchronized void reject(Context ctx, Object val) {
|
||||
if (this.state != STATE_PENDING) return;
|
||||
|
||||
if (val instanceof PromiseLib) ((PromiseLib)val).handle(ctx,
|
||||
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 {
|
||||
Object then;
|
||||
try { then = Values.getMember(ctx, val, "then"); }
|
||||
catch (IllegalArgumentException e) { then = null; }
|
||||
|
||||
try {
|
||||
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; })
|
||||
);
|
||||
else {
|
||||
this.val = val;
|
||||
this.state = STATE_REJECTED;
|
||||
|
||||
ctx.engine.pushMsg(true, ctx.environment(), new NativeFunction((_ctx, _thisArg, _args) -> {
|
||||
for (var handle : handles) handle.rejected.call(handle.ctx, null, val);
|
||||
if (!handled) {
|
||||
Values.printError(new EngineException(val).setCtx(ctx.environment(), ctx.engine), "(in promise)");
|
||||
}
|
||||
handles = null;
|
||||
return null;
|
||||
}), null);
|
||||
}
|
||||
}
|
||||
catch (EngineException err) {
|
||||
this.reject(ctx, err.value);
|
||||
}
|
||||
}
|
||||
public synchronized void reject(Context ctx, EngineException val) {
|
||||
resolve(ctx, val, STATE_REJECTED);
|
||||
}
|
||||
|
||||
private void handle(Context ctx, FunctionValue fulfill, FunctionValue reject) {
|
||||
if (state == STATE_FULFILLED) ctx.engine.pushMsg(true, ctx.environment(), fulfill, null, val);
|
||||
private void handle(Handle handle) {
|
||||
if (state == STATE_FULFILLED) handle.onFulfil(val);
|
||||
else if (state == STATE_REJECTED) {
|
||||
ctx.engine.pushMsg(true, ctx.environment(), reject, null, val);
|
||||
handle.onReject((EngineException)val);
|
||||
handled = true;
|
||||
}
|
||||
else handles.add(new Handle(ctx, fulfill, reject));
|
||||
else handles.add(handle);
|
||||
}
|
||||
|
||||
@Override @Native public String toString() {
|
||||
@Override public String toString() {
|
||||
if (state == STATE_PENDING) return "Promise (pending)";
|
||||
else if (state == STATE_FULFILLED) return "Promise (fulfilled)";
|
||||
else return "Promise (rejected)";
|
||||
}
|
||||
|
||||
/**
|
||||
* NOT THREAD SAFE - must be called from the engine executor thread
|
||||
*/
|
||||
@Native public PromiseLib(Context ctx, FunctionValue func) {
|
||||
if (!(func instanceof FunctionValue)) throw EngineException.ofType("A function must be passed to the promise constructor.");
|
||||
try {
|
||||
func.call(
|
||||
ctx, null,
|
||||
new NativeFunction(null, (e, th, args) -> {
|
||||
fulfill(e, args.length > 0 ? args[0] : null);
|
||||
return null;
|
||||
}),
|
||||
new NativeFunction(null, (e, th, args) -> {
|
||||
reject(e, args.length > 0 ? args[0] : null);
|
||||
return null;
|
||||
})
|
||||
);
|
||||
}
|
||||
catch (EngineException e) {
|
||||
reject(ctx, e.value);
|
||||
}
|
||||
}
|
||||
|
||||
private PromiseLib(int state, Object val) {
|
||||
this.state = state;
|
||||
this.val = val;
|
||||
}
|
||||
public PromiseLib() {
|
||||
this(STATE_PENDING, null);
|
||||
this.state = STATE_PENDING;
|
||||
this.val = null;
|
||||
}
|
||||
|
||||
public static PromiseLib await(Context ctx, PromiseRunner runner) {
|
||||
public static PromiseLib await(Context ctx, ResultRunnable<Object> runner) {
|
||||
var res = new PromiseLib();
|
||||
|
||||
new Thread(() -> {
|
||||
@ -358,10 +116,271 @@ import me.topchetoeu.jscript.interop.Native;
|
||||
res.fulfill(ctx, runner.run());
|
||||
}
|
||||
catch (EngineException e) {
|
||||
res.reject(ctx, e.value);
|
||||
res.reject(ctx, e);
|
||||
}
|
||||
}, "Promisifier").start();
|
||||
|
||||
return res;
|
||||
}
|
||||
public static PromiseLib await(Context ctx, Runnable runner) {
|
||||
return await(ctx, () -> {
|
||||
runner.run();
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
public static void handle(Context ctx, Object obj, Handle handle) {
|
||||
if (Values.isWrapper(obj, PromiseLib.class)) {
|
||||
var promise = Values.wrapper(obj, PromiseLib.class);
|
||||
handle(ctx, promise, handle);
|
||||
return;
|
||||
}
|
||||
if (obj instanceof PromiseLib) {
|
||||
((PromiseLib)obj).handle(handle);
|
||||
return;
|
||||
}
|
||||
|
||||
var rethrow = new boolean[1];
|
||||
|
||||
try {
|
||||
var then = Values.getMember(ctx, obj, "then");
|
||||
Values.call(ctx, then, obj,
|
||||
new NativeFunction(args -> {
|
||||
try { handle.onFulfil(args.get(0)); }
|
||||
catch (Exception e) {
|
||||
rethrow[0] = true;
|
||||
throw e;
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
new NativeFunction(args -> {
|
||||
try { handle.onReject(new EngineException(args.get(0))); }
|
||||
catch (Exception e) {
|
||||
rethrow[0] = true;
|
||||
throw e;
|
||||
}
|
||||
return null;
|
||||
})
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
catch (Exception e) {
|
||||
if (rethrow[0]) throw e;
|
||||
}
|
||||
|
||||
handle.onFulfil(obj);
|
||||
}
|
||||
|
||||
public static PromiseLib ofResolved(Context ctx, Object value) {
|
||||
var res = new PromiseLib();
|
||||
res.fulfill(ctx, value);
|
||||
return res;
|
||||
}
|
||||
public static PromiseLib ofRejected(Context ctx, EngineException value) {
|
||||
var res = new PromiseLib();
|
||||
res.reject(ctx, value);
|
||||
return res;
|
||||
}
|
||||
|
||||
@Expose(value = "resolve", target = ExposeTarget.STATIC)
|
||||
public static PromiseLib __ofResolved(Arguments args) {
|
||||
return ofResolved(args.ctx, args.get(0));
|
||||
}
|
||||
@Expose(value = "reject", target = ExposeTarget.STATIC)
|
||||
public static PromiseLib __ofRejected(Arguments args) {
|
||||
return ofRejected(args.ctx, new EngineException(args.get(0)).setCtx(args.ctx));
|
||||
}
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static PromiseLib __any(Arguments args) {
|
||||
if (!(args.get(0) instanceof ArrayValue)) throw EngineException.ofType("Expected argument for any to be an array.");
|
||||
var promises = args.convert(0, ArrayValue.class);
|
||||
|
||||
if (promises.size() == 0) return ofRejected(args.ctx, EngineException.ofError("No promises passed to 'Promise.any'.").setCtx(args.ctx));
|
||||
var n = new int[] { promises.size() };
|
||||
var res = new PromiseLib();
|
||||
var errors = new ArrayValue();
|
||||
|
||||
for (var i = 0; i < promises.size(); i++) {
|
||||
var index = i;
|
||||
var val = promises.get(i);
|
||||
if (res.state != STATE_PENDING) break;
|
||||
|
||||
handle(args.ctx, val, new Handle() {
|
||||
public void onFulfil(Object val) { res.fulfill(args.ctx, val); }
|
||||
public void onReject(EngineException err) {
|
||||
errors.set(args.ctx, index, err.value);
|
||||
n[0]--;
|
||||
if (n[0] <= 0) res.reject(args.ctx, new EngineException(errors).setCtx(args.ctx));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static PromiseLib __race(Arguments args) {
|
||||
if (!(args.get(0) instanceof ArrayValue)) throw EngineException.ofType("Expected argument for any to be an array.");
|
||||
var promises = args.convert(0, ArrayValue.class);
|
||||
var res = new PromiseLib();
|
||||
|
||||
for (var i = 0; i < promises.size(); i++) {
|
||||
var val = promises.get(i);
|
||||
if (res.state != STATE_PENDING) break;
|
||||
|
||||
handle(args.ctx, val, new Handle() {
|
||||
@Override public void onFulfil(Object val) { res.fulfill(args.ctx, val); }
|
||||
@Override public void onReject(EngineException err) { res.reject(args.ctx, err); }
|
||||
});
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static PromiseLib __all(Arguments args) {
|
||||
if (!(args.get(0) instanceof ArrayValue)) throw EngineException.ofType("Expected argument for any to be an array.");
|
||||
var promises = args.convert(0, ArrayValue.class);
|
||||
var n = new int[] { promises.size() };
|
||||
var res = new PromiseLib();
|
||||
var result = new ArrayValue();
|
||||
|
||||
for (var i = 0; i < promises.size(); i++) {
|
||||
if (res.state != STATE_PENDING) break;
|
||||
|
||||
var index = i;
|
||||
var val = promises.get(i);
|
||||
|
||||
handle(args.ctx, val, new Handle() {
|
||||
@Override public void onFulfil(Object val) {
|
||||
result.set(args.ctx, index, val);
|
||||
n[0]--;
|
||||
if (n[0] <= 0) res.fulfill(args.ctx, result);
|
||||
}
|
||||
@Override public void onReject(EngineException err) {
|
||||
res.reject(args.ctx, err);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (n[0] <= 0) res.fulfill(args.ctx, result);
|
||||
|
||||
return res;
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static PromiseLib __allSettled(Arguments args) {
|
||||
if (!(args.get(0) instanceof ArrayValue)) throw EngineException.ofType("Expected argument for any to be an array.");
|
||||
var promises = args.convert(0, ArrayValue.class);
|
||||
var n = new int[] { promises.size() };
|
||||
var res = new PromiseLib();
|
||||
var result = new ArrayValue();
|
||||
|
||||
for (var i = 0; i < promises.size(); i++) {
|
||||
if (res.state != STATE_PENDING) break;
|
||||
|
||||
var index = i;
|
||||
|
||||
handle(args.ctx, promises.get(i), new Handle() {
|
||||
@Override public void onFulfil(Object val) {
|
||||
var desc = new ObjectValue();
|
||||
desc.defineProperty(args.ctx, "status", "fulfilled");
|
||||
desc.defineProperty(args.ctx, "value", val);
|
||||
|
||||
result.set(args.ctx, index, desc);
|
||||
|
||||
n[0]--;
|
||||
if (n[0] <= 0) res.fulfill(args.ctx, res);
|
||||
}
|
||||
@Override public void onReject(EngineException err) {
|
||||
var desc = new ObjectValue();
|
||||
desc.defineProperty(args.ctx, "status", "reject");
|
||||
desc.defineProperty(args.ctx, "value", err.value);
|
||||
|
||||
result.set(args.ctx, index, desc);
|
||||
|
||||
n[0]--;
|
||||
if (n[0] <= 0) res.fulfill(args.ctx, res);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (n[0] <= 0) res.fulfill(args.ctx, result);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@Expose
|
||||
public static Object __then(Arguments args) {
|
||||
var onFulfill = args.get(0) instanceof FunctionValue ? args.convert(0, FunctionValue.class) : null;
|
||||
var onReject = args.get(1) instanceof FunctionValue ? args.convert(1, FunctionValue.class) : null;
|
||||
|
||||
var res = new PromiseLib();
|
||||
|
||||
handle(args.ctx, args.self, new Handle() {
|
||||
@Override public void onFulfil(Object val) {
|
||||
try { res.fulfill(args.ctx, onFulfill.call(args.ctx, null, val)); }
|
||||
catch (EngineException e) { res.reject(args.ctx, e); }
|
||||
}
|
||||
@Override public void onReject(EngineException err) {
|
||||
try { res.fulfill(args.ctx, onReject.call(args.ctx, null, err.value)); }
|
||||
catch (EngineException e) { res.reject(args.ctx, e); }
|
||||
}
|
||||
}.defer(args.ctx.engine));
|
||||
|
||||
return res;
|
||||
}
|
||||
@Expose
|
||||
public static Object __catch(Arguments args) {
|
||||
return __then(new Arguments(args.ctx, args.self, null, args.get(0)));
|
||||
}
|
||||
@Expose
|
||||
public static Object __finally(Arguments args) {
|
||||
var func = args.get(0) instanceof FunctionValue ? args.convert(0, FunctionValue.class) : null;
|
||||
|
||||
var res = new PromiseLib();
|
||||
|
||||
handle(args.ctx, args.self, new Handle() {
|
||||
@Override public void onFulfil(Object val) {
|
||||
try {
|
||||
func.call(args.ctx);
|
||||
res.fulfill(args.ctx, val);
|
||||
}
|
||||
catch (EngineException e) { res.reject(args.ctx, e); }
|
||||
}
|
||||
@Override public void onReject(EngineException err) {
|
||||
try {
|
||||
func.call(args.ctx);
|
||||
res.reject(args.ctx, err);
|
||||
}
|
||||
catch (EngineException e) { res.reject(args.ctx, e); }
|
||||
}
|
||||
}.defer(args.ctx.engine));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@ExposeConstructor
|
||||
public static PromiseLib __constructor(Arguments args) {
|
||||
var func = args.convert(0, FunctionValue.class);
|
||||
var res = new PromiseLib();
|
||||
|
||||
try {
|
||||
func.call(
|
||||
args.ctx, null,
|
||||
new NativeFunction(null, _args -> {
|
||||
res.fulfill(_args.ctx, _args.get(0));
|
||||
return null;
|
||||
}),
|
||||
new NativeFunction(null, _args -> {
|
||||
res.reject(_args.ctx, new EngineException(_args.get(0)).setCtx(_args.ctx));
|
||||
return null;
|
||||
})
|
||||
);
|
||||
}
|
||||
catch (EngineException e) {
|
||||
res.reject(args.ctx, e);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +1,19 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.Environment;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto;
|
||||
import me.topchetoeu.jscript.interop.InitType;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeConstructor;
|
||||
import me.topchetoeu.jscript.interop.NativeInit;
|
||||
import me.topchetoeu.jscript.interop.WrapperName;
|
||||
import me.topchetoeu.jscript.interop.Arguments;
|
||||
import me.topchetoeu.jscript.interop.ExposeConstructor;
|
||||
import me.topchetoeu.jscript.interop.ExposeField;
|
||||
|
||||
@Native("RangeError") public class RangeErrorLib extends ErrorLib {
|
||||
@NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) {
|
||||
var target = ErrorLib.constructor(ctx, thisArg, message);
|
||||
target.setPrototype(PlaceholderProto.SYNTAX_ERROR);
|
||||
target.defineProperty(ctx, "name", "RangeError");
|
||||
@WrapperName("RangeError")
|
||||
public class RangeErrorLib extends ErrorLib {
|
||||
@ExposeField public static final String __name = "RangeError";
|
||||
|
||||
@ExposeConstructor public static ObjectValue constructor(Arguments args) {
|
||||
var target = ErrorLib.__constructor(args);
|
||||
target.setPrototype(PlaceholderProto.RANGE_ERROR);
|
||||
return target;
|
||||
}
|
||||
@NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) {
|
||||
target.defineProperty(null, "name", "RangeError");
|
||||
}
|
||||
}
|
@ -10,79 +10,63 @@ import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.engine.values.NativeWrapper;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
import me.topchetoeu.jscript.interop.Arguments;
|
||||
import me.topchetoeu.jscript.interop.Expose;
|
||||
import me.topchetoeu.jscript.interop.ExposeConstructor;
|
||||
import me.topchetoeu.jscript.interop.ExposeTarget;
|
||||
import me.topchetoeu.jscript.interop.ExposeType;
|
||||
import me.topchetoeu.jscript.interop.WrapperName;
|
||||
|
||||
@Native("RegExp") public class RegExpLib {
|
||||
@WrapperName("RegExp")
|
||||
public class RegExpLib {
|
||||
// I used Regex to analyze Regex
|
||||
private static final Pattern NAMED_PATTERN = Pattern.compile("\\(\\?<([^=!].*?)>", Pattern.DOTALL);
|
||||
private static final Pattern ESCAPE_PATTERN = Pattern.compile("[/\\-\\\\^$*+?.()|\\[\\]{}]");
|
||||
|
||||
private static String cleanupPattern(Context ctx, Object val) {
|
||||
if (val == null) return "(?:)";
|
||||
if (val instanceof RegExpLib) return ((RegExpLib)val).source;
|
||||
if (val instanceof NativeWrapper && ((NativeWrapper)val).wrapped instanceof RegExpLib) {
|
||||
return ((RegExpLib)((NativeWrapper)val).wrapped).source;
|
||||
}
|
||||
var res = Values.toString(ctx, val);
|
||||
if (res.equals("")) return "(?:)";
|
||||
return res;
|
||||
}
|
||||
private static String cleanupFlags(Context ctx, Object val) {
|
||||
if (val == null) return "";
|
||||
return Values.toString(ctx, val);
|
||||
}
|
||||
|
||||
private static boolean checkEscaped(String s, int pos) {
|
||||
int n = 0;
|
||||
|
||||
while (true) {
|
||||
if (pos <= 0) break;
|
||||
if (s.charAt(pos) != '\\') break;
|
||||
n++;
|
||||
pos--;
|
||||
}
|
||||
|
||||
return (n % 2) != 0;
|
||||
}
|
||||
|
||||
@Native
|
||||
public static RegExpLib escape(Context ctx, Object raw, Object flags) {
|
||||
return escape(Values.toString(ctx, raw), cleanupFlags(ctx, flags));
|
||||
}
|
||||
public static RegExpLib escape(String raw, String flags) {
|
||||
return new RegExpLib(ESCAPE_PATTERN.matcher(raw).replaceAll("\\\\$0"), flags);
|
||||
}
|
||||
|
||||
private Pattern pattern;
|
||||
private String[] namedGroups;
|
||||
private int flags;
|
||||
|
||||
@Native public int lastI = 0;
|
||||
@Native public final String source;
|
||||
@Native public final boolean hasIndices;
|
||||
@Native public final boolean global;
|
||||
@Native public final boolean sticky;
|
||||
@Native("@@Symbol.typeName") public final String name = "RegExp";
|
||||
public int lastI = 0;
|
||||
public final String source;
|
||||
public final boolean hasIndices;
|
||||
public final boolean global;
|
||||
public final boolean sticky;
|
||||
|
||||
@NativeGetter public boolean ignoreCase() { return (flags & Pattern.CASE_INSENSITIVE) != 0; }
|
||||
@NativeGetter public boolean multiline() { return (flags & Pattern.MULTILINE) != 0; }
|
||||
@NativeGetter public boolean unicode() { return (flags & Pattern.UNICODE_CHARACTER_CLASS) != 0; }
|
||||
@NativeGetter public boolean dotAll() { return (flags & Pattern.DOTALL) != 0; }
|
||||
@Expose(type = ExposeType.GETTER)
|
||||
public int __lastIndex() { return lastI; }
|
||||
@Expose(type = ExposeType.SETTER)
|
||||
public void __setLastIndex(Arguments args) { lastI = args.getInt(0); }
|
||||
@Expose(type = ExposeType.GETTER)
|
||||
public String __source() { return source; }
|
||||
|
||||
@NativeGetter("flags") public final String flags() {
|
||||
@Expose(type = ExposeType.GETTER)
|
||||
public boolean __ignoreCase() { return (flags & Pattern.CASE_INSENSITIVE) != 0; }
|
||||
@Expose(type = ExposeType.GETTER)
|
||||
public boolean __multiline() { return (flags & Pattern.MULTILINE) != 0; }
|
||||
@Expose(type = ExposeType.GETTER)
|
||||
public boolean __unicode() { return (flags & Pattern.UNICODE_CHARACTER_CLASS) != 0; }
|
||||
@Expose(type = ExposeType.GETTER)
|
||||
public boolean __dotAll() { return (flags & Pattern.DOTALL) != 0; }
|
||||
@Expose(type = ExposeType.GETTER)
|
||||
public boolean __global() { return global; }
|
||||
@Expose(type = ExposeType.GETTER)
|
||||
public boolean __sticky() { return sticky; }
|
||||
@Expose(type = ExposeType.GETTER)
|
||||
public final String __flags() {
|
||||
String res = "";
|
||||
if (hasIndices) res += 'd';
|
||||
if (global) res += 'g';
|
||||
if (ignoreCase()) res += 'i';
|
||||
if (multiline()) res += 'm';
|
||||
if (dotAll()) res += 's';
|
||||
if (unicode()) res += 'u';
|
||||
if (__ignoreCase()) res += 'i';
|
||||
if (__multiline()) res += 'm';
|
||||
if (__dotAll()) res += 's';
|
||||
if (__unicode()) res += 'u';
|
||||
if (sticky) res += 'y';
|
||||
return res;
|
||||
}
|
||||
|
||||
@Native public Object exec(String str) {
|
||||
@Expose public Object __exec(Arguments args) {
|
||||
var str = args.getString(0);
|
||||
var matcher = pattern.matcher(str);
|
||||
if (lastI > str.length() || !matcher.find(lastI) || sticky && matcher.start() != lastI) {
|
||||
lastI = 0;
|
||||
@ -126,39 +110,36 @@ import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
return obj;
|
||||
}
|
||||
|
||||
@Native public boolean test(String str) {
|
||||
return this.exec(str) != Values.NULL;
|
||||
}
|
||||
@Native public String toString() {
|
||||
return "/" + source + "/" + flags();
|
||||
@Expose public boolean __test(Arguments args) {
|
||||
return this.__exec(args) != Values.NULL;
|
||||
}
|
||||
|
||||
@Native("@@Symbol.match") public Object match(Context ctx, String target) {
|
||||
@Expose("@@Symbol.match") public Object __match(Arguments args) {
|
||||
if (this.global) {
|
||||
var res = new ArrayValue();
|
||||
Object val;
|
||||
while ((val = this.exec(target)) != Values.NULL) {
|
||||
res.set(ctx, res.size(), Values.getMember(ctx, val, 0));
|
||||
while ((val = this.__exec(args)) != Values.NULL) {
|
||||
res.set(args.ctx, res.size(), Values.getMember(args.ctx, val, 0));
|
||||
}
|
||||
lastI = 0;
|
||||
return res;
|
||||
}
|
||||
else {
|
||||
var res = this.exec(target);
|
||||
var res = this.__exec(args);
|
||||
if (!this.sticky) this.lastI = 0;
|
||||
return res;
|
||||
}
|
||||
}
|
||||
|
||||
@Native("@@Symbol.matchAll") public Object matchAll(Context ctx, String target) {
|
||||
var pattern = new RegExpLib(this.source, this.flags() + "g");
|
||||
@Expose("@@Symbol.matchAll") public Object __matchAll(Arguments args) {
|
||||
var pattern = this.toGlobal();
|
||||
|
||||
return Values.toJSIterator(ctx, new Iterator<Object>() {
|
||||
return Values.toJSIterator(args.ctx, new Iterator<Object>() {
|
||||
private Object val = null;
|
||||
private boolean updated = false;
|
||||
|
||||
private void update() {
|
||||
if (!updated) val = pattern.exec(target);
|
||||
if (!updated) val = pattern.__exec(args);
|
||||
}
|
||||
@Override public boolean hasNext() {
|
||||
update();
|
||||
@ -172,17 +153,21 @@ import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
});
|
||||
}
|
||||
|
||||
@Native("@@Symbol.split") public ArrayValue split(Context ctx, String target, Object limit, boolean sensible) {
|
||||
var pattern = new RegExpLib(this.source, this.flags() + "g");
|
||||
@Expose("@@Symbol.split") public ArrayValue __split(Arguments args) {
|
||||
var pattern = this.toGlobal();
|
||||
var target = args.getString(0);
|
||||
var hasLimit = args.get(1) != null;
|
||||
var lim = args.getInt(1);
|
||||
var sensible = args.getBoolean(2);
|
||||
|
||||
Object match;
|
||||
int lastEnd = 0;
|
||||
var res = new ArrayValue();
|
||||
var lim = limit == null ? 0 : Values.toNumber(ctx, limit);
|
||||
|
||||
while ((match = pattern.exec(target)) != Values.NULL) {
|
||||
while ((match = pattern.__exec(args)) != Values.NULL) {
|
||||
var added = new ArrayList<String>();
|
||||
var arrMatch = (ArrayValue)match;
|
||||
int index = (int)Values.toNumber(ctx, Values.getMember(ctx, match, "index"));
|
||||
int index = (int)Values.toNumber(args.ctx, Values.getMember(args.ctx, match, "index"));
|
||||
var matchVal = (String)arrMatch.get(0);
|
||||
|
||||
if (index >= target.length()) break;
|
||||
@ -198,31 +183,33 @@ import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
}
|
||||
|
||||
if (sensible) {
|
||||
if (limit != null && res.size() + added.size() >= lim) break;
|
||||
else for (var i = 0; i < added.size(); i++) res.set(ctx, res.size(), added.get(i));
|
||||
if (hasLimit && res.size() + added.size() >= lim) break;
|
||||
else for (var i = 0; i < added.size(); i++) res.set(args.ctx, res.size(), added.get(i));
|
||||
}
|
||||
else {
|
||||
for (var i = 0; i < added.size(); i++) {
|
||||
if (limit != null && res.size() >= lim) return res;
|
||||
else res.set(ctx, res.size(), added.get(i));
|
||||
if (hasLimit && res.size() >= lim) return res;
|
||||
else res.set(args.ctx, res.size(), added.get(i));
|
||||
}
|
||||
}
|
||||
lastEnd = pattern.lastI;
|
||||
}
|
||||
if (lastEnd < target.length()) {
|
||||
res.set(ctx, res.size(), target.substring(lastEnd));
|
||||
res.set(args.ctx, res.size(), target.substring(lastEnd));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
@Native("@@Symbol.replace") public String replace(Context ctx, String target, Object replacement) {
|
||||
var pattern = new RegExpLib(this.source, this.flags() + "d");
|
||||
@Expose("@@Symbol.replace") public String __replace(Arguments args) {
|
||||
var pattern = this.toIndexed();
|
||||
var target = args.getString(0);
|
||||
var replacement = args.get(1);
|
||||
Object match;
|
||||
var lastEnd = 0;
|
||||
var res = new StringBuilder();
|
||||
|
||||
while ((match = pattern.exec(target)) != Values.NULL) {
|
||||
var indices = (ArrayValue)((ArrayValue)Values.getMember(ctx, match, "indices")).get(0);
|
||||
while ((match = pattern.__exec(args)) != Values.NULL) {
|
||||
var indices = (ArrayValue)((ArrayValue)Values.getMember(args.ctx, match, "indices")).get(0);
|
||||
var arrMatch = (ArrayValue)match;
|
||||
|
||||
var start = ((Number)indices.get(0)).intValue();
|
||||
@ -230,15 +217,15 @@ import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
|
||||
res.append(target.substring(lastEnd, start));
|
||||
if (replacement instanceof FunctionValue) {
|
||||
var args = new Object[arrMatch.size() + 2];
|
||||
args[0] = target.substring(start, end);
|
||||
arrMatch.copyTo(args, 1, 1, arrMatch.size() - 1);
|
||||
args[args.length - 2] = start;
|
||||
args[args.length - 1] = target;
|
||||
res.append(Values.toString(ctx, ((FunctionValue)replacement).call(ctx, null, args)));
|
||||
var callArgs = new Object[arrMatch.size() + 2];
|
||||
callArgs[0] = target.substring(start, end);
|
||||
arrMatch.copyTo(callArgs, 1, 1, arrMatch.size() - 1);
|
||||
callArgs[callArgs.length - 2] = start;
|
||||
callArgs[callArgs.length - 1] = target;
|
||||
res.append(Values.toString(args.ctx, ((FunctionValue)replacement).call(args.ctx, null, callArgs)));
|
||||
}
|
||||
else {
|
||||
res.append(Values.toString(ctx, replacement));
|
||||
res.append(Values.toString(args.ctx, replacement));
|
||||
}
|
||||
lastEnd = end;
|
||||
if (!pattern.global) break;
|
||||
@ -271,9 +258,17 @@ import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
// }
|
||||
// },
|
||||
|
||||
@Native public RegExpLib(Context ctx, Object pattern, Object flags) {
|
||||
this(cleanupPattern(ctx, pattern), cleanupFlags(ctx, flags));
|
||||
public RegExpLib toGlobal() {
|
||||
return new RegExpLib(pattern, namedGroups, flags, source, hasIndices, true, sticky);
|
||||
}
|
||||
public RegExpLib toIndexed() {
|
||||
return new RegExpLib(pattern, namedGroups, flags, source, true, global, sticky);
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return "/" + source + "/" + __flags();
|
||||
}
|
||||
|
||||
public RegExpLib(String pattern, String flags) {
|
||||
if (pattern == null || pattern.equals("")) pattern = "(?:)";
|
||||
if (flags == null || flags.equals("")) flags = "";
|
||||
@ -304,6 +299,56 @@ import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
namedGroups = groups.toArray(String[]::new);
|
||||
}
|
||||
|
||||
private RegExpLib(Pattern pattern, String[] namedGroups, int flags, String source, boolean hasIndices, boolean global, boolean sticky) {
|
||||
this.pattern = pattern;
|
||||
this.namedGroups = namedGroups;
|
||||
this.flags = flags;
|
||||
this.source = source;
|
||||
this.hasIndices = hasIndices;
|
||||
this.global = global;
|
||||
this.sticky = sticky;
|
||||
}
|
||||
public RegExpLib(String pattern) { this(pattern, null); }
|
||||
public RegExpLib() { this(null, null); }
|
||||
|
||||
@ExposeConstructor
|
||||
public static RegExpLib __constructor(Arguments args) {
|
||||
return new RegExpLib(cleanupPattern(args.ctx, args.get(0)), cleanupFlags(args.ctx, args.get(1)));
|
||||
}
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static RegExpLib __escape(Arguments args) {
|
||||
return escape(Values.toString(args.ctx, args.get(0)), cleanupFlags(args.ctx, args.get(1)));
|
||||
}
|
||||
|
||||
private static String cleanupPattern(Context ctx, Object val) {
|
||||
if (val == null) return "(?:)";
|
||||
if (val instanceof RegExpLib) return ((RegExpLib)val).source;
|
||||
if (val instanceof NativeWrapper && ((NativeWrapper)val).wrapped instanceof RegExpLib) {
|
||||
return ((RegExpLib)((NativeWrapper)val).wrapped).source;
|
||||
}
|
||||
var res = Values.toString(ctx, val);
|
||||
if (res.equals("")) return "(?:)";
|
||||
return res;
|
||||
}
|
||||
private static String cleanupFlags(Context ctx, Object val) {
|
||||
if (val == null) return "";
|
||||
return Values.toString(ctx, val);
|
||||
}
|
||||
|
||||
private static boolean checkEscaped(String s, int pos) {
|
||||
int n = 0;
|
||||
|
||||
while (true) {
|
||||
if (pos <= 0) break;
|
||||
if (s.charAt(pos) != '\\') break;
|
||||
n++;
|
||||
pos--;
|
||||
}
|
||||
|
||||
return (n % 2) != 0;
|
||||
}
|
||||
|
||||
public static RegExpLib escape(String raw, String flags) {
|
||||
return new RegExpLib(ESCAPE_PATTERN.matcher(raw).replaceAll("\\\\$0"), flags);
|
||||
}
|
||||
}
|
||||
|
@ -6,55 +6,64 @@ import java.util.stream.Collectors;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
import me.topchetoeu.jscript.interop.Arguments;
|
||||
import me.topchetoeu.jscript.interop.Expose;
|
||||
import me.topchetoeu.jscript.interop.ExposeConstructor;
|
||||
import me.topchetoeu.jscript.interop.ExposeType;
|
||||
import me.topchetoeu.jscript.interop.WrapperName;
|
||||
|
||||
@Native("Set") public class SetLib {
|
||||
@WrapperName("Set")
|
||||
public class SetLib {
|
||||
private LinkedHashSet<Object> set = new LinkedHashSet<>();
|
||||
|
||||
@Native("@@Symbol.typeName") public final String name = "Set";
|
||||
@Native("@@Symbol.iterator") public ObjectValue iterator(Context ctx) {
|
||||
return this.values(ctx);
|
||||
@Expose("@@Symbol.iterator")
|
||||
public ObjectValue __iterator(Arguments args) {
|
||||
return this.__values(args);
|
||||
}
|
||||
|
||||
@Native public ObjectValue entries(Context ctx) {
|
||||
return ArrayValue.of(ctx, set.stream().map(v -> new ArrayValue(ctx, v, v)).collect(Collectors.toList()));
|
||||
@Expose public ObjectValue __entries(Arguments args) {
|
||||
return Values.toJSIterator(args.ctx, set.stream().map(v -> new ArrayValue(args.ctx, v, v)).collect(Collectors.toList()));
|
||||
}
|
||||
@Native public ObjectValue keys(Context ctx) {
|
||||
return ArrayValue.of(ctx, set);
|
||||
@Expose public ObjectValue __keys(Arguments args) {
|
||||
return Values.toJSIterator(args.ctx, set);
|
||||
}
|
||||
@Native public ObjectValue values(Context ctx) {
|
||||
return ArrayValue.of(ctx, set);
|
||||
@Expose public ObjectValue __values(Arguments args) {
|
||||
return Values.toJSIterator(args.ctx, set);
|
||||
}
|
||||
|
||||
@Native public Object add(Object key) {
|
||||
return set.add(key);
|
||||
@Expose public Object __add(Arguments args) {
|
||||
return set.add(args.get(0));
|
||||
}
|
||||
@Native public boolean delete(Object key) {
|
||||
return set.remove(key);
|
||||
@Expose public boolean __delete(Arguments args) {
|
||||
return set.remove(args.get(0));
|
||||
}
|
||||
@Native public boolean has(Object key) {
|
||||
return set.contains(key);
|
||||
@Expose public boolean __has(Arguments args) {
|
||||
return set.contains(args.get(0));
|
||||
}
|
||||
|
||||
@Native public void clear() {
|
||||
@Expose public void __clear() {
|
||||
set.clear();
|
||||
}
|
||||
|
||||
@NativeGetter public int size() {
|
||||
@Expose(type = ExposeType.GETTER)
|
||||
public int __size() {
|
||||
return set.size();
|
||||
}
|
||||
|
||||
@Native public void forEach(Context ctx, FunctionValue func, Object thisArg) {
|
||||
@Expose public void __forEach(Arguments args) {
|
||||
var keys = new ArrayList<>(set);
|
||||
|
||||
for (var el : keys) func.call(ctx, thisArg, el, el, this);
|
||||
for (var el : keys) Values.call(args.ctx, args.get(0), args.get(1), el, el, args.self);
|
||||
}
|
||||
|
||||
@Native public SetLib(Context ctx, Object iterable) {
|
||||
for (var el : Values.fromJSIterator(ctx, iterable)) add(el);
|
||||
public SetLib(Context ctx, Object iterable) {
|
||||
for (var el : Values.fromJSIterator(ctx, iterable)) set.add(el);
|
||||
}
|
||||
|
||||
@ExposeConstructor
|
||||
public static SetLib __constructor(Arguments args) {
|
||||
return new SetLib(args.ctx, args.get(0));
|
||||
}
|
||||
}
|
||||
|
@ -2,22 +2,34 @@ package me.topchetoeu.jscript.lib;
|
||||
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.Environment;
|
||||
import me.topchetoeu.jscript.engine.values.ArrayValue;
|
||||
import me.topchetoeu.jscript.engine.values.FunctionValue;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.Symbol;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeConstructor;
|
||||
import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
import me.topchetoeu.jscript.interop.Arguments;
|
||||
import me.topchetoeu.jscript.interop.Expose;
|
||||
import me.topchetoeu.jscript.interop.ExposeConstructor;
|
||||
import me.topchetoeu.jscript.interop.ExposeTarget;
|
||||
import me.topchetoeu.jscript.interop.ExposeType;
|
||||
import me.topchetoeu.jscript.interop.WrapperName;
|
||||
|
||||
// TODO: implement index wrapping properly
|
||||
@Native("String") public class StringLib {
|
||||
@WrapperName("String")
|
||||
public class StringLib {
|
||||
public final String value;
|
||||
|
||||
private static String passThis(Context ctx, String funcName, Object val) {
|
||||
if (val instanceof StringLib) return ((StringLib)val).value;
|
||||
@Override public String toString() { return value; }
|
||||
|
||||
public StringLib(String val) {
|
||||
this.value = val;
|
||||
}
|
||||
|
||||
private static String passThis(Arguments args, String funcName) {
|
||||
var val = args.self;
|
||||
if (Values.isWrapper(val, StringLib.class)) return Values.wrapper(val, StringLib.class).value;
|
||||
else if (val instanceof String) return (String)val;
|
||||
else throw EngineException.ofType(String.format("'%s' may only be called upon object and primitve strings.", funcName));
|
||||
}
|
||||
@ -25,177 +37,188 @@ import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
if (i < 0) i += len;
|
||||
if (clamp) {
|
||||
if (i < 0) i = 0;
|
||||
if (i >= len) i = len;
|
||||
if (i > len) i = len;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
@NativeGetter(thisArg = true) public static int length(Context ctx, Object thisArg) {
|
||||
return passThis(ctx, "substring", thisArg).length();
|
||||
@Expose(type = ExposeType.GETTER)
|
||||
public static int __length(Arguments args) {
|
||||
return passThis(args, "length").length();
|
||||
}
|
||||
|
||||
@Native(thisArg = true) public static String substring(Context ctx, Object thisArg, int start, Object _end) {
|
||||
var val = passThis(ctx, "substring", thisArg);
|
||||
start = normalizeI(start, val.length(), true);
|
||||
int end = normalizeI(_end == null ? val.length() : (int)Values.toNumber(ctx, _end), val.length(), true);
|
||||
@Expose public static String __substring(Arguments args) {
|
||||
var val = passThis(args, "substring");
|
||||
var start = Math.max(0, Math.min(val.length(), args.getInt(0)));
|
||||
var end = Math.max(0, Math.min(val.length(), args.getInt(1, val.length())));
|
||||
|
||||
if (end < start) {
|
||||
var tmp = end;
|
||||
end = start;
|
||||
start = tmp;
|
||||
}
|
||||
|
||||
return val.substring(start, end);
|
||||
}
|
||||
@Native(thisArg = true) public static String substr(Context ctx, Object thisArg, int start, Object _len) {
|
||||
var val = passThis(ctx, "substr", thisArg);
|
||||
int len = _len == null ? val.length() - start : (int)Values.toNumber(ctx, _len);
|
||||
return substring(ctx, val, start, start + len);
|
||||
@Expose public static String __substr(Arguments args) {
|
||||
var val = passThis(args, "substr");
|
||||
var start = normalizeI(args.getInt(0), val.length(), true);
|
||||
int end = normalizeI(args.getInt(1, val.length() - start) + start, val.length(), true);
|
||||
return val.substring(start, end);
|
||||
}
|
||||
|
||||
@Native(thisArg = true) public static String toLowerCase(Context ctx, Object thisArg) {
|
||||
return passThis(ctx, "toLowerCase", thisArg).toLowerCase();
|
||||
@Expose public static String __toLowerCase(Arguments args) {
|
||||
return passThis(args, "toLowerCase").toLowerCase();
|
||||
}
|
||||
@Native(thisArg = true) public static String toUpperCase(Context ctx, Object thisArg) {
|
||||
return passThis(ctx, "toUpperCase", thisArg).toUpperCase();
|
||||
@Expose public static String __toUpperCase(Arguments args) {
|
||||
return passThis(args, "toUpperCase").toUpperCase();
|
||||
}
|
||||
|
||||
@Native(thisArg = true) public static String charAt(Context ctx, Object thisArg, int i) {
|
||||
return passThis(ctx, "charAt", thisArg).charAt(i) + "";
|
||||
@Expose public static String __charAt(Arguments args) {
|
||||
return passThis(args, "charAt").charAt(args.getInt(0)) + "";
|
||||
}
|
||||
@Native(thisArg = true) public static double charCodeAt(Context ctx, Object thisArg, int i) {
|
||||
var str = passThis(ctx, "charCodeAt", thisArg);
|
||||
@Expose public static double __charCodeAt(Arguments args) {
|
||||
var str = passThis(args, "charCodeAt");
|
||||
var i = args.getInt(0);
|
||||
if (i < 0 || i >= str.length()) return Double.NaN;
|
||||
else return str.charAt(i);
|
||||
}
|
||||
@Native(thisArg = true) public static double codePointAt(Context ctx, Object thisArg, int i) {
|
||||
var str = passThis(ctx, "codePointAt", thisArg);
|
||||
@Expose public static double __codePointAt(Arguments args) {
|
||||
var str = passThis(args, "codePointAt");
|
||||
var i = args.getInt(0);
|
||||
if (i < 0 || i >= str.length()) return Double.NaN;
|
||||
else return str.codePointAt(i);
|
||||
}
|
||||
|
||||
@Native(thisArg = true) public static boolean startsWith(Context ctx, Object thisArg, String term, int pos) {
|
||||
return passThis(ctx, "startsWith", thisArg).startsWith(term, pos);
|
||||
@Expose public static boolean __startsWith(Arguments args) {
|
||||
return passThis(args, "startsWith").startsWith(args.getString(0), args.getInt(1));
|
||||
}
|
||||
@Native(thisArg = true) public static boolean endsWith(Context ctx, Object thisArg, String term, int pos) {
|
||||
var val = passThis(ctx, "endsWith", thisArg);
|
||||
return val.lastIndexOf(term, pos) >= 0;
|
||||
@Expose public static boolean __endsWith(Arguments args) {
|
||||
return passThis(args, "endsWith").lastIndexOf(args.getString(0), args.getInt(1)) >= 0;
|
||||
}
|
||||
|
||||
@Native(thisArg = true) public static int indexOf(Context ctx, Object thisArg, Object term, int start) {
|
||||
var val = passThis(ctx, "indexOf", thisArg);
|
||||
@Expose public static int __indexOf(Arguments args) {
|
||||
var val = passThis(args, "indexOf");
|
||||
var term = args.get(0);
|
||||
var start = args.getInt(1);
|
||||
var search = Values.getMember(args.ctx, term, Symbol.get("Symbol.search"));
|
||||
|
||||
if (term != null && term != Values.NULL && !(term instanceof String)) {
|
||||
var search = Values.getMember(ctx, term, ctx.environment().symbol("Symbol.search"));
|
||||
if (search instanceof FunctionValue) {
|
||||
return (int)Values.toNumber(ctx, ((FunctionValue)search).call(ctx, term, val, false, start));
|
||||
}
|
||||
if (search instanceof FunctionValue) {
|
||||
return (int)Values.toNumber(args.ctx, Values.call(args.ctx, search, term, val, false, start));
|
||||
}
|
||||
|
||||
return val.indexOf(Values.toString(ctx, term), start);
|
||||
else return val.indexOf(Values.toString(args.ctx, term), start);
|
||||
}
|
||||
@Native(thisArg = true) public static int lastIndexOf(Context ctx, Object thisArg, Object term, int pos) {
|
||||
var val = passThis(ctx, "lastIndexOf", thisArg);
|
||||
@Expose public static int __lastIndexOf(Arguments args) {
|
||||
var val = passThis(args, "lastIndexOf");
|
||||
var term = args.get(0);
|
||||
var start = args.getInt(1);
|
||||
var search = Values.getMember(args.ctx, term, Symbol.get("Symbol.search"));
|
||||
|
||||
if (term != null && term != Values.NULL && !(term instanceof String)) {
|
||||
var search = Values.getMember(ctx, term, ctx.environment().symbol("Symbol.search"));
|
||||
if (search instanceof FunctionValue) {
|
||||
return (int)Values.toNumber(ctx, ((FunctionValue)search).call(ctx, term, val, true, pos));
|
||||
}
|
||||
if (search instanceof FunctionValue) {
|
||||
return (int)Values.toNumber(args.ctx, Values.call(args.ctx, search, term, val, true, start));
|
||||
}
|
||||
|
||||
return val.lastIndexOf(Values.toString(ctx, term), pos);
|
||||
else return val.lastIndexOf(Values.toString(args.ctx, term), start);
|
||||
}
|
||||
|
||||
@Native(thisArg = true) public static boolean includes(Context ctx, Object thisArg, Object term, int pos) {
|
||||
return indexOf(ctx, passThis(ctx, "includes", thisArg), term, pos) >= 0;
|
||||
@Expose public static boolean __includes(Arguments args) {
|
||||
return __indexOf(args) >= 0;
|
||||
}
|
||||
|
||||
@Native(thisArg = true) public static String replace(Context ctx, Object thisArg, Object term, Object replacement) {
|
||||
var val = passThis(ctx, "replace", thisArg);
|
||||
@Expose public static String __replace(Arguments args) {
|
||||
var val = passThis(args, "replace");
|
||||
var term = args.get(0);
|
||||
var replacement = args.get(1);
|
||||
var replace = Values.getMember(args.ctx, term, Symbol.get("Symbol.replace"));
|
||||
|
||||
if (term != null && term != Values.NULL && !(term instanceof String)) {
|
||||
var replace = Values.getMember(ctx, term, ctx.environment().symbol("Symbol.replace"));
|
||||
if (replace instanceof FunctionValue) {
|
||||
return Values.toString(ctx, ((FunctionValue)replace).call(ctx, term, val, replacement));
|
||||
}
|
||||
if (replace instanceof FunctionValue) {
|
||||
return Values.toString(args.ctx, Values.call(args.ctx, replace, term, val, replacement));
|
||||
}
|
||||
|
||||
return val.replaceFirst(Pattern.quote(Values.toString(ctx, term)), Values.toString(ctx, replacement));
|
||||
else return val.replaceFirst(Pattern.quote(Values.toString(args.ctx, term)), Values.toString(args.ctx, replacement));
|
||||
}
|
||||
@Native(thisArg = true) public static String replaceAll(Context ctx, Object thisArg, Object term, Object replacement) {
|
||||
var val = passThis(ctx, "replaceAll", thisArg);
|
||||
@Expose public static String __replaceAll(Arguments args) {
|
||||
var val = passThis(args, "replaceAll");
|
||||
var term = args.get(0);
|
||||
var replacement = args.get(1);
|
||||
var replace = Values.getMember(args.ctx, term, Symbol.get("Symbol.replace"));
|
||||
|
||||
if (term != null && term != Values.NULL && !(term instanceof String)) {
|
||||
var replace = Values.getMember(ctx, term, ctx.environment().symbol("Symbol.replace"));
|
||||
if (replace instanceof FunctionValue) {
|
||||
return Values.toString(ctx, ((FunctionValue)replace).call(ctx, term, val, replacement));
|
||||
}
|
||||
if (replace instanceof FunctionValue) {
|
||||
return Values.toString(args.ctx, Values.call(args.ctx, replace, term, val, replacement));
|
||||
}
|
||||
|
||||
return val.replaceFirst(Pattern.quote(Values.toString(ctx, term)), Values.toString(ctx, replacement));
|
||||
else return val.replace(Values.toString(args.ctx, term), Values.toString(args.ctx, replacement));
|
||||
}
|
||||
|
||||
@Native(thisArg = true) public static ArrayValue match(Context ctx, Object thisArg, Object term, String replacement) {
|
||||
var val = passThis(ctx, "match", thisArg);
|
||||
@Expose public static ArrayValue __match(Arguments args) {
|
||||
var val = passThis(args, "match");
|
||||
var term = args.get(0);
|
||||
|
||||
FunctionValue match;
|
||||
|
||||
try {
|
||||
var _match = Values.getMember(ctx, term, ctx.environment().symbol("Symbol.match"));
|
||||
var _match = Values.getMember(args.ctx, term, Symbol.get("Symbol.match"));
|
||||
if (_match instanceof FunctionValue) match = (FunctionValue)_match;
|
||||
else if (ctx.environment().regexConstructor != null) {
|
||||
var regex = Values.callNew(ctx, ctx.environment().regexConstructor, Values.toString(ctx, term), "");
|
||||
_match = Values.getMember(ctx, regex, ctx.environment().symbol("Symbol.match"));
|
||||
else if (args.ctx.hasNotNull(Environment.REGEX_CONSTR)) {
|
||||
var regex = Values.callNew(args.ctx, args.ctx.get(Environment.REGEX_CONSTR), Values.toString(args.ctx, term), "");
|
||||
_match = Values.getMember(args.ctx, regex, Symbol.get("Symbol.match"));
|
||||
if (_match instanceof FunctionValue) match = (FunctionValue)_match;
|
||||
else throw EngineException.ofError("Regular expressions don't support matching.");
|
||||
}
|
||||
else throw EngineException.ofError("Regular expressions not supported.");
|
||||
}
|
||||
catch (IllegalArgumentException e) { return new ArrayValue(ctx, ""); }
|
||||
catch (IllegalArgumentException e) { return new ArrayValue(args.ctx, ""); }
|
||||
|
||||
var res = match.call(ctx, term, val);
|
||||
var res = match.call(args.ctx, term, val);
|
||||
if (res instanceof ArrayValue) return (ArrayValue)res;
|
||||
else return new ArrayValue(ctx, "");
|
||||
else return new ArrayValue(args.ctx, "");
|
||||
}
|
||||
@Native(thisArg = true) public static Object matchAll(Context ctx, Object thisArg, Object term, String replacement) {
|
||||
var val = passThis(ctx, "matchAll", thisArg);
|
||||
@Expose public static Object __matchAll(Arguments args) {
|
||||
var val = passThis(args, "matchAll");
|
||||
var term = args.get(0);
|
||||
|
||||
FunctionValue match = null;
|
||||
|
||||
try {
|
||||
var _match = Values.getMember(ctx, term, ctx.environment().symbol("Symbol.matchAll"));
|
||||
var _match = Values.getMember(args.ctx, term, Symbol.get("Symbol.matchAll"));
|
||||
if (_match instanceof FunctionValue) match = (FunctionValue)_match;
|
||||
}
|
||||
catch (IllegalArgumentException e) { }
|
||||
|
||||
if (match == null && ctx.environment().regexConstructor != null) {
|
||||
var regex = Values.callNew(ctx, ctx.environment().regexConstructor, Values.toString(ctx, term), "g");
|
||||
var _match = Values.getMember(ctx, regex, ctx.environment().symbol("Symbol.matchAll"));
|
||||
if (match == null && args.ctx.hasNotNull(Environment.REGEX_CONSTR)) {
|
||||
var regex = Values.callNew(args.ctx, args.ctx.get(Environment.REGEX_CONSTR), Values.toString(args.ctx, term), "g");
|
||||
var _match = Values.getMember(args.ctx, regex, Symbol.get("Symbol.matchAll"));
|
||||
if (_match instanceof FunctionValue) match = (FunctionValue)_match;
|
||||
else throw EngineException.ofError("Regular expressions don't support matching.");
|
||||
}
|
||||
else throw EngineException.ofError("Regular expressions not supported.");
|
||||
|
||||
return match.call(ctx, term, val);
|
||||
return match.call(args.ctx, term, val);
|
||||
}
|
||||
|
||||
@Native(thisArg = true) public static ArrayValue split(Context ctx, Object thisArg, Object term, Object lim, boolean sensible) {
|
||||
var val = passThis(ctx, "split", thisArg);
|
||||
@Expose public static ArrayValue __split(Arguments args) {
|
||||
var val = passThis(args, "split");
|
||||
var term = args.get(0);
|
||||
var lim = args.get(1);
|
||||
var sensible = args.getBoolean(2);
|
||||
|
||||
if (lim != null) lim = Values.toNumber(ctx, lim);
|
||||
if (lim != null) lim = Values.toNumber(args.ctx, lim);
|
||||
|
||||
if (term != null && term != Values.NULL && !(term instanceof String)) {
|
||||
var replace = Values.getMember(ctx, term, ctx.environment().symbol("Symbol.replace"));
|
||||
var replace = Values.getMember(args.ctx, term, Symbol.get("Symbol.replace"));
|
||||
if (replace instanceof FunctionValue) {
|
||||
var tmp = ((FunctionValue)replace).call(ctx, term, val, lim, sensible);
|
||||
var tmp = ((FunctionValue)replace).call(args.ctx, term, val, lim, sensible);
|
||||
|
||||
if (tmp instanceof ArrayValue) {
|
||||
var parts = new ArrayValue(((ArrayValue)tmp).size());
|
||||
for (int i = 0; i < parts.size(); i++) parts.set(ctx, i, Values.toString(ctx, ((ArrayValue)tmp).get(i)));
|
||||
for (int i = 0; i < parts.size(); i++) parts.set(args.ctx, i, Values.toString(args.ctx, ((ArrayValue)tmp).get(i)));
|
||||
return parts;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String[] parts;
|
||||
var pattern = Pattern.quote(Values.toString(ctx, term));
|
||||
var pattern = Pattern.quote(Values.toString(args.ctx, term));
|
||||
|
||||
if (lim == null) parts = val.split(pattern);
|
||||
else if ((double)lim < 1) return new ArrayValue();
|
||||
else if (sensible) parts = val.split(pattern, (int)(double)lim);
|
||||
else {
|
||||
var limit = (int)(double)lim;
|
||||
@ -205,7 +228,7 @@ import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
if (parts.length > limit) res = new ArrayValue(limit);
|
||||
else res = new ArrayValue(parts.length);
|
||||
|
||||
for (var i = 0; i < parts.length && i < limit; i++) res.set(ctx, i, parts[i]);
|
||||
for (var i = 0; i < parts.length && i < limit; i++) res.set(args.ctx, i, parts[i]);
|
||||
|
||||
return res;
|
||||
}
|
||||
@ -215,52 +238,54 @@ import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
|
||||
for (; i < parts.length; i++) {
|
||||
if (lim != null && (double)lim <= i) break;
|
||||
res.set(ctx, i, parts[i]);
|
||||
res.set(args.ctx, i, parts[i]);
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
@Native(thisArg = true) public static String slice(Context ctx, Object thisArg, int start, Object _end) {
|
||||
return substring(ctx, passThis(ctx, "slice", thisArg), start, _end);
|
||||
@Expose public static String __slice(Arguments args) {
|
||||
var self = passThis(args, "slice");
|
||||
var start = normalizeI(args.getInt(0), self.length(), false);
|
||||
var end = normalizeI(args.getInt(1, self.length()), self.length(), false);
|
||||
|
||||
return __substring(new Arguments(args.ctx, self, start, end));
|
||||
}
|
||||
|
||||
@Native(thisArg = true) public static String concat(Context ctx, Object thisArg, Object... args) {
|
||||
var res = new StringBuilder(passThis(ctx, "concat", thisArg));
|
||||
@Expose public static String __concat(Arguments args) {
|
||||
var res = new StringBuilder(passThis(args, "concat"));
|
||||
|
||||
for (var el : args) res.append(Values.toString(ctx, el));
|
||||
for (var el : args.convert(String.class)) res.append(el);
|
||||
|
||||
return res.toString();
|
||||
}
|
||||
@Native(thisArg = true) public static String trim(Context ctx, Object thisArg) {
|
||||
return passThis(ctx, "trim", thisArg).trim();
|
||||
@Expose public static String __trim(Arguments args) {
|
||||
return passThis(args, "trim").trim();
|
||||
}
|
||||
@Native(thisArg = true) public static String trimStart(Context ctx, Object thisArg) {
|
||||
return passThis(ctx, "trimStart", thisArg).replaceAll("^\\s+", "");
|
||||
@Expose public static String __trimStart(Arguments args) {
|
||||
return passThis(args, "trimStart").replaceAll("^\\s+", "");
|
||||
}
|
||||
@Native(thisArg = true) public static String trimEnd(Context ctx, Object thisArg) {
|
||||
return passThis(ctx, "trimEnd", thisArg).replaceAll("\\s+$", "");
|
||||
@Expose public static String __trimEnd(Arguments args) {
|
||||
return passThis(args, "trimEnd").replaceAll("\\s+$", "");
|
||||
}
|
||||
|
||||
@NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object val) {
|
||||
val = Values.toString(ctx, val);
|
||||
if (thisArg instanceof ObjectValue) return new StringLib((String)val);
|
||||
@ExposeConstructor public static Object __constructor(Arguments args) {
|
||||
var val = args.getString(0, "");
|
||||
if (args.self instanceof ObjectValue) return new StringLib(val);
|
||||
else return val;
|
||||
}
|
||||
@Native(thisArg = true) public static String toString(Context ctx, Object thisArg) {
|
||||
return passThis(ctx, "toString", thisArg);
|
||||
@Expose public static String __toString(Arguments args) {
|
||||
return passThis(args, "toString");
|
||||
}
|
||||
@Native(thisArg = true) public static String valueOf(Context ctx, Object thisArg) {
|
||||
return passThis(ctx, "valueOf", thisArg);
|
||||
@Expose public static String __valueOf(Arguments args) {
|
||||
return passThis(args, "valueOf");
|
||||
}
|
||||
|
||||
@Native public static String fromCharCode(int ...val) {
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static String __fromCharCode(Arguments args) {
|
||||
var val = args.convertInt();
|
||||
char[] arr = new char[val.length];
|
||||
for (var i = 0; i < val.length; i++) arr[i] = (char)val[i];
|
||||
return new String(arr);
|
||||
}
|
||||
|
||||
public StringLib(String val) {
|
||||
this.value = val;
|
||||
}
|
||||
}
|
||||
|
@ -3,49 +3,70 @@ package me.topchetoeu.jscript.lib;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.Symbol;
|
||||
import me.topchetoeu.jscript.engine.values.Values;
|
||||
import me.topchetoeu.jscript.exceptions.EngineException;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeConstructor;
|
||||
import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
import me.topchetoeu.jscript.interop.Arguments;
|
||||
import me.topchetoeu.jscript.interop.Expose;
|
||||
import me.topchetoeu.jscript.interop.ExposeConstructor;
|
||||
import me.topchetoeu.jscript.interop.ExposeField;
|
||||
import me.topchetoeu.jscript.interop.ExposeTarget;
|
||||
import me.topchetoeu.jscript.interop.WrapperName;
|
||||
|
||||
@Native("Symbol") public class SymbolLib {
|
||||
@WrapperName("Symbol")
|
||||
public class SymbolLib {
|
||||
private static final Map<String, Symbol> symbols = new HashMap<>();
|
||||
|
||||
@NativeGetter public static Symbol typeName(Context ctx) { return ctx.environment().symbol("Symbol.typeName"); }
|
||||
@NativeGetter public static Symbol replace(Context ctx) { return ctx.environment().symbol("Symbol.replace"); }
|
||||
@NativeGetter public static Symbol match(Context ctx) { return ctx.environment().symbol("Symbol.match"); }
|
||||
@NativeGetter public static Symbol matchAll(Context ctx) { return ctx.environment().symbol("Symbol.matchAll"); }
|
||||
@NativeGetter public static Symbol split(Context ctx) { return ctx.environment().symbol("Symbol.split"); }
|
||||
@NativeGetter public static Symbol search(Context ctx) { return ctx.environment().symbol("Symbol.search"); }
|
||||
@NativeGetter public static Symbol iterator(Context ctx) { return ctx.environment().symbol("Symbol.iterator"); }
|
||||
@NativeGetter public static Symbol asyncIterator(Context ctx) { return ctx.environment().symbol("Symbol.asyncIterator"); }
|
||||
@NativeGetter public static Symbol cause(Context ctx) { return ctx.environment().symbol("Symbol.cause"); }
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final Symbol __typeName = Symbol.get("Symbol.typeName");
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final Symbol __replace = Symbol.get("Symbol.replace");
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final Symbol __match = Symbol.get("Symbol.match");
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final Symbol __matchAll = Symbol.get("Symbol.matchAll");
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final Symbol __split = Symbol.get("Symbol.split");
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final Symbol __search = Symbol.get("Symbol.search");
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final Symbol __iterator = Symbol.get("Symbol.iterator");
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final Symbol __asyncIterator = Symbol.get("Symbol.asyncIterator");
|
||||
@ExposeField(target = ExposeTarget.STATIC)
|
||||
public static final Symbol __cause = Symbol.get("Symbol.cause");
|
||||
|
||||
public final Symbol value;
|
||||
|
||||
private static Symbol passThis(Context ctx, String funcName, Object val) {
|
||||
if (val instanceof SymbolLib) return ((SymbolLib)val).value;
|
||||
private static Symbol passThis(Arguments args, String funcName) {
|
||||
var val = args.self;
|
||||
if (Values.isWrapper(val, SymbolLib.class)) return Values.wrapper(val, SymbolLib.class).value;
|
||||
else if (val instanceof Symbol) return (Symbol)val;
|
||||
else throw EngineException.ofType(String.format("'%s' may only be called upon object and primitve symbols.", funcName));
|
||||
}
|
||||
|
||||
@NativeConstructor(thisArg = true) public static Object constructor(Context ctx, Object thisArg, Object val) {
|
||||
if (thisArg instanceof ObjectValue) throw EngineException.ofType("Symbol constructor may not be called with new.");
|
||||
if (val == null) return new Symbol("");
|
||||
else return new Symbol(Values.toString(ctx, val));
|
||||
}
|
||||
@Native(thisArg = true) public static String toString(Context ctx, Object thisArg) {
|
||||
return passThis(ctx, "toString", thisArg).value;
|
||||
}
|
||||
@Native(thisArg = true) public static Symbol valueOf(Context ctx, Object thisArg) {
|
||||
return passThis(ctx, "valueOf", thisArg);
|
||||
public SymbolLib(Symbol val) {
|
||||
this.value = val;
|
||||
}
|
||||
|
||||
@Native("for") public static Symbol _for(String key) {
|
||||
@Expose public static String __toString(Arguments args) {
|
||||
return passThis(args, "toString").value;
|
||||
}
|
||||
@Expose public static Symbol __valueOf(Arguments args) {
|
||||
return passThis(args, "valueOf");
|
||||
}
|
||||
|
||||
@ExposeConstructor
|
||||
public static Object __constructor(Arguments args) {
|
||||
if (args.self instanceof ObjectValue) throw EngineException.ofType("Symbol constructor may not be called with new.");
|
||||
if (args.get(0) == null) return new Symbol("");
|
||||
else return new Symbol(args.getString(0));
|
||||
}
|
||||
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static Symbol __for(Arguments args) {
|
||||
var key = args.getString(0);
|
||||
if (symbols.containsKey(key)) return symbols.get(key);
|
||||
else {
|
||||
var sym = new Symbol(key);
|
||||
@ -53,11 +74,8 @@ import me.topchetoeu.jscript.interop.NativeGetter;
|
||||
return sym;
|
||||
}
|
||||
}
|
||||
@Native public static String keyFor(Symbol sym) {
|
||||
return sym.value;
|
||||
}
|
||||
|
||||
public SymbolLib(Symbol val) {
|
||||
this.value = val;
|
||||
@Expose(target = ExposeTarget.STATIC)
|
||||
public static String __keyFor(Arguments args) {
|
||||
return passThis(new Arguments(args.ctx, args.get(0)), "keyFor").value;
|
||||
}
|
||||
}
|
||||
|
@ -1,21 +1,19 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.Environment;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto;
|
||||
import me.topchetoeu.jscript.interop.InitType;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeConstructor;
|
||||
import me.topchetoeu.jscript.interop.NativeInit;
|
||||
import me.topchetoeu.jscript.interop.WrapperName;
|
||||
import me.topchetoeu.jscript.interop.Arguments;
|
||||
import me.topchetoeu.jscript.interop.ExposeConstructor;
|
||||
import me.topchetoeu.jscript.interop.ExposeField;
|
||||
|
||||
@Native("SyntaxError") public class SyntaxErrorLib extends ErrorLib {
|
||||
@NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) {
|
||||
var target = ErrorLib.constructor(ctx, thisArg, message);
|
||||
@WrapperName("SyntaxError")
|
||||
public class SyntaxErrorLib extends ErrorLib {
|
||||
@ExposeField public static final String __name = "SyntaxError";
|
||||
|
||||
@ExposeConstructor public static ObjectValue __constructor(Arguments args) {
|
||||
var target = ErrorLib.__constructor(args);
|
||||
target.setPrototype(PlaceholderProto.SYNTAX_ERROR);
|
||||
return target;
|
||||
}
|
||||
@NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) {
|
||||
target.defineProperty(null, "name", "SyntaxError");
|
||||
}
|
||||
}
|
@ -1,21 +1,19 @@
|
||||
package me.topchetoeu.jscript.lib;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.Environment;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue;
|
||||
import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto;
|
||||
import me.topchetoeu.jscript.interop.InitType;
|
||||
import me.topchetoeu.jscript.interop.Native;
|
||||
import me.topchetoeu.jscript.interop.NativeConstructor;
|
||||
import me.topchetoeu.jscript.interop.NativeInit;
|
||||
import me.topchetoeu.jscript.interop.WrapperName;
|
||||
import me.topchetoeu.jscript.interop.Arguments;
|
||||
import me.topchetoeu.jscript.interop.ExposeConstructor;
|
||||
import me.topchetoeu.jscript.interop.ExposeField;
|
||||
|
||||
@Native("TypeError") public class TypeErrorLib extends ErrorLib {
|
||||
@NativeConstructor(thisArg = true) public static ObjectValue constructor(Context ctx, Object thisArg, Object message) {
|
||||
var target = ErrorLib.constructor(ctx, thisArg, message);
|
||||
target.setPrototype(PlaceholderProto.SYNTAX_ERROR);
|
||||
@WrapperName("TypeError")
|
||||
public class TypeErrorLib extends ErrorLib {
|
||||
@ExposeField public static final String __name = "TypeError";
|
||||
|
||||
@ExposeConstructor public static ObjectValue __constructor(Arguments args) {
|
||||
var target = ErrorLib.__constructor(args);
|
||||
target.setPrototype(PlaceholderProto.TYPE_ERROR);
|
||||
return target;
|
||||
}
|
||||
@NativeInit(InitType.PROTOTYPE) public static void init(Environment env, ObjectValue target) {
|
||||
target.defineProperty(null, "name", "TypeError");
|
||||
}
|
||||
}
|
@ -4,10 +4,15 @@ import java.util.HashMap;
|
||||
|
||||
import me.topchetoeu.jscript.Filename;
|
||||
import me.topchetoeu.jscript.engine.Context;
|
||||
import me.topchetoeu.jscript.engine.Extensions;
|
||||
import me.topchetoeu.jscript.engine.values.Symbol;
|
||||
import me.topchetoeu.jscript.filesystem.Filesystem;
|
||||
import me.topchetoeu.jscript.filesystem.Mode;
|
||||
|
||||
public interface ModuleRepo {
|
||||
public static final Symbol ENV_KEY = Symbol.get("Environment.modules");
|
||||
public static final Symbol CWD = Symbol.get("Environment.moduleCwd");
|
||||
|
||||
public Module getModule(Context ctx, String cwd, String name);
|
||||
|
||||
public static ModuleRepo ofFilesystem(Filesystem fs) {
|
||||
@ -20,8 +25,8 @@ public interface ModuleRepo {
|
||||
|
||||
if (modules.containsKey(name)) return modules.get(name);
|
||||
|
||||
var env = ctx.environment().child();
|
||||
env.moduleCwd = fs.normalize(name, "..");
|
||||
var env = ctx.environment.child();
|
||||
env.add(CWD, fs.normalize(name, ".."));
|
||||
|
||||
var mod = new SourceModule(filename, src, env);
|
||||
modules.put(name, mod);
|
||||
@ -29,4 +34,11 @@ public interface ModuleRepo {
|
||||
return mod;
|
||||
};
|
||||
}
|
||||
|
||||
public static String cwd(Extensions exts) {
|
||||
return exts.init(CWD, "/");
|
||||
}
|
||||
public static ModuleRepo get(Extensions exts) {
|
||||
return exts.get(ENV_KEY);
|
||||
}
|
||||
}
|
||||
|
@ -1178,8 +1178,7 @@ public class Parsing {
|
||||
prevArg = true;
|
||||
}
|
||||
else if (argRes.isError()) return argRes.transform();
|
||||
else if (isOperator(tokens, i + n, Operator.COMMA)) {
|
||||
if (!prevArg) args.add(null);
|
||||
else if (prevArg && isOperator(tokens, i + n, Operator.COMMA)) {
|
||||
prevArg = false;
|
||||
n++;
|
||||
}
|
||||
@ -1187,7 +1186,7 @@ public class Parsing {
|
||||
n++;
|
||||
break;
|
||||
}
|
||||
else return ParseRes.failed();
|
||||
else return ParseRes.error(getLoc(filename, tokens, i + n), prevArg ? "Expected a comma or a closing paren." : "Expected an expression or a closing paren.");
|
||||
}
|
||||
|
||||
return ParseRes.res(new CallStatement(loc, false, prev, args.toArray(Statement[]::new)), n);
|
||||
|
@ -1,6 +1,11 @@
|
||||
package me.topchetoeu.jscript.permissions;
|
||||
|
||||
import me.topchetoeu.jscript.engine.Extensions;
|
||||
import me.topchetoeu.jscript.engine.values.Symbol;
|
||||
|
||||
public interface PermissionsProvider {
|
||||
public static final Symbol ENV_KEY = new Symbol("Environment.perms");
|
||||
|
||||
boolean hasPermission(Permission perm, char delim);
|
||||
boolean hasPermission(Permission perm);
|
||||
|
||||
@ -10,4 +15,17 @@ public interface PermissionsProvider {
|
||||
default boolean hasPermission(String perm) {
|
||||
return hasPermission(new Permission(perm));
|
||||
}
|
||||
|
||||
public static PermissionsProvider get(Extensions exts) {
|
||||
return new PermissionsProvider() {
|
||||
@Override public boolean hasPermission(Permission perm) {
|
||||
if (exts.hasNotNull(ENV_KEY)) return ((PermissionsProvider)exts.get(ENV_KEY)).hasPermission(perm);
|
||||
else return true;
|
||||
}
|
||||
@Override public boolean hasPermission(Permission perm, char delim) {
|
||||
if (exts.hasNotNull(ENV_KEY)) return ((PermissionsProvider)exts.get(ENV_KEY)).hasPermission(perm, delim);
|
||||
else return true;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
@ -3,8 +3,8 @@ return new UnitTest('counters')
|
||||
.add('postfix decrement', function () { var i = 10; i-- === 10; })
|
||||
.add('prefix decrement', function () { var i = 10; --i === 9; })
|
||||
.add('prefix increment', function () { var i = 10; ++i === 11; })
|
||||
.add('ostfix increment of non-number', function () { var i = 'hi mom'; isNaN(i++); })
|
||||
.add('ostfix decrement of non-number', function () { var i = 'hi mom'; isNaN(i--); })
|
||||
.add('postfix increment of non-number', function () { var i = 'hi mom'; isNaN(i++); })
|
||||
.add('postfix decrement of non-number', function () { var i = 'hi mom'; isNaN(i--); })
|
||||
.add('prefix increment of non-number', function () { var i = 'hi mom'; isNaN(++i); })
|
||||
.add('prefix decrement of non-number', function () { var i = 'hi mom'; isNaN(--i); })
|
||||
.add('postfix increment of convertible to number', function () { var i = '10'; i++; i === 11; })
|
@ -1,2 +1,2 @@
|
||||
return new UnitTest('Arithmetics')
|
||||
.add(include('counters.js'))
|
||||
.add(require('counters.js'))
|
12
tests/array/constructor.js
Normal file
12
tests/array/constructor.js
Normal file
@ -0,0 +1,12 @@
|
||||
return new UnitTest('constructor', function() { return typeof Array === 'function'; })
|
||||
.add('no args', function () { return match(new Array(), []); })
|
||||
.add('length', function () {
|
||||
var res = new Array(3);
|
||||
return res.length === 3 &&
|
||||
!(0 in res) &&
|
||||
!(1 in res) &&
|
||||
!(2 in res);
|
||||
})
|
||||
.add('elements', function () {
|
||||
return match(new Array(1, 2, 3), [1, 2, 3]);
|
||||
})
|
19
tests/array/fill.js
Normal file
19
tests/array/fill.js
Normal file
@ -0,0 +1,19 @@
|
||||
return new UnitTest('fill', function() { return typeof Array.prototype.push === 'function'; })
|
||||
.add('simple fill', function() {
|
||||
return match([5, 5, 5, 5, 5], [1, 2, 3, 4, 5].fill(5))
|
||||
})
|
||||
.add('fill empty', function() {
|
||||
return match([], [].fill(5))
|
||||
})
|
||||
.add('fill from', function() {
|
||||
return match([1, 'a', 'a', 'a', 'a'], [1, 2, 3, 4, 5].fill('a', 1))
|
||||
})
|
||||
.add('fill range', function() {
|
||||
return match([1, 'a', 'a', 'a', 5], [1, 2, 3, 4, 5].fill('a', 1, 4))
|
||||
})
|
||||
.add('fill wrap', function() {
|
||||
return match([1, 'a', 'a', 4, 5], [1, 2, 3, 4, 5].fill('a', 1, -2))
|
||||
})
|
||||
.add('fill out of range', function() {
|
||||
return match([1, 2, 'a', 'a', 'a'], [1, 2, 3, 4, 5].fill('a', 2, 8))
|
||||
})
|
19
tests/array/find.js
Normal file
19
tests/array/find.js
Normal file
@ -0,0 +1,19 @@
|
||||
var a = { id: 10, name: 'test1' };
|
||||
var b = { id: 15, name: 'test2' };
|
||||
var c = { id: 20, name: 'test3' };
|
||||
|
||||
return new UnitTest('find', function() { return typeof Array.prototype.find === 'function'; })
|
||||
.add('simple', function() {
|
||||
return [ a, b, c ].find(function (v) { return v.id === 15; }) === b;
|
||||
})
|
||||
.add('sparse', function() {
|
||||
var n = 0;
|
||||
[ a, b,,,, c ].find(function (v) { n++; return v === undefined; });
|
||||
return n === 3;
|
||||
})
|
||||
.add('no occurence', function() {
|
||||
return [ a, b, c ].find(function (v) { return v.id === 30 }) === undefined;
|
||||
})
|
||||
.add('pass this', function() {
|
||||
return [ a, b ].find(function (v) { return this === c; }, c) === a;
|
||||
})
|
@ -1,22 +1,12 @@
|
||||
function runIterator(arr, method, func, n) {
|
||||
var res = [];
|
||||
var j = 1;
|
||||
var args = [ function() {
|
||||
var pushed = [];
|
||||
for (var i = 0; i < n; i++) pushed[i] = arguments[i];
|
||||
res[j++] = pushed;
|
||||
return func.apply(this, arguments);
|
||||
} ];
|
||||
|
||||
for (var i = 4; i < arguments.length; i++) args[i - 3] = arguments[i];
|
||||
|
||||
res[0] = method.apply(arr, args);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
return new UnitTest('Array', function() { []; })
|
||||
.add(include('length.js'))
|
||||
.add(include('reduce.js'))
|
||||
.add(include('sparse.js'))
|
||||
.add(include('concat.js'))
|
||||
.add(require('constructor.js'))
|
||||
.add(require('length.js'))
|
||||
.add(require('reduce.js')('reduce'))
|
||||
.add(require('reduce.js')('reduceRight'))
|
||||
.add(require('sparse.js'))
|
||||
.add(require('concat.js'))
|
||||
.add(require('sort.js'))
|
||||
.add(require('push.js'))
|
||||
.add(require('pop.js'))
|
||||
.add(require('fill.js'))
|
||||
.add(require('find.js'))
|
||||
|
19
tests/array/indexOf.js
Normal file
19
tests/array/indexOf.js
Normal file
@ -0,0 +1,19 @@
|
||||
return new UnitTest('fill', function() { return typeof Array.prototype.push === 'function'; })
|
||||
.add('simple fill', function() {
|
||||
return match([5, 5, 5, 5, 5], [1, 2, 3, 4, 5].fill(5))
|
||||
})
|
||||
.add('fill empty', function() {
|
||||
return match([], [].fill(5))
|
||||
})
|
||||
.add('fill from', function() {
|
||||
return match([1, 'a', 'a', 'a', 'a'], [1, 2, 3, 4, 5].fill('a', 1))
|
||||
})
|
||||
.add('fill range', function() {
|
||||
return match([1, 'a', 'a', 'a', 5], [1, 2, 3, 4, 5].fill('a', 1, 4))
|
||||
})
|
||||
.add('fill wrap', function() {
|
||||
return match([1, 'a', 'a', 4, 5], [1, 2, 3, 4, 5].fill('a', 1, -2))
|
||||
})
|
||||
.add('fill out of range', function() {
|
||||
return match([1, 2, 'a', 'a', 'a'], [1, 2, 3, 4, 5].fill('a', 2, 8))
|
||||
})
|
@ -8,12 +8,12 @@ return new UnitTest('length & capacity', function() { return 'length' in Array.p
|
||||
})
|
||||
.add('length after set', function() {
|
||||
var a = [];
|
||||
a [5]= 5;
|
||||
a[5] = 5;
|
||||
return a.length === 6;
|
||||
})
|
||||
.add('length after set (big', function() {
|
||||
.add('length after set (big)', function() {
|
||||
var a = [1, 2];
|
||||
a [5000]= 5;
|
||||
a[5000] = 5;
|
||||
return a.length === 5001;
|
||||
})
|
||||
.add('expand test', function() {
|
||||
|
8
tests/array/pop.js
Normal file
8
tests/array/pop.js
Normal file
@ -0,0 +1,8 @@
|
||||
return new UnitTest('pop', function() { return typeof Array.prototype.pop === 'function'; })
|
||||
.add('simple pop', function() {
|
||||
var arr = [1, 2, 3];
|
||||
return match(3, arr.pop())
|
||||
})
|
||||
.add('pop from empty', function() {
|
||||
return match(undefined, [].pop())
|
||||
})
|
16
tests/array/push.js
Normal file
16
tests/array/push.js
Normal file
@ -0,0 +1,16 @@
|
||||
return new UnitTest('push', function() { return typeof Array.prototype.push === 'function'; })
|
||||
.add('simple push', function() {
|
||||
var arr = [];
|
||||
arr.push(1, 2, 3);
|
||||
return match(arr, [1, 2, 3])
|
||||
})
|
||||
.add('push array', function() {
|
||||
var arr = [];
|
||||
arr.push([1, 2, 3]);
|
||||
return match(arr, [[1, 2, 3]])
|
||||
})
|
||||
.add('push as concat', function() {
|
||||
var arr = [1, 2, 3];
|
||||
arr.push(4, 5, 6);
|
||||
return match(arr, [1, 2, 3, 4, 5, 6])
|
||||
})
|
@ -1,44 +1,62 @@
|
||||
var res = [];
|
||||
function runIterator(arr, method, func, n) {
|
||||
var res = [];
|
||||
var j = 1;
|
||||
var args = [ function() {
|
||||
var pushed = [];
|
||||
for (var i = 0; i < n; i++) pushed[i] = arguments[i];
|
||||
res[j++] = pushed;
|
||||
return func.apply(this, arguments);
|
||||
} ];
|
||||
|
||||
return new UnitTest('reduceRight', function () { return typeof Array.prototype.reduceRight === 'function' })
|
||||
.add('empty function', function () {match(
|
||||
[ undefined, [4, 3, 2], [undefined, 2, 1], [undefined, 1, 0], ],
|
||||
runIterator([1, 2, 3, 4], Array.prototype.reduceRight, function() { }, 3), 1
|
||||
)})
|
||||
.add('adder', function () {match(
|
||||
[ 10, [4, 3, 2], [7, 2, 1], [9, 1, 0], ],
|
||||
runIterator([1, 2, 3, 4], Array.prototype.reduceRight, function(a, b) { return a + b; }, 3), 1
|
||||
)})
|
||||
.add('sparse array', function () {match(
|
||||
[ 10, [4, 3, 11], [7, 2, 7], [9, 1, 3], ],
|
||||
runIterator([,,,1,,,, 2,,,, 3,,,, 4,,,,], Array.prototype.reduceRight, function(a, b) { return a + b }, 3), 1
|
||||
)})
|
||||
.add('sparse array with one element', function () {match(
|
||||
[ 1 ],
|
||||
runIterator([,,,1,,,,], Array.prototype.reduceRight, function(v) { return v; }, 3), 1
|
||||
)})
|
||||
.add('sparse array with no elements', function () {match(
|
||||
[ undefined ],
|
||||
runIterator([,,,,,,,], Array.prototype.reduceRight, function(v) { return v; }, 3), 1
|
||||
)})
|
||||
for (var i = 4; i < arguments.length; i++) args[i - 3] = arguments[i];
|
||||
|
||||
.add('initial value and empty function', function () {match(
|
||||
[ undefined, [0, 4, 3], [undefined, 3, 2], [undefined, 2, 1], [undefined, 1, 0] ],
|
||||
runIterator([1, 2, 3, 4], Array.prototype.reduceRight, function() { }, 3, 0), 1
|
||||
)})
|
||||
.add('initial value and adder', function () {match(
|
||||
[ 15, [5, 4, 3], [9, 3, 2], [12, 2, 1], [14, 1, 0] ],
|
||||
runIterator([1, 2, 3, 4], Array.prototype.reduceRight, function(a, b) { return a + b; }, 3, 5), 1
|
||||
)})
|
||||
.add('initial value, sparce array and adder', function () {match(
|
||||
[ 15, [5, 4, 15], [9, 3, 11], [12, 2, 7], [14, 1, 3] ],
|
||||
runIterator([,,,1,,,, 2,,,, 3,,,, 4,,,,], Array.prototype.reduceRight, function(a, b) { return a + b; }, 3, 5), 1
|
||||
)})
|
||||
.add('initial value and sparse array with one element', function () {match(
|
||||
[ 6, [5, 1, 3] ],
|
||||
runIterator([,,,1,,,,], Array.prototype.reduceRight, function(a, b) { return a + b; }, 3, 5), 1
|
||||
)})
|
||||
.add('initial value and sparse array with no elements', function () {match(
|
||||
[ 5 ],
|
||||
runIterator([,,,,,,,], Array.prototype.reduceRight, function(v) { return v; }, 3, 5), 1
|
||||
)});
|
||||
res[0] = method.apply(arr, args);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
return function(name) {
|
||||
var func = Array.prototype[name];
|
||||
return new UnitTest(name, function () { return typeof func === 'function' })
|
||||
.add('empty function', function () {match(
|
||||
[ undefined, [4, 3, 2], [undefined, 2, 1], [undefined, 1, 0], ],
|
||||
runIterator([1, 2, 3, 4], func, function() { }, 3), 1
|
||||
)})
|
||||
.add('adder', function () {match(
|
||||
[ 10, [4, 3, 2], [7, 2, 1], [9, 1, 0], ],
|
||||
runIterator([1, 2, 3, 4], func, function(a, b) { return a + b; }, 3), 1
|
||||
)})
|
||||
.add('sparse array', function () {match(
|
||||
[ 10, [4, 3, 11], [7, 2, 7], [9, 1, 3], ],
|
||||
runIterator([,,,1,,,, 2,,,, 3,,,, 4,,,,], func, function(a, b) { return a + b }, 3), 1
|
||||
)})
|
||||
.add('sparse array with one element', function () {match(
|
||||
[ 1 ],
|
||||
runIterator([,,,1,,,,], func, function(v) { return v; }, 3), 1
|
||||
)})
|
||||
.add('sparse array with no elements', function () {match(
|
||||
[ undefined ],
|
||||
runIterator([,,,,,,,], func, function(v) { return v; }, 3), 1
|
||||
)})
|
||||
|
||||
.add('initial value and empty function', function () {match(
|
||||
[ undefined, [0, 4, 3], [undefined, 3, 2], [undefined, 2, 1], [undefined, 1, 0] ],
|
||||
runIterator([1, 2, 3, 4], func, function() { }, 3, 0), 1
|
||||
)})
|
||||
.add('initial value and adder', function () {match(
|
||||
[ 15, [5, 4, 3], [9, 3, 2], [12, 2, 1], [14, 1, 0] ],
|
||||
runIterator([1, 2, 3, 4], func, function(a, b) { return a + b; }, 3, 5), 1
|
||||
)})
|
||||
.add('initial value, sparce array and adder', function () {match(
|
||||
[ 15, [5, 4, 15], [9, 3, 11], [12, 2, 7], [14, 1, 3] ],
|
||||
runIterator([,,,1,,,, 2,,,, 3,,,, 4,,,,], func, function(a, b) { return a + b; }, 3, 5), 1
|
||||
)})
|
||||
.add('initial value and sparse array with one element', function () {match(
|
||||
[ 6, [5, 1, 3] ],
|
||||
runIterator([,,,1,,,,], func, function(a, b) { return a + b; }, 3, 5), 1
|
||||
)})
|
||||
.add('initial value and sparse array with no elements', function () {match(
|
||||
[ 5 ],
|
||||
runIterator([,,,,,,,], func, function(v) { return v; }, 3, 5), 1
|
||||
)});
|
||||
}
|
@ -1,4 +1,6 @@
|
||||
return new UnitTest('concat', function() { return typeof Array.prototype.concat === 'function'; })
|
||||
.add('two arrays', function() { return match([1, 2, 3], [1].concat([2], [3])) })
|
||||
.add('simple spread', function() { return match([1, 2, 3, 4, 5], [1].concat([2], 3, [4, 5])) })
|
||||
.add('sparse concat', function() { return match([1,, 2,,, 3,,, 4, 5], [1,,2].concat([,,3,,,4], 5)) })
|
||||
return new UnitTest('sort', function() { return typeof Array.prototype.sort === 'function'; })
|
||||
.add('simple', function() { return match([4, 6, 2].sort(), [2, 4, 6]) })
|
||||
.add('stringify', function() { return match([4, 6, 2, 10].sort(), [10, 2, 4, 6]) })
|
||||
.add('undefined and empty', function() { return match([4, undefined, 6, , 2].sort(), [2, 4, 6, undefined,,]) })
|
||||
.add('function ascend', function() { return match([3, 1, 2].sort(function (a, b) { return a - b; }), [1, 2, 3]) })
|
||||
.add('function descend', function() { return match([3, 1, 2].sort(function (a, b) { return b - a; }), [3, 2, 1]) })
|
1
tests/entry.js
Normal file
1
tests/entry.js
Normal file
@ -0,0 +1 @@
|
||||
require('tests/index.js')();
|
@ -2,19 +2,24 @@ function assert(cond, msg, locDepth) {
|
||||
if (locDepth < 0 || locDepth === undefined) locDepth = 0;
|
||||
if (!cond) {
|
||||
log('Assert failed', (typeof locDepth === 'string' ? locDepth : Error().stack[locDepth + 1]) + ': ', msg);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function assertMatch(expected, actual, depth, msg) {
|
||||
if (!match(expected, actual, depth)) {
|
||||
log('Assert failed', Error().stack[1] + ': ', msg);
|
||||
log('Expected:', expected);
|
||||
log('Actual:', actual);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
function match(expected, actual, depth) {
|
||||
if (!Array.isArray(expected) || !Array.isArray(actual)) return expected === actual;
|
||||
else if (expected.length !== actual.length) return false;
|
||||
else if (depth === undefined || depth < 0) depth = 0;
|
||||
else if (depth === undefined) depth = Infinity;
|
||||
else if (depth < 0) depth = 0;
|
||||
|
||||
for (var i = 0; i < expected.length; i++) {
|
||||
if (!(i in expected) || !(i in actual)) return !(i in expected) && !(i in actual);
|
||||
@ -43,6 +48,7 @@ UnitTest.prototype.run = function(path) {
|
||||
if (path === undefined) path = [];
|
||||
|
||||
path.push(this.name);
|
||||
var res = true;
|
||||
|
||||
if (typeof this.exec === 'function') {
|
||||
var res = true, err = 'exec() returned false.';
|
||||
@ -50,13 +56,15 @@ UnitTest.prototype.run = function(path) {
|
||||
if (this.exec() === false) res = false;
|
||||
}
|
||||
catch (e) { res = false; err = e; }
|
||||
assert(res, path.join('/') + ': ' + err, this.exec.location());
|
||||
res &= assert(res, path.join('/') + ': ' + err, this.exec.location());
|
||||
}
|
||||
for (var i = 0; i < this.subtests.length; i++) {
|
||||
this.subtests[i].run(path);
|
||||
res &= this.subtests[i].run(path);
|
||||
}
|
||||
|
||||
path.pop();
|
||||
|
||||
return res;
|
||||
}
|
||||
UnitTest.prototype.add = function(test, exec) {
|
||||
if (test instanceof UnitTest) this.subtests.push(test);
|
||||
@ -64,7 +72,10 @@ UnitTest.prototype.add = function(test, exec) {
|
||||
return this;
|
||||
}
|
||||
|
||||
include('arithmetics/index.js').run();
|
||||
include('array/index.js').run();
|
||||
|
||||
log('Tests complete.');
|
||||
return function() {
|
||||
if (
|
||||
require('arithmetics/index.js').run() &&
|
||||
require('array/index.js').run()
|
||||
) log('All tests passed!');
|
||||
exit();
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user