regress: remove ES6 variables and simplify scope

This commit is contained in:
TopchetoEU 2024-11-23 20:06:24 +02:00
parent 5644966dd7
commit c5067cbfdd
Signed by: topchetoeu
GPG Key ID: 6531B8583E5F6ED4
5 changed files with 205 additions and 407 deletions

View File

@ -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<String, Variable> specialVarMap = new HashMap<>();
private final HashMap<String, Variable> functionVarMap = new HashMap<>();
private final HashMap<String, Variable> localsMap = new HashMap<>();
private final HashMap<String, Variable> capturesMap = new HashMap<>();
private final HashSet<String> blacklistNames = new HashSet<>();
private final ArrayList<Variable> catchesMap = new ArrayList<>();
private final HashMap<Variable, Variable> childToParent = new HashMap<>();
private final HashMap<Variable, Variable> 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;
if (capture) {
if (parentToChild.containsKey(var)) return addCaptured(parentToChild.get(var), capture);
var parentVar = captureParent.get(var, true);
if (parentVar == null) return null;
if (parent == null) return null;
var childVar = captures.add(parentVar.clone());
childToParent.put(childVar, parentVar);
parentToChild.put(parentVar, childVar);
var parentVar = parent.get(var, true);
if (parentVar == null) return null;
return childVar;
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<Variable> capturables() {
return capturables;
}
public FunctionScope(boolean passtrough) {
super();
this.captureParent = null;
this.passtrough = passtrough;
this.singleEntry = false;
public Iterable<Variable> 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;
}
}

View File

@ -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<String, Variable> 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<Scope> 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("<temp>", 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<Variable> capturables() {
return capturables.all();
}
public Iterable<Variable> 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;
}
}

View File

@ -4,20 +4,14 @@ import java.util.function.Supplier;
public final class Variable {
private Supplier<VariableIndex> 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<VariableIndex> index) {
this.indexSupplier = index;
return this;

View File

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

View File

@ -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<Variable> {
private final class VariableNode implements Supplier<VariableIndex> {
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<VariableNode> frozenList = null;
private HashMap<Variable, VariableNode> 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<VariableIndex> 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<Variable> all() {
if (frozen()) return () -> frozenList.stream().map(v -> v.var).iterator();
else return () -> new Iterator<Variable>() {
public Iterator<Variable> iterator() {
return new Iterator<Variable>() {
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;
}