j2s/src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java

151 lines
6.3 KiB
Java

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.Variable;
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
public abstract class FunctionNode extends Node {
public final CompoundNode body;
public final Parameters params;
public final Location end;
public abstract String name();
protected final int[] captures(int id, CompileResult target) {
return ((FunctionScope)target.children.get(id).scope).getCaptureIndices();
}
public final CompileResult compileBody(Environment env, FunctionScope scope, boolean lastReturn, String _name, String selfName) {
var name = this.name() != null ? this.name() : _name;
env = env.child()
.remove(LabelContext.BREAK_CTX)
.remove(LabelContext.CONTINUE_CTX);
return new CompileResult(env, scope, params.params.size(), target -> {
if (params.params.size() > 0) {
target.add(Instruction.loadArgs(true));
if (params.params.size() > 1) target.add(Instruction.dup(params.params.size() - 1));
var i = 0;
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);
target.add(Instruction.loadMember(i++));
if (param.node != null) {
var end = new DeferredIntSupplier();
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);
end.set(target.size());
}
target.add(_i -> varI.index().toSet(false));
}
}
if (params.restName != null) {
if (scope.has(params.restName, false)) throw new SyntaxException(params.restLocation, "Duplicate parameter name not allowed");
var restVar = scope.define(new Variable(params.restName, false), params.restLocation);
target.add(Instruction.loadRestArgs(params.params.size()));
target.add(_i -> restVar.index().toSet(false));
}
if (selfName != null && !scope.has(name, false)) {
var i = scope.defineSpecial(new Variable(selfName, true), end);
target.add(Instruction.loadCallee());
target.add(_i -> i.index().toSet(false));
}
body.resolve(target);
body.compile(target, lastReturn, BreakpointType.NONE);
scope.end();
for (var child : target.children) child.buildTask.run();
scope.finish();
});
}
public final CompileResult compileBody(CompileResult parent, String name, String selfName) {
return compileBody(parent.env, new FunctionScope(parent.scope), false, name, selfName);
}
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);
}
@Override public void compile(CompileResult target, boolean pollute, BreakpointType bp) {
compile(target, pollute, (String)null, bp);
}
@Override public void compile(CompileResult target, boolean pollute) {
compile(target, pollute, (String)null, BreakpointType.NONE);
}
public FunctionNode(Location loc, Location end, Parameters params, CompoundNode body) {
super(loc);
this.end = end;
this.params = params;
this.body = body;
}
public static void compileWithName(Node stm, CompileResult target, boolean pollute, String name) {
if (stm instanceof FunctionNode) ((FunctionNode)stm).compile(target, pollute, name);
else stm.compile(target, pollute);
}
public static void compileWithName(Node stm, CompileResult target, boolean pollute, String name, BreakpointType bp) {
if (stm instanceof FunctionNode) ((FunctionNode)stm).compile(target, pollute, name, bp);
else stm.compile(target, pollute, bp);
}
public static ParseRes<FunctionNode> parseFunction(Source src, int i, boolean statement) {
var n = Parsing.skipEmpty(src, i);
var loc = src.loc(i + n);
if (!Parsing.isIdentifier(src, i + n, "function")) return ParseRes.failed();
n += 8;
var name = Parsing.parseIdentifier(src, i + n);
if (!name.isSuccess() && statement) return ParseRes.error(src.loc(i + n), "A statement function requires a name");
n += name.n;
n += Parsing.skipEmpty(src, i + n);
var params = JavaScript.parseParameters(src, i + n);
if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected a parameter list");
n += params.n;
var body = CompoundNode.parse(src, i + n);
if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a compound statement for function");
n += body.n;
if (statement) return ParseRes.res(new FunctionStatementNode(
loc, src.loc(i + n - 1),
params.result, body.result, name.result
), n);
else return ParseRes.res(new FunctionValueNode(
loc, src.loc(i + n - 1),
params.result, body.result, name.result
), n);
}
}