Major code base cleanup #12

Merged
TopchetoEU merged 35 commits from TopchetoEU/cleanup into master 2024-01-06 16:28:11 +00:00
98 changed files with 3433 additions and 2781 deletions

View File

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

View File

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

View File

@ -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();
}
}
public class Main {
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[]) {

View File

@ -1,6 +0,0 @@
package me.topchetoeu.jscript;
public interface MessageReceiver {
void sendMessage(String msg);
void sendError(String msg);
}

View File

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

View File

@ -0,0 +1,5 @@
package me.topchetoeu.jscript;
public interface ResultRunnable<T> {
T run();
}

View File

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

View File

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

View File

@ -1,3 +0,0 @@
package me.topchetoeu.jscript.engine;
public class DataKey<T> { }

View File

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

View File

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

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

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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) {

View File

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

View File

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

View File

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

View File

@ -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) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +0,0 @@
package me.topchetoeu.jscript.events;
public class FinishedException extends RuntimeException {
public FinishedException() {
super("The observable has ended.");
}
}

View File

@ -1,5 +0,0 @@
package me.topchetoeu.jscript.events;
public interface Handle {
void free();
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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

View File

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

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

View File

@ -0,0 +1,8 @@
package me.topchetoeu.jscript.interop;
public enum ExposeType {
INIT,
METHOD,
GETTER,
SETTER,
}

View File

@ -1,7 +0,0 @@
package me.topchetoeu.jscript.interop;
public enum InitType {
CONSTRUCTOR,
PROTOTYPE,
NAMESPACE,
}

View File

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

View File

@ -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 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);
var actual = method.getParameterTypes();
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);

View File

@ -7,6 +7,6 @@ import java.lang.annotation.Target;
@Target({ ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface NativeInit {
InitType value();
public @interface OnWrapperInit {
}

View File

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

View File

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

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

View File

@ -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();
@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;
}
@NativeSetter(thisArg = true) public static void length(Context ctx, ArrayValue thisArg, int len) {
thisArg.setSize(len);
@Expose(value = "length", type = ExposeType.GETTER)
public static int __getLength(Arguments args) {
return args.self(ArrayValue.class).size();
}
@Native(thisArg = true) public static ObjectValue values(Context ctx, ArrayValue thisArg) {
return Values.toJSIterator(ctx, thisArg);
@Expose(value = "length", type = ExposeType.SETTER)
public static void __setLength(Arguments args) {
args.self(ArrayValue.class).setSize(args.getInt(0));
}
@Native(thisArg = true) public static ObjectValue keys(Context ctx, ArrayValue thisArg) {
return Values.toJSIterator(ctx, () -> new Iterator<Object>() {
@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;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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), ",/?:@&=+$#.");
}
}

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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.");

View File

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

View File

@ -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);
@ -76,104 +92,77 @@ public class Internals {
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();
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;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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";
@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; }
public int lastI = 0;
public final String source;
public final boolean hasIndices;
public final boolean global;
public final boolean sticky;
@NativeGetter("flags") public final String flags() {
@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; }
@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);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,2 +1,2 @@
return new UnitTest('Arithmetics')
.add(include('counters.js'))
.add(require('counters.js'))

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

View File

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

View File

@ -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
View 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
View 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])
})

View File

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

View File

@ -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
View File

@ -0,0 +1 @@
require('tests/index.js')();

View File

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