refactor: greatly improve Environment API

This commit is contained in:
TopchetoEU 2023-12-26 17:12:20 +02:00
parent d7f6010319
commit 38acc20a6f
Signed by: topchetoeu
GPG Key ID: 6531B8583E5F6ED4
21 changed files with 371 additions and 240 deletions

View File

@ -131,10 +131,10 @@ public class Main {
private static void initTypescript() {
try {
var tsEnv = Internals.apply(new Environment(null, null, null));
tsEnv.stackVisible = false;
tsEnv.stackHidden = false;
tsEnv.global.define(null, "module", false, new ObjectValue());
var bsEnv = Internals.apply(new Environment(null, null, null));
bsEnv.stackVisible = false;
bsEnv.stackHidden = false;
engine.pushMsg(
false, tsEnv,

View File

@ -10,6 +10,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.frame.CodeFrame;
import me.topchetoeu.jscript.engine.values.ArrayValue;
import me.topchetoeu.jscript.engine.values.FunctionValue;
@ -17,11 +18,16 @@ import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.mapping.SourceMap;
public class Context {
public class Context extends ExtensionStack {
private final Stack<Environment> env = new Stack<>();
private final ArrayList<CodeFrame> frames = new ArrayList<>();
public final Engine engine;
@Override
protected Extensions[] extensionStack() {
return new Extensions[] { environment(), engine.globalEnvironment };
}
public Environment environment() {
return env.empty() ? null : env.peek();
}
@ -36,7 +42,7 @@ public class Context {
public FunctionValue compile(Filename filename, String raw) {
var env = environment();
var result = env.compile.call(this, null, raw, filename.toString(), env);
var result = Environment.compileFunc(this).call(this, null, raw, filename.toString(), env);
var function = (FunctionValue)Values.getMember(this, result, "function");
if (!engine.debugging) return function;
@ -62,24 +68,23 @@ 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);
DebugContext.get(this).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);
DebugContext.get(this).onFramePop(this, frame);
return true;
}
public CodeFrame peekFrame() {

View File

@ -17,7 +17,7 @@ import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.InterruptException;
import me.topchetoeu.jscript.mapping.SourceMap;
public class Engine implements DebugController {
public class Engine {
private class UncompiledFunction extends FunctionValue {
public final Filename filename;
public final String raw;
@ -61,47 +61,15 @@ public class Engine implements DebugController {
private static int nextId = 0;
public static final HashMap<Long, FunctionBody> functions = new HashMap<>();
public final Environment globalEnvironment = new Environment();
public final int id = ++nextId;
public final boolean debugging;
public int maxStackFrames = 10000;
private final HashMap<Filename, String> sources = new HashMap<>();
private final HashMap<Filename, TreeSet<Location>> bpts = new HashMap<>();
private 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);
}
public Location mapToOriginal(Location location) {
var map = maps.get(location.filename());
if (map == null) return location;
return map.toOriginal(location);
}
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));
@ -150,25 +118,6 @@ public class Engine implements DebugController {
return pushMsg(micro, env, new UncompiledFunction(filename, raw), thisArg, args);
}
@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;
}

View File

@ -13,117 +13,109 @@ 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 {
private static int nextId = 0;
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 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);
}
@Native public Symbol symbol(String name) {
return getSymbol(name);
public static FunctionValue compileFunc(Extensions ext) {
return ext.init(COMPILE_FUNC, 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())));
}
return res;
}));
}
public static FunctionValue regexConstructor(Extensions ext) {
return ext.init(COMPILE_FUNC, new NativeFunction("RegExp", (ctx, thisArg, args) -> {
throw EngineException.ofError("Regular expressions not supported.").setCtx(ctx.environment(), ctx.engine);
}));
}
@NativeGetter("global") public ObjectValue getGlobal() {
return global.obj;
}
@NativeSetter("global") public void setGlobal(ObjectValue val) {
global = new GlobalScope(val);
}
public Environment fork() {
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);
res.global = global;
res.data.putAll(data);
return res;
}
@Native public Environment child() {
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;
}
@Override public boolean hasPermission(Permission perm, char delim) {
return permissions == null || permissions.hasPermission(perm, delim);
}
@Override public boolean hasPermission(Permission perm) {
return permissions == null || permissions.hasPermission(perm);
}
public Context context(Engine engine) {
return new Context(engine, 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,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,24 @@
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);
boolean has(Symbol key);
boolean remove(Symbol key);
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;
}
}
}

