ES6 Support Groundwork + Fixes #26
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
|
@ -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) {
|
||||
|
@ -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());
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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(); }
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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; }
|
||||
};
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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"));
|
||||
|
@ -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);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user