regress: remove ES6 variables and simplify scope
This commit is contained in:
parent
5644966dd7
commit
c5067cbfdd
@ -1,64 +1,90 @@
|
|||||||
package me.topchetoeu.jscript.compilation.scope;
|
package me.topchetoeu.jscript.compilation.scope;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.parsing.Location;
|
public final class FunctionScope {
|
||||||
|
protected final VariableList locals = new VariableList(VariableIndex.IndexType.LOCALS);
|
||||||
public class FunctionScope extends Scope {
|
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 VariableList captures = new VariableList(VariableIndex.IndexType.CAPTURES);
|
||||||
|
|
||||||
private final HashMap<String, Variable> specialVarMap = new HashMap<>();
|
private final HashMap<String, Variable> localsMap = new HashMap<>();
|
||||||
private final HashMap<String, Variable> functionVarMap = new HashMap<>();
|
|
||||||
private final HashMap<String, Variable> capturesMap = 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> childToParent = new HashMap<>();
|
||||||
private final HashMap<Variable, Variable> parentToChild = 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;
|
private Variable addCaptured(Variable var, boolean captured) {
|
||||||
|
if (captured && !this.capturables.has(var)) this.capturables.add(var);
|
||||||
@Override public boolean hasNonStrict(String name) {
|
return var;
|
||||||
if (functionVarMap.containsKey(name)) return true;
|
|
||||||
if (blacklistNames.contains(name)) return true;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override public Variable define(Variable var, Location loc) {
|
private Variable getCatchVar(String name) {
|
||||||
checkNotEnded();
|
for (var el : catchesMap) {
|
||||||
if (strictVarMap.containsKey(var.name)) throw alreadyDefinedErr(loc, var.name);
|
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 {
|
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);
|
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);
|
* Creates a catch variable and returns it
|
||||||
if (superRes != null) return superRes;
|
*/
|
||||||
|
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 (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;
|
if (parentVar == null) return null;
|
||||||
|
|
||||||
var childVar = captures.add(parentVar.clone());
|
var childVar = captures.add(parentVar.clone());
|
||||||
@ -69,14 +95,24 @@ public class FunctionScope extends Scope {
|
|||||||
return childVar;
|
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 (captures.has(var)) return addCaptured(var, capture);
|
||||||
if (locals.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 (parent == null) return null;
|
||||||
|
|
||||||
|
var parentVar = parent.get(var, true);
|
||||||
if (parentVar == null) return null;
|
if (parentVar == null) return null;
|
||||||
|
|
||||||
var childVar = captures.add(parentVar.clone());
|
var childVar = captures.add(parentVar.clone());
|
||||||
@ -85,33 +121,41 @@ public class FunctionScope extends Scope {
|
|||||||
|
|
||||||
return childVar;
|
return childVar;
|
||||||
}
|
}
|
||||||
|
else return null;
|
||||||
|
}
|
||||||
|
|
||||||
@Override public boolean has(String name, boolean capture) {
|
/**
|
||||||
if (functionVarMap.containsKey(name)) return true;
|
* Checks if the given variable name is accessible
|
||||||
if (specialVarMap.containsKey(name)) return true;
|
*
|
||||||
|
* @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 (capture) {
|
||||||
if (capturesMap.containsKey(name)) return true;
|
if (capturesMap.containsKey(name)) return true;
|
||||||
if (captureParent != null) return captureParent.has(name, true);
|
if (parent != null) return parent.has(name, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override protected void onFinish() {
|
public int localsCount() {
|
||||||
captures.freeze();
|
return locals.size();
|
||||||
super.onFinish();
|
|
||||||
}
|
}
|
||||||
|
public int capturesCount() {
|
||||||
@Override public int capturesCount() {
|
|
||||||
return captures.size();
|
return captures.size();
|
||||||
}
|
}
|
||||||
|
public int capturablesCount() {
|
||||||
|
return capturables.size();
|
||||||
|
}
|
||||||
|
|
||||||
public int[] getCaptureIndices() {
|
public int[] getCaptureIndices() {
|
||||||
var res = new int[captures.size()];
|
var res = new int[captures.size()];
|
||||||
var i = 0;
|
var i = 0;
|
||||||
|
|
||||||
for (var el : captures.all()) {
|
for (var el : captures) {
|
||||||
assert childToParent.containsKey(el);
|
assert childToParent.containsKey(el);
|
||||||
res[i] = childToParent.get(el).index().toCaptureIndex();
|
res[i] = childToParent.get(el).index().toCaptureIndex();
|
||||||
i++;
|
i++;
|
||||||
@ -120,17 +164,43 @@ public class FunctionScope extends Scope {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
public FunctionScope(Scope parent) {
|
public Iterable<Variable> capturables() {
|
||||||
super();
|
return capturables;
|
||||||
if (parent.finished()) throw new RuntimeException("Parent is finished");
|
|
||||||
this.captureParent = parent;
|
|
||||||
this.passtrough = false;
|
|
||||||
this.singleEntry = false;
|
|
||||||
}
|
}
|
||||||
public FunctionScope(boolean passtrough) {
|
public Iterable<Variable> locals() {
|
||||||
super();
|
return locals;
|
||||||
this.captureParent = null;
|
}
|
||||||
this.passtrough = passtrough;
|
|
||||||
this.singleEntry = false;
|
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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,20 +4,14 @@ import java.util.function.Supplier;
|
|||||||
|
|
||||||
public final class Variable {
|
public final class Variable {
|
||||||
private Supplier<VariableIndex> indexSupplier;
|
private Supplier<VariableIndex> indexSupplier;
|
||||||
private boolean frozen;
|
|
||||||
|
|
||||||
public final boolean readonly;
|
public final boolean readonly;
|
||||||
public final String name;
|
public final String name;
|
||||||
|
|
||||||
public final VariableIndex index() {
|
public final VariableIndex index() {
|
||||||
if (!frozen) throw new IllegalStateException("Tried to access the index of a variable before it was finalized");
|
|
||||||
return indexSupplier.get();
|
return indexSupplier.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
public final void freeze() {
|
|
||||||
this.frozen = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
public final Variable setIndexSupplier(Supplier<VariableIndex> index) {
|
public final Variable setIndexSupplier(Supplier<VariableIndex> index) {
|
||||||
this.indexSupplier = index;
|
this.indexSupplier = index;
|
||||||
return this;
|
return this;
|
||||||
|
@ -4,12 +4,21 @@ import me.topchetoeu.jscript.common.Instruction;
|
|||||||
|
|
||||||
public final class VariableIndex {
|
public final class VariableIndex {
|
||||||
public static enum IndexType {
|
public static enum IndexType {
|
||||||
|
/**
|
||||||
|
* A simple variable that is only ever used within the function
|
||||||
|
*/
|
||||||
LOCALS,
|
LOCALS,
|
||||||
|
/**
|
||||||
|
* A variable that has the ability to be captured by children functions
|
||||||
|
*/
|
||||||
CAPTURABLES,
|
CAPTURABLES,
|
||||||
|
/**
|
||||||
|
* A variable that has been captured from the parent function
|
||||||
|
*/
|
||||||
CAPTURES,
|
CAPTURES,
|
||||||
}
|
}
|
||||||
|
|
||||||
public final VariableIndex.IndexType type;
|
public final IndexType type;
|
||||||
public final int index;
|
public final int index;
|
||||||
|
|
||||||
public final int toCaptureIndex() {
|
public final int toCaptureIndex() {
|
||||||
@ -44,14 +53,6 @@ public final class VariableIndex {
|
|||||||
default: throw new UnsupportedOperationException("Unknown index type " + type);
|
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) {
|
public VariableIndex(VariableIndex.IndexType type, int index) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
|
@ -1,48 +1,36 @@
|
|||||||
package me.topchetoeu.jscript.compilation.scope;
|
package me.topchetoeu.jscript.compilation.scope;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.function.IntSupplier;
|
import java.util.function.IntSupplier;
|
||||||
import java.util.function.Supplier;
|
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> {
|
private final class VariableNode implements Supplier<VariableIndex> {
|
||||||
public Variable var;
|
public Variable var;
|
||||||
public VariableNode next;
|
public VariableNode next;
|
||||||
public VariableNode prev;
|
public VariableNode prev;
|
||||||
public boolean frozen;
|
|
||||||
public int index;
|
public int index;
|
||||||
|
public int indexIteration;
|
||||||
|
|
||||||
public VariableList list() { return VariableList.this; }
|
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() {
|
@Override public VariableIndex get() {
|
||||||
if (frozen) {
|
if (offset == null) return new VariableIndex(indexType, this.getIndex());
|
||||||
if (offset == null) return new VariableIndex(indexType, index);
|
else return new VariableIndex(indexType, offset.getAsInt() + this.getIndex());
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public VariableNode(Variable var, VariableNode next, VariableNode prev) {
|
public VariableNode(Variable var, VariableNode next, VariableNode prev) {
|
||||||
@ -54,32 +42,25 @@ public final class VariableList {
|
|||||||
|
|
||||||
private VariableNode first, last;
|
private VariableNode first, last;
|
||||||
|
|
||||||
private ArrayList<VariableNode> frozenList = null;
|
|
||||||
private HashMap<Variable, VariableNode> varMap = new HashMap<>();
|
private HashMap<Variable, VariableNode> varMap = new HashMap<>();
|
||||||
|
|
||||||
private final IntSupplier offset;
|
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 final VariableIndex.IndexType indexType;
|
||||||
|
|
||||||
public boolean frozen() {
|
/**
|
||||||
if (frozenList != null) {
|
* Adds the given variable to this list. If it already exists, does nothing
|
||||||
assert frozenList != null;
|
* @return val
|
||||||
assert varMap != null;
|
*/
|
||||||
assert first == null;
|
|
||||||
assert last == null;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
assert frozenList == null;
|
|
||||||
assert varMap != null;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Variable add(Variable 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) {
|
if (val.indexSupplier() instanceof VariableNode prevNode) {
|
||||||
prevNode.list().remove(val);
|
prevNode.list().remove(val);
|
||||||
@ -105,14 +86,18 @@ public final class VariableList {
|
|||||||
return val;
|
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) {
|
public Variable remove(Variable var) {
|
||||||
if (frozen()) throw new RuntimeException("The scope has been frozen");
|
|
||||||
|
|
||||||
if (var == null) return null;
|
if (var == null) return null;
|
||||||
|
|
||||||
var node = varMap.get(var);
|
var node = varMap.get(var);
|
||||||
if (node == null) return null;
|
if (node == null) return null;
|
||||||
|
|
||||||
|
this.indexIteration++;
|
||||||
|
|
||||||
if (node.prev != null) {
|
if (node.prev != null) {
|
||||||
assert node != first;
|
assert node != first;
|
||||||
node.prev.next = node.next;
|
node.prev.next = node.next;
|
||||||
@ -139,38 +124,26 @@ public final class VariableList {
|
|||||||
return node.var;
|
return node.var;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if the list has the given variable
|
||||||
|
*/
|
||||||
public boolean has(Variable var) {
|
public boolean has(Variable var) {
|
||||||
return varMap.containsKey(var);
|
return varMap.containsKey(var);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an indexer for the given variable
|
||||||
|
*/
|
||||||
public Supplier<VariableIndex> indexer(Variable var) {
|
public Supplier<VariableIndex> indexer(Variable var) {
|
||||||
return varMap.get(var);
|
return varMap.get(var);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int size() {
|
public int size() {
|
||||||
if (frozen()) return frozenList.size();
|
return varMap.size();
|
||||||
else return varMap.size();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void freeze() {
|
public Iterator<Variable> iterator() {
|
||||||
if (frozen()) return;
|
return new Iterator<Variable>() {
|
||||||
|
|
||||||
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>() {
|
|
||||||
private VariableNode curr = first;
|
private VariableNode curr = first;
|
||||||
|
|
||||||
@Override public boolean hasNext() {
|
@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.indexType = type;
|
||||||
this.offset = offset;
|
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.indexType = type;
|
||||||
this.offset = () -> offset;
|
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.indexType = type;
|
||||||
this.offset = prev::size;
|
this.offset = prev::size;
|
||||||
}
|
}
|
||||||
public VariableList(VariableIndex.IndexType type) {
|
public VariableList(IndexType type) {
|
||||||
this.indexType = type;
|
this.indexType = type;
|
||||||
this.offset = null;
|
this.offset = null;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user