View File

@ -0,0 +1,96 @@
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 DEBUG_CTX = Symbol.get("Engine.debug");
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 DebugContext get(Extensions exts) {
if (exts.has(DEBUG_CTX)) return exts.get(DEBUG_CTX);
else return new DebugContext(false);
}
}

View File

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

@ -15,6 +15,7 @@ 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;
@ -341,7 +342,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.environment().get(Environment.OBJECT_PROTO), "toString");
}
catch (Exception e) { }
@ -495,7 +496,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)) {
@ -870,7 +871,7 @@ public class SimpleDebugger implements Debugger {
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());
@ -953,12 +954,12 @@ public class SimpleDebugger implements Debugger {
}
@Override public synchronized void connect() {
if (!target.attachDebugger(this)) {
if (!DebugContext.get(target.globalEnvironment).attachDebugger(this)) {
ws.send(new V8Error("A debugger is already attached to this engine."));
}
}
@Override public synchronized void disconnect() {
target.detachDebugger();
DebugContext.get(target.globalEnvironment).detachDebugger();
enabled = false;
updateNotifier.next();
}

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;
@ -198,8 +199,7 @@ public class CodeFrame {
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, Runners.NO_RETURN, null, false);
if (instr.location != null) prevLoc = instr.location;
@ -291,11 +291,11 @@ 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);
DebugContext.get(ctx).onInstruction(ctx, this, instr, returnValue, null, false);
return returnValue;
}

View File

