ES6 Support Groundwork + Fixes #26

Merged
TopchetoEU merged 49 commits from ES6 into master 2024-09-05 14:26:07 +00:00
22 changed files with 486 additions and 399 deletions
Showing only changes of commit 5b4adf5286 - Show all commits

View File

@ -3,6 +3,7 @@ package me.topchetoeu.jscript.compilation;
import java.util.List;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.function.Consumer;
import java.util.function.IntFunction;
import me.topchetoeu.jscript.common.FunctionBody;
@ -12,15 +13,28 @@ import me.topchetoeu.jscript.common.environment.Environment;
import me.topchetoeu.jscript.common.mapping.FunctionMap;
import me.topchetoeu.jscript.common.mapping.FunctionMap.FunctionMapBuilder;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.compilation.scope.LocalScope;
import me.topchetoeu.jscript.compilation.scope.Scope;
public final class CompileResult {
public static final class ChildData {
public final int id;
public final CompileResult result;
public ChildData(int id, CompileResult result) {
this.result = result;
this.id = id;
}
}
public final List<IntFunction<Instruction>> instructions;
// public final List<Supplier<CompileResult>> childrenTasks;
public final List<CompileResult> children;
public final FunctionMapBuilder map;
public final Environment env;
public int length;
public Runnable buildTask = () -> {
throw new IllegalStateException("Compile result is not ready to be built");
};
public final Scope scope;
public int temp() {
@ -68,11 +82,16 @@ public final class CompileResult {
setLocationAndDebug(instructions.size() - 1, loc, type);
}
public CompileResult addChild(CompileResult child) {
this.children.add(child);
return child;
public int addChild(CompileResult res) {
this.children.add(res);
return this.children.size() - 1;
}
// public int addChild(Supplier<CompileResult> supplier) {
// this.childrenTasks.add(() -> supplier.get());
// return childrenTasks.size() - 1;
// }
public Instruction[] instructions() {
var res = new Instruction[instructions.size()];
var i = 0;
@ -106,15 +125,17 @@ public final class CompileResult {
}
public CompileResult subtarget() {
return new CompileResult(new LocalScope(scope), this);
return new CompileResult(new Scope(scope), this);
}
public CompileResult(Environment env, Scope scope) {
public CompileResult(Environment env, Scope scope, int length, Consumer<CompileResult> task) {
this.scope = scope;
instructions = new ArrayList<>();
children = new LinkedList<>();
map = FunctionMap.builder();
this.instructions = new ArrayList<>();
this.children = new LinkedList<>();
this.map = FunctionMap.builder();
this.env = env;
this.length = length;
this.buildTask = () -> task.accept(this);
}
private CompileResult(Scope scope, CompileResult parent) {
this.scope = scope;

View File

@ -43,7 +43,7 @@ public class CompoundNode extends Node {
if (alloc) {
subtarget.scope.end();
subtarget.add(Instruction.stackFree(subtarget.scope.allocCount()));
subtarget.add(_i -> Instruction.stackFree(subtarget.scope.allocCount()));
}
if (!polluted && pollute) {

View File

@ -13,12 +13,9 @@ import me.topchetoeu.jscript.compilation.control.ReturnNode;
public class FunctionArrowNode extends FunctionNode {
@Override public String name() { return null; }
@Override protected void compileLoadFunc(CompileResult target, int id, int[] captures, String name) {
target.add(Instruction.loadFunc(id, true, false, true, null, captures));
}
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
compile(target, pollute, false, name, null, bp);
var id = target.addChild(compileBody(target, false, name, null));
target.add(_i -> Instruction.loadFunc(id, true, false, true, null, captures(id, target)));
}
public FunctionArrowNode(Location loc, Location end, Parameters params, Node body) {

View File

@ -3,12 +3,13 @@ package me.topchetoeu.jscript.compilation;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Operation;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.environment.Environment;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.scope.FunctionScope;
import me.topchetoeu.jscript.compilation.scope.LocalScope;
import me.topchetoeu.jscript.compilation.scope.Variable;
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
public abstract class FunctionNode extends Node {
@ -35,87 +36,90 @@ public abstract class FunctionNode extends Node {
// }
// }
protected void compileLoadFunc(CompileResult target, int id, int[] captures, String name) {
target.add(Instruction.loadFunc(id, true, true, false, name, captures));
// protected void compileLoadFunc(CompileResult target, int id, int[] captures, String name) {
// target.add(Instruction.loadFunc(id, true, true, false, name, captures));
// }
protected final int[] captures(int id, CompileResult target) {
return ((FunctionScope)target.children.get(id).scope).getCaptureIndices();
}
private CompileResult compileBody(CompileResult target, boolean hasArgs, String name, String selfName, boolean pollute, BreakpointType bp) {
var env = target.env.child()
public final CompileResult compileBody(Environment env, FunctionScope scope, boolean lastReturn, boolean hasArgs, String _name, String selfName) {
var name = this.name() != null ? this.name() : _name;
env = env.child()
.remove(LabelContext.BREAK_CTX)
.remove(LabelContext.CONTINUE_CTX);
var funcScope = new FunctionScope(target.scope);
var subtarget = new CompileResult(env, new LocalScope(funcScope));
return new CompileResult(env, scope, params.params.size(), target -> {
if (hasArgs || params.params.size() > 0) target.add(Instruction.loadArgs());
subtarget.length = params.params.size();
if (hasArgs || params.params.size() > 0) subtarget.add(Instruction.loadArgs());
if (hasArgs) {
var argsVar = funcScope.defineParam("arguments", true, loc());
subtarget.add(_i -> Instruction.storeVar(argsVar.index(), params.params.size() > 0));
}
if (params.params.size() > 0) {
if (params.params.size() > 1) subtarget.add(Instruction.dup(params.params.size() - 1));
var i = 0;
for (var param : params.params) {
if (funcScope.hasArg(param.name)) throw new SyntaxException(param.loc, "Duplicate parameter name not allowed");
if (!JavaScript.checkVarName(param.name)) {
throw new SyntaxException(param.loc, String.format("Unexpected identifier '%s'", param.name));
}
var varI = funcScope.defineParam(param.name, false, param.loc);
subtarget.add(Instruction.loadMember(i++));
if (param.node != null) {
var end = new DeferredIntSupplier();
subtarget.add(Instruction.dup());
subtarget.add(Instruction.pushUndefined());
subtarget.add(Instruction.operation(Operation.EQUALS));
subtarget.add(Instruction.jmpIfNot(end));
subtarget.add(Instruction.discard());
param.node.compile(subtarget, pollute);
end.set(subtarget.size());
}
subtarget.add(Instruction.storeVar(varI.index()));
if (hasArgs) {
var argsVar = scope.defineStrict(new Variable("arguments", true), loc());
target.add(_i -> Instruction.storeVar(argsVar.index(), params.params.size() > 0));
}
}
if (params.restName != null) {
if (funcScope.hasArg(params.restName)) throw new SyntaxException(params.restLocation, "Duplicate parameter name not allowed");
var restVar = funcScope.defineParam(params.restName, true, params.restLocation);
subtarget.add(Instruction.loadRestArgs(params.params.size()));
subtarget.add(_i -> Instruction.storeVar(restVar.index()));
}
if (params.params.size() > 0) {
if (params.params.size() > 1) target.add(Instruction.dup(params.params.size() - 1));
var i = 0;
if (selfName != null && !funcScope.hasArg(name)) {
var i = funcScope.defineParam(selfName, true, end);
for (var param : params.params) {
if (scope.has(param.name, false)) throw new SyntaxException(param.loc, "Duplicate parameter name not allowed");
if (!JavaScript.checkVarName(param.name)) {
throw new SyntaxException(param.loc, String.format("Unexpected identifier '%s'", param.name));
}
var varI = scope.define(new Variable(param.name, false), param.loc);
subtarget.add(Instruction.loadCallee());
subtarget.add(_i -> Instruction.storeVar(i.index(), false));
}
target.add(Instruction.loadMember(i++));
body.resolve(subtarget);
body.compile(subtarget, false, false, BreakpointType.NONE);
if (param.node != null) {
var end = new DeferredIntSupplier();
subtarget.scope.end();
funcScope.end();
target.add(Instruction.dup());
target.add(Instruction.pushUndefined());
target.add(Instruction.operation(Operation.EQUALS));
target.add(Instruction.jmpIfNot(end));
target.add(Instruction.discard());
param.node.compile(target, true);
if (pollute) compileLoadFunc(target, target.children.size(), funcScope.getCaptureIndices(), name);
end.set(target.size());
}
return target.addChild(subtarget);
target.add(_i -> Instruction.storeVar(varI.index()));
}
}
if (params.restName != null) {
if (scope.has(params.restName, false)) throw new SyntaxException(params.restLocation, "Duplicate parameter name not allowed");
var restVar = scope.defineParam(new Variable(params.restName, false), params.restLocation);
target.add(Instruction.loadRestArgs(params.params.size()));
target.add(_i -> Instruction.storeVar(restVar.index()));
}
if (selfName != null && !scope.has(name, false)) {
var i = scope.defineParam(new Variable(selfName, true), end);
target.add(Instruction.loadCallee());
target.add(_i -> Instruction.storeVar(i.index(), false));
}
body.resolve(target);
body.compile(target, lastReturn, false, BreakpointType.NONE);
scope.end();
for (var child : target.children) child.buildTask.run();
scope.finish();
});
// if (pollute) compileLoadFunc(target, target.children.size(), subscope.getCaptureIndices(), name);
// return target.addChild(subtarget);
}
public final CompileResult compileBody(CompileResult parent, boolean hasArgs, String name, String selfName) {
return compileBody(parent.env, new FunctionScope(parent.scope), false, hasArgs, name, selfName);
}
public void compile(CompileResult target, boolean pollute, boolean hasArgs, String name, String selfName, BreakpointType bp) {
if (this.name() != null) name = this.name();
compileBody(target, hasArgs, name, selfName, pollute, bp);
}
public abstract void compile(CompileResult target, boolean pollute, String name, BreakpointType bp);
public void compile(CompileResult target, boolean pollute, String name) {
compile(target, pollute, name, BreakpointType.NONE);

View File

@ -1,7 +1,9 @@
package me.topchetoeu.jscript.compilation;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.compilation.scope.Variable;
import me.topchetoeu.jscript.compilation.values.VariableNode;
public class FunctionStatementNode extends FunctionNode {
@ -10,11 +12,12 @@ public class FunctionStatementNode extends FunctionNode {
@Override public String name() { return name; }
@Override public void resolve(CompileResult target) {
target.scope.define(name, false, end);
target.scope.define(new Variable(name, false), end);
}
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
compile(target, true, true, name, this.name, bp);
var id = target.addChild(compileBody(target, false, name, null));
target.add(_i -> Instruction.loadFunc(id, true, true, false, name, captures(id, target)));
target.add(VariableNode.toSet(target, end, this.name, pollute, true));
}

View File

@ -1,5 +1,6 @@
package me.topchetoeu.jscript.compilation;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.parsing.Location;
@ -8,8 +9,13 @@ public class FunctionValueNode extends FunctionNode {
@Override public String name() { return name; }
// @Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
// compileBody(target, pollute, true, name, null, bp);
// }
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
compile(target, pollute, true, name, null, bp);
var id = target.addChild(compileBody(target, false, name, null));
target.add(_i -> Instruction.loadFunc(id, true, true, false, name, captures(id, target)));
}
public FunctionValueNode(Location loc, Location end, Parameters params, CompoundNode body, String name) {

View File

@ -1,10 +1,9 @@
package me.topchetoeu.jscript.compilation;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
import me.topchetoeu.jscript.common.environment.Environment;
import me.topchetoeu.jscript.common.parsing.Filename;
import me.topchetoeu.jscript.common.parsing.ParseRes;
@ -24,8 +23,7 @@ import me.topchetoeu.jscript.compilation.control.SwitchNode;
import me.topchetoeu.jscript.compilation.control.ThrowNode;
import me.topchetoeu.jscript.compilation.control.TryNode;
import me.topchetoeu.jscript.compilation.control.WhileNode;
import me.topchetoeu.jscript.compilation.scope.GlobalScope;
import me.topchetoeu.jscript.compilation.scope.LocalScope;
import me.topchetoeu.jscript.compilation.scope.FunctionScope;
import me.topchetoeu.jscript.compilation.values.ArrayNode;
import me.topchetoeu.jscript.compilation.values.ObjectNode;
import me.topchetoeu.jscript.compilation.values.RegexNode;
@ -326,24 +324,32 @@ public final class JavaScript {
}
public static CompileResult compile(Environment env, Node ...statements) {
var target = new CompileResult(env, new LocalScope(new GlobalScope()));
var stm = new CompoundNode(null, statements);
var argsI = target.scope.defineStrict("arguments", true, null);
target.add(Instruction.loadArgs());
target.add(_i -> Instruction.storeVar(argsI.index()));
var func = new FunctionValueNode(null, null, new Parameters(List.of()), new CompoundNode(null, statements), null);
var res = func.compileBody(env, new FunctionScope(true), true, true, null, null);
res.buildTask.run();
return res;
// try {
stm.resolve(target);
stm.compile(target, true, false, BreakpointType.NONE);
// FunctionNode.checkBreakAndCont(target, 0);
// }
// catch (SyntaxException e) {
// target = new CompileResult(env, new LocalScope(new GlobalScope()));
// var target = new CompileResult(env, new FunctionScope(true));
// var stm = ;
// var argsI = target.scope.defineStrict(new Variable("arguments", true), null);
// target.add(Instruction.loadArgs());
// target.add(_i -> Instruction.storeVar(argsI.index()));
// target.add(Instruction.throwSyntax(e)).setLocation(stm.loc());
// }
// // try {
// stm.resolve(target);
// stm.compile(target, true, false, BreakpointType.NONE);
// // FunctionNode.checkBreakAndCont(target, 0);
// // }
// // catch (SyntaxException e) {
// // target = new CompileResult(env, new LocalScope(new GlobalScope()));
return target;
// // target.add(Instruction.throwSyntax(e)).setLocation(stm.loc());
// // }
// target.scope.end();
// target.scope.finish();
// return target;
}
public static CompileResult compile(Environment env, Filename filename, String raw) {

View File

@ -10,6 +10,7 @@ import me.topchetoeu.jscript.common.parsing.ParseRes;
import me.topchetoeu.jscript.common.parsing.Parsing;
import me.topchetoeu.jscript.common.parsing.Source;
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
import me.topchetoeu.jscript.compilation.scope.Variable;
import me.topchetoeu.jscript.compilation.values.VariableNode;
public class VariableDeclareNode extends Node {
@ -31,7 +32,7 @@ public class VariableDeclareNode extends Node {
@Override public void resolve(CompileResult target) {
if (!declType.strict) {
for (var entry : values) {
target.scope.define(entry.name, false, entry.location);
target.scope.define(new Variable(entry.name, false), entry.location);
}
}
}
@ -39,7 +40,7 @@ public class VariableDeclareNode extends Node {
@Override public void compile(CompileResult target, boolean pollute) {
for (var entry : values) {
if (entry.name == null) continue;
if (declType.strict) target.scope.defineStrict(entry.name, declType.readonly, entry.location);
if (declType.strict) target.scope.defineStrict(new Variable(entry.name, declType.readonly), entry.location);
if (entry.value != null) {
FunctionNode.compileWithName(entry.value, target, true, entry.name, BreakpointType.STEP_OVER);

View File

@ -13,6 +13,7 @@ import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.LabelContext;
import me.topchetoeu.jscript.compilation.Node;
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
import me.topchetoeu.jscript.compilation.scope.Variable;
import me.topchetoeu.jscript.compilation.values.VariableNode;
public class ForInNode extends Node {
@ -24,11 +25,11 @@ public class ForInNode extends Node {
@Override public void resolve(CompileResult target) {
body.resolve(target);
if (declType != null && !declType.strict) target.scope.define(varName, false, loc());
if (declType != null && !declType.strict) target.scope.define(new Variable(varName, false), loc());
}
@Override public void compile(CompileResult target, boolean pollute) {
if (declType != null && declType.strict) target.scope.defineStrict(varName, declType.readonly, varLocation);
if (declType != null && declType.strict) target.scope.defineStrict(new Variable(varName, declType.readonly), varLocation);
object.compile(target, true, BreakpointType.STEP_OVER);
target.add(Instruction.keys(true));

View File

@ -47,7 +47,7 @@ public class ForNode extends Node {
if (pollute) subtarget.add(Instruction.pushUndefined());
subtarget.scope.end();
subtarget.add(Instruction.stackFree(subtarget.scope.allocCount()));
subtarget.add(_i -> Instruction.stackFree(subtarget.scope.allocCount()));
}
public ForNode(Location loc, String label, Node declaration, Node condition, Node assignment, Node body) {

View File

@ -12,6 +12,7 @@ import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.LabelContext;
import me.topchetoeu.jscript.compilation.Node;
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
import me.topchetoeu.jscript.compilation.scope.Variable;
import me.topchetoeu.jscript.compilation.values.VariableNode;
public class ForOfNode extends Node {
@ -23,11 +24,11 @@ public class ForOfNode extends Node {
@Override public void resolve(CompileResult target) {
body.resolve(target);
if (declType != null && !declType.strict) target.scope.define(varName, false, varLocation);
if (declType != null && !declType.strict) target.scope.define(new Variable(varName, false), varLocation);
}
@Override public void compile(CompileResult target, boolean pollute) {
if (declType != null && declType.strict) target.scope.defineStrict(varName, declType.readonly, varLocation);
if (declType != null && declType.strict) target.scope.defineStrict(new Variable(varName, declType.readonly), varLocation);
iterable.compile(target, true, BreakpointType.STEP_OVER);
target.add(Instruction.dup());

View File

@ -65,7 +65,7 @@ public class SwitchNode extends Node {
LabelContext.getBreak(target.env).pop(label);
subtarget.scope.end();
subtarget.add(Instruction.stackFree(subtarget.scope.allocCount()));
subtarget.add(_i -> Instruction.stackFree(subtarget.scope.allocCount()));
int endI = subtarget.size();
end.set(endI);

View File

@ -12,6 +12,7 @@ import me.topchetoeu.jscript.compilation.DeferredIntSupplier;
import me.topchetoeu.jscript.compilation.JavaScript;
import me.topchetoeu.jscript.compilation.LabelContext;
import me.topchetoeu.jscript.compilation.Node;
import me.topchetoeu.jscript.compilation.scope.Variable;
public class TryNode extends Node {
public final CompoundNode tryBody;
@ -42,7 +43,7 @@ public class TryNode extends Node {
if (captureName != null) {
var subtarget = target.subtarget();
subtarget.scope.defineStrict(captureName, false, catchBody.loc());
subtarget.scope.defineStrict(new Variable(captureName, false), catchBody.loc());
catchBody.compile(subtarget, false);
subtarget.scope.end();
}

View File

@ -1,79 +1,113 @@
package me.topchetoeu.jscript.compilation.scope;
import java.util.HashMap;
import java.util.HashSet;
import me.topchetoeu.jscript.common.parsing.Location;
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
public class FunctionScope extends Scope {
private final VariableList captures = new VariableList().setIndexMap(v -> ~v);
private final VariableList specials = new VariableList();
private final VariableList locals = new VariableList(specials);
private HashMap<VariableDescriptor, VariableDescriptor> childToParent = new HashMap<>();
private final HashMap<Variable, Variable> childToParent = new HashMap<>();
private final HashSet<String> blacklistNames = new HashSet<>();
private final Scope captureParent;
public final boolean passtrough;
private void removeCapture(String name) {
var res = captures.remove(name);
if (res != null) childToParent.remove(res);
if (res != null) {
childToParent.remove(res);
res.setIndexSupplier(() -> { throw new SyntaxException(null, res.name + " has been shadowed"); });
}
}
@Override public VariableDescriptor define(String name, boolean readonly, Location loc) {
var old = locals.get(name);
if (old != null) return old;
@Override public Variable define(Variable var, Location loc) {
checkNotEnded();
if (locals.has(var.name)) throw alreadyDefinedErr(loc, var.name);
// if (specials.has(var.name)) throw alreadyDefinedErr(loc, var.name);
// if (blacklistNames.contains(var.name)) throw alreadyDefinedErr(loc, var.name);
removeCapture(name);
return locals.add(name, readonly);
if (passtrough) {
blacklistNames.add(var.name);
return null;
}
removeCapture(var.name);
return locals.add(var);
}
@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 Variable defineStrict(Variable var, Location loc) {
checkNotEnded();
if (locals.has(var.name)) throw alreadyDefinedErr(loc, var.name);
if (specials.has(var.name)) throw alreadyDefinedErr(loc, var.name);
if (blacklistNames.contains(var.name)) throw alreadyDefinedErr(loc, var.name);
var res = super.defineStrict(var, loc);
removeCapture(var.name);
return res;
}
public VariableDescriptor defineParam(String name, boolean readonly, Location loc) {
return specials.add(name, readonly);
}
public boolean hasArg(String name) {
return specials.has(name);
public Variable defineParam(Variable var, Location loc) {
return specials.add(var);
}
@Override public VariableDescriptor get(String name, boolean capture) {
@Override public boolean flattenVariable(Variable variable, boolean capturable) {
// if (!ended()) throw new IllegalStateException("Tried to flatten a variable before the scope has ended");
this.locals.overlay(variable);
return true;
}
@Override public Variable get(String name, boolean capture) {
var superRes = super.get(name, capture);
if (superRes != null) return superRes;
if (specials.has(name)) return specials.get(name);
if (locals.has(name)) return locals.get(name);
if (captures.has(name)) return captures.get(name);
if (captureParent == null) return null;
var parentVar = parent.get(name, true);
var parentVar = captureParent.get(name, true);
if (parentVar == null) return null;
var childVar = captures.add(parentVar);
var childVar = captures.add(parentVar.clone());
childToParent.put(childVar, parentVar);
return childVar;
}
@Override public boolean has(String name) {
@Override public boolean has(String name, boolean capture) {
if (specials.has(name)) return true;
if (locals.has(name)) return true;
if (captures.has(name)) return true;
if (parent != null) return parent.has(name);
if (capture) {
if (captures.has(name)) return true;
if (captureParent != null) return captureParent.has(name, true);
}
return false;
}
@Override public boolean end() {
if (!super.end()) return false;
@Override public boolean finish() {
if (!super.finish()) return false;
captures.freeze();
locals.freeze();
specials.freeze();
return true;
}
@Override public int localsCount() {
return locals.size() + specials.size();
@Override public int allocCount() {
return 0;
}
@Override public int capturesCount() {
return captures.size();
}
@Override public int allocCount() {
return 0;
@Override public int localsCount() {
return locals.size() + specials.size() + super.allocCount();
}
public int offset() {
@ -84,7 +118,7 @@ public class FunctionScope extends Scope {
var res = new int[captures.size()];
var i = 0;
for (var el : captures) {
for (var el : captures.all()) {
assert childToParent.containsKey(el);
res[i] = childToParent.get(el).index();
i++;
@ -93,10 +127,15 @@ public class FunctionScope extends Scope {
return res;
}
public FunctionScope() {
super();
}
public FunctionScope(Scope parent) {
super(parent);
super();
if (parent.finished()) throw new RuntimeException("Parent is finished");
this.captureParent = parent;
this.passtrough = false;
}
public FunctionScope(boolean passtrough) {
super();
this.captureParent = null;
this.passtrough = passtrough;
}
}

View File

@ -1,33 +0,0 @@
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;
}
@Override public boolean has(String name) {
return false;
}
@Override public int localsCount() {
return 0;
}
@Override public int capturesCount() {
return 0;
}
@Override public int allocCount() {
return 0;
}
public GlobalScope() { super(); }
}

View File

@ -1,69 +0,0 @@
package me.topchetoeu.jscript.compilation.scope;
import me.topchetoeu.jscript.common.parsing.Location;
public final class LocalScope extends Scope {
private final VariableList locals = new VariableList(() -> {
if (parent != null) return parent.offset();
else return 0;
});
@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 has(String name) {
if (locals.has(name)) return true;
if (parent != null) return parent.has(name);
return false;
}
@Override public boolean end() {
if (!super.end()) return false;
this.locals.freeze();
return true;
}
@Override public int localsCount() {
if (parent == null) return 0;
else return parent.localsCount();
}
@Override public int capturesCount() {
if (parent == null) return 0;
else return parent.capturesCount();
}
@Override public int allocCount() {
return locals.size();
}
public Iterable<VariableDescriptor> all() {
return () -> locals.iterator();
}
public LocalScope(Scope parent) {
super(parent);
}
}

View File

@ -1,24 +1,60 @@
package me.topchetoeu.jscript.compilation.scope;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
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;
public class Scope {
protected final VariableList variables = new VariableList(this::parentOffset);
private boolean ended = false;
private boolean finished = false;
private Scope child;
private List<Scope> prevChildren = new LinkedList<>();
public final Scope parent;
public final HashSet<Variable> captured = new HashSet<>();
/**
* 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;
private final int parentOffset() {
if (parent != null) return parent.offset();
else return 0;
}
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 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);
public Variable define(Variable var, Location loc) {
checkNotEnded();
if (variables.has(var.name)) throw alreadyDefinedErr(loc, var.name);
if (parent != null) return parent.define(var, loc);
return null;
}
/**
* Defines an ES2015-style variable
* @param readonly True if const, false if let
@ -26,29 +62,76 @@ public abstract class Scope {
* @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);
public Variable defineStrict(Variable var, Location loc) {
checkNotEnded();
if (variables.has(var.name)) throw alreadyDefinedErr(loc, var.name);
variables.add(var);
return var.setIndexSupplier(() -> variables.indexOfKey(var.name));
}
/**
* 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
* @param capture If true, the variable is being captured by a function
*/
public abstract VariableDescriptor get(String name, boolean capture);
public abstract boolean has(String name);
public Variable get(String name, boolean capture) {
var res = variables.get(name);
if (res != null) return res;
if (parent != null) return parent.get(name, capture);
return null;
}
/**
* Checks if the given variable name is accessible
*
* @param capture If true, will check beyond this function's scope
*/
public boolean has(String name, boolean capture) {
if (variables.has(name)) return true;
if (parent != null) return parent.has(name, capture);
return false;
}
/**
* Gets the index offset from this scope to its children
*/
public abstract int offset();
public int offset() {
if (parent != null) return parent.offset() + variables.size();
else return variables.size();
}
public abstract int localsCount();
public abstract int capturesCount();
public abstract int allocCount();
/**
* Adds this variable to the current function's locals record. Capturable indicates whether or not the variable
* should still be capturable, or be put in an array (still not implemented)
*
* @return Whether or not the request was actually fuliflled
*/
public boolean flattenVariable(Variable variable, boolean capturable) {
if (parent == null) return false;
return parent.flattenVariable(variable, capturable);
}
public boolean end() {
if (!active) return false;
public int localsCount() { return 0; }
public int capturesCount() { return 0; }
public int allocCount() { return variables.size(); }
/**
* 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;
for (var v : variables.all()) {
if (captured.contains(v)) {
if (singleEntry) this.flattenVariable(v, true);
}
else {
this.flattenVariable(v, false);
}
}
this.active = false;
if (this.parent != null) {
assert this.parent.child == this;
this.parent.child = null;
@ -57,17 +140,39 @@ public abstract class Scope {
return true;
}
public final boolean active() { return active; }
/**
* Finalizes this scope. The scope will become immutable after this call
* @return
*/
public boolean finish() {
if (finished) return false;
if (parent != null && !parent.finished) throw new IllegalStateException("Tried to finish a child before the parent was finished");
this.variables.freeze();
this.finished = true;
for (var child : prevChildren) child.finish();
return true;
}
public final boolean ended() { return ended; }
public final boolean finished() { return finished; }
public final Scope child() { return child; }
public Scope() {
this.parent = null;
this(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");
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 = parent;
this.parent.child = this;
this.parent.prevChildren.add(this);
}
else this.parent = null;
}
}

View File

@ -0,0 +1,41 @@
package me.topchetoeu.jscript.compilation.scope;
import java.util.function.IntSupplier;
public final class Variable {
private IntSupplier indexSupplier;
private boolean frozen;
public final boolean readonly;
public final String name;
public final int index() {
if (!frozen) throw new IllegalStateException("Tried to access the index of a variable before it was finalized");
return indexSupplier.getAsInt();
}
public final void freeze() {
this.frozen = true;
}
public final Variable setIndexSupplier(IntSupplier index) {
this.indexSupplier = index;
return this;
}
public final IntSupplier indexSupplier() {
return indexSupplier;
}
public final Variable clone() {
return new Variable(name, readonly).setIndexSupplier(indexSupplier);
}
public Variable(String name, boolean readonly) {
this.name = name;
this.readonly = readonly;
}
public static Variable of(String name, boolean readonly, int i) {
return new Variable(name, readonly).setIndexSupplier(() -> i);
}
}

View File

@ -1,19 +0,0 @@
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; }
};
}
}

View File

@ -5,15 +5,17 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.function.IntSupplier;
import java.util.function.IntUnaryOperator;
import java.util.stream.StreamSupport;
public final class VariableList implements Iterable<VariableDescriptor> {
private class ListVar extends VariableDescriptor {
private ListVar next;
private ListVar prev;
private boolean frozen;
private int index;
public final class VariableList {
private final class Node implements IntSupplier {
public Variable var;
public Node next;
public Node prev;
public boolean frozen;
public int index;
@Override public int index() {
@Override public int getAsInt() {
if (frozen) {
if (offset == null) {
return indexConverter == null ? index : indexConverter.applyAsInt(index);
@ -35,41 +37,38 @@ public final class VariableList implements Iterable<VariableDescriptor> {
return indexConverter == null ? res : indexConverter.applyAsInt(res);
}
public ListVar freeze() {
if (frozen) return this;
public void freeze() {
if (frozen) return;
this.frozen = true;
this.next = null;
if (prev == null) return this;
this.var.freeze();
if (prev == null) return;
this.index = prev.index + 1;
this.next = null;
return this;
return;
}
public ListVar(String name, boolean readonly, ListVar next, ListVar prev) {
super(name, readonly);
public Node(Variable var, Node next, Node prev) {
this.var = var;
this.next = next;
this.prev = prev;
}
}
private ListVar first, last;
private Node first, last;
private HashMap<String, ListVar> map = new HashMap<>();
private HashMap<String, VariableDescriptor> frozenMap = null;
private ArrayList<VariableDescriptor> frozenList = null;
private final HashMap<String, Node> map = new HashMap<>();
private ArrayList<Node> frozenList = null;
private final IntSupplier offset;
private IntUnaryOperator indexConverter = null;
public boolean frozen() {
if (frozenMap != null) {
if (frozenList != null) {
assert frozenList != null;
assert frozenMap != null;
assert map == null;
assert map != null;
assert first == null;
assert last == null;
@ -77,95 +76,87 @@ public final class VariableList implements Iterable<VariableDescriptor> {
}
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);
if (last != null) {
assert first != null;
last.next = res;
res.prev = last;
last = res;
}
else {
first = last = res;
}
map.put(name, res);
return res;
}
public VariableDescriptor remove(String name) {
private Variable add(Variable val, boolean overlay) {
if (frozen()) throw new RuntimeException("The scope has been frozen");
if (!overlay && map.containsKey(val.name)) {
var node = this.map.get(val.name);
val.setIndexSupplier(node);
return node.var;
}
var el = map.get(name);
if (el == null) return null;
var node = new Node(val, null, last);
if (el.prev != null) {
assert el != first;
el.prev.next = el.next;
if (last != null) {
assert first != null;
last.next = node;
node.prev = last;
last = node;
}
else {
assert el == first;
first = last = node;
}
map.put(val.name, node);
val.setIndexSupplier(node);
return val;
}
public Variable add(Variable val) {
return this.add(val, false);
}
public Variable overlay(Variable val) {
return this.add(val, true);
}
public Variable remove(String key) {
if (frozen()) throw new RuntimeException("The scope has been frozen");
var node = map.get(key);
if (node == null) return null;
if (node.prev != null) {
assert node != first;
node.prev.next = node.next;
}
else {
assert node == first;
first = first.next;
}
if (el.next != null) {
assert el != last;
el.next.prev = el.prev;
if (node.next != null) {
assert node != last;
node.next.prev = node.prev;
}
else {
assert el == last;
assert node == last;
last = last.prev;
}
el.next = null;
el.prev = null;
node.next = null;
node.prev = null;
return el;
return node.var;
}
public VariableDescriptor get(String name) {
if (frozen()) return frozenMap.get(name);
else return map.get(name);
public Variable get(String name) {
var res = map.get(name);
if (res != null) return res.var;
else return null;
}
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 int indexOfKey(String name) {
return map.get(name).getAsInt();
}
public boolean has(String name) {
return this.get(name) != null;
return this.map.containsKey(name);
}
public int size() {
@ -176,47 +167,38 @@ public final class VariableList implements Iterable<VariableDescriptor> {
public void freeze() {
if (frozen()) return;
frozenMap = new HashMap<>();
frozenList = new ArrayList<>();
for (var it = first; it != null; ) {
frozenMap.put(it.name, it);
frozenList.add(it);
for (var node = first; node != null; ) {
frozenList.add(node);
var tmp = it;
it = it.next;
var tmp = node;
node = node.next;
tmp.freeze();
}
map = null;
first = last = null;
}
@Override public Iterator<VariableDescriptor> iterator() {
if (frozen()) return frozenList.iterator();
else return new Iterator<VariableDescriptor>() {
private ListVar curr = first;
public Iterable<Variable> all() {
if (frozen()) return () -> frozenList.stream().map(v -> v.var).iterator();
else return () -> new Iterator<Variable>() {
private Node curr = first;
@Override public boolean hasNext() {
return curr != null;
}
@Override public VariableDescriptor next() {
@Override public Variable next() {
if (curr == null) return null;
var res = curr;
curr = curr.next;
return res;
return res.var;
}
};
}
public VariableDescriptor[] toArray() {
var res = new VariableDescriptor[size()];
var i = 0;
for (var el : this) res[i++] = el;
return res;
public Iterable<String> keys() {
return () -> StreamSupport.stream(all().spliterator(), false).map(v -> v.name).iterator();
}
public VariableList setIndexMap(IntUnaryOperator map) {

View File

@ -35,7 +35,7 @@ public class VariableNode extends Node implements AssignableNode {
if (i == null) {
target.add(_i -> {
if (target.scope.has(name)) throw new SyntaxException(loc(), String.format("Cannot access '%s' before initialization", name));
if (target.scope.has(name, false)) throw new SyntaxException(loc(), String.format("Cannot access '%s' before initialization", name));
return Instruction.globGet(name);
});
@ -50,7 +50,7 @@ public class VariableNode extends Node implements AssignableNode {
var i = target.scope.get(name, true);
if (i == null) return _i -> {
if (target.scope.has(name)) throw new SyntaxException(loc, String.format("Cannot access '%s' before initialization", name));
if (target.scope.has(name, false)) throw new SyntaxException(loc, String.format("Cannot access '%s' before initialization", name));
else return onGlobal.get();
};
else return _i -> Instruction.loadVar(i.index());
@ -64,7 +64,7 @@ public class VariableNode extends Node implements AssignableNode {
var i = target.scope.get(name, true);
if (i == null) return _i -> {
if (target.scope.has(name)) throw new SyntaxException(loc, String.format("Cannot access '%s' before initialization", name));
if (target.scope.has(name, false)) throw new SyntaxException(loc, String.format("Cannot access '%s' before initialization", name));
else return Instruction.globSet(name, keep, define);
};
else if (!define && i.readonly) return _i -> Instruction.throwSyntax(new SyntaxException(loc, "Assignment to constant variable"));

View File

@ -45,7 +45,7 @@ const unwrapThis = (self, type, constr, name, arg, defaultVal) => {
if (typeof self === type) return self;
if (self instanceof constr && valueKey in self) self = self[valueKey];
if (typeof self === type) return self;
if (arguments.length > 5) return defaultVal;
if (defaultVal !== undefined) return defaultVal;
throw new TypeError(name + " requires that '" + arg + "' be a " + constr.name);
}