diff --git a/.gitignore b/.gitignore index 9254aec..edf93b9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ * -!/src +!/src/ !/src/**/* !/doc @@ -21,4 +21,6 @@ !/gradle.properties !/gradle !/gradle/wrapper -!/gradle/wrapper/gradle-wrapper.properties \ No newline at end of file +!/gradle/wrapper/gradle-wrapper.properties + +!/ \ No newline at end of file diff --git a/src/compiler/main.ts b/src/compiler/main.ts new file mode 100644 index 0000000..a4063b3 --- /dev/null +++ b/src/compiler/main.ts @@ -0,0 +1,235 @@ +import { parse } from "acorn"; +import {} from "acorn"; +import { traverse } from "estraverse"; +import { Declaration, Identifier, Node, VariableDeclaration } from "estree"; + +enum VariableType { + Var, + Let, + Const, +} + +class Variable { + public constructor( + public readonly scope: Scope, + public readonly name: string, + public readonly type: VariableType, + public readonly readers = new Set(), + public readonly writers = new Set(), + ) { } + + public child(scope: Scope) { + return new Variable(scope, this.name, this.type, this.readers, this.writers); + } + + public get writable() { + return this.type !== VariableType.Const; + } +} + +class Scope { + private _locals = new Set(); + private _capturables = new Set(); + private _captures = new Set(); + + private _localNames = new Map(); + private _captureNames = new Map(); + private _parentToChild = new Map(); + private _childToParent = new Map(); + + public get locals() { + return Iterator.from(this._locals.values()); + } + + private _addCapture(v?: Variable) { + if (v != null && this._locals.delete(v)) { + this._capturables.add(v); + } + + return v; + } + + public capture(name: string) { + if (this._localNames.has(name)) return this._addCapture(this._localNames.get(name)); + if (this._captureNames.has(name)) return this._addCapture(this._captureNames.get(name)); + + const parent = this.parent?.capture(name); + if (parent == null) return undefined; + + const child = parent.child(this); + + this._parentToChild.set(parent, child); + this._childToParent.set(child, parent); + this._captures.add(child); + this._captureNames.set(child.name, child); + } + + public add(name: string, type: VariableType) { + let res = this.get(name, false); + if (res != null) return res; + + res = new Variable(this, name, type); + this._locals.add(res); + this._localNames.set(name, res); + return res; + } + public get(name: string, capture = true): Variable | undefined { + if (this._localNames.has(name)) return this._localNames.get(name); + if (this._captureNames.has(name)) return this._captureNames.get(name); + + if (capture) this.parent?.capture(name); + else return undefined; + } + + public constructor( + public readonly major: boolean, + public readonly node: Node, + public readonly parent?: Scope, + ) { } +} + +class BiMap implements Iterable<[A, B]> { + private _first = new Map(); + private _second = new Map(); + + public get(val: A): B; + public get(val: B): A; + public get(val: any) { + if (this._first.has(val)) return this._first.get(val); + if (this._second.has(val)) return this._second.get(val); + if (this._same.has(val)) return val; + return undefined; + } + + public set(a: A, b: B) { + this._first.set(a, b); + this._second.set(b, a); + + return this; + } + + public has(val: A | B) { + return this._first.has(val as any) || this._second.has(val as any); + } + + public delete(val: A | B) { + if (this._first.has(val as any)) { + const second = this._first.get(val as any)!; + this._first.delete(val as any); + this._second.delete(second); + + return true; + } + else if (this._second.has(val as any)) { + const first = this._second.get(val as any)!; + this._second.delete(val as any); + this._first.delete(first); + + return true; + } + else return false; + } + + public *[Symbol.iterator]() { + yield *this._first; + } + public *keys() { + yield *this._first.keys(); + } + public *values() { + yield *this._second.keys(); + } + public *entries() { + yield *this._first.entries(); + } +} + +class ResolutionContext { + public readonly variableRefs = new Map(); + public readonly declarations = new BiMap(); + + public resolveVariables() { + for (const el of this.variableRefs) { + } + } +} + +class NodeContext { + public node: Node = undefined!; + public path: Node[] = []; + + public atPath(i: number) { + return this.path[this.path.length - 1 - i]; + } + + public scope: Scope = undefined!; + public declType?: VariableType; +} + +interface Collector { + enter(ctx: NodeContext, root: ResolutionContext): void; + leave(ctx: NodeContext, root: ResolutionContext): void; +} + +function collect(node: Node, root: ResolutionContext, ...collectors: Collector[]) { + const nodeCtx = new NodeContext(); + const path: Node[] = []; + + traverse(node, { + enter(node) { + nodeCtx.node = node; + nodeCtx.path.push(node); + for (let i = 0; i < collectors.length; i++) { + collectors[i].enter(nodeCtx, root); + } + }, + leave(node) { + nodeCtx.node = node; + nodeCtx.path.pop(); + for (let i = 0; i < collectors.length; i++) { + collectors[i].leave(nodeCtx, root); + } + }, + }); +} + +function assertDefined(val: unknown): asserts val is {} { + if (val == null) throw new Error("Undefined or null expression"); +} + +const scopeCollector: Collector = { + enter(ctx, root) { + if (ctx.node.type === "BlockStatement") { + ctx.scope = new Scope(false, ctx.node, ctx.scope.parent); + } + else if (ctx.node.type === "VariableDeclaration") { + switch (ctx.node.kind) { + case "var": ctx.declType = VariableType.Var; break; + case "let": ctx.declType = VariableType.Let; break; + case "const": ctx.declType = VariableType.Const; break; + default: throw new Error(`Unknown variable type '${(ctx.node as any).kind}'`); + } + } + else if (ctx.node.type === "VariableDeclarator") { + ctx.scope. + } + else if (ctx.node.type === "ClassDeclaration") { + + } + else if (ctx.node.type === "Identifier") { + } + }, + leave(ctx, root) { + if (ctx.scope.node === ctx.node) { + assertDefined(ctx.scope.parent); + ctx.scope = ctx.scope.parent; + } + else if (ctx.node.type === "VariableDeclaration") { + ctx.declType = undefined; + } + }, +}; + + +const program = parse("const a = 10;", { ecmaVersion: "latest" }) as Node; +collect(program, domain, ...stage1); \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/common/Instruction.java b/src/main/java/me/topchetoeu/jscript/common/Instruction.java index fa441f5..f5cabb0 100644 --- a/src/main/java/me/topchetoeu/jscript/common/Instruction.java +++ b/src/main/java/me/topchetoeu/jscript/common/Instruction.java @@ -17,11 +17,10 @@ public class Instruction { TRY_END(0x06), CALL(0x10), - CALL_MEMBER(0x11), CALL_NEW(0x12), - JMP_IF(0x13), - JMP_IFN(0x14), - JMP(0x15), + JMP_IF(0x18), + JMP_IFN(0x19), + JMP(0x1A), PUSH_UNDEFINED(0x20), PUSH_NULL(0x21), @@ -38,11 +37,12 @@ public class Instruction { LOAD_GLOB(0x38), LOAD_INTRINSICS(0x39), - LOAD_ARGS(0x3A), - LOAD_REST_ARGS(0x3B), - LOAD_CALLEE(0x3C), - LOAD_THIS(0x3D), - LOAD_ERROR(0x3E), + LOAD_ARG(0x3A), + LOAD_ARGS_N(0x3B), + LOAD_ARGS(0x3C), + LOAD_CALLED(0x3D), + LOAD_THIS(0x3E), + LOAD_ERROR(0x3F), LOAD_VAR(0x40), LOAD_MEMBER(0x41), @@ -62,12 +62,7 @@ public class Instruction { GLOB_GET(0x60), GLOB_SET(0x61), - GLOB_DEF(0x62), - - // CAP_INIT(0x70), - VAR_INIT(0x71), - CAP_FREE(0x72), - VAR_FREE(0x73); + GLOB_DEF(0x62); private static final HashMap types = new HashMap<>(); public final int numeric; @@ -85,8 +80,19 @@ public class Instruction { } } public static enum BreakpointType { + /** + * A debugger should never stop at such instruction, unless a breakpoint has been set on it + */ NONE, + /** + * Debuggers should pause at instructions marked with this breakpoint type + * after any step command + */ STEP_OVER, + /** + * Debuggers should pause at instructions marked with this breakpoint type + * only after a step-in command + */ STEP_IN; public boolean shouldStepIn() { @@ -105,27 +111,6 @@ public class Instruction { if (i >= params.length || i < 0) return null; return (T)params[i]; } - @SuppressWarnings("unchecked") - public T get(int i, T defaultVal) { - if (i >= params.length || i < 0) return defaultVal; - return (T)params[i]; - } - public boolean match(Object ...args) { - if (args.length != params.length) return false; - for (int i = 0; i < args.length; i++) { - var a = params[i]; - var b = args[i]; - if (a == null || b == null) { - if (!(a == null && b == null)) return false; - } - if (!a.equals(b)) return false; - } - return true; - } - public boolean is(int i, Object arg) { - if (params.length <= i) return false; - return params[i].equals(arg); - } // public void write(DataOutputStream writer) throws IOException { // var rawType = type.numeric; @@ -250,55 +235,79 @@ public class Instruction { // } // } + /** + * Signals the start of a protected context + * @param catchStart The point to witch to jump if an error has been caught + * @param finallyStart The point to witch to jump after either the try or catch bodies have exited + * @param end The point to which to jump after exiting the whole protected context + */ public static Instruction tryStart(int catchStart, int finallyStart, int end) { return new Instruction(Type.TRY_START, catchStart, finallyStart, end); } + /** + * Signifies that the current protected section (try, catch or finally) has ended + */ public static Instruction tryEnd() { return new Instruction(Type.TRY_END); } + /** + * Throws the top stack value + */ public static Instruction throwInstr() { return new Instruction(Type.THROW); } + /** + * Converts the given exception to a runtime syntax error and throws it + */ public static Instruction throwSyntax(SyntaxException err) { return new Instruction(Type.THROW_SYNTAX, err.getMessage()); } + /** + * Converts the given exception to a runtime syntax error and throws it + */ public static Instruction throwSyntax(String err) { return new Instruction(Type.THROW_SYNTAX, err); } + /** + * Converts the given exception to a runtime syntax error and throws it + */ public static Instruction throwSyntax(Location loc, String err) { return new Instruction(Type.THROW_SYNTAX, new SyntaxException(loc, err).getMessage()); } + /** + * Performs a JS object property deletion. + * Operands: + * 1. Object to manipulate + * 2. Key to delete + */ public static Instruction delete() { return new Instruction(Type.DELETE); } + /** + * Returns the top stack value + */ public static Instruction ret() { return new Instruction(Type.RETURN); } + /** + * A special NOP instruction telling any debugger to pause + */ public static Instruction debug() { return new Instruction(Type.NOP, "debug"); } + /** + * Does nothing. May be used for metadata or implementation-specific instructions that don't alter the behavior + */ public static Instruction nop(Object ...params) { return new Instruction(Type.NOP, params); } - public static Instruction call(int argn, String name) { - return new Instruction(Type.CALL, argn, name); - } - public static Instruction call(int argn) { - return call(argn, ""); - } - public static Instruction callMember(int argn, String name) { - return new Instruction(Type.CALL_MEMBER, argn, name); - } - public static Instruction callMember(int argn) { - return new Instruction(Type.CALL_MEMBER, argn, ""); - } - public static Instruction callNew(int argn, String name) { - return new Instruction(Type.CALL_NEW, argn, name); + public static Instruction call(int argn, boolean hasSelf) { + return new Instruction(Type.CALL, argn, hasSelf); } public static Instruction callNew(int argn) { - return new Instruction(Type.CALL_NEW, argn, ""); + return new Instruction(Type.CALL_NEW, argn); } public static Instruction jmp(int offset) { @@ -321,7 +330,6 @@ public class Instruction { return i -> new Instruction(Type.JMP_IFN, pos.getAsInt() - i); } - public static Instruction pushUndefined() { return new Instruction(Type.PUSH_UNDEFINED); } @@ -355,14 +363,30 @@ public class Instruction { public static Instruction loadThis() { return new Instruction(Type.LOAD_THIS); } - public static Instruction loadArgs(boolean real) { - return new Instruction(Type.LOAD_ARGS, real); + /** + * Loads the given argument + * @param i The index of the argument to load. If -1, will get the index from the stack instead + */ + public static Instruction loadArg(int i) { + return new Instruction(Type.LOAD_ARG, i); } - public static Instruction loadRestArgs(int offset) { - return new Instruction(Type.LOAD_REST_ARGS, offset); + /** + * Pushes the amount of arguments to the stack + */ + public static Instruction loadArgsN() { + return new Instruction(Type.LOAD_ARGS_N); } - public static Instruction loadCallee() { - return new Instruction(Type.LOAD_CALLEE); + /** + * Pushes the arguments object to the stack + */ + public static Instruction loadArgs() { + return new Instruction(Type.LOAD_ARGS); + } + /** + * Loads a reference to the function being called + */ + public static Instruction loadCalled() { + return new Instruction(Type.LOAD_CALLED); } public static Instruction loadGlob() { return new Instruction(Type.LOAD_GLOB); @@ -386,16 +410,12 @@ public class Instruction { public static Instruction loadRegex(String pattern, String flags) { return new Instruction(Type.LOAD_REGEX, pattern, flags); } - public static Instruction loadFunc(int id, boolean callable, boolean constructible, boolean captureThis, String name, int[] captures) { - if (name == null) name = ""; - - var args = new Object[5 + captures.length]; + // TODO: make this capturing a concern of the compiler + public static Instruction loadFunc(int id, String name, int[] captures) { + var args = new Object[2 + captures.length]; args[0] = id; args[1] = name; - args[2] = callable; - args[3] = constructible; - args[4] = captureThis; - for (var i = 0; i < captures.length; i++) args[i + 5] = captures[i]; + for (var i = 0; i < captures.length; i++) args[i + 2] = captures[i]; return new Instruction(Type.LOAD_FUNC, args); } public static Instruction loadObj() { @@ -465,22 +485,6 @@ public class Instruction { return new Instruction(Type.OPERATION, op); } - public static Instruction capFree(int i) { - return new Instruction(Type.CAP_FREE, i); - } - public static Instruction varFree(int i) { - return new Instruction(Type.VAR_FREE, i); - } - public static Instruction varInit(int i, boolean force) { - return new Instruction(Type.VAR_INIT, i, force); - } - // public static Instruction stackAlloc(int start, int n) { - // return new Instruction(Type.STACK_ALLOC, start, start + n); - // } - // public static Instruction stackRealloc(int start, int n) { - // return new Instruction(Type.STACK_REALLOC, start, start + n); - // } - @Override public String toString() { var res = type.toString(); diff --git a/src/main/java/me/topchetoeu/jscript/common/environment/Environment.java b/src/main/java/me/topchetoeu/jscript/common/environment/Environment.java index 0a33cec..74be652 100644 --- a/src/main/java/me/topchetoeu/jscript/common/environment/Environment.java +++ b/src/main/java/me/topchetoeu/jscript/common/environment/Environment.java @@ -3,7 +3,6 @@ package me.topchetoeu.jscript.common.environment; import java.util.HashMap; import java.util.HashSet; import java.util.Map; -import java.util.Random; import java.util.Set; import java.util.function.Supplier; @@ -12,71 +11,13 @@ public class Environment { private final Map, Object> map = new HashMap<>(); private final Set> hidden = new HashSet<>(); - private final Map, Set> multi = new HashMap<>(); - private final Map, Set> multiHidden = new HashMap<>(); - - @SuppressWarnings("unchecked") - private Set getAll(MultiKey key, boolean forceClone) { - Set parent = null, child = null; - boolean cloned = false; - - if (this.parent != null && !hidden.contains(key)) { - parent = this.parent.getAll(key, false); - if (parent.size() == 0) parent = null; - else if (multiHidden.containsKey(key)) { - parent = new HashSet<>(parent); - parent.removeAll(multiHidden.get(key)); - cloned = true; - } - } - if (multi.containsKey(key)) { - child = (Set)multi.get(key); - if (child.size() == 0) child = null; - } - - if (!forceClone) { - if (parent == null && child == null) return new HashSet<>(); - if (parent == null && child != null) return child; - if (parent != null && child == null) return parent; - } - - if (!cloned) parent = new HashSet<>(); - parent.addAll(child); - return parent; - } - private T getMulti(MultiKey key) { - return key.of(getAll(key, false)); - } - private boolean hasMulti(MultiKey key) { - return getAll(key, false).size() > 0; - } - - @SuppressWarnings("all") - private Environment addMulti(MultiKey key, T value) { - if (!multi.containsKey(key)) { - if (hidden.contains(key)) { - multiHidden.put((MultiKey)key, (Set)parent.getAll(key, true)); - hidden.remove(key); - } - - multi.put((MultiKey)key, new HashSet<>()); - } - - multi.get(key).add(value); - return this; - } - @SuppressWarnings("unchecked") public T get(Key key) { - if (key instanceof MultiKey) return getMulti((MultiKey)key); - if (map.containsKey(key)) return (T)map.get(key); else if (!hidden.contains(key) && parent != null) return parent.get(key); else return null; } public boolean has(Key key) { - if (key instanceof MultiKey) return hasMulti((MultiKey)key); - if (map.containsKey(key)) return true; else if (!hidden.contains(key) && parent != null) return parent.has(key); else return false; @@ -90,15 +31,13 @@ public class Environment { if (has(key)) return get(key); else return defaultVal; } - public T get(Key key, Supplier defaultVal) { + public T getWith(Key key, Supplier defaultVal) { if (has(key)) return get(key); else return defaultVal.get(); } @SuppressWarnings("unchecked") public Environment add(Key key, T val) { - if (key instanceof MultiKey) return add(key, val); - map.put((Key)key, val); hidden.remove(key); return this; @@ -108,14 +47,6 @@ public class Environment { } @SuppressWarnings("all") public Environment addAll(Map, ?> map, boolean iterableAsMulti) { - for (var pair : map.entrySet()) { - if (iterableAsMulti && pair.getKey() instanceof MultiKey && pair.getValue() instanceof Iterable) { - for (var val : (Iterable)pair.getValue()) { - addMulti((MultiKey)pair.getKey(), val); - } - } - else add((Key)pair.getKey(), pair.getValue()); - } map.putAll((Map)map); hidden.removeAll(map.keySet()); return this; @@ -127,26 +58,9 @@ public class Environment { @SuppressWarnings("unchecked") public Environment remove(Key key) { map.remove(key); - multi.remove(key); - multiHidden.remove(key); hidden.add((Key)key); return this; } - @SuppressWarnings("all") - public Environment remove(MultiKey key, T val) { - if (multi.containsKey(key)) { - multi.get(key).remove(val); - multiHidden.get(key).add(val); - - if (multi.get(key).size() == 0) { - multi.remove(key); - multiHidden.remove(key); - hidden.add((Key)key); - } - } - - return this; - } public T init(Key key, T val) { if (!has(key)) this.add(key, val); @@ -180,8 +94,4 @@ public class Environment { public static Environment empty() { return new Environment(); } - - public static int nextId() { - return new Random().nextInt(); - } } diff --git a/src/main/java/me/topchetoeu/jscript/common/environment/Key.java b/src/main/java/me/topchetoeu/jscript/common/environment/Key.java index 1bb96b3..60470dd 100644 --- a/src/main/java/me/topchetoeu/jscript/common/environment/Key.java +++ b/src/main/java/me/topchetoeu/jscript/common/environment/Key.java @@ -1,7 +1,7 @@ package me.topchetoeu.jscript.common.environment; -public interface Key { +public final class Key { public static Key of() { - return new Key<>() { }; + return new Key<>(); } } diff --git a/src/main/java/me/topchetoeu/jscript/common/environment/MultiKey.java b/src/main/java/me/topchetoeu/jscript/common/environment/MultiKey.java deleted file mode 100644 index 79e8404..0000000 --- a/src/main/java/me/topchetoeu/jscript/common/environment/MultiKey.java +++ /dev/null @@ -1,7 +0,0 @@ -package me.topchetoeu.jscript.common.environment; - -import java.util.Set; - -public interface MultiKey extends Key { - public T of(Set values); -} diff --git a/src/main/java/me/topchetoeu/jscript/common/json/JSON.java b/src/main/java/me/topchetoeu/jscript/common/json/JSON.java index 56c34c9..e5dcce0 100644 --- a/src/main/java/me/topchetoeu/jscript/common/json/JSON.java +++ b/src/main/java/me/topchetoeu/jscript/common/json/JSON.java @@ -102,7 +102,7 @@ public class JSON { if (filename == null) filename = new Filename("jscript", "json"); var res = parseValue(new Source(null, filename, raw), 0); - if (res.isFailed()) throw new SyntaxException(null, "Invalid JSON given."); + if (res.isFailed()) throw new SyntaxException(null, "Invalid JSON given"); else if (res.isError()) throw new SyntaxException(null, res.error); else return JSONElement.of(res.result); } diff --git a/src/main/java/me/topchetoeu/jscript/common/json/JSONElement.java b/src/main/java/me/topchetoeu/jscript/common/json/JSONElement.java index a8d8490..258497e 100644 --- a/src/main/java/me/topchetoeu/jscript/common/json/JSONElement.java +++ b/src/main/java/me/topchetoeu/jscript/common/json/JSONElement.java @@ -35,7 +35,7 @@ public class JSONElement { else if (val instanceof Boolean) return bool((Boolean)val); else if (val instanceof Number) return number(((Number)val).doubleValue()); else if (val == null) return NULL; - else throw new IllegalArgumentException("val must be: String, Boolean, Number, JSONList or JSONMap."); + else throw new IllegalArgumentException("val must be: String, Boolean, Number, JSONList or JSONMap"); } public final Type type; @@ -49,23 +49,23 @@ public class JSONElement { public boolean isNull() { return type == Type.NULL; } public JSONMap map() { - if (!isMap()) throw new IllegalStateException("Element is not a map."); + if (!isMap()) throw new IllegalStateException("Element is not a map"); return (JSONMap)value; } public JSONList list() { - if (!isList()) throw new IllegalStateException("Element is not a map."); + if (!isList()) throw new IllegalStateException("Element is not a map"); return (JSONList)value; } public String string() { - if (!isString()) throw new IllegalStateException("Element is not a string."); + if (!isString()) throw new IllegalStateException("Element is not a string"); return (String)value; } public double number() { - if (!isNumber()) throw new IllegalStateException("Element is not a number."); + if (!isNumber()) throw new IllegalStateException("Element is not a number"); return (double)value; } public boolean bool() { - if (!isBoolean()) throw new IllegalStateException("Element is not a boolean."); + if (!isBoolean()) throw new IllegalStateException("Element is not a boolean"); return (boolean)value; } diff --git a/src/main/java/me/topchetoeu/jscript/common/json/JSONMap.java b/src/main/java/me/topchetoeu/jscript/common/json/JSONMap.java index 2e2f1f9..2906910 100644 --- a/src/main/java/me/topchetoeu/jscript/common/json/JSONMap.java +++ b/src/main/java/me/topchetoeu/jscript/common/json/JSONMap.java @@ -51,7 +51,7 @@ public class JSONMap implements Map { public JSONMap map(String path) { var el = get(path); - if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path)); + if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist", path)); return el.map(); } public JSONMap map(String path, JSONMap defaultVal) { @@ -63,7 +63,7 @@ public class JSONMap implements Map { public JSONList list(String path) { var el = get(path); - if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path)); + if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist", path)); return el.list(); } public JSONList list(String path, JSONList defaultVal) { @@ -75,7 +75,7 @@ public class JSONMap implements Map { public String string(String path) { var el = get(path); - if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path)); + if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist", path)); return el.string(); } public String string(String path, String defaultVal) { @@ -87,7 +87,7 @@ public class JSONMap implements Map { public double number(String path) { var el = get(path); - if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path)); + if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist", path)); return el.number(); } public double number(String path, double defaultVal) { @@ -99,7 +99,7 @@ public class JSONMap implements Map { public boolean bool(String path) { var el = get(path); - if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist.", path)); + if (el == null) throw new RuntimeException(String.format("'%s' doesn't exist", path)); return el.bool(); } public boolean bool(String path, boolean defaultVal) { diff --git a/src/main/java/me/topchetoeu/jscript/common/mapping/FunctionMap.java b/src/main/java/me/topchetoeu/jscript/common/mapping/FunctionMap.java index 5b87d81..07f301c 100644 --- a/src/main/java/me/topchetoeu/jscript/common/mapping/FunctionMap.java +++ b/src/main/java/me/topchetoeu/jscript/common/mapping/FunctionMap.java @@ -15,7 +15,6 @@ import java.util.stream.Collectors; import me.topchetoeu.jscript.common.Instruction.BreakpointType; import me.topchetoeu.jscript.common.parsing.Filename; import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.compilation.scope.Scope; public class FunctionMap { public static class FunctionMapBuilder { @@ -54,9 +53,6 @@ public class FunctionMap { public FunctionMap build(String[] localNames, String[] captureNames) { return new FunctionMap(sourceMap, breakpoints, localNames, captureNames); } - public FunctionMap build(Scope scope) { - return new FunctionMap(sourceMap, breakpoints, new String[0], new String[0]); - } public FunctionMap build() { return new FunctionMap(sourceMap, breakpoints, new String[0], new String[0]); } diff --git a/src/main/java/me/topchetoeu/jscript/common/parsing/ParseRes.java b/src/main/java/me/topchetoeu/jscript/common/parsing/ParseRes.java index 3bf2bb8..11502e8 100644 --- a/src/main/java/me/topchetoeu/jscript/common/parsing/ParseRes.java +++ b/src/main/java/me/topchetoeu/jscript/common/parsing/ParseRes.java @@ -34,10 +34,15 @@ public class ParseRes { return new ParseRes<>(state, null, null, result, this.n + n); } public ParseRes chainError() { - if (isSuccess()) throw new RuntimeException("Can't transform a ParseRes that hasn't failed."); + if (isSuccess()) throw new RuntimeException("Can't transform a ParseRes that hasn't failed"); return new ParseRes<>(state, errorLocation, error, null, 0); } @SuppressWarnings("unchecked") + public ParseRes chainError(ParseRes other) { + if (!this.isError()) return other.chainError(); + return (ParseRes) this; + } + @SuppressWarnings("unchecked") public ParseRes chainError(Location loc, String error) { if (!this.isError()) return new ParseRes<>(State.ERROR, loc, error, null, 0); return (ParseRes) this; diff --git a/src/main/java/me/topchetoeu/jscript/common/parsing/Parsing.java b/src/main/java/me/topchetoeu/jscript/common/parsing/Parsing.java index fb5acc6..1af3a10 100644 --- a/src/main/java/me/topchetoeu/jscript/common/parsing/Parsing.java +++ b/src/main/java/me/topchetoeu/jscript/common/parsing/Parsing.java @@ -86,10 +86,10 @@ public class Parsing { var newC = 0; for (var j = 0; j < 2; j++) { - if (i + n >= src.size()) return ParseRes.error(src.loc(i), "Invalid hexadecimal escape sequence."); + if (i + n >= src.size()) return ParseRes.error(src.loc(i), "Invalid hexadecimal escape sequence"); int val = fromHex(src.at(i + n)); - if (val == -1) throw new SyntaxException(src.loc(i + n), "Invalid hexadecimal escape sequence."); + if (val == -1) throw new SyntaxException(src.loc(i + n), "Invalid hexadecimal escape sequence"); n++; newC = (newC << 4) | val; @@ -113,6 +113,7 @@ public class Parsing { return ParseRes.res((char)newC, n); } else if (c == '\n') return ParseRes.res(null, n); + else n--; } return ParseRes.res(src.at(i + n), n + 1); diff --git a/src/main/java/me/topchetoeu/jscript/compilation/ClassNode.java b/src/main/java/me/topchetoeu/jscript/compilation/ClassNode.java deleted file mode 100644 index 9b2ba98..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/ClassNode.java +++ /dev/null @@ -1,158 +0,0 @@ -package me.topchetoeu.jscript.compilation; - -import java.util.ArrayList; -import java.util.LinkedList; -import java.util.List; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.members.FieldMemberNode; -import me.topchetoeu.jscript.compilation.members.MethodMemberNode; -import me.topchetoeu.jscript.compilation.members.PropertyMemberNode; - -public abstract class ClassNode extends FunctionNode { - public static final class ClassBody { - public final List staticMembers; - public final List protoFields; - public final List protoMembers; - public final Parameters constructorParameters; - public final CompoundNode constructorBody; - - public ClassBody( - List staticMembers, List protoFields, List protoMembers, - Parameters constructorParameters, CompoundNode constructorBody - ) { - this.staticMembers = staticMembers; - this.protoFields = protoFields; - this.protoMembers = protoMembers; - this.constructorParameters = constructorParameters; - this.constructorBody = constructorBody; - } - } - - public final ClassBody body; - public final String name; - - @Override public String name() { return name; } - - public void compileStatic(CompileResult target) { - for (var member : body.staticMembers) member.compile(target, true); - } - public void compilePrototype(CompileResult target) { - if (body.protoMembers.size() > 0) { - target.add(Instruction.dup()); - target.add(Instruction.loadMember("prototype")); - - for (var i = 0; i < body.protoMembers.size() - 1; i++) { - body.protoMembers.get(i).compile(target, true); - } - - body.protoMembers.get(body.protoMembers.size() - 1).compile(target, false); - } - } - - @Override protected void compilePreBody(CompileResult target) { - for (var member : body.protoFields) { - target.add(Instruction.loadThis()); - member.compile(target, false); - } - } - - @Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) { - var id = target.addChild(compileBody(target, name, null)); - target.add(_i -> Instruction.loadFunc(id, false, true, false, name, captures(id, target))); - compileStatic(target); - compilePrototype(target); - } - - public ClassNode(Location loc, Location end, String name, ClassBody body) { - super(loc, end, body.constructorParameters, body.constructorBody); - - this.name = name; - this.body = body; - } - - public static ParseRes parseMember(Source src, int i) { - return ParseRes.first(src, i, - PropertyMemberNode::parse, - FieldMemberNode::parseClass, - MethodMemberNode::parse - ); - } - - public static ParseRes parseBody(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - if (!src.is(i + n, "{")) return ParseRes.failed(); - n++; - n += Parsing.skipEmpty(src, i + n); - - var fields = new LinkedList(); - var members = new LinkedList(); - var statics = new LinkedList(); - - var params = new Parameters(new ArrayList<>()); - var body = new CompoundNode(loc, false); - var hasConstr = false; - - if (src.is(i + n, "}")) { - n++; - return ParseRes.res(new ClassBody(statics, fields, members, params, body), n); - } - - while (true) { - ParseRes prop = parseMember(src, i + n); - - if (prop.isSuccess()) { - n += prop.n; - - if (prop.result instanceof FieldMemberNode field) fields.add(field); - else if (prop.result instanceof MethodMemberNode method && method.name().equals("constructor")) { - if (hasConstr) return ParseRes.error(loc, "A class may only have one constructor"); - - params = method.params; - body = method.body; - hasConstr = true; - } - else members.add(prop.result); - } - else if (Parsing.isIdentifier(src, i + n, "static")) { - n += 6; - - var staticProp = parseMember(src, i + n); - if (!staticProp.isSuccess()) { - if (prop.isError()) return prop.chainError(); - else return staticProp.chainError(src.loc(i + n), "Expected a member after 'static' keyword"); - } - n += staticProp.n; - - statics.add(staticProp.result); - } - else { - var end = JavaScript.parseStatementEnd(src, i + n); - if (end.isSuccess()) n += end.n; - else return ParseRes.error(src.loc(i + n), "Expected a member, end of statement or a closing colon"); - } - - n += Parsing.skipEmpty(src, i + n); - - if (src.is(i + n, "}")) { - n++; - break; - } - else ParseRes.error(src.loc(i + n), "Expected a comma or a closing brace."); - } - - return ParseRes.res(new ClassBody(statics, fields, members, params, body), n); - } - - // public FunctionStatementNode(Location loc, Location end, Parameters params, CompoundNode body, String name) { - // super(loc, end, params, body); - // this.name = name; - // } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/ClassStatementNode.java b/src/main/java/me/topchetoeu/jscript/compilation/ClassStatementNode.java deleted file mode 100644 index eaeb32c..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/ClassStatementNode.java +++ /dev/null @@ -1,40 +0,0 @@ -package me.topchetoeu.jscript.compilation; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType; - -public class ClassStatementNode extends ClassNode { - @Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) { - super.compile(target, pollute, name, bp); - var i = target.scope.define(DeclarationType.LET, name(), loc()); - target.add(_i -> i.index().toInit()); - if (pollute) target.add(Instruction.pushUndefined()); - } - - public ClassStatementNode(Location loc, Location end, String name, ClassBody body) { - super(loc, end, name, body); - } - - public static ParseRes parse(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - if (!Parsing.isIdentifier(src, i + n, "class")) return ParseRes.failed(); - n += 5; - - var name = Parsing.parseIdentifier(src, i + n); - if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected a class name"); - n += name.n; - - var body = parseBody(src, i + n); - if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a class body"); - n += body.n; - - return ParseRes.res(new ClassStatementNode(loc, src.loc(i + n), name.result, body.result), n); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/CompileResult.java b/src/main/java/me/topchetoeu/jscript/compilation/CompileResult.java index 04711d5..b70178d 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/CompileResult.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/CompileResult.java @@ -1,40 +1,33 @@ package me.topchetoeu.jscript.compilation; import java.util.List; +import java.util.Map; + import java.util.ArrayList; +import java.util.HashMap; import java.util.LinkedList; -import java.util.function.Consumer; -import java.util.function.IntFunction; import me.topchetoeu.jscript.common.FunctionBody; import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Instruction.BreakpointType; import me.topchetoeu.jscript.common.environment.Environment; +import me.topchetoeu.jscript.common.environment.Key; import me.topchetoeu.jscript.common.mapping.FunctionMap; import me.topchetoeu.jscript.common.mapping.FunctionMap.FunctionMapBuilder; import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.compilation.scope.Scope; +import me.topchetoeu.jscript.compilation.scope.FunctionScope; public final class CompileResult { - public static final class ChildData { - public final int id; - public final CompileResult result; + public static final Key DEBUG_LOG = new Key<>(); - public ChildData(int id, CompileResult result) { - this.result = result; - this.id = id; - } - } - - public final List> instructions; + public final List instructions; public final List children; + public final Map childrenMap = new HashMap<>(); + public final Map childrenIndices = new HashMap<>(); public final FunctionMapBuilder map; public final Environment env; public int length; - public Runnable buildTask = () -> { - throw new IllegalStateException("Compile result is not ready to be built"); - }; - public final Scope scope; + public final FunctionScope scope; public int temp() { instructions.add(null); @@ -42,21 +35,14 @@ public final class CompileResult { } public CompileResult add(Instruction instr) { - instructions.add(i -> instr); - return this; - } - public CompileResult add(IntFunction instr) { instructions.add(instr); return this; } public CompileResult set(int i, Instruction instr) { - instructions.set(i, _i -> instr); - return this; - } - public CompileResult set(int i, IntFunctioninstr) { instructions.set(i, instr); return this; } + public int size() { return instructions.size(); } public void setDebug(Location loc, BreakpointType type) { @@ -78,62 +64,34 @@ public final class CompileResult { setLocationAndDebug(instructions.size() - 1, loc, type); } - public void beginScope() { - // for (var cap : scope.capturables()) { - // add(_i -> Instruction.capInit(cap.index().index)); - // } - } - public void reallocScope() { - for (var cap : scope.capturables()) { - add(_i -> cap.index().toGet()); - add(_i -> Instruction.capFree(cap.index().index)); - add(_i -> cap.index().toInit()); - } - - scope.end(); - } - public void endScope() { - for (var cap : scope.capturables()) { - add(_i -> Instruction.capFree(cap.index().index)); - } - for (var var : scope.locals()) { - add(_i -> Instruction.varFree(var.index().index)); - } - - scope.end(); - } - - public int addChild(CompileResult res) { + public CompileResult addChild(FunctionNode node, CompileResult res) { this.children.add(res); - return this.children.size() - 1; - } - - public Instruction[] instructions() { - var res = new Instruction[instructions.size()]; - var i = 0; - for (var suppl : instructions) { - res[i] = suppl.apply(i); - i++; - } + this.childrenMap.put(node, res); + this.childrenIndices.put(node, this.children.size() - 1); return res; } + public Instruction[] instructions() { + return instructions.toArray(new Instruction[0]); + } + public FunctionMap map() { - return map.build(scope); + return map.build(scope.localNames(), scope.captureNames()); } public FunctionBody body() { var builtChildren = new FunctionBody[children.size()]; - for (var i = 0; i < children.size(); i++) builtChildren[i] = children.get(i).body(); - var instrRes = new Instruction[instructions.size()]; - var i = 0; + var instrRes = instructions(); - for (var suppl : instructions) { - instrRes[i] = suppl.apply(i); - // System.out.println(instrRes[i]); - i++; - } + if (env.has(DEBUG_LOG)) { + System.out.println("================= BODY ================="); + System.out.println("LOCALS: " + scope.localsCount()); + System.out.println("CAPTURABLES: " + scope.capturablesCount()); + System.out.println("CAPTURES: " + scope.capturesCount()); + + for (var instr : instrRes) System.out.println(instr); + } return new FunctionBody( scope.localsCount(), scope.capturablesCount(), scope.capturesCount(), @@ -142,23 +100,36 @@ public final class CompileResult { } public CompileResult subtarget() { - return new CompileResult(new Scope(scope), this); + return new CompileResult(env, new FunctionScope(scope), this); } - public CompileResult(Environment env, Scope scope, int length, Consumer task) { + public CompileResult setEnvironment(Environment env) { + return new CompileResult(env, scope, this); + } + /** + * Returns a compile result with a child of the environment that relates to the given key. + * In essence, this is used to create a compile result which is back at the root environment of the compilation + */ + public CompileResult rootEnvironment(Key env) { + return new CompileResult(this.env.get(env).child(), scope, this); + } + public CompileResult subEnvironment() { + return new CompileResult(env.child(), scope, this); + } + + public CompileResult(Environment env, FunctionScope scope, int length) { this.scope = scope; this.instructions = new ArrayList<>(); this.children = new LinkedList<>(); this.map = FunctionMap.builder(); this.env = env; this.length = length; - this.buildTask = () -> task.accept(this); } - private CompileResult(Scope scope, CompileResult parent) { + private CompileResult(Environment env, FunctionScope scope, CompileResult parent) { this.scope = scope; this.instructions = parent.instructions; this.children = parent.children; this.map = parent.map; - this.env = parent.env; + this.env = env; } } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/CompoundNode.java b/src/main/java/me/topchetoeu/jscript/compilation/CompoundNode.java index b99a75f..0754795 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/CompoundNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/CompoundNode.java @@ -14,22 +14,21 @@ import me.topchetoeu.jscript.common.parsing.Source; public class CompoundNode extends Node { public final Node[] statements; - public boolean hasScope; public Location end; @Override public void resolve(CompileResult target) { for (var stm : statements) stm.resolve(target); } + @Override public void compileFunctions(CompileResult target) { + for (var stm : statements) stm.compileFunctions(target); + } - public void compile(CompileResult target, boolean pollute, boolean singleEntry, BreakpointType type) { + public void compile(CompileResult target, boolean pollute, BreakpointType type) { List statements = new ArrayList(); - var subtarget = hasScope ? target.subtarget() : target; - if (hasScope) subtarget.beginScope(); - for (var stm : this.statements) { if (stm instanceof FunctionStatementNode func) { - func.compile(subtarget, false); + func.compile(target, false); } else statements.add(stm); } @@ -39,41 +38,25 @@ public class CompoundNode extends Node { for (var i = 0; i < statements.size(); i++) { var stm = statements.get(i); - if (i != statements.size() - 1) stm.compile(subtarget, false, BreakpointType.STEP_OVER); - else stm.compile(subtarget, polluted = pollute, BreakpointType.STEP_OVER); + if (i != statements.size() - 1) stm.compile(target, false, BreakpointType.STEP_OVER); + else stm.compile(target, polluted = pollute, BreakpointType.STEP_OVER); } - if (hasScope) subtarget.endScope(); - if (!polluted && pollute) { target.add(Instruction.pushUndefined()); } } - @Override public void compile(CompileResult target, boolean pollute, BreakpointType type) { - compile(target, pollute, true, type); - } - public CompoundNode setEnd(Location loc) { this.end = loc; return this; } - public CompoundNode(Location loc, boolean hasScope, Node ...statements) { + public CompoundNode(Location loc, Node ...statements) { super(loc); - this.hasScope = hasScope; this.statements = statements; } - public static void compileMultiEntry(Node node, CompileResult target, boolean pollute, BreakpointType type) { - if (node instanceof CompoundNode comp) { - comp.compile(target, pollute, false, type); - } - else { - node.compile(target, pollute, type); - } - } - public static ParseRes parseComma(Source src, int i, Node prev, int precedence) { if (precedence > 1) return ParseRes.failed(); @@ -92,9 +75,9 @@ public class CompoundNode extends Node { children.addAll(Arrays.asList(comp.statements)); children.add(curr.result); - return ParseRes.res(new CompoundNode(loc, comp.hasScope, children.toArray(new Node[0])), n); + return ParseRes.res(new CompoundNode(loc, children.toArray(new Node[0])), n); } - else return ParseRes.res(new CompoundNode(loc, false, prev, curr.result), n); + else return ParseRes.res(new CompoundNode(loc, prev, curr.result), n); } public static ParseRes parse(Source src, int i) { var n = Parsing.skipEmpty(src, i); @@ -124,6 +107,6 @@ public class CompoundNode extends Node { statements.add(res.result); } - return ParseRes.res(new CompoundNode(loc, true, statements.toArray(new Node[0])).setEnd(src.loc(i + n - 1)), n); + return ParseRes.res(new CompoundNode(loc, statements.toArray(new Node[0])).setEnd(src.loc(i + n - 1)), n); } } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/FunctionArrowNode.java b/src/main/java/me/topchetoeu/jscript/compilation/FunctionArrowNode.java deleted file mode 100644 index 83cf64e..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/FunctionArrowNode.java +++ /dev/null @@ -1,73 +0,0 @@ -package me.topchetoeu.jscript.compilation; - -import java.util.Arrays; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.control.ReturnNode; -import me.topchetoeu.jscript.compilation.patterns.Pattern; - -public class FunctionArrowNode extends FunctionNode { - @Override public String name() { return null; } - - @Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) { - var id = target.addChild(compileBody(target, name, null)); - target.add(_i -> Instruction.loadFunc(id, true, false, true, null, captures(id, target))); - } - - public FunctionArrowNode(Location loc, Location end, Parameters params, Node body) { - super(loc, end, params, expToBody(body)); - } - - private static final CompoundNode expToBody(Node node) { - if (node instanceof CompoundNode res) return res; - else return new CompoundNode(node.loc(), false, new ReturnNode(node.loc(), node)); - } - - public static ParseRes parse(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - Parameters params; - - if (src.is(i + n, "(")) { - var paramsRes = Parameters.parseParameters(src, i + n); - if (!paramsRes.isSuccess()) return paramsRes.chainError(); - n += paramsRes.n; - n += Parsing.skipEmpty(src, i + n); - - params = paramsRes.result; - } - else { - var singleParam = Pattern.parse(src, i + n, true); - if (!singleParam.isSuccess()) return ParseRes.failed(); - n += singleParam.n; - n += Parsing.skipEmpty(src, i + n); - - params = new Parameters(Arrays.asList(singleParam.result)); - } - - if (!src.is(i + n, "=>")) return ParseRes.failed(); - n += 2; - n += Parsing.skipEmpty(src, i + n); - - if (src.is(i + n, "{")) { - var body = CompoundNode.parse(src, i + n); - if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a compount statement after '=>'"); - n += body.n; - - return ParseRes.res(new FunctionArrowNode(loc, src.loc(i + n - 1), params, body.result), n); - } - else { - var body = JavaScript.parseExpression(src, i + n, 2); - if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a compount statement after '=>'"); - n += body.n; - - return ParseRes.res(new FunctionArrowNode(loc, src.loc(i + n - 1), params, body.result), n); - } - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java b/src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java index 270ea0d..107d0d5 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java @@ -1,5 +1,7 @@ package me.topchetoeu.jscript.compilation; +import java.util.List; + import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Instruction.BreakpointType; import me.topchetoeu.jscript.common.environment.Environment; @@ -7,68 +9,56 @@ import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.common.parsing.ParseRes; import me.topchetoeu.jscript.common.parsing.Parsing; import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType; import me.topchetoeu.jscript.compilation.scope.FunctionScope; -import me.topchetoeu.jscript.compilation.scope.Variable; +import me.topchetoeu.jscript.compilation.values.VariableNode; public abstract class FunctionNode extends Node { public final CompoundNode body; - public final Parameters params; + public final List params; public final Location end; public abstract String name(); - - protected final int[] captures(int id, CompileResult target) { - return ((FunctionScope)target.children.get(id).scope).getCaptureIndices(); + public final String name(String fallback) { + return this.name() != null ? this.name() : fallback; + } + protected final int[] captures(CompileResult target) { + return target.childrenMap.get(this).scope.getCaptureIndices(); } - protected void compilePreBody(CompileResult target) { } - - public final CompileResult compileBody(Environment env, FunctionScope scope, boolean lastReturn, String _name, String selfName) { - var name = this.name() != null ? this.name() : _name; - - env = env.child() - .remove(LabelContext.BREAK_CTX) - .remove(LabelContext.CONTINUE_CTX); - - return new CompileResult(env, scope, params.params.size(), target -> { - compilePreBody(target); - - if (params.params.size() > 0) { - target.add(Instruction.loadArgs(true)); - if (params.params.size() > 1) target.add(Instruction.dup(params.params.size() - 1, 0)); - var i = 0; - - for (var param : params.params) { - target.add(Instruction.loadMember(i++)); - param.destruct(target, DeclarationType.VAR, true); - } - } - - if (params.rest != null) { - target.add(Instruction.loadRestArgs(params.params.size())); - params.rest.destruct(target, DeclarationType.VAR, true); - } - - if (selfName != null && !scope.has(name, false)) { - var i = scope.defineSpecial(new Variable(selfName, true), end); - - target.add(Instruction.loadCallee()); - target.add(_i -> i.index().toInit()); - } - - body.resolve(target); - body.compile(target, lastReturn, BreakpointType.NONE); - - scope.end(); - - for (var child : target.children) child.buildTask.run(); - - scope.finish(); - }); + protected final Environment rootEnv(Environment env) { + return env.get(JavaScript.COMPILE_ROOT); } - public final CompileResult compileBody(CompileResult parent, String name, String selfName) { - return compileBody(parent.env, new FunctionScope(parent.scope), false, name, selfName); + + @Override public void resolve(CompileResult target) { } + + public final CompileResult compileBody(Environment env, FunctionScope scope, boolean lastReturn, String selfName) { + var target = new CompileResult(env, scope, params.size()); + var i = 0; + + body.resolve(target); + + for (var param : params) scope.define(param.name); + + // if (selfName != null && !scope.has(selfName, false)) { + // var i = scope.defineSpecial(new Variable(selfName, true), end); + + // t.add(Instruction.loadCalled()); + // t.add(_i -> i.index().toInit()); + // } + + body.compileFunctions(target); + + for (var param : params) { + target.add(Instruction.loadArg(i++)).setLocation(param.loc()); + target.add(scope.define(param.name).index().toSet(false)).setLocation(param.loc()); + } + + body.compile(target, lastReturn, BreakpointType.NONE); + + return target; + } + public final CompileResult compileBody(CompileResult parent, String selfName) { + return compileBody(rootEnv(parent.env).child(), new FunctionScope(parent.scope), false, selfName); } public abstract void compile(CompileResult target, boolean pollute, String name, BreakpointType bp); @@ -82,13 +72,12 @@ public abstract class FunctionNode extends Node { compile(target, pollute, (String)null, BreakpointType.NONE); } - public FunctionNode(Location loc, Location end, Parameters params, CompoundNode body) { + public FunctionNode(Location loc, Location end, List params, CompoundNode body) { super(loc); this.end = end; this.params = params; this.body = body; - this.body.hasScope = false; } public static void compileWithName(Node stm, CompileResult target, boolean pollute, String name) { @@ -112,7 +101,7 @@ public abstract class FunctionNode extends Node { n += name.n; n += Parsing.skipEmpty(src, i + n); - var params = Parameters.parseParameters(src, i + n); + var params = JavaScript.parseParameters(src, i + n); if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected a parameter list"); n += params.n; diff --git a/src/main/java/me/topchetoeu/jscript/compilation/FunctionStatementNode.java b/src/main/java/me/topchetoeu/jscript/compilation/FunctionStatementNode.java index 6de7691..97a4e09 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/FunctionStatementNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/FunctionStatementNode.java @@ -1,5 +1,7 @@ package me.topchetoeu.jscript.compilation; +import java.util.List; + import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Instruction.BreakpointType; import me.topchetoeu.jscript.common.parsing.Location; @@ -12,17 +14,19 @@ public class FunctionStatementNode extends FunctionNode { @Override public String name() { return name; } @Override public void resolve(CompileResult target) { - target.scope.define(new Variable(name, false), end); + target.scope.define(new Variable(name, false)); } + @Override public void compileFunctions(CompileResult target) { + target.addChild(this, compileBody(target, name())); + } @Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) { - var id = target.addChild(compileBody(target, name, null)); - target.add(_i -> Instruction.loadFunc(id, true, true, false, name, captures(id, target))); - target.add(VariableNode.toInit(target, end, this.name)); + target.add(Instruction.loadFunc(target.childrenIndices.get(this), name(name), captures(target))).setLocation(loc()); + target.add(VariableNode.toSet(target, end, this.name, false, true)).setLocation(loc()); if (pollute) target.add(Instruction.pushUndefined()); } - public FunctionStatementNode(Location loc, Location end, Parameters params, CompoundNode body, String name) { + public FunctionStatementNode(Location loc, Location end, List params, CompoundNode body, String name) { super(loc, end, params, body); this.name = name; } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/FunctionValueNode.java b/src/main/java/me/topchetoeu/jscript/compilation/FunctionValueNode.java index db22d72..05f2db3 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/FunctionValueNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/FunctionValueNode.java @@ -1,20 +1,27 @@ package me.topchetoeu.jscript.compilation; +import java.util.List; + import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Instruction.BreakpointType; import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.compilation.values.VariableNode; public class FunctionValueNode extends FunctionNode { public final String name; @Override public String name() { return name; } + @Override public void compileFunctions(CompileResult target) { + target.addChild(this, compileBody(target, name())); + } + @Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) { - var id = target.addChild(compileBody(target, name, null)); - target.add(_i -> Instruction.loadFunc(id, true, true, false, name, captures(id, target))); + target.add(Instruction.loadFunc(target.childrenIndices.get(this), name(name), captures(target))).setLocation(loc()); + if (!pollute) target.add(Instruction.discard()); } - public FunctionValueNode(Location loc, Location end, Parameters params, CompoundNode body, String name) { + public FunctionValueNode(Location loc, Location end, List params, CompoundNode body, String name) { super(loc, end, params, body); this.name = name; } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/JavaScript.java b/src/main/java/me/topchetoeu/jscript/compilation/JavaScript.java index cc0441d..829d1f0 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/JavaScript.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/JavaScript.java @@ -3,10 +3,12 @@ package me.topchetoeu.jscript.compilation; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; +import java.util.List; import java.util.Set; import me.topchetoeu.jscript.common.SyntaxException; import me.topchetoeu.jscript.common.environment.Environment; +import me.topchetoeu.jscript.common.environment.Key; import me.topchetoeu.jscript.common.parsing.Filename; import me.topchetoeu.jscript.common.parsing.ParseRes; import me.topchetoeu.jscript.common.parsing.Parsing; @@ -18,7 +20,6 @@ import me.topchetoeu.jscript.compilation.control.DeleteNode; import me.topchetoeu.jscript.compilation.control.DoWhileNode; import me.topchetoeu.jscript.compilation.control.ForInNode; import me.topchetoeu.jscript.compilation.control.ForNode; -import me.topchetoeu.jscript.compilation.control.ForOfNode; import me.topchetoeu.jscript.compilation.control.IfNode; import me.topchetoeu.jscript.compilation.control.ReturnNode; import me.topchetoeu.jscript.compilation.control.SwitchNode; @@ -28,7 +29,7 @@ import me.topchetoeu.jscript.compilation.control.WhileNode; import me.topchetoeu.jscript.compilation.scope.FunctionScope; import me.topchetoeu.jscript.compilation.values.ArgumentsNode; import me.topchetoeu.jscript.compilation.values.ArrayNode; -import me.topchetoeu.jscript.compilation.values.ClassValueNode; +import me.topchetoeu.jscript.compilation.values.GlobalThisNode; import me.topchetoeu.jscript.compilation.values.ObjectNode; import me.topchetoeu.jscript.compilation.values.RegexNode; import me.topchetoeu.jscript.compilation.values.ThisNode; @@ -47,24 +48,18 @@ import me.topchetoeu.jscript.compilation.values.operations.TypeofNode; public final class JavaScript { public static enum DeclarationType { - VAR(false, false), - CONST(true, true), - LET(true, false); - - public final boolean strict, readonly; - - private DeclarationType(boolean strict, boolean readonly) { - this.strict = strict; - this.readonly = readonly; - } + @Deprecated + VAR; } + public static final Key COMPILE_ROOT = Key.of(); + static final Set reserved = new HashSet<>(Arrays.asList( "true", "false", "void", "null", "this", "if", "else", "try", "catch", "finally", "for", "do", "while", "switch", "case", "default", "new", "function", "var", "return", "throw", "typeof", "delete", "break", "continue", "debugger", "implements", "interface", "package", "private", - "protected", "public", "static", "arguments", "class" + "protected", "public", "static", "arguments", "class", "extends" )); public static ParseRes parseParens(Source src, int i) { @@ -89,7 +84,6 @@ public final class JavaScript { return ParseRes.first(src, i, (s, j) -> statement ? ParseRes.failed() : ObjectNode.parse(s, j), (s, j) -> statement ? ParseRes.failed() : FunctionNode.parseFunction(s, j, false), - (s, j) -> statement ? ParseRes.failed() : ClassValueNode.parse(s, j), JavaScript::parseLiteral, StringNode::parse, RegexNode::parse, @@ -98,7 +92,6 @@ public final class JavaScript { ChangeNode::parsePrefixIncrease, OperationNode::parsePrefix, ArrayNode::parse, - (s, j) -> statement ? ParseRes.failed() : FunctionArrowNode.parse(s, j), JavaScript::parseParens, CallNode::parseNew, TypeofNode::parse, @@ -121,11 +114,12 @@ public final class JavaScript { if (id.result.equals("null")) return ParseRes.res(new NullNode(loc), n); if (id.result.equals("this")) return ParseRes.res(new ThisNode(loc), n); if (id.result.equals("arguments")) return ParseRes.res(new ArgumentsNode(loc), n); + if (id.result.equals("globalThis")) return ParseRes.res(new GlobalThisNode(loc), n); return ParseRes.failed(); } - public static ParseRes parseExpression(Source src, int i, int precedence, boolean statement) { + public static ParseRes parseExpression(Source src, int i, int precedence, boolean statement) { var n = Parsing.skipEmpty(src, i); Node prev = null; @@ -169,11 +163,11 @@ public final class JavaScript { else return ParseRes.res(prev, n); } - public static ParseRes parseExpression(Source src, int i, int precedence) { + public static ParseRes parseExpression(Source src, int i, int precedence) { return parseExpression(src, i, precedence, false); } - public static ParseRes parseExpressionStatement(Source src, int i) { + public static ParseRes parseExpressionStatement(Source src, int i) { var res = parseExpression(src, i, 0, true); if (!res.isSuccess()) return res.chainError(); @@ -183,14 +177,13 @@ public final class JavaScript { return res.addN(end.n); } - public static ParseRes parseStatement(Source src, int i) { + public static ParseRes parseStatement(Source src, int i) { var n = Parsing.skipEmpty(src, i); if (src.is(i + n, ";")) return ParseRes.res(new DiscardNode(src.loc(i+ n), null), n + 1); if (Parsing.isIdentifier(src, i + n, "with")) return ParseRes.error(src.loc(i + n), "'with' statements are not allowed."); - ParseRes res = ParseRes.first(src, i + n, - ClassStatementNode::parse, + ParseRes res = ParseRes.first(src, i + n, VariableDeclareNode::parse, ReturnNode::parse, ThrowNode::parse, @@ -202,7 +195,6 @@ public final class JavaScript { SwitchNode::parse, ForNode::parse, ForInNode::parse, - ForOfNode::parse, DoWhileNode::parse, TryNode::parse, CompoundNode::parse, @@ -214,7 +206,7 @@ public final class JavaScript { public static ParseRes parseStatementEnd(Source src, int i) { var n = Parsing.skipEmpty(src, i); - if (i >= src.size()) return ParseRes.res(true, n + 1); + if (i + n >= src.size()) return ParseRes.res(true, n); for (var j = i; j < i + n; j++) { if (src.is(j, '\n')) return ParseRes.res(true, n); @@ -226,13 +218,11 @@ public final class JavaScript { return ParseRes.failed(); } - public static ParseRes parseDeclarationType(Source src, int i) { + public static ParseRes parseDeclarationType(Source src, int i) { var res = Parsing.parseIdentifier(src, i); if (!res.isSuccess()) return res.chainError(); - if (res.result.equals("var")) return ParseRes.res(DeclarationType.VAR, res.n); - if (res.result.equals("let")) return ParseRes.res(DeclarationType.LET, res.n); - if (res.result.equals("const")) return ParseRes.res(DeclarationType.CONST, res.n); + if (res.result.equals("var")) return ParseRes.res(true, res.n); return ParseRes.failed(); } @@ -264,9 +254,11 @@ public final class JavaScript { } public static CompileResult compile(Environment env, Node ...statements) { - var func = new FunctionValueNode(null, null, new Parameters(Arrays.asList()), new CompoundNode(null, true, statements), null); - var res = func.compileBody(env, new FunctionScope(true), true, null, null); - res.buildTask.run(); + env = env.child(); + env.add(COMPILE_ROOT, env); + + var func = new FunctionValueNode(null, null, Arrays.asList(), new CompoundNode(null, statements), null); + var res = func.compileBody(env, new FunctionScope(true), true, null); return res; } @@ -291,4 +283,42 @@ public final class JavaScript { return ParseRes.res(nameRes.result, n); } + + public static ParseRes> parseParameters(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + + var openParen = Parsing.parseOperator(src, i + n, "("); + if (!openParen.isSuccess()) return openParen.chainError(src.loc(i + n), "Expected a parameter list"); + n += openParen.n; + + var params = new ArrayList(); + + var closeParen = Parsing.parseOperator(src, i + n, ")"); + n += closeParen.n; + + if (!closeParen.isSuccess()) { + while (true) { + n += Parsing.skipEmpty(src, i + n); + + var param = VariableNode.parse(src, i + n); + if (!param.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a parameter or a closing brace"); + n += param.n; + n += Parsing.skipEmpty(src, i + n); + + params.add(param.result); + + if (src.is(i + n, ",")) { + n++; + n += Parsing.skipEmpty(src, i + n); + } + + if (src.is(i + n, ")")) { + n++; + break; + } + } + } + + return ParseRes.res(params, n); + } } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/LabelContext.java b/src/main/java/me/topchetoeu/jscript/compilation/LabelContext.java index 22f371c..a9cfe03 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/LabelContext.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/LabelContext.java @@ -1,8 +1,9 @@ package me.topchetoeu.jscript.compilation; +import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedList; -import java.util.function.IntFunction; +import java.util.Stack; import java.util.function.IntSupplier; import me.topchetoeu.jscript.common.Instruction; @@ -18,6 +19,8 @@ public class LabelContext { private final LinkedList list = new LinkedList<>(); private final HashMap map = new HashMap<>(); + private final Stack> deferredAdders = new Stack<>(); + public IntSupplier get() { return list.peekLast(); } @@ -25,15 +28,31 @@ public class LabelContext { return map.get(name); } - public IntFunction getJump() { + public void flushAdders() { + for (var adder : deferredAdders.peek()) { + adder.run(); + } + + deferredAdders.pop(); + } + + public boolean jump(CompileResult target) { var res = get(); - if (res == null) return null; - else return i -> Instruction.jmp(res.getAsInt() - i); + if (res != null) { + var tmp = target.temp(); + this.deferredAdders.peek().add(() -> target.set(tmp, Instruction.jmp(res.getAsInt() - tmp))); + return true; + } + else return false; } - public IntFunction getJump(String name) { + public boolean jump(CompileResult target, String name) { var res = get(name); - if (res == null) return null; - else return i -> Instruction.jmp(res.getAsInt() - i); + if (res != null) { + var tmp = target.temp(); + this.deferredAdders.peek().add(() -> target.set(tmp, Instruction.jmp(res.getAsInt() - tmp))); + return true; + } + else return false; } public void push(IntSupplier jumpTarget) { @@ -48,6 +67,7 @@ public class LabelContext { public void pushLoop(Location loc, String name, IntSupplier jumpTarget) { push(jumpTarget); push(loc, name, jumpTarget); + deferredAdders.push(new ArrayList<>()); } public void pop() { @@ -61,6 +81,7 @@ public class LabelContext { public void popLoop(String name) { pop(); pop(name); + flushAdders(); } public static LabelContext getBreak(Environment env) { diff --git a/src/main/java/me/topchetoeu/jscript/compilation/Node.java b/src/main/java/me/topchetoeu/jscript/compilation/Node.java index 7da1c3e..033aa44 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/Node.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/Node.java @@ -17,6 +17,8 @@ public abstract class Node { compile(target, pollute, BreakpointType.NONE); } + public abstract void compileFunctions(CompileResult target); + public Location loc() { return loc; } public void setLoc(Location loc) { this.loc = loc; } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/Parameters.java b/src/main/java/me/topchetoeu/jscript/compilation/Parameters.java deleted file mode 100644 index 712a2e8..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/Parameters.java +++ /dev/null @@ -1,84 +0,0 @@ -package me.topchetoeu.jscript.compilation; - -import java.util.ArrayList; -import java.util.List; - -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.patterns.Pattern; -import me.topchetoeu.jscript.compilation.values.operations.AssignNode; - -public final class Parameters { - public final int length; - public final List params; - public final Pattern rest; - - public Parameters(List params, Pattern rest) { - var len = params.size(); - - for (var i = params.size() - 1; i >= 0; i--) { - if (!(params.get(i) instanceof AssignNode)) break; - len--; - } - - this.params = params; - this.length = len; - this.rest = rest; - } - public Parameters(List params) { - this(params, null); - } - - public static ParseRes parseParameters(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - - var openParen = Parsing.parseOperator(src, i + n, "("); - if (!openParen.isSuccess()) return openParen.chainError(src.loc(i + n), "Expected a parameter list"); - n += openParen.n; - - var params = new ArrayList(); - - var closeParen = Parsing.parseOperator(src, i + n, ")"); - n += closeParen.n; - - if (!closeParen.isSuccess()) { - while (true) { - n += Parsing.skipEmpty(src, i + n); - - if (src.is(i + n, "...")) { - n += 3; - - var rest = Pattern.parse(src, i + n, true); - if (!rest.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a rest parameter"); - n += rest.n; - n += Parsing.skipEmpty(src, i + n); - - if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected an end of parameters list after rest parameter"); - n++; - - return ParseRes.res(new Parameters(params, rest.result), n); - } - - var param = Pattern.parse(src, i + n, true); - if (!param.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a parameter or a closing brace"); - n += param.n; - n += Parsing.skipEmpty(src, i + n); - - params.add(param.result); - - if (src.is(i + n, ",")) { - n++; - n += Parsing.skipEmpty(src, i + n); - } - - if (src.is(i + n, ")")) { - n++; - break; - } - } - } - - return ParseRes.res(new Parameters(params), n); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/VariableDeclareNode.java b/src/main/java/me/topchetoeu/jscript/compilation/VariableDeclareNode.java index 85e3d0f..083bf57 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/VariableDeclareNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/VariableDeclareNode.java @@ -3,58 +3,45 @@ package me.topchetoeu.jscript.compilation; import java.util.ArrayList; import java.util.List; +import com.github.bsideup.jabel.Desugar; + import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.common.parsing.ParseRes; import me.topchetoeu.jscript.common.parsing.Parsing; import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType; -import me.topchetoeu.jscript.compilation.patterns.Pattern; +import me.topchetoeu.jscript.compilation.values.VariableNode; public class VariableDeclareNode extends Node { - public static class Pair { - public final Pattern destructor; - public final Node value; - public final Location location; - - public Pair(Pattern destr, Node value, Location location) { - this.destructor = destr; - this.value = value; - this.location = location; - } - } + @Desugar + public static record Pair(VariableNode var, Node value) { } public final List values; - public final DeclarationType declType; @Override public void resolve(CompileResult target) { - if (!declType.strict) { - for (var entry : values) { - entry.destructor.destructDeclResolve(target); - } - } + for (var entry : values) { + target.scope.define(entry.var.name); + } } + @Override public void compileFunctions(CompileResult target) { + for (var pair : values) { + if (pair.value != null) pair.value.compileFunctions(target); + } + } @Override public void compile(CompileResult target, boolean pollute) { for (var entry : values) { - if (entry.value == null) { - if (declType == DeclarationType.VAR) entry.destructor.declare(target, null, false); - else entry.destructor.declare(target, declType, false); - } - else { + if (entry.value != null) { entry.value.compile(target, true); - - if (declType == DeclarationType.VAR) entry.destructor.destruct(target, null, true); - else entry.destructor.destruct(target, declType, true); + target.add(VariableNode.toSet(target, loc(), entry.var.name, false, true)).setLocation(loc()); } } if (pollute) target.add(Instruction.pushUndefined()); } - public VariableDeclareNode(Location loc, DeclarationType declType, List values) { + public VariableDeclareNode(Location loc, List values) { super(loc); this.values = values; - this.declType = declType; } public static ParseRes parse(Source src, int i) { @@ -70,14 +57,14 @@ public class VariableDeclareNode extends Node { var end = JavaScript.parseStatementEnd(src, i + n); if (end.isSuccess()) { n += end.n; - return ParseRes.res(new VariableDeclareNode(loc, declType.result, res), n); + return ParseRes.res(new VariableDeclareNode(loc, res), n); } while (true) { var nameLoc = src.loc(i + n); - var name = Pattern.parse(src, i + n, false); - if (!name.isSuccess()) return name.chainError(nameLoc, "Expected a variable name or a destructor"); + var name = Parsing.parseIdentifier(src, i + n); + if (!name.isSuccess()) return name.chainError(nameLoc, "Expected a variable name"); n += name.n; Node val = null; @@ -96,7 +83,7 @@ public class VariableDeclareNode extends Node { val = valRes.result; } - res.add(new Pair(name.result, val, nameLoc)); + res.add(new Pair(new VariableNode(nameLoc, name.result), val)); if (src.is(i + n, ",")) { n++; @@ -107,7 +94,7 @@ public class VariableDeclareNode extends Node { if (end.isSuccess()) { n += end.n + endN - n; - return ParseRes.res(new VariableDeclareNode(loc, declType.result, res), n); + return ParseRes.res(new VariableDeclareNode(loc, res), n); } else return end.chainError(src.loc(i + n), "Expected a comma or end of statement"); } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/BreakNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/BreakNode.java index ee2c7aa..a52a47d 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/BreakNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/BreakNode.java @@ -14,13 +14,14 @@ import me.topchetoeu.jscript.compilation.Node; public class BreakNode extends Node { public final String label; - @Override public void compile(CompileResult target, boolean pollute) { - var res = LabelContext.getBreak(target.env).getJump(); - if (res == null) { + @Override public void compileFunctions(CompileResult target) { + } + + @Override public void compile(CompileResult target, boolean pollute) { + if (!LabelContext.getBreak(target.env).jump(target)) { if (label != null) throw new SyntaxException(loc(), String.format("Undefined label '%s'", label)); else throw new SyntaxException(loc(), "Illegal break statement"); } - target.add(res); if (pollute) target.add(Instruction.pushUndefined()); } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/ContinueNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/ContinueNode.java index 2b8a677..8531211 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/ContinueNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/ContinueNode.java @@ -14,13 +14,14 @@ import me.topchetoeu.jscript.compilation.Node; public class ContinueNode extends Node { public final String label; + @Override public void compileFunctions(CompileResult target) { + } + @Override public void compile(CompileResult target, boolean pollute) { - var res = LabelContext.getCont(target.env).getJump(); - if (res == null) { + if (!LabelContext.getCont(target.env).jump(target)) { if (label != null) throw new SyntaxException(loc(), String.format("Undefined label '%s'", label)); else throw new SyntaxException(loc(), "Illegal continue statement"); } - target.add(res); if (pollute) target.add(Instruction.pushUndefined()); } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/DebugNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/DebugNode.java index 9464e9d..bb0bd36 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/DebugNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/DebugNode.java @@ -10,6 +10,9 @@ import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.Node; public class DebugNode extends Node { + @Override public void compileFunctions(CompileResult target) { + } + @Override public void compile(CompileResult target, boolean pollute) { target.add(Instruction.debug()); if (pollute) target.add(Instruction.pushUndefined()); diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/DeleteNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/DeleteNode.java index 6df811d..60886bc 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/DeleteNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/DeleteNode.java @@ -16,6 +16,10 @@ public class DeleteNode extends Node { public final Node key; public final Node value; + @Override public void compileFunctions(CompileResult target) { + key.compileFunctions(target); + value.compileFunctions(target); + } @Override public void compile(CompileResult target, boolean pollute) { value.compile(target, true); key.compile(target, true); diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/DoWhileNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/DoWhileNode.java index 1f3de82..704218b 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/DoWhileNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/DoWhileNode.java @@ -16,6 +16,10 @@ public class DoWhileNode extends Node { public final Node condition, body; public final String label; + @Override public void compileFunctions(CompileResult target) { + condition.compileFunctions(target); + body.compileFunctions(target); + } @Override public void resolve(CompileResult target) { body.resolve(target); } @@ -27,13 +31,14 @@ public class DoWhileNode extends Node { LabelContext.pushLoop(target.env, loc(), label, end, start); body.compile(target, false, BreakpointType.STEP_OVER); - LabelContext.popLoop(target.env, label); mid.set(target.size()); condition.compile(target, true, BreakpointType.STEP_OVER); int endI = target.size(); end.set(endI + 1); + LabelContext.popLoop(target.env, label); + target.add(Instruction.jmpIf(start - endI)); } @@ -57,6 +62,7 @@ public class DoWhileNode extends Node { var bodyRes = JavaScript.parseStatement(src, i + n); if (!bodyRes.isSuccess()) return bodyRes.chainError(src.loc(i + n), "Expected a do-while body."); n += bodyRes.n; + n += Parsing.skipEmpty(src, i + n); if (!Parsing.isIdentifier(src, i + n, "while")) return ParseRes.failed(); n += 5; diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/ForInNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/ForInNode.java index 454cfb3..b176632 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/ForInNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/ForInNode.java @@ -7,15 +7,15 @@ import me.topchetoeu.jscript.common.parsing.ParseRes; import me.topchetoeu.jscript.common.parsing.Parsing; import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.CompoundNode; -import me.topchetoeu.jscript.compilation.DeferredIntSupplier; import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.LabelContext; +import me.topchetoeu.jscript.compilation.DeferredIntSupplier; import me.topchetoeu.jscript.compilation.Node; -import me.topchetoeu.jscript.compilation.patterns.Binding; +import me.topchetoeu.jscript.compilation.values.VariableNode; public class ForInNode extends Node { - public final Binding binding; + public final boolean isDecl; + public final VariableNode binding; public final Node object, body; public final String label; @@ -24,9 +24,11 @@ public class ForInNode extends Node { binding.resolve(target); } + @Override public void compileFunctions(CompileResult target) { + object.compileFunctions(target); + body.compileFunctions(target); + } @Override public void compile(CompileResult target, boolean pollute) { - binding.declareLateInit(target); - object.compile(target, true, BreakpointType.STEP_OVER); target.add(Instruction.keys(false, true)); @@ -35,27 +37,32 @@ public class ForInNode extends Node { int mid = target.temp(); target.add(Instruction.loadMember("value")).setLocation(binding.loc()); - binding.assign(target, false); + target.add(VariableNode.toSet(target, loc(), binding.name, false, true)).setLocation(binding.loc()); + target.setLocationAndDebug(object.loc(), BreakpointType.STEP_OVER); var end = new DeferredIntSupplier(); LabelContext.pushLoop(target.env, loc(), label, end, start); - CompoundNode.compileMultiEntry(body, target, false, BreakpointType.STEP_OVER); - LabelContext.popLoop(target.env, label); + body.compile(target, false, BreakpointType.STEP_OVER); int endI = target.size(); target.add(Instruction.jmp(start - endI)); target.add(Instruction.discard()); target.set(mid, Instruction.jmpIfNot(endI - mid + 1)); + + end.set(endI); + LabelContext.popLoop(target.env, label); + if (pollute) target.add(Instruction.pushUndefined()); } - public ForInNode(Location loc, String label, Binding binding, Node object, Node body) { + public ForInNode(Location loc, String label, VariableNode binding, boolean isDecl, Node object, Node body) { super(loc); this.label = label; this.binding = binding; + this.isDecl = isDecl; this.object = object; this.body = body; } @@ -76,9 +83,15 @@ public class ForInNode extends Node { n++; n += Parsing.skipEmpty(src, i + n); - var binding = Binding.parse(src, i + n); - if (!binding.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a binding in for-in loop"); - n += binding.n; + var varKw = JavaScript.parseDeclarationType(src, i + n); + n += varKw.n; + n += Parsing.skipEmpty(src, i + n); + + var bindingLoc = src.loc(i + n); + + var name = Parsing.parseIdentifier(src, i + n); + if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected a variable name"); + n += name.n; n += Parsing.skipEmpty(src, i + n); if (!Parsing.isIdentifier(src, i + n, "in")) return ParseRes.error(src.loc(i + n), "Expected 'in' keyword after variable declaration"); @@ -96,6 +109,6 @@ public class ForInNode extends Node { if (!bodyRes.isSuccess()) return bodyRes.chainError(src.loc(i + n), "Expected a for-in body"); n += bodyRes.n; - return ParseRes.res(new ForInNode(loc, label.result, binding.result, obj.result, bodyRes.result), n); + return ParseRes.res(new ForInNode(loc, label.result, new VariableNode(bindingLoc, name.result), varKw.isSuccess(), obj.result, bodyRes.result), n); } } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/ForNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/ForNode.java index 2b78601..7eb2852 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/ForNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/ForNode.java @@ -7,7 +7,6 @@ import me.topchetoeu.jscript.common.parsing.ParseRes; import me.topchetoeu.jscript.common.parsing.Parsing; import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.CompoundNode; import me.topchetoeu.jscript.compilation.DeferredIntSupplier; import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.LabelContext; @@ -23,35 +22,36 @@ public class ForNode extends Node { declaration.resolve(target); body.resolve(target); } + @Override public void compileFunctions(CompileResult target) { + if (declaration != null) declaration.compileFunctions(target); + if (assignment != null) assignment.compileFunctions(target); + if (condition != null) condition.compileFunctions(target); + body.compileFunctions(target); + } @Override public void compile(CompileResult target, boolean pollute) { - var subtarget = target.subtarget(); - subtarget.scope.singleEntry = false; - subtarget.beginScope(); + if (declaration != null) declaration.compile(target, false, BreakpointType.STEP_OVER); - declaration.compile(subtarget, false, BreakpointType.STEP_OVER); - - int start = subtarget.size(); - CompoundNode.compileMultiEntry(condition, subtarget, true, BreakpointType.STEP_OVER); - int mid = subtarget.temp(); + int start = target.size(); + int mid = -1; + if (condition != null) { + condition.compile(target, true, BreakpointType.STEP_OVER); + mid = target.temp(); + } var end = new DeferredIntSupplier(); - LabelContext.pushLoop(subtarget.env, loc(), label, end, start); - CompoundNode.compileMultiEntry(body, subtarget, false, BreakpointType.STEP_OVER); - LabelContext.popLoop(subtarget.env, label); + LabelContext.pushLoop(target.env, loc(), label, end, start); + body.compile(target, false, BreakpointType.STEP_OVER); - subtarget.reallocScope(); - - CompoundNode.compileMultiEntry(assignment, subtarget, false, BreakpointType.STEP_OVER); - int endI = subtarget.size(); + if (assignment != null) assignment.compile(target, false, BreakpointType.STEP_OVER); + int endI = target.size(); end.set(endI); + LabelContext.popLoop(target.env, label); - subtarget.add(Instruction.jmp(start - endI)); - subtarget.set(mid, Instruction.jmpIfNot(endI - mid + 1)); - if (pollute) subtarget.add(Instruction.pushUndefined()); - - subtarget.endScope(); + target.add(Instruction.jmp(start - endI)); + if (mid >= 0) target.set(mid, Instruction.jmpIfNot(endI - mid + 1)); + if (pollute) target.add(Instruction.pushUndefined()); } public ForNode(Location loc, String label, Node declaration, Node condition, Node assignment, Node body) { diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/ForOfNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/ForOfNode.java deleted file mode 100644 index 6438c9f..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/ForOfNode.java +++ /dev/null @@ -1,110 +0,0 @@ -package me.topchetoeu.jscript.compilation.control; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.CompoundNode; -import me.topchetoeu.jscript.compilation.DeferredIntSupplier; -import me.topchetoeu.jscript.compilation.JavaScript; -import me.topchetoeu.jscript.compilation.LabelContext; -import me.topchetoeu.jscript.compilation.Node; -import me.topchetoeu.jscript.compilation.patterns.Binding; - -public class ForOfNode extends Node { - public final Binding binding; - public final Node iterable, body; - public final String label; - - @Override public void resolve(CompileResult target) { - body.resolve(target); - binding.resolve(target); - } - - @Override public void compile(CompileResult target, boolean pollute) { - binding.declareLateInit(target); - - iterable.compile(target, true, BreakpointType.STEP_OVER); - target.add(Instruction.dup()); - target.add(Instruction.loadIntrinsics("it_key")); - target.add(Instruction.loadMember()).setLocation(iterable.loc()); - target.add(Instruction.call(0)).setLocation(iterable.loc()); - - int start = target.size(); - target.add(Instruction.dup()); - target.add(Instruction.dup()); - target.add(Instruction.loadMember("next")).setLocation(iterable.loc()); - target.add(Instruction.call(0)).setLocation(iterable.loc()); - target.add(Instruction.dup()); - target.add(Instruction.loadMember("done")).setLocation(iterable.loc()); - int mid = target.temp(); - - target.add(Instruction.loadMember("value")).setLocation(binding.loc); - binding.assign(target, false); - - var end = new DeferredIntSupplier(); - - LabelContext.pushLoop(target.env, loc(), label, end, start); - CompoundNode.compileMultiEntry(body, target, false, BreakpointType.STEP_OVER); - LabelContext.popLoop(target.env, label); - - int endI = target.size(); - end.set(endI); - - target.add(Instruction.jmp(start - endI)); - target.add(Instruction.discard()); - target.add(Instruction.discard()); - target.set(mid, Instruction.jmpIf(endI - mid + 1)); - if (pollute) target.add(Instruction.pushUndefined()); - } - - public ForOfNode(Location loc, String label, Binding binding, Node object, Node body) { - super(loc); - this.label = label; - this.binding = binding; - this.iterable = object; - this.body = body; - } - - public static ParseRes parse(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - var label = JavaScript.parseLabel(src, i + n); - n += label.n; - n += Parsing.skipEmpty(src, i + n); - - if (!Parsing.isIdentifier(src, i + n, "for")) return ParseRes.failed(); - n += 3; - n += Parsing.skipEmpty(src, i + n); - - if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected an opening paren"); - n++; - n += Parsing.skipEmpty(src, i + n); - - var binding = Binding.parse(src, i + n); - if (!binding.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a binding in for-of loop"); - n += binding.n; - n += Parsing.skipEmpty(src, i + n); - - if (!Parsing.isIdentifier(src, i + n, "of")) return ParseRes.error(src.loc(i + n), "Expected 'of' keyword after variable declaration"); - n += 2; - - var obj = JavaScript.parseExpression(src, i + n, 0); - if (!obj.isSuccess()) return obj.chainError(src.loc(i + n), "Expected a value"); - n += obj.n; - n += Parsing.skipEmpty(src, i + n); - - if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren"); - n++; - - var bodyRes = JavaScript.parseStatement(src, i + n); - if (!bodyRes.isSuccess()) return bodyRes.chainError(src.loc(i + n), "Expected a for-of body"); - n += bodyRes.n; - - return ParseRes.res(new ForOfNode(loc, label.result, binding.result, obj.result, bodyRes.result), n); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/IfNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/IfNode.java index ef6546b..d6013b9 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/IfNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/IfNode.java @@ -20,7 +20,11 @@ public class IfNode extends Node { body.resolve(target); if (elseBody != null) elseBody.resolve(target); } - + @Override public void compileFunctions(CompileResult target) { + condition.compileFunctions(target); + body.compileFunctions(target); + if (elseBody != null) elseBody.compileFunctions(target); + } @Override public void compile(CompileResult target, boolean pollute, BreakpointType breakpoint) { condition.compile(target, true, breakpoint); @@ -29,7 +33,7 @@ public class IfNode extends Node { var end = new DeferredIntSupplier(); LabelContext.getBreak(target.env).push(loc(), label, end); - body.compile(target, false, BreakpointType.STEP_OVER); + body.compile(target, pollute, BreakpointType.STEP_OVER); LabelContext.getBreak(target.env).pop(label); int endI = target.size(); @@ -42,11 +46,11 @@ public class IfNode extends Node { var end = new DeferredIntSupplier(); LabelContext.getBreak(target.env).push(loc(), label, end); - body.compile(target, false, BreakpointType.STEP_OVER); + body.compile(target, pollute, BreakpointType.STEP_OVER); int mid = target.temp(); - elseBody.compile(target, false, BreakpointType.STEP_OVER); + elseBody.compile(target, pollute, BreakpointType.STEP_OVER); LabelContext.getBreak(target.env).pop(label); int endI = target.size(); @@ -80,7 +84,7 @@ public class IfNode extends Node { var a = JavaScript.parseExpression(src, i + n, 2); if (!a.isSuccess()) return a.chainError(src.loc(i + n), "Expected a value after the ternary operator."); n += a.n; - n += Parsing.skipEmpty(src, i); + n += Parsing.skipEmpty(src, i + n); if (!src.is(i + n, ":")) return ParseRes.failed(); n++; diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/ReturnNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/ReturnNode.java index 3caccea..7e7d708 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/ReturnNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/ReturnNode.java @@ -12,6 +12,9 @@ import me.topchetoeu.jscript.compilation.Node; public class ReturnNode extends Node { public final Node value; + @Override public void compileFunctions(CompileResult target) { + if (value != null) value.compileFunctions(target); + } @Override public void compile(CompileResult target, boolean pollute) { if (value == null) target.add(Instruction.pushUndefined()); else value.compile(target, true); diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/SwitchNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/SwitchNode.java index ee00222..f32baa2 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/SwitchNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/SwitchNode.java @@ -36,48 +36,52 @@ public class SwitchNode extends Node { @Override public void resolve(CompileResult target) { for (var stm : body) stm.resolve(target); } - + @Override public void compileFunctions(CompileResult target) { + value.compileFunctions(target); + for (var _case : cases) { + _case.value.compileFunctions(target); + } + for (var stm : body) { + stm.compileFunctions(target); + } + } @Override public void compile(CompileResult target, boolean pollute) { var caseToStatement = new HashMap(); var statementToIndex = new HashMap(); value.compile(target, true, BreakpointType.STEP_OVER); - var subtarget = target.subtarget(); - subtarget.beginScope(); - // TODO: create a jump map for (var ccase : cases) { - subtarget.add(Instruction.dup()); - ccase.value.compile(subtarget, true); - subtarget.add(Instruction.operation(Operation.EQUALS)); - caseToStatement.put(subtarget.temp(), ccase.statementI); + target.add(Instruction.dup()); + ccase.value.compile(target, true); + target.add(Instruction.operation(Operation.EQUALS)); + caseToStatement.put(target.temp(), ccase.statementI); } - int start = subtarget.temp(); + int start = target.temp(); var end = new DeferredIntSupplier(); - LabelContext.getBreak(target.env).push(loc(), label, end); + LabelContext.getBreak(target.env).pushLoop(loc(), label, end); for (var stm : body) { - statementToIndex.put(statementToIndex.size(), subtarget.size()); - stm.compile(subtarget, false, BreakpointType.STEP_OVER); + statementToIndex.put(statementToIndex.size(), target.size()); + stm.compile(target, false, BreakpointType.STEP_OVER); } - LabelContext.getBreak(target.env).pop(label); - subtarget.endScope(); - - int endI = subtarget.size(); + int endI = target.size(); end.set(endI); - subtarget.add(Instruction.discard()); - if (pollute) subtarget.add(Instruction.pushUndefined()); + LabelContext.getBreak(target.env).popLoop(label); - if (defaultI < 0 || defaultI >= body.length) subtarget.set(start, Instruction.jmp(endI - start)); - else subtarget.set(start, Instruction.jmp(statementToIndex.get(defaultI) - start)); + target.add(Instruction.discard()); + if (pollute) target.add(Instruction.pushUndefined()); + + if (defaultI < 0 || defaultI >= body.length) target.set(start, Instruction.jmp(endI - start)); + else target.set(start, Instruction.jmp(statementToIndex.get(defaultI) - start)); for (var el : caseToStatement.entrySet()) { var i = statementToIndex.get(el.getValue()); if (i == null) i = endI; - subtarget.set(el.getKey(), Instruction.jmpIf(i - el.getKey())); + target.set(el.getKey(), Instruction.jmpIf(i - el.getKey())); } } @@ -100,6 +104,7 @@ public class SwitchNode extends Node { var val = JavaScript.parseExpression(src, i + n, 0); if (!val.isSuccess()) return val.chainError(src.loc(i + n), "Expected a value after 'case'"); n += val.n; + n += Parsing.skipEmpty(src, i + n); if (!src.is(i + n, ":")) return ParseRes.error(src.loc(i + n), "Expected colons after 'case' value"); n++; @@ -118,7 +123,6 @@ public class SwitchNode extends Node { return ParseRes.res(null, n); } - @SuppressWarnings("unused") public static ParseRes parse(Source src, int i) { var n = Parsing.skipEmpty(src, i); var loc = src.loc(i + n); diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/ThrowNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/ThrowNode.java index 2c6cb37..9f05e2f 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/ThrowNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/ThrowNode.java @@ -12,6 +12,9 @@ import me.topchetoeu.jscript.compilation.Node; public class ThrowNode extends Node { public final Node value; + @Override public void compileFunctions(CompileResult target) { + value.compileFunctions(target); + } @Override public void compile(CompileResult target, boolean pollute) { value.compile(target, true); target.add(Instruction.throwInstr()).setLocation(loc()); diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/TryNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/TryNode.java index 18700b3..f2dcda1 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/TryNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/TryNode.java @@ -12,7 +12,6 @@ import me.topchetoeu.jscript.compilation.DeferredIntSupplier; import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.LabelContext; import me.topchetoeu.jscript.compilation.Node; -import me.topchetoeu.jscript.compilation.scope.Variable; public class TryNode extends Node { public final CompoundNode tryBody; @@ -26,7 +25,11 @@ public class TryNode extends Node { if (catchBody != null) catchBody.resolve(target); if (finallyBody != null) finallyBody.resolve(target); } - + @Override public void compileFunctions(CompileResult target) { + tryBody.compileFunctions(target); + if (catchBody != null) catchBody.compileFunctions(target); + if (finallyBody != null) finallyBody.compileFunctions(target); + } @Override public void compile(CompileResult target, boolean pollute, BreakpointType bpt) { int replace = target.temp(); var endSuppl = new DeferredIntSupplier(); @@ -42,17 +45,11 @@ public class TryNode extends Node { catchStart = target.size() - start; if (captureName != null) { - var subtarget = target.subtarget(); - subtarget.beginScope(); - subtarget.scope.singleEntry = true; - - var catchVar = subtarget.scope.defineStrict(new Variable(captureName, false), catchBody.loc()); - subtarget.add(Instruction.loadError()); - subtarget.add(catchVar.index().toInit()); - catchBody.compile(subtarget, false); - subtarget.endScope(); - - subtarget.scope.end(); + var catchVar = target.scope.defineCatch(captureName); + target.add(Instruction.loadError()).setLocation(catchBody.loc()); + target.add(catchVar.index().toSet(false)).setLocation(catchBody.loc()); + catchBody.compile(target, false); + target.scope.undefineCatch(); } else catchBody.compile(target, false); diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/WhileNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/WhileNode.java index d6d9447..13bbc2f 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/WhileNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/WhileNode.java @@ -7,7 +7,6 @@ import me.topchetoeu.jscript.common.parsing.ParseRes; import me.topchetoeu.jscript.common.parsing.Parsing; import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.CompoundNode; import me.topchetoeu.jscript.compilation.DeferredIntSupplier; import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.LabelContext; @@ -20,6 +19,10 @@ public class WhileNode extends Node { @Override public void resolve(CompileResult target) { body.resolve(target); } + @Override public void compileFunctions(CompileResult target) { + condition.compileFunctions(target); + body.compileFunctions(target); + } @Override public void compile(CompileResult target, boolean pollute) { int start = target.size(); condition.compile(target, true); @@ -29,11 +32,11 @@ public class WhileNode extends Node { LabelContext.pushLoop(target.env, loc(), label, end, start); - CompoundNode.compileMultiEntry(body, target, false, BreakpointType.STEP_OVER); - LabelContext.popLoop(target.env, label); + body.compile(target, false, BreakpointType.STEP_OVER); var endI = target.size(); end.set(endI + 1); + LabelContext.popLoop(target.env, label); target.add(Instruction.jmp(start - end.getAsInt())); target.set(mid, Instruction.jmpIfNot(end.getAsInt() - mid + 1)); diff --git a/src/main/java/me/topchetoeu/jscript/compilation/members/AssignShorthandNode.java b/src/main/java/me/topchetoeu/jscript/compilation/members/AssignShorthandNode.java deleted file mode 100644 index 5fc491c..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/members/AssignShorthandNode.java +++ /dev/null @@ -1,54 +0,0 @@ -package me.topchetoeu.jscript.compilation.members; - -import me.topchetoeu.jscript.common.SyntaxException; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.JavaScript; -import me.topchetoeu.jscript.compilation.Node; -import me.topchetoeu.jscript.compilation.patterns.AssignTarget; -import me.topchetoeu.jscript.compilation.values.VariableNode; -import me.topchetoeu.jscript.compilation.values.constants.StringNode; -import me.topchetoeu.jscript.compilation.values.operations.AssignNode; - -public class AssignShorthandNode extends Node { - public final Node key; - public final AssignTarget target; - public final Node value; - - @Override public void compile(CompileResult target, boolean pollute) { - throw new SyntaxException(loc(), "Unexpected assign shorthand in non-destructor context"); - } - - public AssignShorthandNode(Location loc, Node key, AssignTarget target, Node value) { - super(loc); - this.key = key; - this.target = target; - this.value = value; - } - - public AssignTarget target() { - return new AssignNode(loc(), target, value); - } - - public static ParseRes parse(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - var var = VariableNode.parse(src, i + n); - if (!var.isSuccess()) return var.chainError(); - n += var.n; - n += Parsing.skipEmpty(src, i + n); - - if (!src.is(i + n, "=")) return ParseRes.failed(); - n++; - - var value = JavaScript.parseExpression(src, i + n, 2); - if (!value.isSuccess()) return value.chainError(src.loc(i + n), "Expected a shorthand initializer"); - n += value.n; - - return ParseRes.res(new AssignShorthandNode(loc, new StringNode(loc, var.result.name), var.result, value.result), n); - } -} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/compilation/members/FieldMemberNode.java b/src/main/java/me/topchetoeu/jscript/compilation/members/FieldMemberNode.java index 9706722..f369f65 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/members/FieldMemberNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/members/FieldMemberNode.java @@ -9,13 +9,19 @@ import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.Node; import me.topchetoeu.jscript.compilation.values.ObjectNode; -import me.topchetoeu.jscript.compilation.values.VariableNode; -import me.topchetoeu.jscript.compilation.values.constants.StringNode; -public class FieldMemberNode extends Node { +public class FieldMemberNode implements Member { + public final Location loc; public final Node key; public final Node value; + @Override public Location loc() { return loc; } + + @Override public void compileFunctions(CompileResult target) { + key.compileFunctions(target); + value.compileFunctions(target); + } + @Override public void compile(CompileResult target, boolean pollute) { if (pollute) target.add(Instruction.dup()); key.compile(target, true); @@ -27,12 +33,12 @@ public class FieldMemberNode extends Node { } public FieldMemberNode(Location loc, Node key, Node value) { - super(loc); + this.loc = loc; this.key = key; this.value = value; } - public static ParseRes parseObject(Source src, int i) { + public static ParseRes parse(Source src, int i) { var n = Parsing.skipEmpty(src, i); var loc = src.loc(i + n); @@ -50,40 +56,4 @@ public class FieldMemberNode extends Node { return ParseRes.res(new FieldMemberNode(loc, name.result, value.result), n); } - - public static ParseRes parseShorthand(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - var var = VariableNode.parse(src, i + n); - if (!var.isSuccess()) return var.chainError(); - n += var.n; - - return ParseRes.res(new FieldMemberNode(loc, new StringNode(loc, var.result.name), var.result), n); - } - - public static ParseRes parseClass(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - var name = ObjectNode.parsePropName(src, i + n); - if (!name.isSuccess()) return name.chainError(); - n += name.n; - n += Parsing.skipEmpty(src, i + n); - - if (!src.is(i + n, "=")) { - var end = JavaScript.parseStatement(src, i + n); - if (!end.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected an end of statement or a field initializer"); - n += end.n; - - return ParseRes.res(new FieldMemberNode(loc, name.result, null), n); - } - n++; - - var value = JavaScript.parseExpression(src, i + n, 2); - if (!value.isSuccess()) return value.chainError(src.loc(i + n), "Expected a value"); - n += value.n; - - return ParseRes.res(new FieldMemberNode(loc, name.result, value.result), n); - } } \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/compilation/members/Member.java b/src/main/java/me/topchetoeu/jscript/compilation/members/Member.java new file mode 100644 index 0000000..84ebf68 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/members/Member.java @@ -0,0 +1,11 @@ +package me.topchetoeu.jscript.compilation.members; + +import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.compilation.CompileResult; + +public interface Member { + Location loc(); + + void compileFunctions(CompileResult target); + void compile(CompileResult target, boolean pollute); +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/members/MethodMemberNode.java b/src/main/java/me/topchetoeu/jscript/compilation/members/MethodMemberNode.java deleted file mode 100644 index 8636c3d..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/members/MethodMemberNode.java +++ /dev/null @@ -1,62 +0,0 @@ -package me.topchetoeu.jscript.compilation.members; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.CompoundNode; -import me.topchetoeu.jscript.compilation.FunctionNode; -import me.topchetoeu.jscript.compilation.Node; -import me.topchetoeu.jscript.compilation.Parameters; -import me.topchetoeu.jscript.compilation.values.ObjectNode; -import me.topchetoeu.jscript.compilation.values.constants.StringNode; - -public class MethodMemberNode extends FunctionNode { - public final Node key; - - @Override public String name() { - if (key instanceof StringNode str) return str.value; - else return null; - } - - @Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) { - if (pollute) target.add(Instruction.dup()); - key.compile(target, true); - - var id = target.addChild(compileBody(target, name, null)); - target.add(_i -> Instruction.loadFunc(id, true, false, false, name, captures(id, target))); - - target.add(Instruction.defField()); - } - - public MethodMemberNode(Location loc, Location end, Node key, Parameters params, CompoundNode body) { - super(loc, end, params, body); - this.key = key; - } - - public static ParseRes parse(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - var name = ObjectNode.parsePropName(src, i + n); - if (!name.isSuccess()) return name.chainError(); - n += name.n; - - var params = Parameters.parseParameters(src, i + n); - if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected an argument list"); - n += params.n; - - var body = CompoundNode.parse(src, i + n); - if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a compound statement for property accessor."); - n += body.n; - - var end = src.loc(i + n - 1); - - return ParseRes.res(new MethodMemberNode( - loc, end, name.result, params.result, body.result - ), n); - } -} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/compilation/members/PropertyMemberNode.java b/src/main/java/me/topchetoeu/jscript/compilation/members/PropertyMemberNode.java index 6e49e62..279e560 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/members/PropertyMemberNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/members/PropertyMemberNode.java @@ -11,15 +11,15 @@ import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.CompoundNode; import me.topchetoeu.jscript.compilation.FunctionNode; +import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.Node; -import me.topchetoeu.jscript.compilation.Parameters; -import me.topchetoeu.jscript.compilation.patterns.Pattern; import me.topchetoeu.jscript.compilation.values.ObjectNode; +import me.topchetoeu.jscript.compilation.values.VariableNode; import me.topchetoeu.jscript.compilation.values.constants.StringNode; -public class PropertyMemberNode extends FunctionNode { +public final class PropertyMemberNode extends FunctionNode implements Member { public final Node key; - public final Pattern argument; + public final VariableNode argument; @Override public String name() { if (key instanceof StringNode str) { @@ -32,18 +32,21 @@ public class PropertyMemberNode extends FunctionNode { public boolean isGetter() { return argument == null; } public boolean isSetter() { return argument != null; } + @Override public void compileFunctions(CompileResult target) { + key.compileFunctions(target); + target.addChild(this, compileBody(target, null)); + } + @Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) { if (pollute) target.add(Instruction.dup()); key.compile(target, true); - var id = target.addChild(compileBody(target, name, null)); - target.add(_i -> Instruction.loadFunc(id, true, false, false, name, captures(id, target))); - + target.add(Instruction.loadFunc(target.childrenIndices.get(this), name(name), captures(target))).setLocation(loc()); target.add(Instruction.defProp(isSetter())); } - public PropertyMemberNode(Location loc, Location end, Node key, Pattern argument, CompoundNode body) { - super(loc, end, argument == null ? new Parameters(Arrays.asList()) : new Parameters(Arrays.asList(argument)), body); + public PropertyMemberNode(Location loc, Location end, Node key, VariableNode argument, CompoundNode body) { + super(loc, end, argument == null ? Arrays.asList() : Arrays.asList(argument), body); this.key = key; this.argument = argument; } @@ -61,11 +64,10 @@ public class PropertyMemberNode extends FunctionNode { if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected a property name after '" + access + "'"); n += name.n; - var params = Parameters.parseParameters(src, i + n); + var params = JavaScript.parseParameters(src, i + n); if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected an argument list"); - if (access.result.equals("get") && params.result.params.size() != 0) return ParseRes.error(src.loc(i + n), "Getter must not have any parameters"); - if (access.result.equals("set") && params.result.params.size() != 1) return ParseRes.error(src.loc(i + n), "Setter must have exactly one parameter"); - if (params.result.rest != null) return ParseRes.error(params.result.rest.loc(), "Property members may not have rest arguments"); + if (access.result.equals("get") && params.result.size() != 0) return ParseRes.error(src.loc(i + n), "Getter must not have any parameters"); + if (access.result.equals("set") && params.result.size() != 1) return ParseRes.error(src.loc(i + n), "Setter must have exactly one parameter"); n += params.n; var body = CompoundNode.parse(src, i + n); @@ -75,7 +77,7 @@ public class PropertyMemberNode extends FunctionNode { var end = src.loc(i + n - 1); return ParseRes.res(new PropertyMemberNode( - loc, end, name.result, access.result.equals("get") ? null : params.result.params.get(0), body.result + loc, end, name.result, access.result.equals("get") ? null : params.result.get(0), body.result ), n); } } \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/compilation/patterns/AssignPattern.java b/src/main/java/me/topchetoeu/jscript/compilation/patterns/AssignPattern.java deleted file mode 100644 index e6ea21a..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/patterns/AssignPattern.java +++ /dev/null @@ -1,84 +0,0 @@ -package me.topchetoeu.jscript.compilation.patterns; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Operation; -import me.topchetoeu.jscript.common.SyntaxException; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.JavaScript; -import me.topchetoeu.jscript.compilation.Node; -import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType; - -public class AssignPattern implements Pattern { - public final Location loc; - public final AssignTarget assignable; - public final Node value; - - @Override public Location loc() { return loc; } - - @Override public void destructDeclResolve(CompileResult target) { - if (!(assignable instanceof Pattern p)) throw new SyntaxException(assignable.loc(), "Unexpected non-pattern in destruct context"); - p.destructDeclResolve(target); - } - - private void common(CompileResult target) { - target.add(Instruction.dup()); - target.add(Instruction.pushUndefined()); - target.add(Instruction.operation(Operation.EQUALS)); - var start = target.temp(); - target.add(Instruction.discard()); - - value.compile(target, true); - - target.set(start, Instruction.jmpIfNot(target.size() - start)); - } - - @Override public void declare(CompileResult target, DeclarationType decl, boolean lateInitializer) { - if (lateInitializer) { - if (assignable instanceof Pattern p) p.declare(target, decl, lateInitializer); - else throw new SyntaxException(assignable.loc(), "Unexpected non-pattern in destruct context"); - } - else throw new SyntaxException(loc(), "Expected an assignment value for destructor declaration"); - } - @Override public void destruct(CompileResult target, DeclarationType decl, boolean shouldDeclare) { - if (!(assignable instanceof Pattern p)) throw new SyntaxException(assignable.loc(), "Unexpected non-pattern in destruct context"); - common(target); - p.destruct(target, decl, shouldDeclare); - } - - @Override public void beforeAssign(CompileResult target) { - assignable.beforeAssign(target); - } - @Override public void afterAssign(CompileResult target, boolean pollute) { - common(target); - assignable.afterAssign(target, false); - } - - public AssignPattern(Location loc, Pattern assignable, Node value) { - this.loc = loc; - this.assignable = assignable; - this.value = value; - } - - public static ParseRes parse(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - var pattern = Pattern.parse(src, i + n, false); - if (!pattern.isSuccess()) return pattern.chainError(); - n += pattern.n; - n += Parsing.skipEmpty(src, i + n); - - if (!src.is(i + n, "=")) return ParseRes.failed(); - n++; - - var value = JavaScript.parseExpression(src, i + n, 2); - if (!value.isSuccess()) return value.chainError(src.loc(i + n), "Expected a default value"); - n += value.n; - - return ParseRes.res(new AssignPattern(loc, pattern.result, value.result), n); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/patterns/Binding.java b/src/main/java/me/topchetoeu/jscript/compilation/patterns/Binding.java deleted file mode 100644 index bf7524f..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/patterns/Binding.java +++ /dev/null @@ -1,80 +0,0 @@ -package me.topchetoeu.jscript.compilation.patterns; - -import me.topchetoeu.jscript.common.SyntaxException; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.JavaScript; -import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType; - -public class Binding implements Pattern { - public final Location loc; - public final DeclarationType type; - public final AssignTarget assignable; - - @Override public Location loc() { return loc; } - - @Override public void destructDeclResolve(CompileResult target) { - if (type != null && !type.strict) { - if (!(assignable instanceof Pattern p)) throw new SyntaxException(assignable.loc(), "Unexpected non-pattern in destruct context"); - p.destructDeclResolve(target); - } - } - - @Override public void destruct(CompileResult target, DeclarationType decl, boolean shouldDeclare) { - if (!(assignable instanceof Pattern p)) throw new SyntaxException(assignable.loc(), "Unexpected non-pattern in destruct context"); - p.destruct(target, decl, shouldDeclare); - } - @Override public void declare(CompileResult target, DeclarationType decl, boolean lateInitializer) { - if (!(assignable instanceof Pattern p)) throw new SyntaxException(assignable.loc(), "Unexpected non-pattern in destruct context"); - p.declare(target, decl, lateInitializer); - } - - public void resolve(CompileResult target) { - if (type != null) destructDeclResolve(target); - } - - public void declare(CompileResult target, boolean hasInit) { - if (type != null) destructVar(target, type, hasInit); - } - public void declareLateInit(CompileResult target) { - if (type != null) declare(target, type, true); - } - - @Override public void afterAssign(CompileResult target, boolean pollute) { - assignable.assign(target, pollute); - } - - public Binding(Location loc, DeclarationType type, AssignTarget assignable) { - this.loc = loc; - this.type = type; - this.assignable = assignable; - } - - public static ParseRes parse(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - var declType = JavaScript.parseDeclarationType(src, i + n); - if (!declType.isSuccess()) { - var res = JavaScript.parseExpression(src, i + n, 13); - if (res.isSuccess() && res.result instanceof AssignTargetLike target) { - n += res.n; - return ParseRes.res(new Binding(loc, null, target.toAssignTarget()), n); - } - else return ParseRes.failed(); - } - else { - n += declType.n; - n += Parsing.skipEmpty(src, i + n); - - var res = Pattern.parse(src, i + n, false); - if (!res.isSuccess()) return ParseRes.failed(); - n += res.n; - - return ParseRes.res(new Binding(loc, declType.result, res.result), n); - } - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/patterns/ObjectPattern.java b/src/main/java/me/topchetoeu/jscript/compilation/patterns/ObjectPattern.java deleted file mode 100644 index 65e508d..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/patterns/ObjectPattern.java +++ /dev/null @@ -1,161 +0,0 @@ -package me.topchetoeu.jscript.compilation.patterns; - -import java.util.LinkedList; -import java.util.List; -import java.util.function.Consumer; - -import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.SyntaxException; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.Node; -import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType; -import me.topchetoeu.jscript.compilation.values.ObjectNode; -import me.topchetoeu.jscript.compilation.values.VariableNode; -import me.topchetoeu.jscript.compilation.values.constants.StringNode; -import me.topchetoeu.jscript.compilation.values.operations.IndexNode; - -public class ObjectPattern extends Node implements Pattern { - public static final class Member { - public final Node key; - public final AssignTarget consumable; - - public Member(Node key, AssignTarget consumer) { - this.key = key; - this.consumable = consumer; - } - } - - public final List members; - - public void compile(CompileResult target, Consumer consumer, boolean pollute) { - for (var el : members) { - target.add(Instruction.dup()); - IndexNode.indexLoad(target, el.key, true); - consumer.accept(el.consumable); - } - - if (!pollute) target.add(Instruction.discard()); - } - - @Override public void destructDeclResolve(CompileResult target) { - for (var t : members) { - if (t.consumable instanceof Pattern p) p.destructDeclResolve(target); - else throw new SyntaxException(t.consumable.loc(), "Unexpected non-pattern in destruct context"); - } - } - - @Override public void destruct(CompileResult target, DeclarationType decl, boolean shouldDeclare) { - compile(target, t -> { - if (t instanceof Pattern p) p.destruct(target, decl, shouldDeclare); - else throw new SyntaxException(t.loc(), "Unexpected non-pattern in destruct context"); - }, false); - } - - @Override public void afterAssign(CompileResult target, boolean pollute) { - compile(target, t -> t.assign(target, false), pollute); - } - - @Override public void declare(CompileResult target, DeclarationType decl, boolean lateInitializer) { - if (lateInitializer) { - for (var t : members) { - if (t.consumable instanceof Pattern p) p.declare(target, decl, lateInitializer); - else throw new SyntaxException(t.consumable.loc(), "Unexpected non-pattern in destruct context"); - } - } - else throw new SyntaxException(loc(), "Object pattern must be initialized"); - } - - public ObjectPattern(Location loc, List members) { - super(loc); - this.members = members; - } - - private static ParseRes parseShorthand(Source src, int i) { - ParseRes res = ParseRes.first(src, i, - AssignPattern::parse, - VariableNode::parse - ); - - if (res.isSuccess()) { - if (res.result instanceof AssignPattern assign) { - if (assign.assignable instanceof VariableNode var) { - return ParseRes.res(new Member(new StringNode(var.loc(), var.name), res.result), res.n); - } - } - else if (res.result instanceof VariableNode var) { - return ParseRes.res(new Member(new StringNode(var.loc(), var.name), res.result), res.n); - } - } - - return res.chainError(); - } - private static ParseRes parseKeyed(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - - var key = ObjectNode.parsePropName(src, i + n); - if (!key.isSuccess()) return key.chainError(); - n += key.n; - n += Parsing.skipEmpty(src, i + n); - - if (!src.is(i + n , ":")) return ParseRes.failed(); - n++; - - ParseRes res = Pattern.parse(src, i + n, true); - if (!res.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a pattern after colon"); - n += res.n; - - return ParseRes.res(new Member(key.result, res.result), n); - } - - public static ParseRes parse(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - if (!src.is(i + n, "{")) return ParseRes.failed(); - n++; - n += Parsing.skipEmpty(src, i + n); - - var members = new LinkedList(); - - if (src.is(i + n, "}")) { - n++; - return ParseRes.res(new ObjectPattern(loc, members), n); - } - - while (true) { - ParseRes prop = ParseRes.first(src, i + n, - ObjectPattern::parseKeyed, - ObjectPattern::parseShorthand - ); - - if (!prop.isSuccess()) return prop.chainError(src.loc(i + n), "Expected a member in object pattern"); - n += prop.n; - - members.add(prop.result); - - n += Parsing.skipEmpty(src, i + n); - if (src.is(i + n, ",")) { - n++; - n += Parsing.skipEmpty(src, i + n); - - if (src.is(i + n, "}")) { - n++; - break; - } - - continue; - } - else if (src.is(i + n, "}")) { - n++; - break; - } - else ParseRes.error(src.loc(i + n), "Expected a comma or a closing brace."); - } - - return ParseRes.res(new ObjectPattern(loc, members), n); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/patterns/Pattern.java b/src/main/java/me/topchetoeu/jscript/compilation/patterns/Pattern.java deleted file mode 100644 index 07d07fb..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/patterns/Pattern.java +++ /dev/null @@ -1,59 +0,0 @@ -package me.topchetoeu.jscript.compilation.patterns; - -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.CompileResult; -import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType; -import me.topchetoeu.jscript.compilation.values.VariableNode; - -/** - * Represents all nodes that can be a destructors (note that all destructors are assign targets, too) - */ -public interface Pattern extends AssignTarget { - Location loc(); - - /** - * Called when the destructor has to declare - * @param target - */ - void destructDeclResolve(CompileResult target); - - /** - * Called when a declaration-like is being destructed - * @param decl The variable type the destructor must declare, if it is a named pne - */ - void destruct(CompileResult target, DeclarationType decl, boolean shouldDeclare); - - /** - * Run when destructing a declaration without an initializer - */ - void declare(CompileResult target, DeclarationType decl, boolean lateInitializer); - - public default void destructArg(CompileResult target, DeclarationType decl) { - destruct(target, decl, false); - } - public default void destructVar(CompileResult target, DeclarationType decl, boolean hasInitializer) { - if (hasInitializer) { - if (decl == null || !decl.strict) destruct(target, null, true); - else destruct(target, decl, true); - } - else { - if (decl == null || !decl.strict) declare(target, null, false); - else declare(target, decl, false); - } - } - - public static ParseRes parse(Source src, int i, boolean withDefault) { - return withDefault ? - ParseRes.first(src, i, - AssignPattern::parse, - ObjectPattern::parse, - VariableNode::parse - ) : - ParseRes.first(src, i, - ObjectPattern::parse, - VariableNode::parse - ); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/scope/FunctionScope.java b/src/main/java/me/topchetoeu/jscript/compilation/scope/FunctionScope.java index c5319bc..213f020 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/scope/FunctionScope.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/scope/FunctionScope.java @@ -1,98 +1,161 @@ package me.topchetoeu.jscript.compilation.scope; +import java.util.ArrayList; import java.util.HashMap; -import java.util.HashSet; -import me.topchetoeu.jscript.common.parsing.Location; - -public class FunctionScope extends Scope { +public final class FunctionScope { + protected final VariableList locals = new VariableList(VariableIndex.IndexType.LOCALS); + protected final VariableList capturables = new VariableList(VariableIndex.IndexType.CAPTURABLES, this.locals); private final VariableList captures = new VariableList(VariableIndex.IndexType.CAPTURES); - private final HashMap specialVarMap = new HashMap<>(); - private final HashMap functionVarMap = new HashMap<>(); + private final HashMap localsMap = new HashMap<>(); private final HashMap capturesMap = new HashMap<>(); - private final HashSet blacklistNames = new HashSet<>(); + private final ArrayList catchesMap = new ArrayList<>(); private final HashMap childToParent = new HashMap<>(); + private final HashMap parentToChild = new HashMap<>(); - private final Scope captureParent; + public final FunctionScope parent; + public final boolean passthrough; - public final boolean passtrough; - - @Override public boolean hasNonStrict(String name) { - if (functionVarMap.containsKey(name)) return true; - if (blacklistNames.contains(name)) return true; - - return false; + private Variable addCaptured(Variable var, boolean captured) { + if (captured && !this.capturables.has(var)) this.capturables.add(var); + return var; } - @Override public Variable define(Variable var, Location loc) { - checkNotEnded(); - if (strictVarMap.containsKey(var.name)) throw alreadyDefinedErr(loc, var.name); + private Variable getCatchVar(String name) { + for (var el : catchesMap) { + if (el.name.equals(name)) return el; + } - if (passtrough) { - blacklistNames.add(var.name); - return null; - } + return null; + } + + /** + * @returns If a variable with the same name exists, the old variable. Otherwise, the given variable + */ + public Variable define(Variable var) { + if (passthrough) return null; else { - functionVarMap.put(var.name, var); + var catchVar = getCatchVar(var.name); + if (catchVar != null) return catchVar; + if (localsMap.containsKey(var.name)) return localsMap.get(var.name); + if (capturesMap.containsKey(var.name)) throw new RuntimeException("HEY!"); + + localsMap.put(var.name, var); return locals.add(var); } } - public Variable defineSpecial(Variable var, Location loc) { - checkNotEnded(); - if (strictVarMap.containsKey(var.name)) throw alreadyDefinedErr(loc, var.name); - specialVarMap.put(var.name, var); - return locals.add(var); + /** + * @returns A variable with the given name, or null if a global variable + */ + public Variable define(String name) { + return define(new Variable(name, false)); } - @Override public Variable get(String name, boolean capture) { - var superRes = super.get(name, capture); - if (superRes != null) return superRes; + /** + * Creates a catch variable and returns it + */ + public Variable defineCatch(String name) { + var var = new Variable(name, false); + this.locals.add(var); + this.catchesMap.add(var); + return var; + } + /** + * Removes the last catch variable. + * NOTE: the variable is still in the internal list. It just won't be findable by its name + */ + public void undefineCatch() { + this.catchesMap.remove(this.catchesMap.size() - 1); + } - if (specialVarMap.containsKey(name)) return addCaptured(specialVarMap.get(name), capture); - if (functionVarMap.containsKey(name)) return addCaptured(functionVarMap.get(name), capture); + /** + * Gets the index supplier of the given variable name, or null if it is a global + * + * @param capture If true, the variable is being captured by a function + */ + public Variable get(String name, boolean capture) { + var catchVar = getCatchVar(name); + + if (catchVar != null) return addCaptured(catchVar, capture); + if (localsMap.containsKey(name)) return addCaptured(localsMap.get(name), capture); if (capturesMap.containsKey(name)) return addCaptured(capturesMap.get(name), capture); - if (captureParent == null) return null; + if (parent == null) return null; - var parentVar = captureParent.get(name, true); + var parentVar = parent.get(name, true); if (parentVar == null) return null; - var childVar = captures.add(parentVar.clone()); + var childVar = captures.add(parentVar.clone().setIndexSupplier(null)); capturesMap.put(childVar.name, childVar); childToParent.put(childVar, parentVar); + parentToChild.put(parentVar, childVar); return childVar; } - @Override public boolean has(String name, boolean capture) { - if (functionVarMap.containsKey(name)) return true; - if (specialVarMap.containsKey(name)) return true; + /** + * If the variable given is contained in this function, just returns the variable itself. + * However, this function is important to handle cases in which you might want to access + * a captured variable. In such cases, this function will return a capture to the given variable. + * + * @param capture Whether or not to execute this capturing logic + */ + public Variable get(Variable var, boolean capture) { + if (captures.has(var)) return addCaptured(var, capture); + if (locals.has(var)) return addCaptured(var, capture); + + if (capture) { + if (parentToChild.containsKey(var)) return addCaptured(parentToChild.get(var), capture); + + if (parent == null) return null; + + var parentVar = parent.get(var, true); + if (parentVar == null) return null; + + var childVar = captures.add(parentVar.clone()); + childToParent.put(childVar, parentVar); + parentToChild.put(parentVar, childVar); + + return childVar; + } + else return null; + } + + /** + * Checks if the given variable name is accessible + * + * @param capture If true, will check beyond this function's scope + */ + public boolean has(String name, boolean capture) { + if (localsMap.containsKey(name)) return true; + // if (specialVarMap.containsKey(name)) return true; if (capture) { if (capturesMap.containsKey(name)) return true; - if (captureParent != null) return captureParent.has(name, true); + if (parent != null) return parent.has(name, true); } return false; } - @Override protected void onFinish() { - captures.freeze(); - super.onFinish(); + public int localsCount() { + return locals.size(); } - - @Override public int capturesCount() { + public int capturesCount() { return captures.size(); } + public int capturablesCount() { + return capturables.size(); + } public int[] getCaptureIndices() { var res = new int[captures.size()]; var i = 0; - for (var el : captures.all()) { + for (var el : captures) { assert childToParent.containsKey(el); res[i] = childToParent.get(el).index().toCaptureIndex(); i++; @@ -101,17 +164,43 @@ public class FunctionScope extends Scope { return res; } - public FunctionScope(Scope parent) { - super(); - if (parent.finished()) throw new RuntimeException("Parent is finished"); - this.captureParent = parent; - this.passtrough = false; - this.singleEntry = false; + public Iterable capturables() { + return capturables; } - public FunctionScope(boolean passtrough) { - super(); - this.captureParent = null; - this.passtrough = passtrough; - this.singleEntry = false; + public Iterable locals() { + return locals; + } + + public String[] captureNames() { + var res = new String[this.captures.size()]; + var i = 0; + + for (var el : this.captures) { + res[i++] = el.name; + } + + return res; + } + public String[] localNames() { + var res = new String[this.locals.size() + this.capturables.size()]; + var i = 0; + + for (var el : this.locals) { + res[i++] = el.name; + } + for (var el : this.capturables) { + res[i++] = el.name; + } + + return res; + } + + public FunctionScope(FunctionScope parent) { + this.parent = parent; + this.passthrough = false; + } + public FunctionScope(boolean passthrough) { + this.parent = null; + this.passthrough = passthrough; } } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java b/src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java deleted file mode 100644 index bed8f93..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java +++ /dev/null @@ -1,237 +0,0 @@ -package me.topchetoeu.jscript.compilation.scope; - -import java.util.HashMap; -import java.util.LinkedList; - -import me.topchetoeu.jscript.common.SyntaxException; -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType; - -public class Scope { - protected final HashMap strictVarMap = new HashMap<>(); - - protected final VariableList locals = new VariableList(VariableIndex.IndexType.LOCALS, this::variableOffset); - protected final VariableList capturables = new VariableList(VariableIndex.IndexType.CAPTURABLES, this::capturablesOffset); - - private boolean ended = false; - private boolean finished = false; - private Scope child; - private LinkedList children = new LinkedList<>(); - - public final Scope parent; - - /** - * Wether or not the scope is going to be entered multiple times. - * If set to true, captured variables will be kept as allocations, otherwise will be converted to locals - */ - public boolean singleEntry = true; - - - protected void transferCaptured(Variable var) { - if (!singleEntry) { - this.capturables.add(var); - } - else if (parent != null) { - parent.transferCaptured(var); - } - else throw new IllegalStateException("Couldn't transfer captured variable"); - } - - protected final Variable addCaptured(Variable var, boolean captured) { - if (captured) transferCaptured(var); - return var; - } - - protected final SyntaxException alreadyDefinedErr(Location loc, String name) { - return new SyntaxException(loc, String.format("Identifier '%s' has already been declared", name)); - } - - /** - * Throws if the scope is ended - */ - protected final void checkNotEnded() { - if (ended) throw new IllegalStateException("Cannot define in an ended scope"); - } - - /** - * Defines a nameless variable for holding intermediate temporary values - * - * @throws RuntimeException If the scope is finalized or has an active child - */ - public Variable defineTemp() { - checkNotEnded(); - return this.locals.add(new Variable("", false)); - } - - /** - * Defines an ES5-style variable - * - * @returns The index supplier of the variable if it is a local, or null if it is a global - * @throws SyntaxException If an ES2015-style variable with the same name exists anywhere from the current function to the current scope - * @throws RuntimeException If the scope is finalized or has an active child - */ - public Variable define(Variable var, Location loc) { - checkNotEnded(); - if (strictVarMap.containsKey(var.name)) throw alreadyDefinedErr(loc, var.name); - if (parent != null) return parent.define(var, loc); - - return null; - } - - /** - * Checks if this scope's function parent has a non-strict variable of the given name - */ - public boolean hasNonStrict(String name) { return false; } - - /** - * Defines an ES2015-style variable - * @param readonly True if const, false if let - * @return The index supplier of the variable - * @throws SyntaxException If any variable with the same name exists in the current scope - * @throws RuntimeException If the scope is finalized or has an active child - */ - public Variable defineStrict(Variable var, Location loc) { - checkNotEnded(); - if (strictVarMap.containsKey(var.name)) throw alreadyDefinedErr(loc, var.name); - if (hasNonStrict(var.name)) throw alreadyDefinedErr(loc, var.name); - - strictVarMap.put(var.name, var); - return locals.add(var); - } - /** - * Gets the index supplier of the given variable name, or null if it is a global - * - * @param capture If true, the variable is being captured by a function - */ - public Variable get(String name, boolean capture) { - var res = strictVarMap.get(name); - - if (res != null) return addCaptured(res, capture); - if (parent != null) return parent.get(name, capture); - - return null; - } - /** - * Checks if the given variable name is accessible - * - * @param capture If true, will check beyond this function's scope - */ - public boolean has(String name, boolean capture) { - if (strictVarMap.containsKey(name)) return true; - if (parent != null) return parent.has(name, capture); - - return false; - } - /** - * Gets the index offset from this scope to its children - */ - public final int variableOffset() { - var res = 0; - - for (var curr = parent; curr != null; curr = curr.parent) { - res += parent.locals.size(); - } - - return res; - } - public final int capturablesOffset() { - var res = 0; - - for (var curr = this; curr != null; curr = curr.parent) { - if (curr != this) res += parent.capturables.size(); - if (curr.parent == null) res += curr.localsCount(); - } - - return res; - } - - public final Variable define(DeclarationType type, String name, Location loc) { - if (type.strict) return defineStrict(new Variable(name, type.readonly), loc); - else return define(new Variable(name, type.readonly), loc); - } - - public int localsCount() { - var res = 0; - for (var child : children) { - var childN = child.localsCount(); - if (res < childN) res = childN; - } - - return res + locals.size(); - } - public int capturesCount() { return 0; } - public int allocCount() { - var res = capturables.size(); - return res; - } - public int capturablesCount() { - var res = capturables.size(); - for (var child : children) res += child.capturablesCount(); - return res; - } - - public Iterable capturables() { - return capturables.all(); - } - public Iterable locals() { - return locals.all(); - } - - /** - * Ends this scope. This will make it possible for another child to take its place - */ - public boolean end() { - if (ended) return false; - - this.ended = true; - - if (this.parent != null) { - assert this.parent.child == this; - this.parent.child = null; - } - - return true; - } - - protected void onFinish() { - this.locals.freeze(); - this.capturables.freeze(); - } - - /** - * Finalizes this scope. The scope will become immutable after this call - * @return - */ - public final boolean finish() { - if (finished) return false; - - if (parent != null && parent.finished) throw new IllegalStateException("Tried to finish a child after the parent was finished"); - this.onFinish(); - - for (var child : children) child.finish(); - - this.finished = true; - - return true; - } - - public final boolean ended() { return ended; } - public final boolean finished() { return finished; } - public final Scope child() { return child; } - - public Scope() { - this(null); - } - public Scope(Scope parent) { - if (parent != null) { - if (parent.ended) throw new RuntimeException("Parent is not active"); - if (parent.finished) throw new RuntimeException("Parent is finished"); - if (parent.child != null) throw new RuntimeException("Parent has an active child"); - - this.parent = parent; - this.parent.child = this; - this.parent.children.add(this); - } - else this.parent = null; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/scope/Variable.java b/src/main/java/me/topchetoeu/jscript/compilation/scope/Variable.java index 3a91792..571f796 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/scope/Variable.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/scope/Variable.java @@ -4,20 +4,14 @@ import java.util.function.Supplier; public final class Variable { private Supplier indexSupplier; - private boolean frozen; public final boolean readonly; public final String name; public final VariableIndex index() { - if (!frozen) throw new IllegalStateException("Tried to access the index of a variable before it was finalized"); return indexSupplier.get(); } - public final void freeze() { - this.frozen = true; - } - public final Variable setIndexSupplier(Supplier index) { this.indexSupplier = index; return this; diff --git a/src/main/java/me/topchetoeu/jscript/compilation/scope/VariableIndex.java b/src/main/java/me/topchetoeu/jscript/compilation/scope/VariableIndex.java index 374d7b1..ea01ac9 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/scope/VariableIndex.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/scope/VariableIndex.java @@ -4,12 +4,21 @@ import me.topchetoeu.jscript.common.Instruction; public final class VariableIndex { public static enum IndexType { + /** + * A simple variable that is only ever used within the function + */ LOCALS, + /** + * A variable that has the ability to be captured by children functions + */ CAPTURABLES, + /** + * A variable that has been captured from the parent function + */ CAPTURES, } - public final VariableIndex.IndexType type; + public final IndexType type; public final int index; public final int toCaptureIndex() { @@ -36,22 +45,6 @@ public final class VariableIndex { default: throw new UnsupportedOperationException("Unknown index type " + type); } } - public final Instruction toInit() { - switch (type) { - case CAPTURES: throw new UnsupportedOperationException("Unknown index type " + type); - case CAPTURABLES: return Instruction.storeVar(index, false, true); - case LOCALS: return Instruction.storeVar(index, false, true); - default: throw new UnsupportedOperationException("Unknown index type " + type); - } - } - public final Instruction toUndefinedInit(boolean force) { - switch (type) { - case CAPTURES: throw new UnsupportedOperationException("Unknown index type " + type); - case CAPTURABLES: return Instruction.varInit(index, force); - case LOCALS: return Instruction.varInit(index, force); - default: throw new UnsupportedOperationException("Unknown index type " + type); - } - } public VariableIndex(VariableIndex.IndexType type, int index) { this.type = type; diff --git a/src/main/java/me/topchetoeu/jscript/compilation/scope/VariableList.java b/src/main/java/me/topchetoeu/jscript/compilation/scope/VariableList.java index 3e71689..414b4d2 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/scope/VariableList.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/scope/VariableList.java @@ -1,48 +1,36 @@ package me.topchetoeu.jscript.compilation.scope; -import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.function.IntSupplier; import java.util.function.Supplier; -public final class VariableList { +import me.topchetoeu.jscript.compilation.scope.VariableIndex.IndexType; + +public final class VariableList implements Iterable { private final class VariableNode implements Supplier { public Variable var; public VariableNode next; public VariableNode prev; - public boolean frozen; public int index; + public int indexIteration = -1; public VariableList list() { return VariableList.this; } + private int getIndex() { + if (this.indexIteration != VariableList.this.indexIteration) { + this.indexIteration = VariableList.this.indexIteration; + + if (prev == null) this.index = 0; + else this.index = prev.getIndex() + 1; + } + + return this.index; + } + @Override public VariableIndex get() { - if (frozen) { - if (offset == null) return new VariableIndex(indexType, index); - else new VariableIndex(indexType, index + offset.getAsInt()); - } - - var res = 0; - if (offset != null) res = offset.getAsInt(); - - for (var it = prev; it != null; it = it.prev) { - res++; - } - - return new VariableIndex(indexType, res); - } - - public void freeze() { - if (frozen) return; - this.frozen = true; - this.next = null; - this.var.freeze(); - if (prev == null) return; - - this.index = prev.index + 1; - this.next = null; - - return; + if (offset == null) return new VariableIndex(indexType, this.getIndex()); + else return new VariableIndex(indexType, offset.getAsInt() + this.getIndex()); } public VariableNode(Variable var, VariableNode next, VariableNode prev) { @@ -54,32 +42,25 @@ public final class VariableList { private VariableNode first, last; - private ArrayList frozenList = null; private HashMap varMap = new HashMap<>(); private final IntSupplier offset; + /** + * Increased when indices need recalculation. VariableNode will check if + * its internal indexIteration is up to date with this, and if not, will + * recalculate its index + */ + private int indexIteration = 0; public final VariableIndex.IndexType indexType; - public boolean frozen() { - if (frozenList != null) { - assert frozenList != null; - assert varMap == null; - assert first == null; - assert last == null; - - return true; - } - else { - assert frozenList == null; - assert varMap != null; - - return false; - } - } - + /** + * Adds the given variable to this list. If it already exists, does nothing + * @return val + */ public Variable add(Variable val) { - if (frozen()) throw new RuntimeException("The scope has been frozen"); + if (this.varMap.containsKey(val)) return val; + this.indexIteration++; if (val.indexSupplier() instanceof VariableNode prevNode) { prevNode.list().remove(val); @@ -105,14 +86,18 @@ public final class VariableList { return val; } + /** + * If the variable is not in the list, does nothing. Otherwise, removes the variable from the list + * @return null if nothing was done, else the deleted variable (should be var) + */ public Variable remove(Variable var) { - if (frozen()) throw new RuntimeException("The scope has been frozen"); - if (var == null) return null; var node = varMap.get(var); if (node == null) return null; + this.indexIteration++; + if (node.prev != null) { assert node != first; node.prev.next = node.next; @@ -135,39 +120,31 @@ public final class VariableList { node.prev = null; varMap.remove(node.var); + node.var.setIndexSupplier(null); return node.var; } + /** + * Checks if the list has the given variable + */ + public boolean has(Variable var) { + return varMap.containsKey(var); + } + + /** + * Returns an indexer for the given variable + */ public Supplier indexer(Variable var) { return varMap.get(var); } public int size() { - if (frozen()) return frozenList.size(); - else return varMap.size(); + return varMap.size(); } - public void freeze() { - if (frozen()) return; - - frozenList = new ArrayList<>(); - - for (var node = first; node != null; ) { - frozenList.add(node); - - var tmp = node; - node = node.next; - tmp.freeze(); - } - - first = last = null; - varMap = null; - } - - public Iterable all() { - if (frozen()) return () -> frozenList.stream().map(v -> v.var).iterator(); - else return () -> new Iterator() { + public Iterator iterator() { + return new Iterator() { private VariableNode curr = first; @Override public boolean hasNext() { @@ -183,19 +160,31 @@ public final class VariableList { }; } - public VariableList(VariableIndex.IndexType type, IntSupplier offset) { + /** + * @param offset Will offset the indices by the given amount from the supplier + */ + public VariableList(IndexType type, IntSupplier offset) { this.indexType = type; this.offset = offset; } - public VariableList(VariableIndex.IndexType type, int offset) { + /** + * @param offset Will offset the indices by the given amount + */ + public VariableList(IndexType type, int offset) { this.indexType = type; this.offset = () -> offset; } - public VariableList(VariableIndex.IndexType type, VariableList prev) { + /** + * @param offset Will offset the indices by the size of the given list + */ + public VariableList(IndexType type, VariableList prev) { this.indexType = type; - this.offset = prev::size; + this.offset = () -> { + if (prev.offset != null) return prev.offset.getAsInt() + prev.size(); + else return prev.size(); + }; } - public VariableList(VariableIndex.IndexType type) { + public VariableList(IndexType type) { this.indexType = type; this.offset = null; } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/ArgumentsNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/ArgumentsNode.java index 38ab529..2ed09d0 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/ArgumentsNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/ArgumentsNode.java @@ -7,8 +7,11 @@ import me.topchetoeu.jscript.compilation.Node; public class ArgumentsNode extends Node { + @Override public void compileFunctions(CompileResult target) { + } + @Override public void compile(CompileResult target, boolean pollute) { - if (pollute) target.add(Instruction.loadArgs(false)); + if (pollute) target.add(Instruction.loadArgs()); } public ArgumentsNode(Location loc) { diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/ArrayNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/ArrayNode.java index 44db447..8e4ba22 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/ArrayNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/ArrayNode.java @@ -15,6 +15,12 @@ import me.topchetoeu.jscript.compilation.Node; public class ArrayNode extends Node { public final Node[] statements; + @Override public void compileFunctions(CompileResult target) { + for (var stm : statements) { + if (stm != null) stm.compileFunctions(target); + } + } + @Override public void compile(CompileResult target, boolean pollute) { target.add(Instruction.loadArr(statements.length)); diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/ClassValueNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/ClassValueNode.java deleted file mode 100644 index cf4434e..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/ClassValueNode.java +++ /dev/null @@ -1,30 +0,0 @@ -package me.topchetoeu.jscript.compilation.values; - -import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.common.parsing.ParseRes; -import me.topchetoeu.jscript.common.parsing.Parsing; -import me.topchetoeu.jscript.common.parsing.Source; -import me.topchetoeu.jscript.compilation.ClassNode; - -public class ClassValueNode extends ClassNode { - public ClassValueNode(Location loc, Location end, String name, ClassBody body) { - super(loc, end, name, body); - } - - public static ParseRes parse(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - if (!Parsing.isIdentifier(src, i + n, "class")) return ParseRes.failed(); - n += 5; - - var name = Parsing.parseIdentifier(src, i + n); - n += name.n; - - var body = parseBody(src, i + n); - if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a class body"); - n += body.n; - - return ParseRes.res(new ClassValueNode(loc, src.loc(i + n), name.result, body.result), n); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/GlobalThisNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/GlobalThisNode.java index 11823a3..ac83d7d 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/GlobalThisNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/GlobalThisNode.java @@ -7,6 +7,9 @@ import me.topchetoeu.jscript.compilation.Node; public class GlobalThisNode extends Node { + @Override public void compileFunctions(CompileResult target) { + } + @Override public void compile(CompileResult target, boolean pollute) { if (pollute) target.add(Instruction.loadGlob()); } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectNode.java index eee8f17..7b41a28 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectNode.java @@ -4,7 +4,6 @@ import java.util.LinkedList; import java.util.List; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.SyntaxException; import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.common.parsing.ParseRes; import me.topchetoeu.jscript.common.parsing.Parsing; @@ -12,77 +11,25 @@ import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.Node; -import me.topchetoeu.jscript.compilation.members.AssignShorthandNode; import me.topchetoeu.jscript.compilation.members.FieldMemberNode; -import me.topchetoeu.jscript.compilation.members.MethodMemberNode; +import me.topchetoeu.jscript.compilation.members.Member; import me.topchetoeu.jscript.compilation.members.PropertyMemberNode; -import me.topchetoeu.jscript.compilation.patterns.AssignTarget; -import me.topchetoeu.jscript.compilation.patterns.AssignTargetLike; -import me.topchetoeu.jscript.compilation.patterns.ObjectPattern; import me.topchetoeu.jscript.compilation.values.constants.NumberNode; import me.topchetoeu.jscript.compilation.values.constants.StringNode; -public class ObjectNode extends Node implements AssignTargetLike { - public final List members; +public class ObjectNode extends Node { + public final List members; - // TODO: Implement spreading into object - - // private void compileRestObjBuilder(CompileResult target, int srcDupN) { - // var subtarget = target.subtarget(); - // var src = subtarget.scope.defineTemp(); - // var dst = subtarget.scope.defineTemp(); - - // target.add(Instruction.loadObj()); - // target.add(_i -> src.index().toSet(true)); - // target.add(_i -> dst.index().toSet(destructors.size() > 0)); - - // target.add(Instruction.keys(true, true)); - // var start = target.size(); - - // target.add(Instruction.dup()); - // var mid = target.temp(); - - // target.add(_i -> src.index().toGet()); - // target.add(Instruction.dup(1, 1)); - // target.add(Instruction.loadMember()); - - // target.add(_i -> dst.index().toGet()); - // target.add(Instruction.dup(1, 1)); - // target.add(Instruction.storeMember()); - - // target.add(Instruction.discard()); - // var end = target.size(); - // target.add(Instruction.jmp(start - end)); - // target.set(mid, Instruction.jmpIfNot(end - mid + 1)); - - // target.add(Instruction.discard()); - - // target.add(Instruction.dup(srcDupN, 1)); - - // target.scope.end(); - // } + @Override public void compileFunctions(CompileResult target) { + for (var member : members) member.compileFunctions(target); + } @Override public void compile(CompileResult target, boolean pollute) { target.add(Instruction.loadObj()); for (var el : members) el.compile(target, true); } - @Override public AssignTarget toAssignTarget() { - var newMembers = new LinkedList(); - - for (var el : members) { - if (el instanceof FieldMemberNode field) { - if (field.value instanceof AssignTargetLike target) newMembers.add(new ObjectPattern.Member(field.key, target.toAssignTarget())); - else throw new SyntaxException(field.value.loc(), "Expected an assignable in deconstructor"); - } - else if (el instanceof AssignShorthandNode shorthand) newMembers.add(new ObjectPattern.Member(shorthand.key, shorthand.target())); - else throw new SyntaxException(el.loc(), "Unexpected member in deconstructor"); - } - - return new ObjectPattern(loc(), newMembers); - } - - public ObjectNode(Location loc, List map) { + public ObjectNode(Location loc, List map) { super(loc); this.members = map; } @@ -126,7 +73,7 @@ public class ObjectNode extends Node implements AssignTargetLike { n++; n += Parsing.skipEmpty(src, i + n); - var members = new LinkedList(); + var members = new LinkedList(); if (src.is(i + n, "}")) { n++; @@ -134,12 +81,9 @@ public class ObjectNode extends Node implements AssignTargetLike { } while (true) { - ParseRes prop = ParseRes.first(src, i + n, - MethodMemberNode::parse, + ParseRes prop = ParseRes.first(src, i + n, PropertyMemberNode::parse, - FieldMemberNode::parseObject, - AssignShorthandNode::parse, - FieldMemberNode::parseShorthand + FieldMemberNode::parse ); if (!prop.isSuccess()) return prop.chainError(src.loc(i + n), "Expected a member in object literal"); n += prop.n; diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/RegexNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/RegexNode.java index 7ac7c51..11cab99 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/RegexNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/RegexNode.java @@ -11,12 +11,14 @@ import me.topchetoeu.jscript.compilation.Node; public class RegexNode extends Node { public final String pattern, flags; + @Override public void compileFunctions(CompileResult target) { + } + @Override public void compile(CompileResult target, boolean pollute) { target.add(Instruction.loadRegex(pattern, flags)); if (!pollute) target.add(Instruction.discard()); } - public static ParseRes parse(Source src, int i) { var n = Parsing.skipEmpty(src, i); @@ -29,28 +31,35 @@ public class RegexNode extends Node { var inBrackets = false; - while (true) { - if (src.is(i + n, '[')) { - n++; - inBrackets = true; - source.append(src.at(i + n)); - continue; - } - else if (src.is(i + n, ']')) { - n++; - inBrackets = false; - source.append(src.at(i + n)); - continue; - } - else if (src.is(i + n, '/') && !inBrackets) { - n++; - break; - } - - var charRes = Parsing.parseChar(src, i + n); - if (charRes.result == null) return ParseRes.error(src.loc(i + n), "Multiline regular expressions are not allowed"); - source.append(charRes.result); - n++; + loop: while (true) { + switch (src.at(i + n)) { + case '[': + inBrackets = true; + source.append('['); + n++; + continue; + case ']': + inBrackets = false; + source.append(']'); + n++; + continue; + case '/': + n++; + if (inBrackets) { + source.append('/'); + continue; + } + else break loop; + case '\\': + source.append('\\'); + source.append(src.at(i + n + 1)); + n += 2; + break; + default: + source.append(src.at(i + n)); + n++; + break; + } } while (true) { diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/ThisNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/ThisNode.java index ad74007..27a05b3 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/ThisNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/ThisNode.java @@ -7,6 +7,9 @@ import me.topchetoeu.jscript.compilation.Node; public class ThisNode extends Node { + @Override public void compileFunctions(CompileResult target) { + } + @Override public void compile(CompileResult target, boolean pollute) { if (pollute) target.add(Instruction.loadThis()); } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/VariableNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/VariableNode.java index ae32c9c..9e76617 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/VariableNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/VariableNode.java @@ -1,9 +1,6 @@ package me.topchetoeu.jscript.compilation.values; -import java.util.function.IntFunction; - import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.SyntaxException; import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.common.parsing.ParseRes; import me.topchetoeu.jscript.common.parsing.Parsing; @@ -11,98 +8,46 @@ import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.Node; -import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType; import me.topchetoeu.jscript.compilation.patterns.ChangeTarget; -import me.topchetoeu.jscript.compilation.patterns.Pattern; -import me.topchetoeu.jscript.compilation.scope.Variable; -public class VariableNode extends Node implements Pattern, ChangeTarget { +public class VariableNode extends Node implements ChangeTarget { public final String name; + @Override public void compileFunctions(CompileResult target) { + } + public String assignName() { return name; } @Override public void beforeChange(CompileResult target) { target.add(VariableNode.toGet(target, loc(), name)); } - @Override public void destructDeclResolve(CompileResult target) { - var i = target.scope.define(new Variable(name, false), loc()); - if (i != null) target.add(_i -> i.index().toUndefinedInit(false)); - } - @Override public void afterAssign(CompileResult target, boolean pollute) { - target.add(VariableNode.toSet(target, loc(), name, pollute)); - } - - @Override public void declare(CompileResult target, DeclarationType decl, boolean lateInitializer) { - if (decl != null) { - var i = target.scope.define(decl, name, loc()); - target.add(_i -> i.index().toUndefinedInit(decl.strict)); - } - else target.add(_i -> { - var i = target.scope.get(name, false); - - if (i == null) return Instruction.globDef(name); - else return Instruction.nop(); - }); - } - - @Override public void destruct(CompileResult target, DeclarationType decl, boolean shouldDeclare) { - if (!shouldDeclare || decl == null) { - if (shouldDeclare) target.add(VariableNode.toInit(target, loc(), name)); - else target.add(VariableNode.toInit(target, loc(), name)); - } - else { - if (decl == DeclarationType.VAR && target.scope.has(name, false)) throw new SyntaxException(loc(), "Duplicate parameter name not allowed"); - var v = target.scope.define(decl, name, loc()); - target.add(_i -> v.index().toInit()); - } + target.add(VariableNode.toSet(target, loc(), name, pollute, false)).setLocation(loc()); } @Override public void compile(CompileResult target, boolean pollute) { - target.add(toGet(target, loc(), name, true, false)); + target.add(toGet(target, loc(), name, true, false)).setLocation(loc()); } - public static IntFunction toGet(CompileResult target, Location loc, String name, boolean keep, boolean forceGet) { - var oldI = target.scope.get(name, false); + public static Instruction toGet(CompileResult target, Location loc, String name, boolean keep, boolean forceGet) { + var i = target.scope.get(name, false); - if (oldI != null) { - if (keep) return _i -> oldI.index().toGet(); - else return _i -> Instruction.nop(); - } - else return _i -> { - var newI = target.scope.get(name, false); - - if (newI == null) return Instruction.globGet(name, forceGet); - else if (keep) return newI.index().toGet(); + if (i != null) { + if (keep) return i.index().toGet(); else return Instruction.nop(); - }; + } + else return Instruction.globGet(name, forceGet); } - public static IntFunction toGet(CompileResult target, Location loc, String name) { + public static Instruction toGet(CompileResult target, Location loc, String name) { return toGet(target, loc, name, true, false); } - public static IntFunction toInit(CompileResult target, Location loc, String name) { - var oldI = target.scope.get(name, false); + public static Instruction toSet(CompileResult target, Location loc, String name, boolean keep, boolean init) { + var i = target.scope.get(name, false); - if (oldI != null) return _i -> oldI.index().toInit(); - else return _i -> { - var i = target.scope.get(name, false); - - if (i == null) return Instruction.globSet(name, false, true); - else return i.index().toInit(); - }; - } - public static IntFunction toSet(CompileResult target, Location loc, String name, boolean keep) { - var oldI = target.scope.get(name, false); - - if (oldI != null) return _i -> oldI.index().toSet(keep); - else return _i -> { - var i = target.scope.get(name, false); - - if (i == null) return Instruction.globSet(name, keep, false); - else return i.index().toSet(keep); - }; + if (i != null) return i.index().toSet(keep); + else return Instruction.globSet(name, keep, init); } public VariableNode(Location loc, String name) { diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/constants/BoolNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/constants/BoolNode.java index 8a5462b..4eab801 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/constants/BoolNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/constants/BoolNode.java @@ -8,6 +8,9 @@ import me.topchetoeu.jscript.compilation.Node; public class BoolNode extends Node { public final boolean value; + @Override public void compileFunctions(CompileResult target) { + } + @Override public void compile(CompileResult target, boolean pollute) { if (pollute) target.add(Instruction.pushValue(value)); } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/constants/NullNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/constants/NullNode.java index cae8bd6..3b1ff02 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/constants/NullNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/constants/NullNode.java @@ -6,6 +6,9 @@ import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.Node; public class NullNode extends Node { + @Override public void compileFunctions(CompileResult target) { + } + @Override public void compile(CompileResult target, boolean pollute) { if (pollute) target.add(Instruction.pushNull()); } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/constants/NumberNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/constants/NumberNode.java index 5afd40e..d76a949 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/constants/NumberNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/constants/NumberNode.java @@ -11,6 +11,9 @@ import me.topchetoeu.jscript.compilation.Node; public class NumberNode extends Node { public final double value; + @Override public void compileFunctions(CompileResult target) { + } + @Override public void compile(CompileResult target, boolean pollute) { if (pollute) target.add(Instruction.pushValue(value)); } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/constants/StringNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/constants/StringNode.java index e50d82b..ede91c3 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/constants/StringNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/constants/StringNode.java @@ -11,6 +11,9 @@ import me.topchetoeu.jscript.compilation.Node; public class StringNode extends Node { public final String value; + @Override public void compileFunctions(CompileResult target) { + } + @Override public void compile(CompileResult target, boolean pollute) { if (pollute) target.add(Instruction.pushValue(value)); } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/AssignNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/AssignNode.java index 20ccbb2..6860f09 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/AssignNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/AssignNode.java @@ -12,6 +12,11 @@ public class AssignNode extends Node implements AssignTarget { public final AssignTarget assignable; public final Node value; + @Override public void compileFunctions(CompileResult target) { + ((Node)assignable).compileFunctions(target); + value.compileFunctions(target); + } + @Override public void compile(CompileResult target, boolean pollute) { if (assignable instanceof AssignNode other) throw new SyntaxException(other.loc(), "Assign deconstructor not allowed here"); diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/CallNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/CallNode.java index d1414bb..150ea36 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/CallNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/CallNode.java @@ -4,8 +4,6 @@ import java.util.ArrayList; import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Instruction.BreakpointType; -import me.topchetoeu.jscript.common.json.JSON; -import me.topchetoeu.jscript.common.json.JSONElement; import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.common.parsing.ParseRes; import me.topchetoeu.jscript.common.parsing.Parsing; @@ -13,104 +11,38 @@ import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.Node; -import me.topchetoeu.jscript.compilation.values.ArgumentsNode; -import me.topchetoeu.jscript.compilation.values.ArrayNode; -import me.topchetoeu.jscript.compilation.values.ObjectNode; -import me.topchetoeu.jscript.compilation.values.ThisNode; -import me.topchetoeu.jscript.compilation.values.VariableNode; -import me.topchetoeu.jscript.compilation.values.constants.BoolNode; -import me.topchetoeu.jscript.compilation.values.constants.NumberNode; -import me.topchetoeu.jscript.compilation.values.constants.StringNode; public class CallNode extends Node { - public static boolean ATTACH_NAME = true; - public final Node func; public final Node[] args; public final boolean isNew; - private String generateName(Node func, Node index) { - String res = "(intermediate value)"; - boolean shouldParen = false; - - if (func instanceof ObjectNode) { - var obj = (ObjectNode)func; - - shouldParen = true; - - if (obj.members.size() > 0) res = "{}"; - else res = "{(intermediate value)}"; - } - else if (func instanceof StringNode) { - res = JSON.stringify(JSONElement.string(((StringNode)func).value)); - } - else if (func instanceof NumberNode) { - res = JSON.stringify(JSONElement.number(((NumberNode)func).value)); - } - else if (func instanceof BoolNode) { - res = ((BoolNode)func).value ? "true" : "false"; - } - else if (func instanceof VariableNode) { - res = ((VariableNode)func).name; - } - else if (func instanceof ThisNode) { - res = "this"; - } - else if (func instanceof ArgumentsNode) { - res = "arguments"; - } - else if (func instanceof ArrayNode) { - var els = new ArrayList(); - - for (var el : ((ArrayNode)func).statements) { - if (el != null) els.add(generateName(el, null)); - else els.add("(intermediate value)"); - } - - res = "[" + String.join(",", els) + "]"; - } - - if (index == null) return res; - - if (shouldParen) res = "(" + res + ")"; - - if (index instanceof StringNode) { - var val = ((StringNode)index).value; - var bracket = JSON.stringify(JSONElement.string(val)); - - if (!bracket.substring(1, bracket.length() - 1).equals(val)) return res + "[" + bracket + "]"; - if (Parsing.parseIdentifier(new Source(val), 0).n != val.length()) return res + "[" + bracket + "]"; - - return res + "." + val; - } - - return res + "[" + generateName(index, null) + "]"; - } + @Override public void compileFunctions(CompileResult target) { + func.compileFunctions(target); + for (var arg : args) arg.compileFunctions(target); + } @Override public void compile(CompileResult target, boolean pollute, BreakpointType type) { - if (!isNew && func instanceof IndexNode) { - var obj = ((IndexNode)func).object; - var index = ((IndexNode)func).index; - String name = ""; + if (!isNew && func instanceof IndexNode indexNode) { + var obj = indexNode.object; + var index = indexNode.index; + + obj.compile(target, true); + target.add(Instruction.dup()); + IndexNode.indexLoad(target, index, true); - obj.compile(target, true); - index.compile(target, true); for (var arg : args) arg.compile(target, true); - if (ATTACH_NAME) name = generateName(obj, index); + target.add(Instruction.call(args.length, true)); - target.add(Instruction.callMember(args.length, name)).setLocationAndDebug(loc(), type); + target.setLocationAndDebug(loc(), type); } else { - String name = ""; - func.compile(target, true); for (var arg : args) arg.compile(target, true); - if (ATTACH_NAME) name = generateName(func, null); - - if (isNew) target.add(Instruction.callNew(args.length, name)).setLocationAndDebug(loc(), type); - else target.add(Instruction.call(args.length, name)).setLocationAndDebug(loc(), type); + if (isNew) target.add(Instruction.callNew(args.length)).setLocationAndDebug(loc(), type); + else target.add(Instruction.call(args.length, false)).setLocationAndDebug(loc(), type); } if (!pollute) target.add(Instruction.discard()); } @@ -147,7 +79,8 @@ public class CallNode extends Node { prevArg = true; } else if (argRes.isError()) return argRes.chainError(); - else if (prevArg && src.is(i + n, ",")) { + + if (prevArg && src.is(i + n, ",")) { prevArg = false; n++; } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/ChangeNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/ChangeNode.java index e5c316c..160d875 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/ChangeNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/ChangeNode.java @@ -17,6 +17,11 @@ public class ChangeNode extends Node { public final Node value; public final Operation op; + @Override public void compileFunctions(CompileResult target) { + ((Node)changable).compileFunctions(target); + value.compileFunctions(target); + } + @Override public void compile(CompileResult target, boolean pollute) { changable.beforeChange(target); value.compile(target, true); diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/DiscardNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/DiscardNode.java index eaf7b89..3e2315c 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/DiscardNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/DiscardNode.java @@ -13,6 +13,10 @@ import me.topchetoeu.jscript.compilation.Node; public class DiscardNode extends Node { public final Node value; + @Override public void compileFunctions(CompileResult target) { + if (value != null) value.compileFunctions(target); + } + @Override public void compile(CompileResult target, boolean pollute) { if (value != null) value.compile(target, false); if (pollute) target.add(Instruction.pushUndefined()); diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/IndexNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/IndexNode.java index 826b8cd..d108eea 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/IndexNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/IndexNode.java @@ -17,9 +17,13 @@ public class IndexNode extends Node implements ChangeTarget { public final Node object; public final Node index; + @Override public void compileFunctions(CompileResult target) { + object.compileFunctions(target); + index.compileFunctions(target); + } + @Override public void beforeAssign(CompileResult target) { object.compile(target, true); - indexStorePushKey(target, index); } @Override public void beforeChange(CompileResult target) { diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/LazyAndNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/LazyAndNode.java index 318751e..152551a 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/LazyAndNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/LazyAndNode.java @@ -12,6 +12,11 @@ import me.topchetoeu.jscript.compilation.Node; public class LazyAndNode extends Node { public final Node first, second; + @Override public void compileFunctions(CompileResult target) { + first.compileFunctions(target); + second.compileFunctions(target); + } + @Override public void compile(CompileResult target, boolean pollute) { first.compile(target, true); if (pollute) target.add(Instruction.dup()); diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/LazyOrNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/LazyOrNode.java index 9b1e5a9..f75864a 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/LazyOrNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/LazyOrNode.java @@ -13,6 +13,11 @@ import me.topchetoeu.jscript.compilation.Node; public class LazyOrNode extends Node { public final Node first, second; + @Override public void compileFunctions(CompileResult target) { + first.compileFunctions(target); + second.compileFunctions(target); + } + @Override public void compile(CompileResult target, boolean pollute) { first.compile(target, true); if (pollute) target.add(Instruction.dup()); diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/OperationNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/OperationNode.java index 5189330..b9ac637 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/OperationNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/OperationNode.java @@ -105,13 +105,17 @@ public class OperationNode extends Node { public final Node[] args; public final Operation operation; + @Override public void compileFunctions(CompileResult target) { + for (var arg : args) arg.compileFunctions(target); + } + @Override public void compile(CompileResult target, boolean pollute) { for (var arg : args) { arg.compile(target, true); } - if (pollute) target.add(Instruction.operation(operation)); - else target.add(Instruction.discard()); + target.add(Instruction.operation(operation)); + if (!pollute) target.add(Instruction.discard()); } public OperationNode(Location loc, Operation operation, Node ...args) { diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/PostfixNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/PostfixNode.java index 14b5f77..492b581 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/PostfixNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/PostfixNode.java @@ -12,6 +12,10 @@ import me.topchetoeu.jscript.compilation.patterns.ChangeTarget; import me.topchetoeu.jscript.compilation.values.constants.NumberNode; public class PostfixNode extends ChangeNode { + @Override public void compileFunctions(CompileResult target) { + ((Node)changable).compileFunctions(target); + } + @Override public void compile(CompileResult target, boolean pollute) { super.compile(target, pollute); diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/TypeofNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/TypeofNode.java index ba89965..68683dd 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/TypeofNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/TypeofNode.java @@ -14,6 +14,10 @@ import me.topchetoeu.jscript.compilation.values.VariableNode; public class TypeofNode extends Node { public final Node value; + @Override public void compileFunctions(CompileResult target) { + value.compileFunctions(target); + } + @Override public void compile(CompileResult target, boolean pollute) { if (value instanceof VariableNode varNode) { target.add(VariableNode.toGet(target, varNode.loc(), varNode.name, true, true)); diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/VariableAssignNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/VariableAssignNode.java index 9f11b5b..7474a0e 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/VariableAssignNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/VariableAssignNode.java @@ -13,16 +13,20 @@ public class VariableAssignNode extends Node { public final Node value; public final Operation operation; + @Override public void compileFunctions(CompileResult target) { + value.compileFunctions(target); + } + @Override public void compile(CompileResult target, boolean pollute) { if (operation != null) { target.add(VariableNode.toGet(target, loc(), name)); FunctionNode.compileWithName(value, target, true, name); target.add(Instruction.operation(operation)); - target.add(VariableNode.toSet(target, loc(), name, pollute)); + target.add(VariableNode.toSet(target, loc(), name, pollute, false)).setLocation(loc()); } else { FunctionNode.compileWithName(value, target, true, name); - target.add(VariableNode.toSet(target, loc(), name, pollute)); + target.add(VariableNode.toSet(target, loc(), name, pollute, false)).setLocation(loc()); } } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/Compiler.java b/src/main/java/me/topchetoeu/jscript/runtime/Compiler.java index 526e646..42266dc 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/Compiler.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/Compiler.java @@ -34,7 +34,7 @@ public interface Compiler { public static Compiler get(Environment ext) { return ext.get(KEY, (env, filename, src) -> { - throw EngineException.ofError("No compiler attached to engine."); + throw EngineException.ofError("No compiler attached to engine"); }); } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/EventLoop.java b/src/main/java/me/topchetoeu/jscript/runtime/EventLoop.java index 6a929a0..6126684 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/EventLoop.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/EventLoop.java @@ -28,9 +28,9 @@ public interface EventLoop { } public default Future pushMsg(boolean micro, Environment env, FunctionValue func, Value thisArg, Value ...args) { - return pushMsg(() -> func.invoke(env, thisArg, args), micro); + return pushMsg(() -> func.apply(env, thisArg, args), micro); } public default Future pushMsg(boolean micro, Environment env, Filename filename, String raw, Value thisArg, Value ...args) { - return pushMsg(() -> Compiler.compileFunc(env, filename, raw).invoke(env, thisArg, args), micro); + return pushMsg(() -> Compiler.compileFunc(env, filename, raw).apply(env, thisArg, args), micro); } } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/Frame.java b/src/main/java/me/topchetoeu/jscript/runtime/Frame.java index 17e5a2c..62ef129 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/Frame.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/Frame.java @@ -1,5 +1,6 @@ package me.topchetoeu.jscript.runtime; +import java.util.Arrays; import java.util.Stack; import java.util.concurrent.CancellationException; @@ -12,9 +13,15 @@ import me.topchetoeu.jscript.runtime.values.Value; import me.topchetoeu.jscript.runtime.values.functions.CodeFunction; import me.topchetoeu.jscript.runtime.values.objects.ArrayLikeValue; import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; +import me.topchetoeu.jscript.runtime.values.primitives.numbers.IntValue; public final class Frame { public static final Key KEY = Key.of(); + public static final EngineException STACK_OVERFLOW; + static { + STACK_OVERFLOW = EngineException.ofRange("Stack overflow!"); + STACK_OVERFLOW.value.freeze(); + } public static enum TryState { TRY, @@ -90,17 +97,26 @@ public final class Frame { } /** - * A list of one-element arrays of values. This is so that we can pass captures to other functions + * An array of captures from the parent function */ public final Value[][] captures; + /** + * An array of non-capture variables + */ public final Value[] locals; + /** + * An array of children-captured variables + */ public final Value[][] capturables; - public final Value argsVal; - public Value self; - public Value fakeArgs; + + public final Value self; public final Value[] args; - public final boolean isNew; - public final Stack tryStack = new Stack<>(); + public final Value argsVal; + public final Value argsLen; + + public final boolean isNew; + + public final Stack tryStack = new Stack<>(); public final CodeFunction function; public final Environment env; private final DebugContext dbg; @@ -193,6 +209,7 @@ public final class Frame { } } } + catch (StackOverflowError e) { throw STACK_OVERFLOW; } catch (EngineException e) { error = e; } catch (RuntimeException e) { System.out.println(dbg.getMapOrEmpty(function).toLocation(codePtr, true)); @@ -268,6 +285,10 @@ public final class Frame { } } + if (returnValue != null) { + dbg.onInstruction(env, this, instr, returnValue, null, false); + return returnValue; + } if (error != null) { var caught = false; @@ -280,10 +301,6 @@ public final class Frame { dbg.onInstruction(env, this, instr, null, error, caught); throw error; } - if (returnValue != null) { - dbg.onInstruction(env, this, instr, returnValue, null, false); - return returnValue; - } return null; } @@ -349,18 +366,21 @@ public final class Frame { }; } - public Frame(Environment env, boolean isNew, Value thisArg, Value[] args, CodeFunction func) { + public Frame(Environment env, boolean isNew, Value self, Value[] args, CodeFunction func) { this.env = env; this.dbg = DebugContext.get(env); this.function = func; this.isNew = isNew; - this.self = thisArg; + this.self = self; this.args = args; this.argsVal = new ArgumentsValue(this, args); + this.argsLen = new IntValue(args.length); this.captures = func.captures; this.locals = new Value[func.body.localsN]; + Arrays.fill(this.locals, Value.UNDEFINED); this.capturables = new Value[func.body.capturablesN][1]; + for (var i = 0; i < this.capturables.length; i++) this.capturables[i][0] = Value.UNDEFINED; } } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java b/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java index a019369..ec6efa7 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java @@ -7,6 +7,7 @@ import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Operation; import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.runtime.exceptions.EngineException; +import me.topchetoeu.jscript.runtime.values.Member.FieldMember; import me.topchetoeu.jscript.runtime.values.Member.PropertyMember; import me.topchetoeu.jscript.runtime.values.Value; import me.topchetoeu.jscript.runtime.values.functions.CodeFunction; @@ -31,18 +32,9 @@ public class InstructionRunner { private static Value execCall(Environment env, Instruction instr, Frame frame) { var callArgs = frame.take(instr.get(0)); var func = frame.pop(); + var self = (boolean)instr.get(1) ? frame.pop() : Value.UNDEFINED; - frame.push(func.call(env, false, instr.get(1), Value.UNDEFINED, callArgs)); - - frame.codePtr++; - return null; - } - private static Value execCallMember(Environment env, Instruction instr, Frame frame) { - var callArgs = frame.take(instr.get(0)); - var index = frame.pop(); - var obj = frame.pop(); - - frame.push(obj.getMember(env, index).call(env, false, instr.get(1), obj, callArgs)); + frame.push(func.apply(env, self, callArgs)); frame.codePtr++; return null; @@ -51,7 +43,7 @@ public class InstructionRunner { var callArgs = frame.take(instr.get(0)); var funcObj = frame.pop(); - frame.push(funcObj.construct(env, instr.get(1), callArgs)); + frame.push(funcObj.construct(env, callArgs)); frame.codePtr++; return null; @@ -66,7 +58,7 @@ public class InstructionRunner { if (val == Value.UNDEFINED) accessor = null; else if (val instanceof FunctionValue func) accessor = func; - else throw EngineException.ofType("Getter must be a function or undefined."); + else throw EngineException.ofType("Getter must be a function or undefined"); if ((boolean)instr.get(0)) obj.defineOwnMember(env, key, new PropertyMember(obj, null, accessor, true, true)); else obj.defineOwnMember(env, key, new PropertyMember(obj, accessor, null, true, true)); @@ -79,7 +71,7 @@ public class InstructionRunner { var key = frame.pop(); var obj = frame.pop(); - obj.defineOwnMember(env, key, val); + obj.defineOwnMember(env, key, FieldMember.of(obj, val, true, true, true)); frame.codePtr++; return null; @@ -147,9 +139,7 @@ public class InstructionRunner { private static Value execLoadVar(Environment env, Instruction instr, Frame frame) { int i = instr.get(0); - var res = frame.getVar(i); - if (res == null) throw EngineException.ofSyntax("Uninitialized variable"); - frame.push(res); + frame.push(frame.getVar(i)); frame.codePtr++; return null; @@ -181,23 +171,15 @@ public class InstructionRunner { private static Value execLoadFunc(Environment env, Instruction instr, Frame frame) { int id = instr.get(0); String name = instr.get(1); - boolean callable = instr.get(2); - boolean constructible = instr.get(3); - boolean captureThis = instr.get(4); - - var captures = new Value[instr.params.length - 5][]; - for (var i = 5; i < instr.params.length; i++) { - captures[i - 5] = frame.captureVar(instr.get(i)); + var captures = new Value[instr.params.length - 2][]; + + for (var i = 2; i < instr.params.length; i++) { + captures[i - 2] = frame.captureVar(instr.get(i)); } var func = new CodeFunction(env, name, frame.function.body.children[id], captures); - if (!callable) func.enableCall = false; - if (!constructible) func.enableNew = false; - if (captureThis) { - func.self = frame.self; - func.argsVal = frame.argsVal; - } + frame.push(func); frame.codePtr++; @@ -243,7 +225,7 @@ public class InstructionRunner { frame.push(env.get(Value.REGEX_CONSTR).construct(env, instr.get(0), instr.get(1))); } else { - throw EngineException.ofSyntax("Regex is not supported."); + throw EngineException.ofSyntax("Regex is not supported"); } frame.codePtr++; @@ -260,7 +242,7 @@ public class InstructionRunner { var key = frame.pop(); var obj = frame.pop(); - if (!obj.setMember(env, key, val)) throw EngineException.ofSyntax("Can't set member '" + key.toReadable(env) + "'."); + if (!obj.setMember(env, key, val)) throw EngineException.ofSyntax("Can't set member '" + key.toReadable(env) + "'"); if ((boolean)instr.get(0)) frame.push(val); frame.codePtr++; return null; @@ -269,7 +251,7 @@ public class InstructionRunner { var val = frame.pop(); var obj = frame.pop(); - if (!obj.setMember(env, (String)instr.get(0), val)) throw EngineException.ofSyntax("Can't set member '" + instr.get(0) + "'."); + if (!obj.setMember(env, (String)instr.get(0), val)) throw EngineException.ofSyntax("Can't set member '" + instr.get(0) + "'"); if ((boolean)instr.get(1)) frame.push(val); frame.codePtr++; return null; @@ -278,7 +260,7 @@ public class InstructionRunner { var val = frame.pop(); var obj = frame.pop(); - if (!obj.setMember(env, (int)instr.get(0), val)) throw EngineException.ofSyntax("Can't set member '" + instr.get(0) + "'."); + if (!obj.setMember(env, (int)instr.get(0), val)) throw EngineException.ofSyntax("Can't set member '" + instr.get(0) + "'"); if ((boolean)instr.get(1)) frame.push(val); frame.codePtr++; return null; @@ -287,7 +269,6 @@ public class InstructionRunner { var val = (boolean)instr.get(1) ? frame.peek() : frame.pop(); int i = instr.get(0); - if (!(boolean)instr.get(2) && frame.getVar(i) == null) throw EngineException.ofSyntax("Uninitialized variable"); frame.setVar(i, val); frame.codePtr++; @@ -337,7 +318,7 @@ public class InstructionRunner { var key = frame.pop(); var val = frame.pop(); - if (!val.deleteMember(env, key)) throw EngineException.ofSyntax("Can't delete member '" + key.toReadable(env) + "'."); + if (!val.deleteMember(env, key)) throw EngineException.ofSyntax("Can't delete member '" + key.toReadable(env) + "'"); frame.codePtr++; return null; } @@ -445,7 +426,7 @@ public class InstructionRunner { return null; } - private static Value exexGlobDef(Environment env, Instruction instr, Frame frame) { + private static Value execGlobDef(Environment env, Instruction instr, Frame frame) { var name = (String)instr.get(0); if (!Value.global(env).hasMember(env, name, false)) { @@ -455,7 +436,7 @@ public class InstructionRunner { frame.codePtr++; return null; } - private static Value exexGlobGet(Environment env, Instruction instr, Frame frame) { + private static Value execGlobGet(Environment env, Instruction instr, Frame frame) { var name = (String)instr.get(0); if ((boolean)instr.get(1)) { frame.push(Value.global(env).getMember(env, name)); @@ -470,7 +451,7 @@ public class InstructionRunner { frame.codePtr++; return null; } - private static Value exexGlobSet(Environment env, Instruction instr, Frame frame) { + private static Value execGlobSet(Environment env, Instruction instr, Frame frame) { var name = (String)instr.get(0); var keep = (boolean)instr.get(1); var define = (boolean)instr.get(2); @@ -487,19 +468,20 @@ public class InstructionRunner { return null; } - private static Value execLoadArgs(Environment env, Instruction instr, Frame frame) { - if ((boolean)instr.get(0) || frame.fakeArgs == null) frame.push(frame.argsVal); - else frame.push(frame.fakeArgs); + private static Value execLoadArg(Environment env, Instruction instr, Frame frame) { + int i = instr.get(0); + if (i >= frame.args.length) frame.push(Value.UNDEFINED); + else frame.push(frame.args[i]); frame.codePtr++; return null; } - private static Value execLoadRestArgs(Environment env, Instruction instr, Frame frame) { - int offset = instr.get(0); - var res = new ArrayValue(); - - if (offset < frame.args.length) res.copyFrom(frame.args, instr.get(0), 0, frame.args.length - offset); - - frame.push(res); + private static Value execLoadArgsN(Environment env, Instruction instr, Frame frame) { + frame.push(frame.argsLen); + frame.codePtr++; + return null; + } + private static Value execLoadArgs(Environment env, Instruction instr, Frame frame) { + frame.push(frame.argsVal); frame.codePtr++; return null; } @@ -509,6 +491,7 @@ public class InstructionRunner { return null; } private static Value execLoadThis(Environment env, Instruction instr, Frame frame) { + if (frame.self == null) throw EngineException.ofError("Super constructor must be called before 'this' is accessed"); frame.push(frame.self); frame.codePtr++; return null; @@ -519,26 +502,7 @@ public class InstructionRunner { return null; } - private static Value execVarInit(Environment env, Instruction instr, Frame frame) { - if ((boolean)instr.get(1) || frame.getVar(instr.get(0)) == null) { - frame.setVar(instr.get(0), Value.UNDEFINED); - } - - frame.codePtr++; - return null; - } - private static Value execVarFree(Environment env, Instruction instr, Frame frame) { - frame.locals[(int)instr.get(0)] = null; - frame.codePtr++; - return null; - } - private static Value execCapFree(Environment env, Instruction instr, Frame frame) { - frame.capturables[(int)instr.get(0) - frame.locals.length] = new Value[1]; - frame.codePtr++; - return null; - } - - public static Value exec(Environment env, Instruction instr, Frame frame) { + public static Value exec(Environment env, Instruction instr, Frame frame) { switch (instr.type) { case NOP: return execNop(env, instr, frame); case RETURN: return execReturn(env, instr, frame); @@ -546,7 +510,6 @@ public class InstructionRunner { case THROW_SYNTAX: return execThrowSyntax(env, instr, frame); case CALL: return execCall(env, instr, frame); case CALL_NEW: return execCallNew(env, instr, frame); - case CALL_MEMBER: return execCallMember(env, instr, frame); case TRY_START: return execTryStart(env, instr, frame); case TRY_END: return execTryEnd(env, instr, frame); @@ -567,12 +530,14 @@ public class InstructionRunner { case LOAD_REGEX: return execLoadRegEx(env, instr, frame); case LOAD_GLOB: return execLoadGlob(env, instr, frame); case LOAD_INTRINSICS: return execLoadIntrinsics(env, instr, frame); - case LOAD_ARGS: return execLoadArgs(env, instr, frame); - case LOAD_REST_ARGS: return execLoadRestArgs(env, instr, frame); - case LOAD_CALLEE: return execLoadCallee(env, instr, frame); - case LOAD_THIS: return execLoadThis(env, instr, frame); case LOAD_ERROR: return execLoadError(env, instr, frame); + case LOAD_THIS: return execLoadThis(env, instr, frame); + case LOAD_ARG: return execLoadArg(env, instr, frame); + case LOAD_ARGS: return execLoadArgs(env, instr, frame); + case LOAD_ARGS_N: return execLoadArgsN(env, instr, frame); + case LOAD_CALLED: return execLoadCallee(env, instr, frame); + case DISCARD: return execDiscard(env, instr, frame); case STORE_MEMBER: return execStoreMember(env, instr, frame); case STORE_MEMBER_STR: return execStoreMemberStr(env, instr, frame); @@ -591,15 +556,11 @@ public class InstructionRunner { case OPERATION: return execOperation(env, instr, frame); - case GLOB_DEF: return exexGlobDef(env, instr, frame); - case GLOB_GET: return exexGlobGet(env, instr, frame); - case GLOB_SET: return exexGlobSet(env, instr, frame); + case GLOB_DEF: return execGlobDef(env, instr, frame); + case GLOB_GET: return execGlobGet(env, instr, frame); + case GLOB_SET: return execGlobSet(env, instr, frame); - case VAR_INIT: return execVarInit(env, instr, frame); - case VAR_FREE: return execVarFree(env, instr, frame); - case CAP_FREE: return execCapFree(env, instr, frame); - - default: throw EngineException.ofSyntax("Invalid instruction " + instr.type.name() + "."); + default: throw EngineException.ofSyntax("Invalid instruction " + instr.type.name() + ""); } } } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java b/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java index 5518ec7..eefc6eb 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java @@ -10,8 +10,10 @@ import me.topchetoeu.jscript.common.Metadata; import me.topchetoeu.jscript.common.Reading; import me.topchetoeu.jscript.common.SyntaxException; import me.topchetoeu.jscript.common.environment.Environment; +import me.topchetoeu.jscript.common.environment.Key; import me.topchetoeu.jscript.common.json.JSON; import me.topchetoeu.jscript.common.parsing.Filename; +import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.runtime.debug.DebugContext; import me.topchetoeu.jscript.runtime.exceptions.EngineException; import me.topchetoeu.jscript.runtime.values.Member.FieldMember; @@ -212,12 +214,12 @@ public class SimpleRepl { res.defineOwnMember(env, "setCallable", new NativeFunction(args -> { var func = (FunctionValue)args.get(0); - func.enableCall = args.get(1).toBoolean(); + func.enableApply = args.get(1).toBoolean(); return Value.UNDEFINED; })); res.defineOwnMember(env, "setConstructable", new NativeFunction(args -> { var func = (FunctionValue)args.get(0); - func.enableNew = args.get(1).toBoolean(); + func.enableConstruct = args.get(1).toBoolean(); return Value.UNDEFINED; })); res.defineOwnMember(env, "invokeType", new NativeFunction(args -> { @@ -229,16 +231,14 @@ public class SimpleRepl { var func = (FunctionValue)args.get(0); var self = args.get(1); var funcArgs = (ArrayValue)args.get(2); - var name = args.get(3).toString(env); - return func.invoke(env, name, self, funcArgs.toArray()); + return func.apply(env, self, funcArgs.toArray()); })); res.defineOwnMember(env, "construct", new NativeFunction(args -> { var func = (FunctionValue)args.get(0); var funcArgs = (ArrayValue)args.get(1); - var name = args.get(2).toString(env); - return func.construct(env, name, funcArgs.toArray()); + return func.construct(env, funcArgs.toArray()); })); return res; @@ -254,21 +254,17 @@ public class SimpleRepl { res.defineOwnMember(env, "parse", new NativeFunction(args -> { return JSONConverter.toJs(JSON.parse(null, args.get(0).toString(env))); })); - res.defineOwnMember(env, "invokeType", new NativeFunction(args -> { - if (((ArgumentsValue)args.get(0)).frame.isNew) return StringValue.of("new"); - else return StringValue.of("call"); - })); - res.defineOwnMember(env, "invoke", new NativeFunction(args -> { - var func = (FunctionValue)args.get(0); - var self = args.get(1); - var funcArgs = (ArrayValue)args.get(2); - - return func.invoke(env, self, funcArgs.toArray()); - })); return res; } + private static void setProto(Environment env, Environment target, Key key, ObjectValue repo, String name) { + var val = repo.getMember(env, name); + if (val instanceof ObjectValue obj) { + target.add(key, obj); + } + } + private static ObjectValue primordials(Environment env) { var res = new ObjectValue(); res.setPrototype(null, null); @@ -282,45 +278,20 @@ public class SimpleRepl { int[] i = new int[1]; - res.defineOwnMember(env, "setGlobalPrototype", new NativeFunction(args -> { - var type = args.get(0).toString(env); - var obj = (ObjectValue)args.get(1); + res.defineOwnMember(env, "setGlobalPrototypes", new NativeFunction(args -> { + var obj = (ObjectValue)args.get(0); - switch (type) { - case "object": - args.env.add(Value.OBJECT_PROTO, obj); - break; - case "function": - args.env.add(Value.FUNCTION_PROTO, obj); - break; - case "array": - args.env.add(Value.ARRAY_PROTO, obj); - break; - case "boolean": - args.env.add(Value.BOOL_PROTO, obj); - break; - case "number": - args.env.add(Value.NUMBER_PROTO, obj); - break; - case "string": - args.env.add(Value.STRING_PROTO, obj); - break; - case "symbol": - args.env.add(Value.SYMBOL_PROTO, obj); - break; - case "error": - args.env.add(Value.ERROR_PROTO, obj); - break; - case "syntax": - args.env.add(Value.SYNTAX_ERR_PROTO, obj); - break; - case "type": - args.env.add(Value.TYPE_ERR_PROTO, obj); - break; - case "range": - args.env.add(Value.RANGE_ERR_PROTO, obj); - break; - } + setProto(args.env, env, Value.OBJECT_PROTO, obj, "object"); + setProto(args.env, env, Value.FUNCTION_PROTO, obj, "function"); + setProto(args.env, env, Value.ARRAY_PROTO, obj, "array"); + setProto(args.env, env, Value.BOOL_PROTO, obj, "boolean"); + setProto(args.env, env, Value.NUMBER_PROTO, obj, "number"); + setProto(args.env, env, Value.STRING_PROTO, obj, "string"); + setProto(args.env, env, Value.SYMBOL_PROTO, obj, "symbol"); + setProto(args.env, env, Value.ERROR_PROTO, obj, "error"); + setProto(args.env, env, Value.SYNTAX_ERR_PROTO, obj, "syntax"); + setProto(args.env, env, Value.TYPE_ERR_PROTO, obj, "type"); + setProto(args.env, env, Value.RANGE_ERR_PROTO, obj, "range"); return Value.UNDEFINED; })); @@ -343,6 +314,7 @@ public class SimpleRepl { environment.add(EventLoop.KEY, engine); environment.add(DebugContext.KEY, new DebugContext()); environment.add(Compiler.KEY, Compiler.DEFAULT); + // environment.add(CompileResult.DEBUG_LOG); var glob = Value.global(environment); @@ -362,7 +334,7 @@ public class SimpleRepl { glob.defineOwnMember(null, "measure", new NativeFunction("measure", args -> { var start = System.nanoTime(); - ((FunctionValue)args.get(0)).invoke(args.env, Value.UNDEFINED); + ((FunctionValue)args.get(0)).apply(args.env, Value.UNDEFINED); System.out.println(String.format("Finished in %sns", System.nanoTime() - start)); diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/Member.java b/src/main/java/me/topchetoeu/jscript/runtime/values/Member.java index 36c7ac1..9e3d964 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/Member.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/Member.java @@ -14,12 +14,12 @@ public interface Member { public boolean enumerable; @Override public Value get(Environment env, Value self) { - if (getter != null) return getter.call(env, false, "", self); + if (getter != null) return getter.apply(env, self); else return Value.UNDEFINED; } @Override public boolean set(Environment env, Value val, Value self) { if (setter == null) return false; - setter.call(env, false, "", self, val); + setter.apply(env, self, val); return true; } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java b/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java index 0604cdd..dc7f293 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java @@ -3,27 +3,25 @@ package me.topchetoeu.jscript.runtime.values; import java.io.ByteArrayOutputStream; import java.io.PrintStream; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; import java.util.Set; import me.topchetoeu.jscript.common.SyntaxException; import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.common.environment.Key; -import me.topchetoeu.jscript.common.json.JSON; -import me.topchetoeu.jscript.common.json.JSONElement; import me.topchetoeu.jscript.runtime.EventLoop; -import me.topchetoeu.jscript.runtime.debug.DebugContext; import me.topchetoeu.jscript.runtime.exceptions.EngineException; import me.topchetoeu.jscript.runtime.values.Member.FieldMember; import me.topchetoeu.jscript.runtime.values.Member.PropertyMember; import me.topchetoeu.jscript.runtime.values.functions.FunctionValue; import me.topchetoeu.jscript.runtime.values.functions.NativeFunction; -import me.topchetoeu.jscript.runtime.values.objects.ArrayValue; import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; import me.topchetoeu.jscript.runtime.values.primitives.BoolValue; import me.topchetoeu.jscript.runtime.values.primitives.StringValue; @@ -84,35 +82,25 @@ public abstract class Value { return this == NumberValue.NAN || this instanceof NumberValue num && Double.isNaN(num.getDouble()); } - public Value call(Environment env, boolean isNew, String name, Value self, Value ...args) { - if (name == null || name.equals("")) name = "(intermediate value)"; - - if (isNew) throw EngineException.ofType(name + " is not a constructor"); - else throw EngineException.ofType(name + " is not a function"); + public Value apply(Environment env, Value self, Value ...args) { + throw EngineException.ofType("Value is not a function"); } + public Value construct(Environment env, Value self, Value ...args) { + throw EngineException.ofType("Value is not a constructor"); + } - public final Value invoke(Environment env, String name, Value self, Value ...args) { - return call(env, false, name, self, args); - } - public final Value construct(Environment env, String name, Value ...args) { + public final Value construct(Environment env, Value ...args) { var res = new ObjectValue(); var proto = getMember(env, StringValue.of("prototype")); if (proto instanceof ObjectValue) res.setPrototype(env, (ObjectValue)proto); - else res.setPrototype(env, null); - var ret = this.call(env, true, name, res, args); + var ret = this.construct(env, res, args); - if (!ret.isPrimitive()) return ret; - return res; + if (ret == Value.UNDEFINED || ret.isPrimitive()) return res; + return ret; } - public final Value invoke(Environment env, Value self, Value ...args) { - return invoke(env, "", self, args); - } - public final Value construct(Environment env, Value ...args) { - return construct(env, "", args); - } public abstract Value toPrimitive(Environment env); public abstract NumberValue toNumber(Environment env); @@ -120,7 +108,7 @@ public abstract class Value { public abstract boolean toBoolean(); public final boolean isInstanceOf(Environment env, Value proto) { - for (var val = getPrototype(env); val != null; val = getPrototype(env)) { + for (var val = getPrototype(env); val != null; val = val.getPrototype(env)) { if (val.equals(proto)) return true; } @@ -392,7 +380,7 @@ public abstract class Value { private void loadNext() { if (supplier == null) value = null; else if (consumed) { - var curr = supplier.invoke(env, Value.UNDEFINED); + var curr = supplier.apply(env, Value.UNDEFINED); if (curr == null) { supplier = null; value = null; } if (curr.getMember(env, StringValue.of("done")).toBoolean()) { supplier = null; value = null; } @@ -420,114 +408,22 @@ public abstract class Value { public void callWith(Environment env, Iterable it) { for (var el : it) { - this.invoke(env, Value.UNDEFINED, el); + this.apply(env, Value.UNDEFINED, el); } } public void callWithAsync(Environment env, Iterable it, boolean async) { for (var el : it) { - env.get(EventLoop.KEY).pushMsg(() -> this.invoke(env, Value.UNDEFINED, el), true); + env.get(EventLoop.KEY).pushMsg(() -> this.apply(env, Value.UNDEFINED, el), true); } } - private final String toReadable(Environment env, HashSet passed, int tab) { - if (passed.contains(this)) return "[circular]"; - - if (this instanceof ObjectValue obj) { - var res = new StringBuilder(); - var dbg = DebugContext.get(env); - var printed = true; - var keys = this.getMembers(env, true, false); - - if (this instanceof FunctionValue func) { - res.append(this.toString()); - var loc = dbg.getMapOrEmpty(func).start(); - - if (loc != null) res.append(" @ " + loc); - - if ( - func.prototype instanceof ObjectValue objProto && - objProto.getMember(env, "constructor") == func && - objProto.getOwnMembers(env, true).size() + objProto.getOwnSymbolMembers(env, true).size() == 1 - ) { keys.remove("constructor"); } - } - else if (this instanceof ArrayValue) { - res.append("["); - var arr = (ArrayValue)this; - - for (int i = 0; i < arr.size(); i++) { - if (i != 0) res.append(", "); - else res.append(" "); - - if (arr.hasMember(env, i, true)) { - res.append(arr.getMember(env, i).toReadable(env, passed, tab)); - keys.remove(i + ""); - } - else res.append(""); - } - - res.append(" ] "); - } - else printed = false; - - - passed.add(this); - - if (keys.size() + obj.getOwnSymbolMembers(env, true).size() == 0) { - if (!printed) res.append("{}"); - } - else if (!printed) { - if (tab > 3) return "{...}"; - res.append("{\n"); - - for (var entry : obj.getOwnSymbolMembers(env, true)) { - for (int i = 0; i < tab + 1; i++) res.append(" "); - res.append("[" + entry.value + "]" + ": "); - - var member = obj.getOwnMember(env, entry); - if (member instanceof FieldMember field) res.append(field.get(env, obj).toReadable(env, passed, tab + 1)); - else if (member instanceof PropertyMember prop) { - if (prop.getter == null && prop.setter == null) res.append("[No accessors]"); - else if (prop.getter == null) res.append("[Setter]"); - else if (prop.setter == null) res.append("[Getter]"); - else res.append("[Getter/Setter]"); - } - else res.append("[???]"); - - res.append(",\n"); - } - for (var entry : obj.getOwnMembers(env, true)) { - for (int i = 0; i < tab + 1; i++) res.append(" "); - res.append(entry + ": "); - - var member = obj.getOwnMember(env, entry); - if (member instanceof FieldMember field) res.append(field.get(env, obj).toReadable(env, passed, tab + 1)); - else if (member instanceof PropertyMember prop) { - if (prop.getter == null && prop.setter == null) res.append("[No accessors]"); - else if (prop.getter == null) res.append("[Setter]"); - else if (prop.setter == null) res.append("[Getter]"); - else res.append("[Getter/Setter]"); - } - else res.append("[???]"); - - res.append(",\n"); - } - - for (int i = 0; i < tab; i++) res.append(" "); - res.append("}"); - } - - passed.remove(this); - return res.toString(); - } - else if (this instanceof VoidValue) return ((VoidValue)this).name; - else if (this instanceof StringValue) return JSON.stringify(JSONElement.string(((StringValue)this).value)); - else if (this instanceof SymbolValue) return this.toString(); - else if (this instanceof NumberValue num && num.isLong()) return num.getLong() + "i"; - else return this.toString(env); + /** @internal */ + public List toReadableLines(Environment env, HashSet passed) { + return Arrays.asList(toString(env)); } public final String toReadable(Environment ext) { - return toReadable(ext, new HashSet<>(), 0); + return String.join("\n", toReadableLines(ext, new HashSet<>())); } public static final ObjectValue global(Environment env) { @@ -638,8 +534,8 @@ public abstract class Value { var na = a.toNumber(env); var nb = b.toNumber(env); - if (na.isInt() && nb.isInt()) return NumberValue.of(na.getInt() - nb.getInt()); - else return NumberValue.of(na.getDouble() - nb.getDouble()); + if (na.isInt() && nb.isInt()) return NumberValue.of(na.getInt() * nb.getInt()); + else return NumberValue.of(na.getDouble() * nb.getDouble()); } public static final NumberValue divide(Environment env, Value a, Value b) { var na = a.toNumber(env); diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/functions/Arguments.java b/src/main/java/me/topchetoeu/jscript/runtime/values/functions/Arguments.java index 1a7f276..7b280c4 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/functions/Arguments.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/functions/Arguments.java @@ -3,6 +3,7 @@ package me.topchetoeu.jscript.runtime.values.functions; import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.runtime.values.Value; +import me.topchetoeu.jscript.runtime.values.primitives.UserValue; public class Arguments { public final Value self; @@ -21,6 +22,11 @@ public class Arguments { public Value self() { return get(-1); } + @SuppressWarnings("unchecked") + public T self(Class clazz) { + if (self instanceof UserValue user && clazz.isInstance(user.value)) return (T)user.value; + else return null; + } public Value get(int i) { if (i >= args.length || i < -1) return Value.UNDEFINED; else if (i == -1) return self; diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/functions/CodeFunction.java b/src/main/java/me/topchetoeu/jscript/runtime/values/functions/CodeFunction.java index 7d900fa..5183b62 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/functions/CodeFunction.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/functions/CodeFunction.java @@ -8,8 +8,6 @@ import me.topchetoeu.jscript.runtime.values.Value; public final class CodeFunction extends FunctionValue { public final FunctionBody body; public final Value[][] captures; - public Value self; - public Value argsVal; public Environment env; private Value onCall(Frame frame) { @@ -26,12 +24,13 @@ public final class CodeFunction extends FunctionValue { } } - @Override public Value onCall(Environment env, boolean isNew, String name, Value thisArg, Value ...args) { - var frame = new Frame(env, isNew, thisArg, args, this); - if (argsVal != null) frame.fakeArgs = argsVal; - if (self != null) frame.self = self; + @Override public Value onCall(Environment env, boolean isNew, Value self, Value ...args) { + var frame = new Frame(env, isNew, self, args, this); - return onCall(frame); + var res = onCall(frame); + + if (isNew) return frame.self; + else return res; } public CodeFunction(Environment env, String name, FunctionBody body, Value[][] captures) { diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/functions/FunctionValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/functions/FunctionValue.java index 7e49161..5b3fcc7 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/functions/FunctionValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/functions/FunctionValue.java @@ -1,6 +1,12 @@ package me.topchetoeu.jscript.runtime.values.functions; +import java.util.Arrays; +import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; + import me.topchetoeu.jscript.common.environment.Environment; +import me.topchetoeu.jscript.runtime.debug.DebugContext; import me.topchetoeu.jscript.runtime.exceptions.EngineException; import me.topchetoeu.jscript.runtime.values.KeyCache; import me.topchetoeu.jscript.runtime.values.Member; @@ -15,8 +21,8 @@ public abstract class FunctionValue extends ObjectValue { public int length; public Value prototype = new ObjectValue(); - public boolean enableCall = true; - public boolean enableNew = true; + public boolean enableApply = true; + public boolean enableConstruct = true; private final FieldMember nameField = new FieldMember(this, true, false, false) { @Override public Value get(Environment env, Value self) { @@ -46,18 +52,17 @@ public abstract class FunctionValue extends ObjectValue { } }; - protected abstract Value onCall(Environment ext, boolean isNew, String name, Value thisArg, Value ...args); + protected abstract Value onCall(Environment ext, boolean isNew, Value thisArg, Value ...args); @Override public String toString() { return String.format("function %s(...)", name); } - @Override public Value call(Environment ext, boolean isNew, String name, Value thisArg, Value ...args) { - if (isNew && !enableNew) super.call(ext, isNew, name, thisArg, args); - if (!isNew && !enableCall) { - if (name == null || name.equals("")) name = "(intermediate value)"; - throw EngineException.ofType(name + " is not invokable"); - } - - return onCall(ext, isNew, name, thisArg, args); - } + @Override public Value apply(Environment env, Value self, Value... args) { + if (!enableApply) throw EngineException.ofType("Function cannot be applied"); + return onCall(env, false, self, args); + } + @Override public Value construct(Environment env, Value self, Value... args) { + if (!enableConstruct) throw EngineException.ofType("Function cannot be constructed"); + return onCall(env, true, self, args); + } @Override public Member getOwnMember(Environment env, KeyCache key) { switch (key.toString(env)) { @@ -83,6 +88,20 @@ public abstract class FunctionValue extends ObjectValue { @Override public StringValue type() { return StringValue.of("function"); } + @Override public List toReadableLines(Environment env, HashSet passed) { + var dbg = DebugContext.get(env); + var res = new StringBuilder(this.toString()); + var loc = dbg.getMapOrEmpty(this).start(); + + if (loc != null) res.append(" @ " + loc); + + var lines = new LinkedList(super.toReadableLines(env, passed)); + if (lines.size() == 1 && lines.getFirst().equals("{}")) return Arrays.asList(res.toString()); + lines.set(0, res.toString() + " " + lines.getFirst()); + + return lines; + } + public void setName(String val) { if (this.name == null || this.name.equals("")) this.name = val; } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/functions/NativeFunction.java b/src/main/java/me/topchetoeu/jscript/runtime/values/functions/NativeFunction.java index 0ad250a..f2bdc47 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/functions/NativeFunction.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/functions/NativeFunction.java @@ -10,7 +10,7 @@ public final class NativeFunction extends FunctionValue { public final NativeFunctionRunner action; - @Override public Value onCall(Environment env, boolean isNew, String name, Value self, Value ...args) { + @Override public Value onCall(Environment env, boolean isNew, Value self, Value ...args) { return action.run(new Arguments(env, isNew, self, args)); } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayLikeValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayLikeValue.java index 4b39653..6de8e2f 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayLikeValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayLikeValue.java @@ -1,6 +1,10 @@ package me.topchetoeu.jscript.runtime.values.objects; +import java.util.Arrays; +import java.util.HashSet; import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; import java.util.Set; import me.topchetoeu.jscript.common.environment.Environment; @@ -104,4 +108,90 @@ public abstract class ArrayLikeValue extends ObjectValue { return res; } + + private LinkedList toReadableBase(Environment env, HashSet passed, HashSet ignoredKeys) { + var stringified = new LinkedList>(); + + passed.add(this); + + var emptyN = 0; + + for (int i = 0; i < size(); i++) { + if (has(i)) { + String emptyStr = null; + + if (emptyN == 1) emptyStr = ""; + else if (emptyN > 1) emptyStr = ""; + + if (emptyStr != null) stringified.add(new LinkedList<>(Arrays.asList(emptyStr + ","))); + emptyN = 0; + + stringified.add(new LinkedList<>(get(i).toReadableLines(env, passed))); + ignoredKeys.add(i + ""); + + var entry = stringified.getLast(); + entry.set(entry.size() - 1, entry.getLast() + ","); + } + else { + emptyN++; + } + } + + String emptyStr = null; + + if (emptyN == 1) emptyStr = ""; + else if (emptyN > 1) emptyStr = ""; + + if (emptyStr != null) stringified.add(new LinkedList<>(Arrays.asList(emptyStr))); + else if (stringified.size() > 0) { + var lastEntry = stringified.getLast(); + lastEntry.set(lastEntry.size() - 1, lastEntry.getLast().substring(0, lastEntry.getLast().length() - 1)); + } + + + passed.remove(this); + + if (stringified.size() == 0) return new LinkedList<>(Arrays.asList("[]")); + var concat = new StringBuilder(); + for (var entry : stringified) { + // We make a one-liner only when all members are one-liners + if (entry.size() != 1) { + concat = null; + break; + } + + if (concat.length() != 0) concat.append(" "); + concat.append(entry.get(0)); + } + + // We don't want too long one-liners + if (concat != null && concat.length() < 160) return new LinkedList<>(Arrays.asList("[" + concat.toString() + "]")); + + var res = new LinkedList(); + + res.add("["); + + for (var entry : stringified) { + for (var line : entry) { + res.add(" " + line); + } + } + res.set(res.size() - 1, res.getLast().substring(0, res.getLast().length() - 1)); + res.add("]"); + + return res; + } + + @Override public List toReadableLines(Environment env, HashSet passed) { + var ignored = new HashSet(); + var lines = toReadableBase(env, passed, ignored); + + var superLines = new LinkedList(super.toReadableLines(env, passed, ignored)); + if (superLines.size() == 1 && superLines.getFirst().equals("{}")) return lines; + + lines.set(lines.size() - 1, lines.getLast() + " " + superLines.getFirst()); + lines.addAll(superLines.subList(1, superLines.size())); + + return lines; + } } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ObjectValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ObjectValue.java index c492bbe..12e67b0 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ObjectValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ObjectValue.java @@ -1,7 +1,11 @@ package me.topchetoeu.jscript.runtime.values.objects; +import java.util.Arrays; +import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedHashSet; +import java.util.LinkedList; +import java.util.List; import java.util.Set; import me.topchetoeu.jscript.common.environment.Environment; @@ -10,6 +14,7 @@ import me.topchetoeu.jscript.runtime.exceptions.EngineException; import me.topchetoeu.jscript.runtime.values.KeyCache; import me.topchetoeu.jscript.runtime.values.Member; import me.topchetoeu.jscript.runtime.values.Value; +import me.topchetoeu.jscript.runtime.values.Member.PropertyMember; import me.topchetoeu.jscript.runtime.values.functions.FunctionValue; import me.topchetoeu.jscript.runtime.values.primitives.StringValue; import me.topchetoeu.jscript.runtime.values.primitives.SymbolValue; @@ -41,13 +46,13 @@ public class ObjectValue extends Value { var valueOf = getMember(env, "valueOf"); if (valueOf instanceof FunctionValue) { - var res = valueOf.invoke(env, this); + var res = valueOf.apply(env, this); if (res.isPrimitive()) return res; } var toString = getMember(env, "toString"); if (toString instanceof FunctionValue) { - var res = toString.invoke(env, this); + var res = toString.apply(env, this); if (res.isPrimitive()) return res; } } @@ -134,6 +139,81 @@ public class ObjectValue extends Value { return setPrototype(_env -> val); } + private final LinkedList memberToReadable(Environment env, String key, Member member, HashSet passed) { + if (member instanceof PropertyMember prop) { + if (prop.getter == null && prop.setter == null) return new LinkedList<>(Arrays.asList(key + ": [No accessors]")); + else if (prop.getter == null) return new LinkedList<>(Arrays.asList(key + ": [Setter]")); + else if (prop.setter == null) return new LinkedList<>(Arrays.asList(key + ": [Getter]")); + else return new LinkedList<>(Arrays.asList(key + ": [Getter/Setter]")); + } + else { + var res = new LinkedList(); + var first = true; + + for (var line : member.get(env, this).toReadableLines(env, passed)) { + if (first) res.add(key + ": " + line); + else res.add(line); + first = false; + } + + return res; + } + } + + public List toReadableLines(Environment env, HashSet passed, HashSet ignoredKeys) { + passed.add(this); + + var stringified = new LinkedList>(); + + for (var entry : getOwnSymbolMembers(env, true)) { + var member = getOwnMember(env, entry); + stringified.add(memberToReadable(env, "[" + entry.value + "]", member, passed)); + } + for (var entry : getOwnMembers(env, true)) { + if (ignoredKeys.contains(entry)) continue; + + var member = getOwnMember(env, entry); + stringified.add(memberToReadable(env, entry, member, passed)); + } + + passed.remove(this); + + if (stringified.size() == 0) return Arrays.asList("{}"); + var concat = new StringBuilder(); + for (var entry : stringified) { + // We make a one-liner only when all members are one-liners + if (entry.size() != 1) { + concat = null; + break; + } + + if (concat.length() != 0) concat.append(", "); + concat.append(entry.get(0)); + } + + // We don't want too long one-liners + if (concat != null && concat.length() < 80) return Arrays.asList("{ " + concat.toString() + " }"); + + var res = new LinkedList(); + + res.add("{"); + + for (var entry : stringified) { + for (var line : entry) { + res.add(" " + line); + } + + res.set(res.size() - 1, res.getLast() + ","); + } + res.set(res.size() - 1, res.getLast().substring(0, res.getLast().length() - 1)); + res.add("}"); + + return res; + } + @Override public List toReadableLines(Environment env, HashSet passed) { + return toReadableLines(env, passed, new HashSet<>()); + } + public final boolean setPrototype(PrototypeProvider val) { if (!getState().extendable) return false; prototype = val; diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/BoolValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/BoolValue.java index cb963e4..7fb5398 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/BoolValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/BoolValue.java @@ -20,6 +20,9 @@ public final class BoolValue extends PrimitiveValue { return env.get(BOOL_PROTO); } + @Override public int hashCode() { + return Boolean.hashCode(value); + } @Override public boolean equals(Object other) { if (other == this) return true; else if (other instanceof BoolValue bool) return value == bool.value; diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/StringValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/StringValue.java index c715c51..378dab8 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/StringValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/StringValue.java @@ -1,10 +1,15 @@ package me.topchetoeu.jscript.runtime.values.primitives; +import java.util.Arrays; +import java.util.HashSet; import java.util.LinkedHashSet; +import java.util.List; import java.util.Set; import java.util.WeakHashMap; import me.topchetoeu.jscript.common.environment.Environment; +import me.topchetoeu.jscript.common.json.JSON; +import me.topchetoeu.jscript.common.json.JSONElement; import me.topchetoeu.jscript.common.parsing.Parsing; import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.runtime.values.KeyCache; @@ -31,6 +36,9 @@ public final class StringValue extends PrimitiveValue { } @Override public String toString(Environment ext) { return value; } + @Override public int hashCode() { + return value.hashCode(); + } @Override public boolean equals(Object other) { if (this == other) return true; else if (other instanceof StringValue val) return value.length() == val.value.length() && value.equals(val.value); @@ -67,6 +75,10 @@ public final class StringValue extends PrimitiveValue { return res; } + @Override public List toReadableLines(Environment env, HashSet passed) { + return Arrays.asList(JSON.stringify(JSONElement.string(value))); + } + private StringValue(String value) { this.value = value; } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/SymbolValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/SymbolValue.java index 15e96c1..08d37d0 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/SymbolValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/SymbolValue.java @@ -1,6 +1,9 @@ package me.topchetoeu.jscript.runtime.values.primitives; +import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; +import java.util.List; import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.runtime.exceptions.EngineException; @@ -34,6 +37,11 @@ public final class SymbolValue extends PrimitiveValue { else return "Symbol(" + value + ")"; } + @Override + public List toReadableLines(Environment env, HashSet passed) { + return Arrays.asList(toString()); + } + public SymbolValue(String value) { this.value = value; } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/UserValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/UserValue.java new file mode 100644 index 0000000..33985d8 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/UserValue.java @@ -0,0 +1,69 @@ +package me.topchetoeu.jscript.runtime.values.primitives; + +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import me.topchetoeu.jscript.common.environment.Environment; +import me.topchetoeu.jscript.runtime.values.KeyCache; +import me.topchetoeu.jscript.runtime.values.Member; +import me.topchetoeu.jscript.runtime.values.Value; +import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; +import me.topchetoeu.jscript.runtime.values.primitives.numbers.NumberValue; + +public final class UserValue extends Value { + public final T value; + public final ObjectValue prototype; + + @Override public StringValue type() { return StringValue.of("object"); } + + @Override public boolean toBoolean() { return true; } + @Override public NumberValue toNumber(Environment ext) { return NumberValue.NAN; } + @Override public String toString(Environment ext) { return "[user value]"; } + + @Override public final boolean defineOwnMember(Environment env, KeyCache key, Member member) { return false; } + @Override public final boolean deleteOwnMember(Environment env, KeyCache key) { return false; } + @Override public final boolean isPrimitive() { return false; } + @Override public final Value toPrimitive(Environment env) { return NumberValue.NAN; } + + @Override public final boolean setPrototype(Environment env, ObjectValue val) { return false; } + + @Override public Member getOwnMember(Environment env, KeyCache key) { return null; } + @Override public Set getOwnMembers(Environment env, boolean onlyEnumerable) { return new HashSet<>(); } + @Override public Set getOwnSymbolMembers(Environment env, boolean onlyEnumerable) { return new HashSet<>(); } + + @Override public State getState() { return State.FROZEN; } + + @Override public void preventExtensions() {} + @Override public void seal() {} + @Override public void freeze() {} + + @Override public int hashCode() { + return value.hashCode(); + } + @Override public boolean equals(Object other) { + if (this == other) return true; + else if (other instanceof UserValue val) return Objects.equals(value, val.value); + else return false; + } + + @Override public ObjectValue getPrototype(Environment env) { return prototype; } + + @Override public List toReadableLines(Environment env, HashSet passed) { + return Arrays.asList(value.toString()); + } + + private UserValue(T value, ObjectValue prototype) { + this.value = value; + this.prototype = prototype; + } + + public static UserValue of(T value) { + return new UserValue(value, null); + } + public static UserValue of(T value, ObjectValue prototype) { + return new UserValue(value, prototype); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/VoidValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/VoidValue.java index 0949ef3..938db35 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/VoidValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/VoidValue.java @@ -1,5 +1,9 @@ package me.topchetoeu.jscript.runtime.values.primitives; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; + import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.runtime.exceptions.EngineException; import me.topchetoeu.jscript.runtime.values.KeyCache; @@ -19,7 +23,12 @@ public final class VoidValue extends PrimitiveValue { @Override public ObjectValue getPrototype(Environment env) { return null; } @Override public Member getOwnMember(Environment env, KeyCache key) { - throw EngineException.ofError(String.format("Cannot read properties of %s (reading '%s')", name, key.toString(env))); + if (key.isSymbol()) throw EngineException.ofError(String.format("Cannot read properties of %s (reading '%s')", name, key.toSymbol().toString())); + else throw EngineException.ofError(String.format("Cannot read properties of %s (reading '%s')", name, key.toString(env))); + } + + @Override public List toReadableLines(Environment env, HashSet passed) { + return Arrays.asList(name); } public VoidValue(String name, String type) { diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/numbers/DoubleValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/numbers/DoubleValue.java index 183ff49..0c75f61 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/numbers/DoubleValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/numbers/DoubleValue.java @@ -24,6 +24,9 @@ public final class DoubleValue extends NumberValue { @Override public String toString() { return JSON.stringify(JSONElement.number(value)); } + @Override public int hashCode() { + return Double.hashCode(value); + } @Override public boolean equals(Object other) { if (this == other) return true; else if (other instanceof NumberValue val) return value == val.getDouble(); diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/numbers/IntValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/numbers/IntValue.java index 9d90a75..b624bdb 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/numbers/IntValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/numbers/IntValue.java @@ -1,5 +1,12 @@ package me.topchetoeu.jscript.runtime.values.primitives.numbers; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; + +import me.topchetoeu.jscript.common.environment.Environment; +import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; + public final class IntValue extends NumberValue { public final long value; @@ -19,6 +26,9 @@ public final class IntValue extends NumberValue { return value; } + @Override public int hashCode() { + return Long.hashCode(value); + } @Override public String toString() { return value + ""; } @Override public boolean equals(Object other) { if (this == other) return true; @@ -26,6 +36,10 @@ public final class IntValue extends NumberValue { else return false; } + @Override public List toReadableLines(Environment env, HashSet passed) { + return Arrays.asList(value + "i"); + } + public IntValue(long value) { this.value = value; } diff --git a/src/main/resources/lib/index.js b/src/main/resources/lib/index.js index b7711eb..33708fb 100644 --- a/src/main/resources/lib/index.js +++ b/src/main/resources/lib/index.js @@ -1,455 +1,408 @@ -// return; - -const target = arguments[0]; -const primordials = arguments[1]; - -const symbol = primordials.symbol || (() => { - const repo = {}; - - return { - makeSymbol: (name) => { name }, - getSymbol(name) { - if (name in repo) return repo[name]; - else return repo[name] = { name }; - }, - getSymbolKey(symbol) { - if (symbol.name in repo && repo[symbol.name] === symbol) return symbol.name; - else return undefined; - }, - getSymbolDescription: ({ name }) => name, - }; -}); - -const number = primordials.number || (() => { - return { - parseInt() { throw new Error("parseInt not supported"); }, - parseFloat() { throw new Error("parseFloat not supported"); }, - isNaN: (val) => val !== val, - NaN: 0 / 0, - Infinity: 1 / 0, - }; -}); - -const fromCharCode = primordials.string.fromCharCode; -const fromCodePoint = primordials.string.fromCodePoint; -const stringBuild = primordials.string.stringBuild; - -const defineProperty = primordials.object.defineProperty; -const defineField = primordials.object.defineField; -const getOwnMember = primordials.object.getMember; -const getOwnSymbolMember = primordials.object.getOwnSymbolMember; -const getOwnMembers = primordials.object.getOwnMembers; -const getOwnSymbolMembers = primordials.object.getOwnSymbolMembers; -const getPrototype = primordials.object.getPrototype; -const setPrototype = primordials.object.setPrototype; - -const invokeType = primordials.function.invokeType; -const setConstructable = primordials.function.setConstructable; -const setCallable = primordials.function.setCallable; -const invoke = primordials.function.invoke; -const construct = primordials.function.construct; - -const json = primordials.json; - -const setGlobalPrototype = primordials.setGlobalPrototype; -const compile = primordials.compile; -const setIntrinsic = primordials.setIntrinsic; - -const valueKey = symbol.makeSymbol("Primitive.value"); -const undefined = ({}).definitelyDefined; - -target.undefined = undefined; - -const unwrapThis = (self, type, constr, name, arg = "this", defaultVal) => { - if (typeof self === type) return self; - if (self instanceof constr && valueKey in self) self = self[valueKey]; - if (typeof self === type) return self; - if (defaultVal !== undefined) return defaultVal; - - throw new TypeError(name + " requires that '" + arg + "' be a " + constr.name); -} - -const wrapIndex = (i, len) => {}; - -class Symbol { - get description() { - return symbol.getSymbolDescriptor(unwrapThis(this, "symbol", Symbol, "Symbol.prototype.description")); - } - toString() { - return "Symbol(" + unwrapThis(this, "symbol", Symbol, "Symbol.prototype.toString").description + ")"; - } - valueOf() { - return unwrapThis(this, "symbol", Symbol, "Symbol.prototype.valueOf"); - } - - constructor(name = "") { - return symbol.makeSymbol(name); - } - - static for(name) { - return symbol.getSymbol(name + ""); - } - static keyFor(value) { - return symbol.getSymbolKey(unwrapThis(value, "symbol", Symbol, "Symbol.keyFor")); - } -} - -setCallable(Symbol, true); -setConstructable(Symbol, false); - -defineField(Symbol, "asyncIterator", false, false, false, Symbol("Symbol.asyncIterator")); -defineField(Symbol, "iterator", false, false, false, Symbol("Symbol.iterator")); -defineField(Symbol, "match", false, false, false, Symbol("Symbol.match")); -defineField(Symbol, "matchAll", false, false, false, Symbol("Symbol.matchAll")); -defineField(Symbol, "replace", false, false, false, Symbol("Symbol.replace")); -defineField(Symbol, "search", false, false, false, Symbol("Symbol.search")); -defineField(Symbol, "split", false, false, false, Symbol("Symbol.split")); -defineField(Symbol, "toStringTag", false, false, false, Symbol("Symbol.toStringTag")); - -Symbol(); -target.Symbol = Symbol; - -class Number { - toString() { - return "" + unwrapThis(this, "number", Number, "Number.prototype.toString"); - } - valueOf() { - return unwrapThis(this, "number", Number, "Number.prototype.toString"); - } - - constructor(value) { - if (invokeType(arguments) === "call") { - if (arguments.length === 0) return 0; - else return +value; - } - - this[valueKey] = target.Number(value); - } - - static isFinite(value) { - value = unwrapThis(value, "number", Number, "Number.isFinite", "value", undefined); - - if (value === undefined || value !== value) return false; - if (value === Infinity || value === -Infinity) return false; - - return true; - } - static isInteger(value) { - value = unwrapThis(value, "number", Number, "Number.isInteger", "value", undefined); - if (value === undefined) return false; - return number.parseInt(value) === value; - } - static isNaN(value) { - return number.isNaN(value); - } - static isSafeInteger(value) { - value = unwrapThis(value, "number", Number, "Number.isSafeInteger", "value", undefined); - if (value === undefined || number.parseInt(value) !== value) return false; - return value >= -9007199254740991 && value <= 9007199254740991; - } - static parseFloat(value) { - value = 0 + value; - return number.parseFloat(value); - } - static parseInt(value, radix) { - value = 0 + value; - radix = +radix; - if (number.isNaN(radix)) radix = 10; - - return number.parseInt(value, radix); - } -} - -defineField(Number, "EPSILON", false, false, false, 2.220446049250313e-16); -defineField(Number, "MIN_SAFE_INTEGER", false, false, false, -9007199254740991); -defineField(Number, "MAX_SAFE_INTEGER", false, false, false, 9007199254740991); -defineField(Number, "POSITIVE_INFINITY", false, false, false, +number.Infinity); -defineField(Number, "NEGATIVE_INFINITY", false, false, false, -number.Infinity); -defineField(Number, "NaN", false, false, false, number.NaN); -defineField(Number, "MAX_VALUE", false, false, false, 1.7976931348623157e+308); -defineField(Number, "MIN_VALUE", false, false, false, 5e-324); - -setCallable(Number, true); -target.Number = Number; -target.parseInt = Number.parseInt; -target.parseFloat = Number.parseFloat; -target.NaN = Number.NaN; -target.Infinity = Number.POSITIVE_INFINITY; - -class String { - at(index) { - throw "Not implemented :/"; - return unwrapThis(this, "string", String, "String.prototype.at")[index]; - } - toString() { - return unwrapThis(this, "string", String, "String.prototype.toString"); - } - valueOf() { - return unwrapThis(this, "string", String, "String.prototype.valueOf"); - } - - constructor(value) { - if (invokeType(arguments) === "call") { - if (arguments.length === 0) return ""; - else return value + ""; - } - - this[valueKey] = String(value); - } - - static fromCharCode() { - const res = []; - res[arguments.length] = 0; - - for (let i = 0; i < arguments.length; i++) { - res[i] = fromCharCode(+arguments[i]); - } - - return stringBuild(res); - } - static fromCodePoint() { - const res = []; - res[arguments.length] = 0; - - for (let i = 0; i < arguments.length; i++) { - res[i] = fromCodePoint(+arguments[i]); - } - - return stringBuild(res); - } -} - -setCallable(String, true); -target.String = String; - -class Boolean { - toString() { - return "" + unwrapThis(this, "boolean", Boolean, "Boolean.prototype.toString"); - } - valueOf() { - return unwrapThis(this, "boolean", Boolean, "Boolean.prototype.valueOf"); - } - - constructor(value) { - if (invokeType(arguments) === "call") { - if (arguments.length === 0) return false; - else return !!value; - } - - this[valueKey] = Boolean(value); - } -} - -setCallable(Boolean, true); -target.Boolean = Boolean; - -class Object { - toString() { - print("2"); - if (this !== null && this !== undefined && (Symbol.toStringTag in this)) return "[object " + this[Symbol.toStringTag] + "]"; - else if (typeof this === "number" || this instanceof Number) return "[object Number]"; - else if (typeof this === "symbol" || this instanceof Symbol) return "[object Symbol]"; - else if (typeof this === "string" || this instanceof String) return "[object String]"; - else if (typeof this === "boolean" || this instanceof Boolean) return "[object Boolean]"; - else if (typeof this === "function") return "[object Function]"; - else return "[object Object]"; - } - valueOf() { - print("1"); - return this; - } - - constructor(value) { - if (typeof value === 'object' && value !== null) return value; - - if (typeof value === 'string') return new String(value); - if (typeof value === 'number') return new Number(value); - if (typeof value === 'boolean') return new Boolean(value); - if (typeof value === 'symbol') { - const res = {}; - setPrototype(res, Symbol.prototype); - res[valueKey] = value; - return res; - } - - const target = this; - // TODO: use new.target.prototype as proto - if (target == null || typeof target !== 'object') target = {}; - - this[valueKey] = Object(value); - } - - static defineProperty(obj, key, desc) { - if (typeof obj !== "object" || obj === null) { - print(obj); - print(typeof obj); - throw new TypeError("Object.defineProperty called on non-object"); - } - if (typeof desc !== "object" || desc === null) throw new TypeError("Property description must be an object: " + desc); - - if ("get" in desc || "set" in desc) { - let get = desc.get, set = desc.set; - - print(typeof get); - - if (get !== undefined && typeof get !== "function") throw new TypeError("Getter must be a function: " + get); - if (set !== undefined && typeof set !== "function") throw new TypeError("Setter must be a function: " + set); - - if ("value" in desc || "writable" in desc) { - throw new TypeError("Invalid property descriptor. Cannot both specify accessors and a value or writable attribute"); - } - - if (!defineProperty(obj, key, desc.enumerable, desc.configurable, get, set)) { - throw new TypeError("Cannot redefine property: " + key); - } - } - else if (!defineField(obj, key, desc.writable, desc.enumerable, desc.configurable, desc.value)) { - throw new TypeError("Cannot redefine property: " + key); - } - - return obj; - } -} - -setCallable(Object, true); -setPrototype(Object.prototype, null); -target.Object = Object; - -class Function { - toString() { - if (this.name !== "") return "function " + this.name + "(...) { ... }"; - else return "function (...) { ... }"; - } - - constructor() { - const parts = ["return function annonymous("]; - - for (let i = 0; i < arguments.length - 1; i++) { - if (i > 0) parts[parts.length] = ","; - parts[parts.length] = arguments[i]; - } - parts[parts.length] = "){\n"; - parts[parts.length] = String(arguments[arguments.length - 1]); - parts[parts.length] = "\n}"; - - const res = compile(stringBuild(parts))(); - return res; - } - - static compile(src = "", { globals = [], wrap = false } = {}) { - const parts = []; - - if (wrap) parts[parts.length] = "return (function() {\n"; - if (globals.length > 0) { - parts[parts.length] = "var "; - - for (let i = 0; i < globals.length; i++) { - if (i > 0) parts[parts.length] = ","; - parts[parts.length] = globals[i]; - } - - parts[parts.length] = ";((g=arguments[0])=>{"; - - for (let i = 0; i < globals.length; i++) { - const name = globals[i]; - parts[parts.length] = name + "=g[" + json.stringify(name) + "];"; - } - - parts[parts.length] = "})()\n"; - } - - parts[parts.length] = src; - if (wrap) parts[parts.length] = "\n})(arguments[0])"; - - const res = compile(stringBuild(parts)); - return res; - } -} - -setCallable(Function, true); -target.Function = Function; - -class Array { - constructor(len) { - if (arguments.length === 1 && typeof len === "number") { - const res = []; - res.length = len; - return res; - } - // TODO: Implement spreading - else throw new Error("Spreading not implemented"); - } -} - -setCallable(Array, true); -target.Array = Array; - -class Error { - toString() { - let res = this.name || "Error"; - - const msg = this.message; - if (msg) res += ": " + msg; - - return res; - } - - constructor (msg = "") { - if (invokeType(arguments) === "call") return new Error(msg); - this.message = msg + ""; - } -} - -defineField(Error.prototype, "name", true, false, true, "Error"); -defineField(Error.prototype, "message", true, false, true, ""); -setCallable(Error, true); -target.Error = Error; - -class SyntaxError { - constructor (msg = "") { - if (invokeType(arguments) === "call") return new SyntaxError(msg); - this.message = msg + ""; - } -} - -defineField(SyntaxError.prototype, "name", true, false, true, "SyntaxError"); -setPrototype(SyntaxError, Error); -setPrototype(SyntaxError.prototype, Error.prototype); -setCallable(SyntaxError, true); -target.SyntaxError = SyntaxError; - -class TypeError { - constructor (msg = "") { - if (invokeType(arguments) === "call") return new TypeError(msg); - this.message = msg + ""; - } -} - -defineField(TypeError.prototype, "name", true, false, true, "TypeError"); -setPrototype(TypeError, Error); -setPrototype(TypeError.prototype, Error.prototype); -setCallable(TypeError, true); -target.TypeError = TypeError; - -class RangeError { - constructor (msg = "") { - if (invokeType(arguments) === "call") return new RangeError(msg); - this.message = msg + ""; - } -} - -defineField(RangeError.prototype, "name", true, false, true, "RangeError"); -setPrototype(RangeError, Error); -setPrototype(RangeError.prototype, Error.prototype); -setCallable(RangeError, true); -target.RangeError = RangeError; - -setGlobalPrototype("string", String.prototype); -setGlobalPrototype("number", Number.prototype); -setGlobalPrototype("boolean", Boolean.prototype); -setGlobalPrototype("symbol", Symbol.prototype); -setGlobalPrototype("object", Object.prototype); -setGlobalPrototype("array", Array.prototype); -setGlobalPrototype("function", Function.prototype); -setGlobalPrototype("error", Error.prototype); -setGlobalPrototype("syntax", SyntaxError.prototype); +(function main() { + function extend(derived, base) { + if (base == null) { + object.setPrototype(derived.prototype, null); + } + else { + object.setPrototype(derived, base); + object.setPrototype(derived.prototype, base.prototype); + } + } + + var target = arguments[0]; + var primordials = arguments[1]; + var symbol = primordials.symbol || (function () { + var repo = {}; + return { + makeSymbol: function (name) { return { name: name }; }, + getSymbol: function (name) { + if (name in repo) return repo[name]; + else return repo[name] = { name: name }; + }, + getSymbolKey: function (symbol) { + if (symbol.name in repo && repo[symbol.name] === symbol) return symbol.name; + else return undefined; + }, + getSymbolDescription: function (symbol) { + return symbol.name; + } + }; + }); + var number = primordials.number || { + parseInt: function () { throw new Error("parseInt not supported"); }, + parseFloat: function () { throw new Error("parseFloat not supported"); }, + isNaN: function (val) { return val !== val; }, + NaN: 0 / 0, + Infinity: 1 / 0, + }; + var string = primordials.string; + var object = primordials.object || { + defineProperty: function () { throw new Error("Define property not polyfillable"); }, + defineField: function (obj, key, a, b, c, value) { obj[key] = value; }, + getOwnMember: function () { throw new Error("Get own member not polyfillable"); }, + getOwnSymbolMember: function () { throw new Error("Get own symbol member not polyfillable"); }, + getOwnMembers: function () { throw new Error("Get own members not polyfillable"); }, + getOwnSymbolMembers: function () { throw new Error("Get own symbol members not polyfillable"); }, + getPrototype: function () { throw new Error("Get prototype not polyfillable"); }, + setPrototype: function () { throw new Error("Set prototype not polyfillable"); }, + }; + var func = primordials.function || { + invokeType: function (args, self) { + if (typeof self === "object") return "new"; + else return "call"; + }, + setConstructable: function () { throw new Error("Set constructable not polyfillable"); }, + setCallable: function () { throw new Error("Set callable not polyfillable"); }, + invoke: function () { throw new Error("Invoke not polyfillable"); }, + construct: function () { throw new Error("Construct not polyfillable"); }, + }; + var json = primordials.json || { + stringify: function (val) { throw new Error("JSON stringify not polyfillable"); }, + parse: function (val) { throw new Error("JSON parse not polyfillable"); }, + } + + var setGlobalPrototypes = primordials.setGlobalPrototypes; + var compile = primordials.compile; + var valueKey = symbol.makeSymbol("Primitive.value"); + var undefined = void 0; + target.undefined = undefined; + + function unwrapThis(self, type, constr, name, arg, defaultVal) { + if (arg === void 0) { arg = "this"; } + if (typeof self === type) + return self; + if (self instanceof constr && valueKey in self) + self = self[valueKey]; + if (typeof self === type) + return self; + if (defaultVal !== undefined) + return defaultVal; + throw new TypeError(name + " requires that '" + arg + "' be a " + constr.name); + } + function wrapIndex(i, len) { } + function Symbol(name) { + if (name === undefined) name = ""; + return symbol.makeSymbol(name); + } + Symbol.prototype.toString = function () { + return "Symbol(" + unwrapThis(this, "symbol", Symbol, "Symbol.prototype.toString").description + ")"; + }; + Symbol.prototype.valueOf = function () { + return unwrapThis(this, "symbol", Symbol, "Symbol.prototype.valueOf"); + }; + Symbol.for = function (name) { + return symbol.getSymbol(name + ""); + }; + Symbol.keyFor = function (value) { + return symbol.getSymbolKey(unwrapThis(value, "symbol", Symbol, "Symbol.keyFor")); + }; + object.defineProperty(Symbol.prototype, "desc", false, true, function () { + return symbol.getSymbolDescriptor(unwrapThis(this, "symbol", Symbol, "Symbol.prototype.description")); + }); + object.defineField(Symbol, "asyncIterator", false, false, false, Symbol("Symbol.asyncIterator")); + object.defineField(Symbol, "iterator", false, false, false, Symbol("Symbol.iterator")); + object.defineField(Symbol, "match", false, false, false, Symbol("Symbol.match")); + object.defineField(Symbol, "matchAll", false, false, false, Symbol("Symbol.matchAll")); + object.defineField(Symbol, "replace", false, false, false, Symbol("Symbol.replace")); + object.defineField(Symbol, "search", false, false, false, Symbol("Symbol.search")); + object.defineField(Symbol, "split", false, false, false, Symbol("Symbol.split")); + object.defineField(Symbol, "toStringTag", false, false, false, Symbol("Symbol.toStringTag")); + func.setConstructable(Symbol, false); + target.Symbol = Symbol; + + function Number(value) { + if (func.invokeType(arguments, this) === "call") { + if (arguments.length === 0) return 0; + else return +value; + } + this[valueKey] = target.Number(value); + } + Number.prototype.toString = function () { + return "" + unwrapThis(this, "number", Number, "Number.prototype.toString"); + }; + Number.prototype.valueOf = function () { + return unwrapThis(this, "number", Number, "Number.prototype.toString"); + }; + Number.isFinite = function (value) { + value = unwrapThis(value, "number", Number, "Number.isFinite", "value", undefined); + if (value === undefined || value !== value) + return false; + if (value === Infinity || value === -Infinity) + return false; + return true; + }; + Number.isInteger = function (value) { + value = unwrapThis(value, "number", Number, "Number.isInteger", "value", undefined); + if (value === undefined) + return false; + return number.parseInt(value) === value; + }; + Number.isNaN = function (value) { + return number.isNaN(value); + }; + Number.isSafeInteger = function (value) { + value = unwrapThis(value, "number", Number, "Number.isSafeInteger", "value", undefined); + if (value === undefined || number.parseInt(value) !== value) + return false; + return value >= -9007199254740991 && value <= 9007199254740991; + }; + Number.parseFloat = function (value) { + value = 0 + value; + return number.parseFloat(value); + }; + Number.parseInt = function (value, radix) { + value = 0 + value; + radix = +radix; + if (number.isNaN(radix)) + radix = 10; + return number.parseInt(value, radix); + }; + + object.defineField(Number, "EPSILON", false, false, false, 2.220446049250313e-16); + object.defineField(Number, "MIN_SAFE_INTEGER", false, false, false, -9007199254740991); + object.defineField(Number, "MAX_SAFE_INTEGER", false, false, false, 9007199254740991); + object.defineField(Number, "POSITIVE_INFINITY", false, false, false, +number.Infinity); + object.defineField(Number, "NEGATIVE_INFINITY", false, false, false, -number.Infinity); + object.defineField(Number, "NaN", false, false, false, number.NaN); + object.defineField(Number, "MAX_VALUE", false, false, false, 1.7976931348623157e+308); + object.defineField(Number, "MIN_VALUE", false, false, false, 5e-324); + func.setCallable(Number, true); + target.Number = Number; + target.parseInt = Number.parseInt; + target.parseFloat = Number.parseFloat; + target.NaN = Number.NaN; + target.Infinity = Number.POSITIVE_INFINITY; + + function String(value) { + if (func.invokeType(arguments, this) === "call") { + if (arguments.length === 0) + return ""; + else + return value + ""; + } + this[valueKey] = String(value); + } + String.prototype.at = function (index) { + throw "Not implemented :/"; + return unwrapThis(this, "string", String, "String.prototype.at")[index]; + }; + String.prototype.toString = function () { + return unwrapThis(this, "string", String, "String.prototype.toString"); + }; + String.prototype.valueOf = function () { + return unwrapThis(this, "string", String, "String.prototype.valueOf"); + }; + String.fromCharCode = function () { + var res = []; + res[arguments.length] = 0; + for (var i = 0; i < arguments.length; i++) { + res[i] = string.fromCharCode(+arguments[i]); + } + return string.stringBuild(res); + }; + String.fromCodePoint = function () { + var res = []; + res[arguments.length] = 0; + + for (var i = 0; i < arguments.length; i++) { + res[i] = string.fromCodePoint(+arguments[i]); + } + return string.stringBuild(res); + }; + func.setCallable(String, true); + target.String = String; + + function Boolean(value) { + if (func.invokeType(arguments, this) === "call") { + if (arguments.length === 0) return false; + else return !!value; + } + this[valueKey] = Boolean(value); + } + Boolean.prototype.toString = function () { + return "" + unwrapThis(this, "boolean", Boolean, "Boolean.prototype.toString"); + }; + Boolean.prototype.valueOf = function () { + return unwrapThis(this, "boolean", Boolean, "Boolean.prototype.valueOf"); + }; + func.setCallable(Boolean, true); + target.Boolean = Boolean; + + function Object(value) { + if (typeof value === 'object' && value !== null) return value; + if (typeof value === 'string') return new String(value); + if (typeof value === 'number') return new Number(value); + if (typeof value === 'boolean') return new Boolean(value); + if (typeof value === 'symbol') { + var res = {}; + setPrototype(res, Symbol.prototype); + res[valueKey] = value; + return res; + } + + return {}; + // // TODO: use new.target.prototype as proto + // if (target == null || typeof target !== 'object') target = {}; + // return target; + } + Object.prototype.toString = function () { + if (this !== null && this !== undefined && (Symbol.toStringTag in this)) return "[object " + this[Symbol.toStringTag] + "]"; + else if (typeof this === "number" || this instanceof Number) return "[object Number]"; + else if (typeof this === "symbol" || this instanceof Symbol) return "[object Symbol]"; + else if (typeof this === "string" || this instanceof String) return "[object String]"; + else if (typeof this === "boolean" || this instanceof Boolean) return "[object Boolean]"; + else if (typeof this === "function") return "[object Function]"; + else return "[object Object]"; + }; + Object.prototype.valueOf = function () { + return this; + }; + Object.defineProperty = function (obj, key, desc) { + if (typeof obj !== "object" || obj === null) throw new TypeError("Object.defineProperty called on non-object"); + if (typeof desc !== "object" || desc === null) throw new TypeError("Property description must be an object: " + desc); + if ("get" in desc || "set" in desc) { + var get = desc.get, set = desc.set; + + if (get !== undefined && typeof get !== "function") throw new TypeError("Getter must be a function: " + get); + if (set !== undefined && typeof set !== "function") throw new TypeError("Setter must be a function: " + set); + if ("value" in desc || "writable" in desc) { + throw new TypeError("Invalid property descriptor. Cannot both specify accessors and a value or writable attribute"); + } + if (!object.defineProperty(obj, key, desc.enumerable, desc.configurable, get, set)) { + throw new TypeError("Cannot redefine property: " + key); + } + } + else if (!object.defineField(obj, key, desc.writable, desc.enumerable, desc.configurable, desc.value)) { + throw new TypeError("Cannot redefine property: " + key); + } + + return obj; + }; + func.setCallable(Object, true); + extend(Object, null); + object.setPrototype(Object.prototype, null); + target.Object = Object; + + function Function() { + var parts = ["(function annonymous("]; + for (var i = 0; i < arguments.length - 1; i++) { + if (i > 0) + parts[parts.length] = ","; + parts[parts.length] = arguments[i]; + } + parts[parts.length] = "){\n"; + parts[parts.length] = String(arguments[arguments.length - 1]); + parts[parts.length] = "\n})"; + var res = compile(string.stringBuild(parts))(); + return res; + } + Function.prototype.toString = function () { + if (this.name !== "") + return "function " + this.name + "(...) { ... }"; + else + return "function (...) { ... }"; + }; + Function.compile = function (src, opts) { + if (src === void 0) src = ""; + if (opts === void 0) opts = {}; + if (opts.globals === void 0) opts.globals = []; + if (opts.wrap === void 0) opts.wrap = false; + + var globals = opts.globals; + var wrap = opts.wrap; + var parts = []; + + if (wrap) parts[parts.length] = "return (function() {\n"; + if (globals.length > 0) { + parts[parts.length] = "let {"; + for (var i = 0; i < globals.length; i++) { + if (i > 0) parts[parts.length] = ","; + parts[parts.length] = globals[i]; + } + parts[parts.length] = "} = arguments[0];"; + } + parts[parts.length] = src; + if (wrap) parts[parts.length] = "\n})(arguments[0])"; + + var res = compile(string.stringBuild(parts)); + return res; + }; + func.setCallable(Function, true); + target.Function = Function; + + function Array(len) { + if (arguments.length === 1 && typeof len === "number") { + var res = []; + res.length = len; + return res; + } + // TODO: Implement spreading + else throw new Error("Spreading not implemented"); + } + func.setCallable(Array, true); + target.Array = Array; + + function Error(msg) { + if (msg === void 0) { msg = ""; } + if (func.invokeType(arguments, this) === "call") + return new Error(msg); + this.message = msg + ""; + } + Error.prototype.toString = function () { + var res = this.name || "Error"; + var msg = this.message; + if (msg) + res += ": " + msg; + return res; + }; + object.defineField(Error.prototype, "name", true, false, true, "Error"); + object.defineField(Error.prototype, "message", true, false, true, ""); + func.setCallable(Error, true); + target.Error = Error; + + extend(SyntaxError, Error); + function SyntaxError(msg) { + if (func.invokeType(arguments, this) === "call") + return new SyntaxError(msg); + return _super.call(this, msg) || this; + } + object.defineField(SyntaxError.prototype, "name", true, false, true, "SyntaxError"); + func.setCallable(SyntaxError, true); + target.SyntaxError = SyntaxError; + + extend(TypeError, Error); + function TypeError(msg) { + if (func.invokeType(arguments, this) === "call") + return new TypeError(msg); + return _super.call(this, msg) || this; + } + object.defineField(TypeError.prototype, "name", true, false, true, "TypeError"); + func.setCallable(TypeError, true); + target.TypeError = TypeError; + + extend(RangeError, Error); + function RangeError(msg) { + if (func.invokeType(arguments, this) === "call") + return new RangeError(msg); + return _super.call(this, msg) || this; + } + object.defineField(RangeError.prototype, "name", true, false, true, "RangeError"); + func.setCallable(RangeError, true); + target.RangeError = RangeError; + + target.uint8 = primordials.uint8; + + setGlobalPrototypes({ + string: String.prototype, + number: Number.prototype, + boolean: Boolean.prototype, + symbol: Symbol.prototype, + object: Object.prototype, + array: Array.prototype, + function: Function.prototype, + error: Error.prototype, + syntax: SyntaxError.prototype, + range: RangeError.prototype, + type: TypeError.prototype, + }); +})(arguments[0], arguments[1]);