@ -5,6 +5,7 @@ import java.util.Collections;
import me.topchetoeu.jscript.compilation.Instruction;
import me.topchetoeu.jscript.engine.Context;
import me.topchetoeu.jscript.engine.Engine;
import me.topchetoeu.jscript.engine.Environment;
import me.topchetoeu.jscript.engine.Operation;
import me.topchetoeu.jscript.engine.scope.ValueVariable;
import me.topchetoeu.jscript.engine.values.ArrayValue;
@ -197,7 +198,13 @@ public class Runners {
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)));
var env = ctx.environment();
if (env.has(Environment.REGEX_CONSTR)) {
frame.push(ctx, Values.callNew(ctx, env.get(Environment.REGEX_CONSTR)));
}
else {
throw EngineException.ofSyntax("Regex is not supported.");
}
frame.codePtr++;
return NO_RETURN;
}

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.environment().get(Environment.OBJECT_PROTO);
if (prototype == ARR_PROTO) return ctx.environment().get(Environment.ARRAY_PROTO);
if (prototype == FUNC_PROTO) return ctx.environment().get(Environment.FUNCTION_PROTO);
if (prototype == ERR_PROTO) return ctx.environment().get(Environment.ERROR_PROTO);
if (prototype == RANGE_ERR_PROTO) return ctx.environment().get(Environment.RANGE_ERR_PROTO);
if (prototype == SYNTAX_ERR_PROTO) return ctx.environment().get(Environment.SYNTAX_ERR_PROTO);
if (prototype == TYPE_ERR_PROTO) return ctx.environment().get(Environment.TYPE_ERR_PROTO);
}
catch (NullPointerException e) { return null; }
@ -170,13 +171,13 @@ public class 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 (obj == ctx.environment().get(Environment.OBJECT_PROTO)) prototype = OBJ_PROTO;
else if (obj == ctx.environment().get(Environment.ARRAY_PROTO)) prototype = ARR_PROTO;
else if (obj == ctx.environment().get(Environment.FUNCTION_PROTO)) prototype = FUNC_PROTO;
else if (obj == ctx.environment().get(Environment.ERROR_PROTO)) prototype = ERR_PROTO;
else if (obj == ctx.environment().get(Environment.SYNTAX_ERR_PROTO)) prototype = SYNTAX_ERR_PROTO;
else if (obj == ctx.environment().get(Environment.TYPE_ERR_PROTO)) prototype = TYPE_ERR_PROTO;
else if (obj == ctx.environment().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;
@ -346,10 +347,10 @@ public class Values {
if (isObject(obj)) 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.environment().get(Environment.STRING_PROTO);
else if (obj instanceof Number) return ctx.environment().get(Environment.NUMBER_PROTO);
else if (obj instanceof Boolean) return ctx.environment().get(Environment.BOOL_PROTO);
else if (obj instanceof Symbol) return ctx.environment().get(Environment.SYMBOL_PROTO);
return null;
}
@ -550,7 +551,7 @@ 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();
@ -604,7 +605,7 @@ public class Values {
var res = new ObjectValue();
try {
var key = getMember(ctx, getMember(ctx, ctx.environment().proto("symbol"), "constructor"), "iterator");
var key = getMember(ctx, getMember(ctx, ctx.environment().get(Environment.SYMBOL_PROTO), "constructor"), "iterator");
res.defineProperty(ctx, key, new NativeFunction("", (_ctx, thisArg, args) -> thisArg));
}
catch (IllegalArgumentException | NullPointerException e) { }
@ -629,7 +630,7 @@ public class Values {
var res = new ObjectValue();
try {
var key = getMemberPath(ctx, ctx.environment().proto("symbol"), "constructor", "asyncIterator");
var key = getMemberPath(ctx, ctx.environment().get(Environment.SYMBOL_PROTO), "constructor", "asyncIterator");
res.defineProperty(ctx, key, new NativeFunction("", (_ctx, thisArg, args) -> thisArg));
}
catch (IllegalArgumentException | NullPointerException e) { }

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.environment() == null || !ctx.environment().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 + " ";

View File

@ -8,6 +8,7 @@ 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.exceptions.EngineException;
import me.topchetoeu.jscript.exceptions.UncheckedException;
@ -28,7 +29,7 @@ public class NativeWrapperProvider implements WrappersProvider {
if (nat.thisArg() && !member || !nat.thisArg() && !memberMatch) continue;
Object name = nat.value();
if (((String)name).startsWith("@@")) name = env.symbol(((String)name).substring(2));
if (name.toString().startsWith("@@")) name = Symbol.get(name.toString().substring(2));
else if (name.equals("")) name = method.getName();
var val = target.values.get(name);
@ -42,7 +43,7 @@ public class NativeWrapperProvider implements WrappersProvider {
if (get.thisArg() && !member || !get.thisArg() && !memberMatch) continue;
Object name = get.value();
if (((String)name).startsWith("@@")) name = env.symbol(((String)name).substring(2));
if (((String)name).startsWith("@@")) name = Symbol.get(((String)name).substring(2));
else if (name.equals("")) name = method.getName();
var prop = target.properties.get(name);
@ -59,7 +60,7 @@ public class NativeWrapperProvider implements WrappersProvider {
if (set.thisArg() && !member || !set.thisArg() && !memberMatch) continue;
Object name = set.value();
if (((String)name).startsWith("@@")) name = env.symbol(((String)name).substring(2));
if (((String)name).startsWith("@@")) name = Symbol.get(((String)name).substring(2));
else if (name.equals("")) name = method.getName();
var prop = target.properties.get(name);
@ -82,7 +83,7 @@ public class NativeWrapperProvider implements WrappersProvider {
if (nat != null) {
Object name = nat.value();
if (((String)name).startsWith("@@")) name = env.symbol(((String)name).substring(2));
if (((String)name).startsWith("@@")) name = Symbol.get(((String)name).substring(2));
else if (name.equals("")) name = field.getName();
var getter = OverloadFunction.of("get " + name, Overload.getterFromField(field));
@ -98,7 +99,7 @@ public class NativeWrapperProvider implements WrappersProvider {
if (nat != null) {
Object name = nat.value();
if (((String)name).startsWith("@@")) name = env.symbol(((String)name).substring(2));
if (((String)name).startsWith("@@")) name = Symbol.get(((String)name).substring(2));
else if (name.equals("")) name = cl.getName();
var getter = new NativeFunction("get " + name, (ctx, thisArg, args) -> cl);
@ -123,7 +124,7 @@ public class NativeWrapperProvider implements WrappersProvider {
public static ObjectValue makeProto(Environment ctx, Class<?> clazz) {
var res = new ObjectValue();
res.defineProperty(null, ctx.symbol("Symbol.typeName"), getName(clazz));
res.defineProperty(null, Symbol.get("Symbol.typeName"), getName(clazz));
for (var overload : clazz.getDeclaredMethods()) {
var init = overload.getAnnotation(NativeInit.class);

View File

@ -4,6 +4,7 @@ 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.Symbol;
import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.engine.values.ObjectValue.PlaceholderProto;
import me.topchetoeu.jscript.interop.InitType;
@ -35,7 +36,7 @@ import me.topchetoeu.jscript.interop.NativeInit;
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"));
var cause = Values.getMember(ctx, thisArg, Symbol.get("Symbol.cause"));
return toString(ctx,
thisArg == cause,
cause,

View File

@ -10,6 +10,7 @@ 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.ObjectValue;
import me.topchetoeu.jscript.engine.values.Values;
import me.topchetoeu.jscript.exceptions.EngineException;
import me.topchetoeu.jscript.interop.Native;
@ -17,9 +18,6 @@ import me.topchetoeu.jscript.interop.NativeGetter;
import me.topchetoeu.jscript.parsing.Parsing;
public class Internals {
private static final DataKey<HashMap<Integer, Thread>> THREADS = new DataKey<>();
private static final DataKey<Integer> I = new DataKey<>();
@Native public static Object require(Context ctx, String name) {
var env = ctx.environment();
var res = env.modules.getModule(ctx, env.moduleCwd, name);
@ -47,7 +45,7 @@ public class Internals {
}
}
@Native public static int setTimeout(Context ctx, FunctionValue func, int delay, Object ...args) {
@Native public static Thread setTimeout(Context ctx, FunctionValue func, int delay, Object ...args) {
var thread = new Thread(() -> {
var ms = (long)delay;
var ns = (int)((delay - ms) * 10000000);
@ -61,12 +59,9 @@ public class Internals {
});
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;
return thread;
}
@Native public static int setInterval(Context ctx, FunctionValue func, int delay, Object ...args) {
@Native public static Thread setInterval(Context ctx, FunctionValue func, int delay, Object ...args) {
var thread = new Thread(() -> {
var ms = (long)delay;
var ns = (int)((delay - ms) * 10000000);
@ -80,22 +75,15 @@ public class Internals {
ctx.engine.pushMsg(false, ctx.environment(), func, null, args);
}
});
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;
return thread;
}
@Native public static void clearTimeout(Context ctx, int i) {
var threads = ctx.environment().data.get(THREADS, new HashMap<>());
var thread = threads.remove(i);
if (thread != null) thread.interrupt();
@Native public static void clearTimeout(Context ctx, Thread t) {
t.interrupt();
}
@Native public static void clearInterval(Context ctx, int i) {
clearTimeout(ctx, i);
@Native public static void clearInterval(Context ctx, Thread t) {
t.interrupt();
}
@Native public static double parseInt(Context ctx, String val) {
@ -205,22 +193,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

@ -191,7 +191,7 @@ import me.topchetoeu.jscript.interop.NativeConstructor;
return thisArg;
}
@Native(thisArg = true) public static String toString(Context ctx, Object thisArg) {
var name = Values.getMember(ctx, thisArg, ctx.environment().symbol("Symbol.typeName"));
var name = Values.getMember(ctx, thisArg, Symbol.get("Symbol.typeName"));
if (name == null) name = "Unknown";
else name = Values.toString(ctx, name);

View File

@ -3,9 +3,11 @@ 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;
@ -80,7 +82,7 @@ import me.topchetoeu.jscript.interop.NativeGetter;
var val = passThis(ctx, "indexOf", thisArg);
if (term != null && term != Values.NULL && !(term instanceof String)) {
var search = Values.getMember(ctx, term, ctx.environment().symbol("Symbol.search"));
var search = Values.getMember(ctx, term, Symbol.get("Symbol.search"));
if (search instanceof FunctionValue) {
return (int)Values.toNumber(ctx, ((FunctionValue)search).call(ctx, term, val, false, start));
}
@ -92,7 +94,7 @@ import me.topchetoeu.jscript.interop.NativeGetter;
var val = passThis(ctx, "lastIndexOf", thisArg);
if (term != null && term != Values.NULL && !(term instanceof String)) {
var search = Values.getMember(ctx, term, ctx.environment().symbol("Symbol.search"));
var search = Values.getMember(ctx, term, Symbol.get("Symbol.search"));
if (search instanceof FunctionValue) {
return (int)Values.toNumber(ctx, ((FunctionValue)search).call(ctx, term, val, true, pos));
}
@ -109,7 +111,7 @@ import me.topchetoeu.jscript.interop.NativeGetter;
var val = passThis(ctx, "replace", thisArg);
if (term != null && term != Values.NULL && !(term instanceof String)) {
var replace = Values.getMember(ctx, term, ctx.environment().symbol("Symbol.replace"));
var replace = Values.getMember(ctx, term, Symbol.get("Symbol.replace"));
if (replace instanceof FunctionValue) {
return Values.toString(ctx, ((FunctionValue)replace).call(ctx, term, val, replacement));
}
@ -121,7 +123,7 @@ import me.topchetoeu.jscript.interop.NativeGetter;
var val = passThis(ctx, "replaceAll", thisArg);
if (term != null && term != Values.NULL && !(term instanceof String)) {
var replace = Values.getMember(ctx, term, ctx.environment().symbol("Symbol.replace"));
var replace = Values.getMember(ctx, term, Symbol.get("Symbol.replace"));
if (replace instanceof FunctionValue) {
return Values.toString(ctx, ((FunctionValue)replace).call(ctx, term, val, replacement));
}
@ -136,11 +138,11 @@ import me.topchetoeu.jscript.interop.NativeGetter;
FunctionValue match;
try {
var _match = Values.getMember(ctx, term, ctx.environment().symbol("Symbol.match"));
var _match = Values.getMember(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 (ctx.environment().has(Environment.REGEX_CONSTR)) {
var regex = Values.callNew(ctx, ctx.environment().get(Environment.REGEX_CONSTR), Values.toString(ctx, term), "");
_match = Values.getMember(ctx, regex, Symbol.get("Symbol.match"));
if (_match instanceof FunctionValue) match = (FunctionValue)_match;
else throw EngineException.ofError("Regular expressions don't support matching.");
}
@ -158,14 +160,14 @@ import me.topchetoeu.jscript.interop.NativeGetter;
FunctionValue match = null;
try {
var _match = Values.getMember(ctx, term, ctx.environment().symbol("Symbol.matchAll"));
var _match = Values.getMember(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 && ctx.environment().has(Environment.REGEX_CONSTR)) {
var regex = Values.callNew(ctx, ctx.environment().get(Environment.REGEX_CONSTR), Values.toString(ctx, term), "g");
var _match = Values.getMember(ctx, regex, Symbol.get("Symbol.matchAll"));
if (_match instanceof FunctionValue) match = (FunctionValue)_match;
else throw EngineException.ofError("Regular expressions don't support matching.");
}
@ -180,7 +182,7 @@ import me.topchetoeu.jscript.interop.NativeGetter;
if (lim != null) lim = Values.toNumber(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(ctx, term, Symbol.get("Symbol.replace"));
if (replace instanceof FunctionValue) {
var tmp = ((FunctionValue)replace).call(ctx, term, val, lim, sensible);

View File

@ -10,20 +10,19 @@ 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;
@Native("Symbol") public class SymbolLib {
private static final Map<String, Symbol> symbols = new HashMap<>();
@NativeGetter public static Symbol typeName(Context ctx) { return ctx.environment().symbol("Symbol.typeName"); }
@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"); }
@Native public static final Symbol typeName = Symbol.get("Symbol.typeName");
@Native public static final Symbol replace = Symbol.get("Symbol.replace");
@Native public static final Symbol match = Symbol.get("Symbol.match");
@Native public static final Symbol matchAll = Symbol.get("Symbol.matchAll");
@Native public static final Symbol split = Symbol.get("Symbol.split");
@Native public static final Symbol search = Symbol.get("Symbol.search");
@Native public static final Symbol iterator = Symbol.get("Symbol.iterator");
@Native public static final Symbol asyncIterator = Symbol.get("Symbol.asyncIterator");
@Native public static final Symbol cause = Symbol.get("Symbol.cause");
public final Symbol value;