From c5067cbfdd4d84e26fa99e45885ab8507f56b322 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 23 Nov 2024 20:06:24 +0200 Subject: [PATCH] regress: remove ES6 variables and simplify scope --- .../compilation/scope/FunctionScope.java | 206 ++++++++++----- .../jscript/compilation/scope/Scope.java | 249 ------------------ .../jscript/compilation/scope/Variable.java | 6 - .../compilation/scope/VariableIndex.java | 19 +- .../compilation/scope/VariableList.java | 132 ++++------ 5 files changed, 205 insertions(+), 407 deletions(-) delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java 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 81850c3..8d38d50 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/scope/FunctionScope.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/scope/FunctionScope.java @@ -1,64 +1,90 @@ 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 catches = new VariableList(VariableIndex.IndexType.LOCALS, this.locals); + protected final VariableList capturables = new VariableList(VariableIndex.IndexType.CAPTURABLES, this.catches); 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 old = get(var.name, false); + if (old != null) return old; + + 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.catches.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()); @@ -69,49 +95,67 @@ public class FunctionScope extends Scope { return childVar; } - @Override public Variable get(Variable var, boolean capture) { - if (parentToChild.containsKey(var)) return addCaptured(parentToChild.get(var), capture); + /** + * 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 (catches.has(var)) return addCaptured(var, capture); if (captures.has(var)) return addCaptured(var, capture); if (locals.has(var)) return addCaptured(var, capture); - if (captureParent == null) return null; - - var parentVar = captureParent.get(var, true); - if (parentVar == null) return null; - - var childVar = captures.add(parentVar.clone()); - childToParent.put(childVar, parentVar); - parentToChild.put(parentVar, childVar); - - return childVar; + 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; } - @Override public boolean has(String name, boolean capture) { - if (functionVarMap.containsKey(name)) return true; - if (specialVarMap.containsKey(name)) return true; + /** + * 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++; @@ -120,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 92ffdd4..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java +++ /dev/null @@ -1,249 +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) { - if (!this.capturables.has(var)) 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; - } - /** - * Gets the index supplier of the given variable, or null if it isn't in this scope chain. - * This will also execute capturing logic. - * - * @param capture If true, the variable is being captured by a function - */ - public Variable get(Variable var, boolean capture) { - if (locals.has(var)) return addCaptured(var, capture); - if (parent != null) return parent.get(var, 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 += curr.locals.size(); - } - - return res; - } - public final int capturablesOffset() { - var res = 0; - - for (var curr = this; curr != null; curr = curr.parent) { - if (curr != this) res += curr.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..278ab88 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() { @@ -44,14 +53,6 @@ public final class VariableIndex { 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 5d8dcb5..9943083 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; 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; @@ -139,38 +124,26 @@ public final class VariableList { 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; - } - - 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() { @@ -186,19 +159,28 @@ 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; } - public VariableList(VariableIndex.IndexType type) { + public VariableList(IndexType type) { this.indexType = type; this.offset = null; }