Add first test #23
@ -0,0 +1,63 @@
|
||||
package me.topchetoeu.jscript.compilation.scope;
|
||||
|
||||
import java.util.HashMap;
|
||||
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
|
||||
public class FunctionScope extends Scope {
|
||||
private final VariableList captures = new VariableList();
|
||||
private final VariableList locals = new VariableList(captures);
|
||||
private HashMap<VariableDescriptor, VariableDescriptor> childToParent = new HashMap<>();
|
||||
|
||||
private void removeCapture(String name) {
|
||||
var res = captures.remove(name);
|
||||
if (res != null) childToParent.remove(res);
|
||||
}
|
||||
|
||||
@Override public VariableDescriptor define(String name, boolean readonly, Location loc) {
|
||||
var old = locals.get(name);
|
||||
if (old != null) return old;
|
||||
|
||||
removeCapture(name);
|
||||
return locals.add(name, readonly);
|
||||
}
|
||||
@Override public VariableDescriptor defineStrict(String name, boolean readonly, Location loc) {
|
||||
if (locals.has(name)) throw alreadyDefinedErr(loc, name);
|
||||
else if (parent == null) throw new RuntimeException("Strict variables may be defined only in local scopes");
|
||||
else return parent.defineStrict(name, readonly, loc);
|
||||
}
|
||||
|
||||
@Override public VariableDescriptor get(String name, boolean capture) {
|
||||
if (locals.has(name)) return locals.get(name);
|
||||
if (captures.has(name)) return captures.get(name);
|
||||
|
||||
var parentVar = parent.get(name, true);
|
||||
var childVar = captures.add(parentVar);
|
||||
|
||||
childToParent.put(childVar, parentVar);
|
||||
|
||||
return childVar;
|
||||
}
|
||||
|
||||
public int localsCount() {
|
||||
return locals.size();
|
||||
}
|
||||
public int offset() {
|
||||
return captures.size() + locals.size();
|
||||
}
|
||||
|
||||
public int[] getCaptureIndices() {
|
||||
var res = new int[captures.size()];
|
||||
var i = 0;
|
||||
|
||||
for (var el : captures) {
|
||||
assert childToParent.containsKey(el);
|
||||
res[i] = childToParent.get(el).index();
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public FunctionScope() { super(); }
|
||||
public FunctionScope(Scope parent) { super(parent); }
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package me.topchetoeu.jscript.compilation.scope;
|
||||
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
|
||||
public final class GlobalScope extends Scope {
|
||||
@Override public VariableDescriptor define(String name, boolean readonly, Location loc) {
|
||||
return null;
|
||||
}
|
||||
@Override public VariableDescriptor defineStrict(String name, boolean readonly, Location loc) {
|
||||
return null;
|
||||
}
|
||||
@Override public VariableDescriptor get(String name, boolean capture) {
|
||||
return null;
|
||||
}
|
||||
@Override public int offset() {
|
||||
return 0;
|
||||
}
|
||||
}
|
@ -0,0 +1,46 @@
|
||||
package me.topchetoeu.jscript.compilation.scope;
|
||||
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
|
||||
public class LocalScope extends Scope {
|
||||
private final VariableList locals = new VariableList();
|
||||
|
||||
@Override public int offset() {
|
||||
if (parent != null) return parent.offset() + locals.size();
|
||||
else return locals.size();
|
||||
}
|
||||
|
||||
@Override public VariableDescriptor define(String name, boolean readonly, Location loc) {
|
||||
if (locals.has(name)) throw alreadyDefinedErr(loc, name);
|
||||
|
||||
return parent.define(name, readonly, loc);
|
||||
}
|
||||
@Override public VariableDescriptor defineStrict(String name, boolean readonly, Location loc) {
|
||||
if (locals.has(name)) throw alreadyDefinedErr(loc, name);
|
||||
return locals.add(name, readonly);
|
||||
}
|
||||
|
||||
@Override public VariableDescriptor get(String name, boolean capture) {
|
||||
var res = locals.get(name);
|
||||
|
||||
if (res != null) return res;
|
||||
if (parent != null) return parent.get(name, capture);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override public boolean end() {
|
||||
if (!super.end()) return false;
|
||||
|
||||
this.locals.freeze();
|
||||
return true;
|
||||
}
|
||||
|
||||
public Iterable<VariableDescriptor> all() {
|
||||
return () -> locals.iterator();
|
||||
}
|
||||
|
||||
public LocalScope(Scope parent) {
|
||||
super(parent);
|
||||
}
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
package me.topchetoeu.jscript.compilation.scope;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
public class LocalScopeRecord implements ScopeRecord {
|
||||
public final LocalScopeRecord parent;
|
||||
|
||||
private final ArrayList<String> captures = new ArrayList<>();
|
||||
private final ArrayList<String> locals = new ArrayList<>();
|
||||
|
||||
public String[] captures() {
|
||||
return captures.toArray(String[]::new);
|
||||
}
|
||||
public String[] locals() {
|
||||
return locals.toArray(String[]::new);
|
||||
}
|
||||
|
||||
public LocalScopeRecord child() {
|
||||
return new LocalScopeRecord(this);
|
||||
}
|
||||
|
||||
public int localsCount() {
|
||||
return locals.size();
|
||||
}
|
||||
public int capturesCount() {
|
||||
return captures.size();
|
||||
}
|
||||
|
||||
public int[] getCaptures() {
|
||||
var buff = new int[captures.size()];
|
||||
var i = 0;
|
||||
|
||||
for (var name : captures) {
|
||||
var index = parent.getKey(name);
|
||||
if (index instanceof Integer) buff[i++] = (int)index;
|
||||
}
|
||||
|
||||
var res = new int[i];
|
||||
System.arraycopy(buff, 0, res, 0, i);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public Object getKey(String name) {
|
||||
var capI = captures.indexOf(name);
|
||||
var locI = locals.lastIndexOf(name);
|
||||
if (locI >= 0) return locI;
|
||||
if (capI >= 0) return ~capI;
|
||||
if (parent != null) {
|
||||
var res = parent.getKey(name);
|
||||
if (res != null && res instanceof Integer) {
|
||||
captures.add(name);
|
||||
return -captures.size();
|
||||
}
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
public Object define(String name, boolean force) {
|
||||
if (!force && locals.contains(name)) return locals.indexOf(name);
|
||||
locals.add(name);
|
||||
return locals.size() - 1;
|
||||
}
|
||||
public Object define(String name) {
|
||||
return define(name, false);
|
||||
}
|
||||
public void undefine() {
|
||||
locals.remove(locals.size() - 1);
|
||||
}
|
||||
|
||||
public LocalScopeRecord() {
|
||||
this.parent = null;
|
||||
}
|
||||
public LocalScopeRecord(LocalScopeRecord parent) {
|
||||
this.parent = parent;
|
||||
}
|
||||
}
|
68
src/java/me/topchetoeu/jscript/compilation/scope/Scope.java
Normal file
68
src/java/me/topchetoeu/jscript/compilation/scope/Scope.java
Normal file
@ -0,0 +1,68 @@
|
||||
package me.topchetoeu.jscript.compilation.scope;
|
||||
|
||||
import me.topchetoeu.jscript.common.parsing.Location;
|
||||
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
||||
|
||||
public abstract class Scope {
|
||||
public final Scope parent;
|
||||
private boolean active = true;
|
||||
private Scope child;
|
||||
|
||||
protected final SyntaxException alreadyDefinedErr(Location loc, String name) {
|
||||
return new SyntaxException(loc, String.format("Identifier '%s' has already been declared", name));
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 abstract VariableDescriptor define(String name, boolean readonly, Location loc);
|
||||
/**
|
||||
* 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 abstract VariableDescriptor defineStrict(String name, boolean readonly, Location loc);
|
||||
/**
|
||||
* Gets the index supplier of the given variable name, or null if it is a global
|
||||
*
|
||||
* @param capture Used to signal to the scope that the variable is going to be captured.
|
||||
* Not passing this could lead to a local variable being optimized out as an ES5-style variable,
|
||||
* which could break the semantics of a capture
|
||||
*/
|
||||
public abstract VariableDescriptor get(String name, boolean capture);
|
||||
/**
|
||||
* Gets the index offset from this scope to its children
|
||||
*/
|
||||
public abstract int offset();
|
||||
|
||||
public boolean end() {
|
||||
if (!active) return false;
|
||||
|
||||
this.active = false;
|
||||
if (this.parent != null) {
|
||||
assert this.parent.child == this;
|
||||
this.parent.child = this;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public final boolean active() { return active; }
|
||||
public final Scope child() { return child; }
|
||||
|
||||
public Scope() {
|
||||
this.parent = null;
|
||||
}
|
||||
public Scope(Scope parent) {
|
||||
if (!parent.active) throw new RuntimeException("Parent is not active");
|
||||
if (parent.child != null) throw new RuntimeException("Parent has an active child");
|
||||
|
||||
this.parent = parent;
|
||||
this.parent.child = this;
|
||||
}
|
||||
}
|
@ -1,7 +0,0 @@
|
||||
package me.topchetoeu.jscript.compilation.scope;
|
||||
|
||||
public interface ScopeRecord {
|
||||
public Object getKey(String name);
|
||||
public Object define(String name);
|
||||
public LocalScopeRecord child();
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package me.topchetoeu.jscript.compilation.scope;
|
||||
|
||||
public abstract class VariableDescriptor {
|
||||
public final boolean readonly;
|
||||
public final String name;
|
||||
|
||||
public abstract int index();
|
||||
|
||||
public VariableDescriptor(String name, boolean readonly) {
|
||||
this.name = name;
|
||||
this.readonly = readonly;
|
||||
}
|
||||
|
||||
public static VariableDescriptor of(String name, boolean readonly, int i) {
|
||||
return new VariableDescriptor(name, readonly) {
|
||||
@Override public int index() { return i; }
|
||||
};
|
||||
}
|
||||
}
|
@ -0,0 +1,176 @@
|
||||
package me.topchetoeu.jscript.compilation.scope;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.function.IntSupplier;
|
||||
|
||||
public class VariableList implements Iterable<VariableDescriptor> {
|
||||
private class ListVar extends VariableDescriptor {
|
||||
private ListVar next;
|
||||
private ListVar prev;
|
||||
|
||||
@Override public int index() {
|
||||
throw new RuntimeException("The index of a variable may not be retrieved until the scope has been finalized");
|
||||
// var res = 0;
|
||||
// if (offset != null) res = offset.getAsInt();
|
||||
|
||||
// for (var it = prev; it != null; it = it.prev) {
|
||||
// res++;
|
||||
// }
|
||||
|
||||
// return res;
|
||||
}
|
||||
|
||||
public ListVar(String name, boolean readonly, ListVar next, ListVar prev) {
|
||||
super(name, readonly);
|
||||
|
||||
this.next = next;
|
||||
this.prev = prev;
|
||||
}
|
||||
}
|
||||
|
||||
private ListVar first, last;
|
||||
|
||||
private HashMap<String, ListVar> map = new HashMap<>();
|
||||
|
||||
private HashMap<String, VariableDescriptor> frozenMap = null;
|
||||
private ArrayList<VariableDescriptor> frozenList = null;
|
||||
|
||||
private final IntSupplier offset;
|
||||
|
||||
public boolean frozen() {
|
||||
if (frozenMap != null) {
|
||||
assert frozenList != null;
|
||||
assert frozenMap != null;
|
||||
assert map == null;
|
||||
assert first == null;
|
||||
assert last == null;
|
||||
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
assert frozenList == null;
|
||||
assert frozenMap == null;
|
||||
assert map != null;
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public VariableDescriptor add(VariableDescriptor val) {
|
||||
return add(val.name, val.readonly);
|
||||
}
|
||||
public VariableDescriptor add(String name, boolean readonly) {
|
||||
if (frozen()) throw new RuntimeException("The scope has been frozen");
|
||||
if (map.containsKey(name)) return map.get(name);
|
||||
|
||||
var res = new ListVar(name, readonly, null, last);
|
||||
last.next = res;
|
||||
last = res;
|
||||
map.put(name, res);
|
||||
|
||||
return res;
|
||||
}
|
||||
public VariableDescriptor remove(String name) {
|
||||
if (frozen()) throw new RuntimeException("The scope has been frozen");
|
||||
|
||||
var el = map.get(name);
|
||||
if (el == null) return null;
|
||||
|
||||
el.prev.next = el.next;
|
||||
el.next.prev = el.prev;
|
||||
|
||||
el.next = null;
|
||||
el.prev = null;
|
||||
|
||||
return el;
|
||||
}
|
||||
|
||||
public VariableDescriptor get(String name) {
|
||||
return map.get(name);
|
||||
}
|
||||
public VariableDescriptor get(int i) {
|
||||
if (frozen()) {
|
||||
if (i < 0 || i >= frozenList.size()) return null;
|
||||
return frozenList.get(i);
|
||||
}
|
||||
else {
|
||||
if (i < 0 || i >= map.size()) return null;
|
||||
|
||||
if (i < map.size() / 2) {
|
||||
var it = first;
|
||||
for (var j = 0; j < i; it = it.next, j++);
|
||||
return it;
|
||||
}
|
||||
else {
|
||||
var it = last;
|
||||
for (var j = map.size() - 1; j >= i; it = it.prev, j--);
|
||||
return it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean has(String name) {
|
||||
return this.get(name) != null;
|
||||
}
|
||||
|
||||
public int size() {
|
||||
if (frozen()) return frozenList.size();
|
||||
else return map.size();
|
||||
}
|
||||
|
||||
public void freeze() {
|
||||
if (frozen()) return;
|
||||
|
||||
frozenMap = new HashMap<>();
|
||||
frozenList = new ArrayList<>();
|
||||
|
||||
var i = 0;
|
||||
if (offset != null) i = offset.getAsInt();
|
||||
|
||||
for (var it = first; it != null; it = it.next) {
|
||||
frozenMap.put(it.name, VariableDescriptor.of(it.name, it.readonly, i++));
|
||||
}
|
||||
}
|
||||
|
||||
@Override public Iterator<VariableDescriptor> iterator() {
|
||||
if (frozen()) return frozenList.iterator();
|
||||
else return new Iterator<VariableDescriptor>() {
|
||||
private ListVar curr = first;
|
||||
|
||||
@Override public boolean hasNext() {
|
||||
return curr != null;
|
||||
}
|
||||
@Override public VariableDescriptor next() {
|
||||
if (curr == null) return null;
|
||||
|
||||
var res = curr;
|
||||
curr = curr.next;
|
||||
return res;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public VariableDescriptor[] toArray() {
|
||||
var res = new VariableDescriptor[size()];
|
||||
var i = 0;
|
||||
|
||||
for (var el : this) res[i++] = el;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public VariableList(IntSupplier offset) {
|
||||
this.offset = offset;
|
||||
}
|
||||
public VariableList(int offset) {
|
||||
this.offset = () -> offset;
|
||||
}
|
||||
public VariableList(VariableList prev) {
|
||||
this.offset = prev::size;
|
||||
}
|
||||
public VariableList() {
|
||||
this.offset = null;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user