feat: implement optional arguments

This commit is contained in:
TopchetoEU 2024-09-01 23:09:52 +03:00
parent c39c06b792
commit 4cbc108686
Signed by: topchetoeu
GPG Key ID: 6531B8583E5F6ED4
19 changed files with 166 additions and 83 deletions

View File

@ -3,10 +3,11 @@ package me.topchetoeu.jscript.common;
public class FunctionBody {
public final FunctionBody[] children;
public final Instruction[] instructions;
public final int localsN, capturesN, argsN;
public final int localsN, capturesN, argsN, length;
public FunctionBody(int localsN, int capturesN, int argsN, Instruction[] instructions, FunctionBody[] children) {
public FunctionBody(int localsN, int capturesN, int length, int argsN, Instruction[] instructions, FunctionBody[] children) {
this.children = children;
this.length = length;
this.argsN = argsN;
this.localsN = localsN;
this.capturesN = capturesN;

View File

@ -3,7 +3,7 @@ package me.topchetoeu.jscript.compilation;
import java.util.List;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.function.Supplier;
import java.util.function.IntFunction;
import me.topchetoeu.jscript.common.FunctionBody;
import me.topchetoeu.jscript.common.Instruction;
@ -16,11 +16,11 @@ import me.topchetoeu.jscript.compilation.scope.LocalScope;
import me.topchetoeu.jscript.compilation.scope.Scope;
public final class CompileResult {
public final List<Supplier<Instruction>> instructions;
public final List<IntFunction<Instruction>> instructions;
public final List<CompileResult> children;
public final FunctionMapBuilder map;
public final Environment env;
public int length = 0;
public int length, assignN;
public final Scope scope;
public int temp() {
@ -29,18 +29,18 @@ public final class CompileResult {
}
public CompileResult add(Instruction instr) {
instructions.add(() -> instr);
instructions.add(i -> instr);
return this;
}
public CompileResult add(Supplier<Instruction> instr) {
public CompileResult add(IntFunction<Instruction> instr) {
instructions.add(instr);
return this;
}
public CompileResult set(int i, Instruction instr) {
instructions.set(i, () -> instr);
instructions.set(i, _i -> instr);
return this;
}
public CompileResult set(int i, Supplier<Instruction>instr) {
public CompileResult set(int i, IntFunction<Instruction>instr) {
instructions.set(i, instr);
return this;
}
@ -76,7 +76,10 @@ public final class CompileResult {
public Instruction[] instructions() {
var res = new Instruction[instructions.size()];
var i = 0;
for (var suppl : instructions) res[i++] = suppl.get();
for (var suppl : instructions) {
res[i] = suppl.apply(i);
i++;
}
return res;
}
@ -90,10 +93,15 @@ public final class CompileResult {
var instrRes = new Instruction[instructions.size()];
var i = 0;
for (var suppl : instructions) instrRes[i++] = suppl.get();
for (var suppl : instructions) {
instrRes[i] = suppl.apply(i);
i++;
}
return new FunctionBody(
scope.localsCount() + scope.allocCount(), scope.capturesCount(), length,
scope.localsCount() + scope.allocCount(), scope.capturesCount(),
length, assignN,
instrRes, builtChildren
);
}

View File

@ -19,11 +19,11 @@ public class CompoundNode extends Node {
for (var stm : statements) stm.resolve(target);
}
@Override public void compile(CompileResult target, boolean pollute, BreakpointType type) {
public void compile(CompileResult target, boolean pollute, boolean alloc, BreakpointType type) {
List<Node> statements = new ArrayList<Node>();
var subtarget = target.subtarget();
subtarget.add(() -> Instruction.stackAlloc(subtarget.scope.allocCount()));
if (alloc) subtarget.add(i -> Instruction.stackAlloc(subtarget.scope.allocCount()));
for (var stm : this.statements) {
if (stm instanceof FunctionStatementNode func) {
@ -42,13 +42,17 @@ public class CompoundNode extends Node {
}
subtarget.scope.end();
subtarget.add(Instruction.stackFree(subtarget.scope.allocCount()));
if (alloc) subtarget.add(Instruction.stackFree(subtarget.scope.allocCount()));
if (!polluted && pollute) {
target.add(Instruction.pushUndefined());
}
}
@Override public void compile(CompileResult target, boolean pollute, BreakpointType type) {
compile(target, pollute, true, type);
}
public CompoundNode setEnd(Location loc) {
this.end = loc;
return this;

View File

@ -1,6 +1,7 @@
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.parsing.Location;
import me.topchetoeu.jscript.common.parsing.ParseRes;
@ -12,7 +13,7 @@ import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
public abstract class FunctionNode extends Node {
public final CompoundNode body;
public final String[] args;
public final Parameters params;
public final Location end;
public abstract String name();
@ -39,14 +40,6 @@ public abstract class FunctionNode extends Node {
}
private CompileResult compileBody(CompileResult target, String name, boolean storeSelf, boolean pollute, BreakpointType bp) {
for (var i = 0; i < args.length; i++) {
for (var j = 0; j < i; j++) {
if (args[i].equals(args[j])) {
throw new SyntaxException(loc(), "Duplicate parameter '" + args[i] + "'.");
}
}
}
var env = target.env.child()
.remove(LabelContext.BREAK_CTX)
.remove(LabelContext.CONTINUE_CTX);
@ -54,16 +47,31 @@ public abstract class FunctionNode extends Node {
var funcScope = new FunctionScope(target.scope);
var subtarget = new CompileResult(env, new LocalScope(funcScope));
for (var arg : args) {
for (var param : params.params) {
// TODO: Implement default values
// TODO: Implement argument location
funcScope.defineArg(arg, loc());
if (funcScope.hasArg(param.name)) throw new SyntaxException(param.loc, "Duplicate parameter name not allowed");
var i = funcScope.defineParam(param.name, param.loc);
if (param.node != null) {
var end = new DeferredIntSupplier();
subtarget.add(_i -> Instruction.loadVar(i.index()));
subtarget.add(Instruction.pushUndefined());
subtarget.add(Instruction.operation(Operation.EQUALS));
subtarget.add(_i -> Instruction.jmpIfNot(end.getAsInt() - _i));
param.node.compile(subtarget, pollute);
subtarget.add(_i -> Instruction.storeVar(i.index()));
end.set(subtarget.size());
}
}
body.resolve(subtarget);
body.compile(subtarget, false);
body.compile(subtarget, false, false, BreakpointType.NONE);
subtarget.length = args.length;
subtarget.length = params.length;
subtarget.assignN = params.params.size();
subtarget.scope.end();
funcScope.end();
@ -88,11 +96,11 @@ public abstract class FunctionNode extends Node {
compile(target, pollute, (String)null, BreakpointType.NONE);
}
public FunctionNode(Location loc, Location end, String[] args, CompoundNode body) {
public FunctionNode(Location loc, Location end, Parameters params, CompoundNode body) {
super(loc);
this.end = end;
this.args = args;
this.params = params;
this.body = body;
}
@ -117,21 +125,21 @@ public abstract class FunctionNode extends Node {
n += name.n;
n += Parsing.skipEmpty(src, i + n);
var args = JavaScript.parseParamList(src, i + n);
if (!args.isSuccess()) return args.chainError(src.loc(i + n), "Expected a parameter list");
n += args.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.");
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),
args.result.toArray(String[]::new), body.result, name.result
params.result, body.result, name.result
), n);
else return ParseRes.res(new FunctionValueNode(
loc, src.loc(i + n - 1),
args.result.toArray(String[]::new), body.result, name.result
params.result, body.result, name.result
), n);
}
}

View File

@ -18,8 +18,8 @@ public class FunctionStatementNode extends FunctionNode {
target.add(VariableNode.toSet(target, end, this.name, pollute, true));
}
public FunctionStatementNode(Location loc, Location end, String[] args, CompoundNode body, String name) {
super(loc, end, args, body);
public FunctionStatementNode(Location loc, Location end, Parameters params, CompoundNode body, String name) {
super(loc, end, params, body);
this.name = name;
}
}

View File

@ -12,8 +12,8 @@ public class FunctionValueNode extends FunctionNode {
compile(target, pollute, true, name, bp);
}
public FunctionValueNode(Location loc, Location end, String[] args, CompoundNode body, String name) {
super(loc, end, args, body);
public FunctionValueNode(Location loc, Location end, Parameters params, CompoundNode body, String name) {
super(loc, end, params, body);
this.name = name;
}
}

View File

@ -1,10 +1,10 @@
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;
@ -212,40 +212,53 @@ public class JavaScript {
return ParseRes.failed();
}
public static ParseRes<List<String>> parseParamList(Source src, int i) {
public static ParseRes<Parameters> parseParameters(Source src, int i) {
var n = Parsing.skipEmpty(src, i);
var openParen = Parsing.parseOperator(src, i + n, "(");
if (!openParen.isSuccess()) return openParen.chainError(src.loc(i + n), "Expected a parameter list.");
if (!openParen.isSuccess()) return openParen.chainError(src.loc(i + n), "Expected a parameter list");
n += openParen.n;
var args = new ArrayList<String>();
var params = new ArrayList<Parameter>();
var closeParen = Parsing.parseOperator(src, i + n, ")");
n += closeParen.n;
if (!closeParen.isSuccess()) {
while (true) {
var argRes = Parsing.parseIdentifier(src, i + n);
if (argRes.isSuccess()) {
args.add(argRes.result);
n += argRes.n;
n += Parsing.skipEmpty(src, i);
n += Parsing.skipEmpty(src, i + n);
if (src.is(i + n, ",")) {
n++;
n += Parsing.skipEmpty(src, i + n);
}
if (src.is(i + n, ")")) {
n++;
break;
}
var paramLoc = src.loc(i);
var name = Parsing.parseIdentifier(src, i + n);
if (!name.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected an argument or a closing brace");
n += name.n;
n += Parsing.skipEmpty(src, i + n);
if (src.is(i + n, "=")) {
n++;
var val = parseExpression(src, i + n, 2);
if (!val.isSuccess()) return openParen.chainError(src.loc(i + n), "Expected a default value");
n += val.n;
n += Parsing.skipEmpty(src, i + n);
params.add(new Parameter(paramLoc, name.result, val.result));
}
else params.add(new Parameter(paramLoc, name.result, null));
if (src.is(i + n, ",")) {
n++;
n += Parsing.skipEmpty(src, i + n);
}
if (src.is(i + n, ")")) {
n++;
break;
}
else return ParseRes.error(src.loc(i + n), "Expected an argument, or a closing brace.");
}
}
return ParseRes.res(args, n);
return ParseRes.res(new Parameters(params), n);
}
public static Node[] parse(Environment env, Filename filename, String raw) {
@ -279,7 +292,7 @@ public class JavaScript {
try {
stm.resolve(target);
stm.compile(target, true);
stm.compile(target, true, false, BreakpointType.NONE);
// FunctionNode.checkBreakAndCont(target, 0);
}
catch (SyntaxException e) {

View File

@ -2,8 +2,8 @@ package me.topchetoeu.jscript.compilation;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.function.IntFunction;
import java.util.function.IntSupplier;
import java.util.function.Supplier;
import me.topchetoeu.jscript.common.Instruction;
import me.topchetoeu.jscript.common.environment.Environment;
@ -25,15 +25,15 @@ public class LabelContext {
return map.get(name);
}
public Supplier<Instruction> getJump(int offset) {
public IntFunction<Instruction> getJump() {
var res = get();
if (res == null) return null;
else return () -> Instruction.jmp(res.getAsInt() - offset);
else return i -> Instruction.jmp(res.getAsInt() - i);
}
public Supplier<Instruction> getJump(int offset, String name) {
public IntFunction<Instruction> getJump(String name) {
var res = get(name);
if (res == null) return null;
else return () -> Instruction.jmp(res.getAsInt() - offset);
else return i -> Instruction.jmp(res.getAsInt() - i);
}
public void push(IntSupplier jumpTarget) {

View File

@ -0,0 +1,15 @@
package me.topchetoeu.jscript.compilation;
import me.topchetoeu.jscript.common.parsing.Location;
public final class Parameter {
public final Location loc;
public final String name;
public final Node node;
public Parameter(Location loc, String name, Node node) {
this.name = name;
this.node = node;
this.loc = loc;
}
}

View File

@ -0,0 +1,28 @@
package me.topchetoeu.jscript.compilation;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public final class Parameters {
public final int length;
public final List<Parameter> params;
public final Set<String> names;
public Parameters(List<Parameter> params) {
this.names = new HashSet<>();
var len = params.size();
for (var i = params.size() - 1; i >= 0; i--) {
if (params.get(i).node == null) break;
len--;
}
for (var param : params) {
this.names.add(param.name);
}
this.params = params;
this.length = len;
}
}

View File

@ -40,7 +40,7 @@ public class VariableDeclareNode extends Node {
target.add(VariableNode.toSet(target, entry.location, entry.name, false, true));
}
else {
target.add(() -> {
target.add(_i -> {
var i = target.scope.get(entry.name, true);
if (i == null) return Instruction.globDef(entry.name);

View File

@ -15,7 +15,7 @@ public class BreakNode extends Node {
public final String label;
@Override public void compile(CompileResult target, boolean pollute) {
var res = LabelContext.getBreak(target.env).getJump(target.size());
var res = LabelContext.getBreak(target.env).getJump();
if (res == null) {
if (label != null) throw new SyntaxException(loc(), String.format("Undefined label '%s'", label));
else throw new SyntaxException(loc(), "Illegal break statement");

View File

@ -15,7 +15,7 @@ public class ContinueNode extends Node {
public final String label;
@Override public void compile(CompileResult target, boolean pollute) {
var res = LabelContext.getCont(target.env).getJump(target.size());
var res = LabelContext.getCont(target.env).getJump();
if (res == null) {
if (label != null) throw new SyntaxException(loc(), String.format("Undefined label '%s'", label));
else throw new SyntaxException(loc(), "Illegal continue statement");

View File

@ -44,7 +44,7 @@ public class SwitchNode extends Node {
value.compile(target, true, BreakpointType.STEP_OVER);
var subtarget = target.subtarget();
subtarget.add(() -> Instruction.stackAlloc(subtarget.scope.allocCount()));
subtarget.add(_i -> Instruction.stackAlloc(subtarget.scope.allocCount()));
// TODO: create a jump map
for (var ccase : cases) {

View File

@ -27,7 +27,7 @@ public class FunctionScope extends Scope {
else if (parent == null) throw new RuntimeException("Strict variables may be defined only in local scopes");
else return parent.defineStrict(name, readonly, loc);
}
public VariableDescriptor defineArg(String name, Location loc) {
public VariableDescriptor defineParam(String name, Location loc) {
return specials.add(name, false);
}
public boolean hasArg(String name) {

View File

@ -97,7 +97,7 @@ public class ObjectNode extends Node {
if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected a property name after '" + access + "'");
n += name.n;
var params = JavaScript.parseParamList(src, i + n);
var params = JavaScript.parseParameters(src, i + n);
if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected an argument list");
n += params.n;
@ -109,7 +109,7 @@ public class ObjectNode extends Node {
return ParseRes.res(new ObjProp(
name.result, access.result,
new FunctionValueNode(loc, end, params.result.toArray(String[]::new), body.result, access + " " + name.result.toString())
new FunctionValueNode(loc, end, params.result, body.result, access + " " + name.result.toString())
), n);
}

View File

@ -1,5 +1,6 @@
package me.topchetoeu.jscript.compilation.values;
import java.util.function.IntFunction;
import java.util.function.Supplier;
import me.topchetoeu.jscript.common.Instruction;
@ -33,7 +34,7 @@ public class VariableNode extends Node implements AssignableNode {
var i = target.scope.get(name, true);
if (i == null) {
target.add((Supplier<Instruction>)() -> {
target.add(_i -> {
if (target.scope.has(name)) throw new SyntaxException(loc(), String.format("Cannot access '%s' before initialization", name));
return Instruction.globGet(name);
});
@ -45,28 +46,28 @@ public class VariableNode extends Node implements AssignableNode {
}
}
public static Supplier<Instruction> toGet(CompileResult target, Location loc, String name, Supplier<Instruction> onGlobal) {
public static IntFunction<Instruction> toGet(CompileResult target, Location loc, String name, Supplier<Instruction> onGlobal) {
var i = target.scope.get(name, true);
if (i == null) return () -> {
if (i == null) return _i -> {
if (target.scope.has(name)) throw new SyntaxException(loc, String.format("Cannot access '%s' before initialization", name));
else return onGlobal.get();
};
else return () -> Instruction.loadVar(i.index());
else return _i -> Instruction.loadVar(i.index());
}
public static Supplier<Instruction> toGet(CompileResult target, Location loc, String name) {
public static IntFunction<Instruction> toGet(CompileResult target, Location loc, String name) {
return toGet(target, loc, name, () -> Instruction.globGet(name));
}
public static Supplier<Instruction> toSet(CompileResult target, Location loc, String name, boolean keep, boolean define) {
public static IntFunction<Instruction> toSet(CompileResult target, Location loc, String name, boolean keep, boolean define) {
var i = target.scope.get(name, true);
if (i == null) return () -> {
if (i == null) return _i -> {
if (target.scope.has(name)) throw new SyntaxException(loc, String.format("Cannot access '%s' before initialization", name));
else return Instruction.globSet(name, keep, define);
};
else return () -> Instruction.storeVar(i.index(), keep);
else return _i -> Instruction.storeVar(i.index(), keep);
}

View File

@ -420,11 +420,16 @@ public final class Frame {
this.argsVal = new ArgumentsValue(this, args);
this.captures = func.captures;
for (var i = 0; i < func.body.argsN; i++) {
var i = 0;
for (; i < func.body.argsN && i < args.length; i++) {
this.locals.add(new Value[] { args[i] });
}
for (; i < args.length; i++) {
this.locals.add(new Value[] { Value.UNDEFINED });
}
for (var i = 0; i < func.body.localsN; i++) {
for (i = 0; i < func.body.localsN; i++) {
this.locals.add(new Value[] { Value.UNDEFINED });
}

View File

@ -30,7 +30,7 @@ public final class CodeFunction extends FunctionValue {
}
public CodeFunction(Environment env, String name, FunctionBody body, Value[][] captures) {
super(name, body.argsN);
super(name, body.length);
this.captures = captures;
this.env = env;
this.body = body;