feat: implement patterns
This commit is contained in:
parent
23ae2b2e46
commit
cb82f4cf32
@ -9,6 +9,7 @@ import me.topchetoeu.jscript.common.parsing.ParseRes;
|
|||||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||||
import me.topchetoeu.jscript.common.parsing.Source;
|
import me.topchetoeu.jscript.common.parsing.Source;
|
||||||
import me.topchetoeu.jscript.compilation.control.ReturnNode;
|
import me.topchetoeu.jscript.compilation.control.ReturnNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.patterns.Pattern;
|
||||||
|
|
||||||
public class FunctionArrowNode extends FunctionNode {
|
public class FunctionArrowNode extends FunctionNode {
|
||||||
@Override public String name() { return null; }
|
@Override public String name() { return null; }
|
||||||
@ -34,7 +35,7 @@ public class FunctionArrowNode extends FunctionNode {
|
|||||||
Parameters params;
|
Parameters params;
|
||||||
|
|
||||||
if (src.is(i + n, "(")) {
|
if (src.is(i + n, "(")) {
|
||||||
var paramsRes = JavaScript.parseParameters(src, i + n);
|
var paramsRes = Parameters.parseParameters(src, i + n);
|
||||||
if (!paramsRes.isSuccess()) return paramsRes.chainError();
|
if (!paramsRes.isSuccess()) return paramsRes.chainError();
|
||||||
n += paramsRes.n;
|
n += paramsRes.n;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
@ -42,14 +43,12 @@ public class FunctionArrowNode extends FunctionNode {
|
|||||||
params = paramsRes.result;
|
params = paramsRes.result;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
var singleParam = Parsing.parseIdentifier(src, i + n);
|
var singleParam = Pattern.parse(src, i + n, true);
|
||||||
if (!singleParam.isSuccess()) return ParseRes.failed();
|
if (!singleParam.isSuccess()) return ParseRes.failed();
|
||||||
|
|
||||||
var paramLoc = src.loc(i + n);
|
|
||||||
n += singleParam.n;
|
n += singleParam.n;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
params = new Parameters(List.of(new Parameter(paramLoc, singleParam.result, null)));
|
params = new Parameters(List.of(singleParam.result));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!src.is(i + n, "=>")) return ParseRes.failed();
|
if (!src.is(i + n, "=>")) return ParseRes.failed();
|
||||||
|
@ -1,16 +1,15 @@
|
|||||||
package me.topchetoeu.jscript.compilation;
|
package me.topchetoeu.jscript.compilation;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Instruction;
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
import me.topchetoeu.jscript.common.Operation;
|
|
||||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||||
import me.topchetoeu.jscript.common.environment.Environment;
|
import me.topchetoeu.jscript.common.environment.Environment;
|
||||||
import me.topchetoeu.jscript.common.parsing.Location;
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||||
import me.topchetoeu.jscript.common.parsing.Source;
|
import me.topchetoeu.jscript.common.parsing.Source;
|
||||||
|
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
|
||||||
import me.topchetoeu.jscript.compilation.scope.FunctionScope;
|
import me.topchetoeu.jscript.compilation.scope.FunctionScope;
|
||||||
import me.topchetoeu.jscript.compilation.scope.Variable;
|
import me.topchetoeu.jscript.compilation.scope.Variable;
|
||||||
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
|
||||||
|
|
||||||
public abstract class FunctionNode extends Node {
|
public abstract class FunctionNode extends Node {
|
||||||
public final CompoundNode body;
|
public final CompoundNode body;
|
||||||
@ -37,36 +36,37 @@ public abstract class FunctionNode extends Node {
|
|||||||
var i = 0;
|
var i = 0;
|
||||||
|
|
||||||
for (var param : params.params) {
|
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++));
|
target.add(Instruction.loadMember(i++));
|
||||||
|
param.destruct(target, DeclarationType.VAR, true);
|
||||||
|
// 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);
|
||||||
|
|
||||||
if (param.node != null) {
|
// if (param.node != null) {
|
||||||
var end = new DeferredIntSupplier();
|
// var end = new DeferredIntSupplier();
|
||||||
|
|
||||||
target.add(Instruction.dup());
|
// target.add(Instruction.dup());
|
||||||
target.add(Instruction.pushUndefined());
|
// target.add(Instruction.pushUndefined());
|
||||||
target.add(Instruction.operation(Operation.EQUALS));
|
// target.add(Instruction.operation(Operation.EQUALS));
|
||||||
target.add(Instruction.jmpIfNot(end));
|
// target.add(Instruction.jmpIfNot(end));
|
||||||
target.add(Instruction.discard());
|
// target.add(Instruction.discard());
|
||||||
param.node.compile(target, true);
|
// param.node.compile(target, true);
|
||||||
|
|
||||||
end.set(target.size());
|
// end.set(target.size());
|
||||||
}
|
// }
|
||||||
|
|
||||||
target.add(_i -> varI.index().toSet(false));
|
// target.add(_i -> varI.index().toSet(false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (params.restName != null) {
|
if (params.rest != 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(Instruction.loadRestArgs(params.params.size()));
|
||||||
target.add(_i -> restVar.index().toSet(false));
|
params.rest.destruct(target, DeclarationType.VAR, true);
|
||||||
|
// 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(_i -> restVar.index().toSet(false));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selfName != null && !scope.has(name, false)) {
|
if (selfName != null && !scope.has(name, false)) {
|
||||||
@ -131,7 +131,7 @@ public abstract class FunctionNode extends Node {
|
|||||||
n += name.n;
|
n += name.n;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
var params = JavaScript.parseParameters(src, i + n);
|
var params = Parameters.parseParameters(src, i + n);
|
||||||
if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected a parameter list");
|
if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected a parameter list");
|
||||||
n += params.n;
|
n += params.n;
|
||||||
|
|
||||||
|
@ -222,71 +222,6 @@ public final class JavaScript {
|
|||||||
return ParseRes.failed();
|
return ParseRes.failed();
|
||||||
}
|
}
|
||||||
|
|
||||||
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");
|
|
||||||
n += openParen.n;
|
|
||||||
|
|
||||||
var params = new ArrayList<Parameter>();
|
|
||||||
|
|
||||||
var closeParen = Parsing.parseOperator(src, i + n, ")");
|
|
||||||
n += closeParen.n;
|
|
||||||
|
|
||||||
if (!closeParen.isSuccess()) {
|
|
||||||
while (true) {
|
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
|
||||||
|
|
||||||
if (src.is(i + n, "...")) {
|
|
||||||
n += 3;
|
|
||||||
var restLoc = src.loc(i);
|
|
||||||
|
|
||||||
var restName = Parsing.parseIdentifier(src, i + n);
|
|
||||||
if (!restName.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a rest parameter");
|
|
||||||
n += restName.n;
|
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
|
||||||
|
|
||||||
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected an end of parameters list after rest parameter");
|
|
||||||
n++;
|
|
||||||
|
|
||||||
return ParseRes.res(new Parameters(params, restName.result, restLoc), n);
|
|
||||||
}
|
|
||||||
|
|
||||||
var paramLoc = src.loc(i);
|
|
||||||
|
|
||||||
var name = Parsing.parseIdentifier(src, i + n);
|
|
||||||
if (!name.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a parameter 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 parameter 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ParseRes.res(new Parameters(params), n);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ParseRes<DeclarationType> parseDeclarationType(Source src, int i) {
|
public static ParseRes<DeclarationType> parseDeclarationType(Source src, int i) {
|
||||||
var res = Parsing.parseIdentifier(src, i);
|
var res = Parsing.parseIdentifier(src, i);
|
||||||
if (!res.isSuccess()) return res.chainError();
|
if (!res.isSuccess()) return res.chainError();
|
||||||
|
@ -1,29 +1,84 @@
|
|||||||
package me.topchetoeu.jscript.compilation;
|
package me.topchetoeu.jscript.compilation;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
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.patterns.Pattern;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.operations.AssignNode;
|
||||||
|
|
||||||
public final class Parameters {
|
public final class Parameters {
|
||||||
public final int length;
|
public final int length;
|
||||||
public final List<Parameter> params;
|
public final List<Pattern> params;
|
||||||
public final String restName;
|
public final Pattern rest;
|
||||||
public final Location restLocation;
|
|
||||||
|
|
||||||
public Parameters(List<Parameter> params, String restName, Location restLocation) {
|
public Parameters(List<Pattern> params, Pattern rest) {
|
||||||
var len = params.size();
|
var len = params.size();
|
||||||
|
|
||||||
for (var i = params.size() - 1; i >= 0; i--) {
|
for (var i = params.size() - 1; i >= 0; i--) {
|
||||||
if (params.get(i).node == null) break;
|
if (!(params.get(i) instanceof AssignNode)) break;
|
||||||
len--;
|
len--;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.params = params;
|
this.params = params;
|
||||||
this.length = len;
|
this.length = len;
|
||||||
this.restName = restName;
|
this.rest = rest;
|
||||||
this.restLocation = restLocation;
|
|
||||||
}
|
}
|
||||||
public Parameters(List<Parameter> params) {
|
public Parameters(List<Pattern> params) {
|
||||||
this(params, null, null);
|
this(params, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
n += openParen.n;
|
||||||
|
|
||||||
|
var params = new ArrayList<Pattern>();
|
||||||
|
|
||||||
|
var closeParen = Parsing.parseOperator(src, i + n, ")");
|
||||||
|
n += closeParen.n;
|
||||||
|
|
||||||
|
if (!closeParen.isSuccess()) {
|
||||||
|
while (true) {
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (src.is(i + n, "...")) {
|
||||||
|
n += 3;
|
||||||
|
|
||||||
|
var rest = Pattern.parse(src, i + n, true);
|
||||||
|
if (!rest.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a rest parameter");
|
||||||
|
n += rest.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected an end of parameters list after rest parameter");
|
||||||
|
n++;
|
||||||
|
|
||||||
|
return ParseRes.res(new Parameters(params, rest.result), n);
|
||||||
|
}
|
||||||
|
|
||||||
|
var param = Pattern.parse(src, i + n, true);
|
||||||
|
if (!param.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a parameter or a closing brace");
|
||||||
|
n += param.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
params.add(param.result);
|
||||||
|
|
||||||
|
if (src.is(i + n, ",")) {
|
||||||
|
n++;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (src.is(i + n, ")")) {
|
||||||
|
n++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParseRes.res(new Parameters(params), n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,23 +4,21 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Instruction;
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
|
||||||
import me.topchetoeu.jscript.common.parsing.Location;
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||||
import me.topchetoeu.jscript.common.parsing.Source;
|
import me.topchetoeu.jscript.common.parsing.Source;
|
||||||
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
|
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
|
||||||
import me.topchetoeu.jscript.compilation.scope.Variable;
|
import me.topchetoeu.jscript.compilation.patterns.Pattern;
|
||||||
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
|
||||||
|
|
||||||
public class VariableDeclareNode extends Node {
|
public class VariableDeclareNode extends Node {
|
||||||
public static class Pair {
|
public static class Pair {
|
||||||
public final String name;
|
public final Pattern destructor;
|
||||||
public final Node value;
|
public final Node value;
|
||||||
public final Location location;
|
public final Location location;
|
||||||
|
|
||||||
public Pair(String name, Node value, Location location) {
|
public Pair(Pattern destr, Node value, Location location) {
|
||||||
this.name = name;
|
this.destructor = destr;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
this.location = location;
|
this.location = location;
|
||||||
}
|
}
|
||||||
@ -32,25 +30,22 @@ public class VariableDeclareNode extends Node {
|
|||||||
@Override public void resolve(CompileResult target) {
|
@Override public void resolve(CompileResult target) {
|
||||||
if (!declType.strict) {
|
if (!declType.strict) {
|
||||||
for (var entry : values) {
|
for (var entry : values) {
|
||||||
target.scope.define(new Variable(entry.name, false), entry.location);
|
entry.destructor.destructDeclResolve(target);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
for (var entry : values) {
|
for (var entry : values) {
|
||||||
if (entry.name == null) continue;
|
if (entry.value == null) {
|
||||||
if (declType.strict) target.scope.defineStrict(new Variable(entry.name, declType.readonly), entry.location);
|
if (declType == DeclarationType.VAR) entry.destructor.declare(target, null);
|
||||||
|
else entry.destructor.declare(target, declType);
|
||||||
if (entry.value != null) {
|
|
||||||
FunctionNode.compileWithName(entry.value, target, true, entry.name, BreakpointType.STEP_OVER);
|
|
||||||
target.add(VariableNode.toSet(target, entry.location, entry.name, false, true));
|
|
||||||
}
|
}
|
||||||
else target.add(_i -> {
|
else {
|
||||||
var i = target.scope.get(entry.name, false);
|
entry.value.compile(target, true);
|
||||||
|
|
||||||
if (i == null) return Instruction.globDef(entry.name);
|
if (declType == DeclarationType.VAR) entry.destructor.destruct(target, null, true);
|
||||||
else return Instruction.nop();
|
else entry.destructor.destruct(target, declType, true);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pollute) target.add(Instruction.pushUndefined());
|
if (pollute) target.add(Instruction.pushUndefined());
|
||||||
@ -80,13 +75,10 @@ public class VariableDeclareNode extends Node {
|
|||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
var nameLoc = src.loc(i + n);
|
var nameLoc = src.loc(i + n);
|
||||||
var name = Parsing.parseIdentifier(src, i + n);
|
|
||||||
if (!name.isSuccess()) return name.chainError(nameLoc, "Expected a variable name");
|
|
||||||
n += name.n;
|
|
||||||
|
|
||||||
if (!JavaScript.checkVarName(name.result)) {
|
var name = Pattern.parse(src, i + n, false);
|
||||||
return ParseRes.error(src.loc(i + n), String.format("Unexpected identifier '%s'", name.result));
|
if (!name.isSuccess()) return name.chainError(nameLoc, "Expected a variable name or a destructor");
|
||||||
}
|
n += name.n;
|
||||||
|
|
||||||
Node val = null;
|
Node val = null;
|
||||||
var endN = n;
|
var endN = n;
|
||||||
|
@ -1,69 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.destructing;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Instruction;
|
|
||||||
import me.topchetoeu.jscript.common.Operation;
|
|
||||||
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.CompileResult;
|
|
||||||
import me.topchetoeu.jscript.compilation.DeferredIntSupplier;
|
|
||||||
import me.topchetoeu.jscript.compilation.JavaScript;
|
|
||||||
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 AssignDestructorNode extends Node implements Destructor {
|
|
||||||
public final String name;
|
|
||||||
public final Node value;
|
|
||||||
|
|
||||||
@Override public void destructDeclResolve(CompileResult target) {
|
|
||||||
target.scope.define(new Variable(name, false), loc());
|
|
||||||
}
|
|
||||||
@Override public void destructArg(CompileResult target) {
|
|
||||||
var v = target.scope.define(new Variable(name, false), loc());
|
|
||||||
destructAssign(target);
|
|
||||||
target.add(_i -> v.index().toSet(false));
|
|
||||||
}
|
|
||||||
@Override public void afterAssign(CompileResult target, DeclarationType decl) {
|
|
||||||
if (decl != null && decl.strict) target.scope.define(new Variable(name, decl.readonly), loc());
|
|
||||||
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());
|
|
||||||
value.compile(target, true);
|
|
||||||
|
|
||||||
end.set(target.size());
|
|
||||||
|
|
||||||
target.add(VariableNode.toSet(target, loc(), name, false, decl != null));
|
|
||||||
}
|
|
||||||
|
|
||||||
public AssignDestructorNode(Location loc, String name, Node value) {
|
|
||||||
super(loc);
|
|
||||||
this.name = name;
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ParseRes<AssignDestructorNode> parse(Source src, int i) {
|
|
||||||
var n = Parsing.skipEmpty(src, i);
|
|
||||||
var loc = src.loc(i + n);
|
|
||||||
|
|
||||||
var name = Parsing.parseIdentifier(src, i);
|
|
||||||
if (!JavaScript.checkVarName(null)) return ParseRes.error(src.loc(i + n), String.format("Unexpected keyword '%s'", name.result));
|
|
||||||
n += name.n;
|
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
|
||||||
|
|
||||||
if (!src.is(i, "=")) return ParseRes.failed();
|
|
||||||
n++;
|
|
||||||
|
|
||||||
var value = JavaScript.parseExpression(src, i, 2);
|
|
||||||
if (value.isError()) return ParseRes.error(src.loc(i + n), "Expected a value after '='");
|
|
||||||
n += value.n;
|
|
||||||
|
|
||||||
return ParseRes.res(new AssignDestructorNode(loc, name.result, value.result), n);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,4 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.destructing;
|
|
||||||
|
|
||||||
public interface AssignTarget extends Destructor {
|
|
||||||
}
|
|
@ -1,38 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.destructing;
|
|
||||||
|
|
||||||
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.CompileResult;
|
|
||||||
import me.topchetoeu.jscript.compilation.JavaScript;
|
|
||||||
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
|
|
||||||
import me.topchetoeu.jscript.compilation.values.ObjectDestructorNode;
|
|
||||||
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
|
||||||
|
|
||||||
public interface Destructor {
|
|
||||||
void destructDeclResolve(CompileResult target);
|
|
||||||
|
|
||||||
default void destructArg(CompileResult target) {
|
|
||||||
beforeAssign(target, null);
|
|
||||||
afterAssign(target, null);
|
|
||||||
}
|
|
||||||
default void beforeAssign(CompileResult target, DeclarationType decl) {}
|
|
||||||
void afterAssign(CompileResult target, DeclarationType decl, boolean pollute);
|
|
||||||
|
|
||||||
public static ParseRes<Destructor> parse(Source src, int i) {
|
|
||||||
var n = Parsing.skipEmpty(src, i);
|
|
||||||
|
|
||||||
ParseRes<Destructor> first = ParseRes.first(src, i + n,
|
|
||||||
ObjectDestructorNode::parse,
|
|
||||||
AssignDestructorNode::parse,
|
|
||||||
VariableNode::parse
|
|
||||||
);
|
|
||||||
if (first.isSuccess()) return first.addN(n);
|
|
||||||
|
|
||||||
var exp = JavaScript.parseExpression(src, i, 2);
|
|
||||||
if (!(exp.result instanceof Destructor destructor)) return ParseRes.error(src.loc(i + n), "Expected a destructor expression");
|
|
||||||
n += exp.n;
|
|
||||||
|
|
||||||
return ParseRes.res(destructor, n);
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.destructing;
|
|
||||||
|
|
||||||
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.values.VariableNode;
|
|
||||||
|
|
||||||
public interface NamedDestructor extends Destructor {
|
|
||||||
String name();
|
|
||||||
|
|
||||||
public static ParseRes<Destructor> parse(Source src, int i) {
|
|
||||||
var n = Parsing.skipEmpty(src, i);
|
|
||||||
|
|
||||||
ParseRes<Destructor> first = ParseRes.first(src, i + n,
|
|
||||||
AssignDestructorNode::parse,
|
|
||||||
VariableNode::parse
|
|
||||||
);
|
|
||||||
return first.addN(n);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,69 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.patterns;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Operation;
|
||||||
|
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.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||||
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
|
||||||
|
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
||||||
|
|
||||||
|
public class AssignPattern implements Pattern {
|
||||||
|
public final Location loc;
|
||||||
|
public final Pattern assignable;
|
||||||
|
public final Node value;
|
||||||
|
|
||||||
|
@Override public Location loc() { return loc; }
|
||||||
|
|
||||||
|
@Override public void destructDeclResolve(CompileResult target) {
|
||||||
|
assignable.destructDeclResolve(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void declare(CompileResult target, DeclarationType decl) {
|
||||||
|
throw new SyntaxException(loc(), "Expected an assignment value for destructor declaration");
|
||||||
|
}
|
||||||
|
@Override public void destruct(CompileResult target, DeclarationType decl, boolean shouldDeclare) {
|
||||||
|
// if (assignable instanceof AssignPattern other) throw new SyntaxException(other.loc(), "Unexpected destruction target");
|
||||||
|
|
||||||
|
target.add(Instruction.dup());
|
||||||
|
target.add(Instruction.pushUndefined());
|
||||||
|
target.add(Instruction.operation(Operation.EQUALS));
|
||||||
|
var start = target.temp();
|
||||||
|
target.add(Instruction.discard());
|
||||||
|
|
||||||
|
value.compile(target, true);
|
||||||
|
|
||||||
|
target.set(start, Instruction.jmpIfNot(target.size() - start));
|
||||||
|
|
||||||
|
assignable.destruct(target, decl, shouldDeclare);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AssignPattern(Location loc, Pattern assignable, Node value) {
|
||||||
|
this.loc = loc;
|
||||||
|
this.assignable = assignable;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<AssignPattern> parse(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
|
var pattern = Pattern.parse(src, i + n, false);
|
||||||
|
if (!pattern.isSuccess()) return pattern.chainError();
|
||||||
|
n += pattern.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (!src.is(i + n, "=")) return ParseRes.failed();
|
||||||
|
n++;
|
||||||
|
|
||||||
|
var value = JavaScript.parseExpression(src, i + n, 2);
|
||||||
|
if (!value.isSuccess()) return value.chainError(src.loc(i + n), "Expected a default value");
|
||||||
|
n += value.n;
|
||||||
|
|
||||||
|
return ParseRes.res(new AssignPattern(loc, pattern.result, value.result), n);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,27 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.patterns;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents all nodes that can be assign targets
|
||||||
|
*/
|
||||||
|
public interface AssignTarget extends AssignTargetLike {
|
||||||
|
Location loc();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called to perform calculations before the assigned value is calculated
|
||||||
|
*/
|
||||||
|
default void beforeAssign(CompileResult target) {}
|
||||||
|
/**
|
||||||
|
* Called to perform the actual assignemnt. Between the `beforeAssign` and this call a single value will have been pushed to the stack
|
||||||
|
* @param pollute Whether or not to leave the original value on the stack
|
||||||
|
*/
|
||||||
|
void afterAssign(CompileResult target, boolean pollute);
|
||||||
|
|
||||||
|
default void assign(CompileResult target, boolean pollute) {
|
||||||
|
afterAssign(target, pollute);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override default AssignTarget toAssignTarget() { return this; }
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.patterns;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents all nodes that can be assign targets
|
||||||
|
*/
|
||||||
|
public interface AssignTargetLike {
|
||||||
|
AssignTarget toAssignTarget();
|
||||||
|
}
|
@ -1,8 +1,7 @@
|
|||||||
package me.topchetoeu.jscript.compilation.destructing;
|
package me.topchetoeu.jscript.compilation.patterns;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
|
||||||
public interface ChangeTarget extends AssignTarget {
|
public interface ChangeTarget extends AssignTarget {
|
||||||
void beforeChange(CompileResult target);
|
void beforeChange(CompileResult target);
|
||||||
void afterChange(CompileResult target, boolean pollute);
|
|
||||||
}
|
}
|
@ -0,0 +1,15 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.patterns;
|
||||||
|
|
||||||
|
public interface NamedDestructor extends Pattern {
|
||||||
|
String name();
|
||||||
|
|
||||||
|
// public static ParseRes<Destructor> parse(Source src, int i) {
|
||||||
|
// var n = Parsing.skipEmpty(src, i);
|
||||||
|
|
||||||
|
// ParseRes<Destructor> first = ParseRes.first(src, i + n,
|
||||||
|
// AssignDestructorNode::parse,
|
||||||
|
// VariableNode::parse
|
||||||
|
// );
|
||||||
|
// return first.addN(n);
|
||||||
|
// }
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.patterns;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
|
||||||
|
public class ObjectAssignable extends ObjectDestructor<AssignTarget> implements AssignTarget {
|
||||||
|
@Override public void afterAssign(CompileResult target, boolean pollute) {
|
||||||
|
compile(target, t -> t.assign(target, false), pollute);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectAssignable(Location loc, List<Member<AssignTarget>> members) {
|
||||||
|
super(loc, members);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.patterns;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.operations.IndexNode;
|
||||||
|
|
||||||
|
public abstract class ObjectDestructor<T> extends Node {
|
||||||
|
public static final class Member<T> {
|
||||||
|
public final Node key;
|
||||||
|
public final T consumable;
|
||||||
|
|
||||||
|
public Member(Node key, T consumer) {
|
||||||
|
this.key = key;
|
||||||
|
this.consumable = consumer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final List<Member<T>> members;
|
||||||
|
|
||||||
|
public void consume(Consumer<T> consumer) {
|
||||||
|
for (var el : members) {
|
||||||
|
consumer.accept(el.consumable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public void compile(CompileResult target, Consumer<T> consumer, boolean pollute) {
|
||||||
|
for (var el : members) {
|
||||||
|
target.add(Instruction.dup());
|
||||||
|
IndexNode.indexLoad(target, el.key, true);
|
||||||
|
consumer.accept(el.consumable);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pollute) target.add(Instruction.discard());
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectDestructor(Location loc, List<Member<T>> members) {
|
||||||
|
super(loc);
|
||||||
|
this.members = members;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,118 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.patterns;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
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.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.ObjectNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.constants.StringNode;
|
||||||
|
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
||||||
|
|
||||||
|
public class ObjectPattern extends ObjectDestructor<Pattern> implements Pattern {
|
||||||
|
@Override public void destructDeclResolve(CompileResult target) {
|
||||||
|
consume(t -> t.destructDeclResolve(target));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void destruct(CompileResult target, DeclarationType decl, boolean shouldDeclare) {
|
||||||
|
compile(target, t -> t.destruct(target, decl, shouldDeclare), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void declare(CompileResult target, DeclarationType decl) {
|
||||||
|
throw new SyntaxException(loc(), "Object pattern must be initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectPattern(Location loc, List<Member<Pattern>> members) {
|
||||||
|
super(loc, members);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ParseRes<Member<Pattern>> parseShorthand(Source src, int i) {
|
||||||
|
ParseRes<Pattern> res = ParseRes.first(src, i,
|
||||||
|
AssignPattern::parse,
|
||||||
|
VariableNode::parse
|
||||||
|
);
|
||||||
|
|
||||||
|
if (res.isSuccess()) {
|
||||||
|
if (res.result instanceof AssignPattern assign) {
|
||||||
|
if (assign.assignable instanceof VariableNode var) {
|
||||||
|
return ParseRes.res(new Member<>(new StringNode(var.loc(), var.name), res.result), res.n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (res.result instanceof VariableNode var) {
|
||||||
|
return ParseRes.res(new Member<>(new StringNode(var.loc(), var.name), res.result), res.n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.chainError();
|
||||||
|
}
|
||||||
|
private static ParseRes<Member<Pattern>> parseKeyed(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
|
||||||
|
var key = ObjectNode.parsePropName(src, i + n);
|
||||||
|
if (!key.isSuccess()) return key.chainError();
|
||||||
|
n += key.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (!src.is(i + n , ":")) return ParseRes.failed();
|
||||||
|
n++;
|
||||||
|
|
||||||
|
ParseRes<Pattern> res = Pattern.parse(src, i + n, true);
|
||||||
|
if (!res.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a pattern after colon");
|
||||||
|
n += res.n;
|
||||||
|
|
||||||
|
return ParseRes.res(new Member<>(key.result, res.result), n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<ObjectPattern> parse(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
|
if (!src.is(i + n, "{")) return ParseRes.failed();
|
||||||
|
n++;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
var members = new LinkedList<Member<Pattern>>();
|
||||||
|
|
||||||
|
if (src.is(i + n, "}")) {
|
||||||
|
n++;
|
||||||
|
return ParseRes.res(new ObjectPattern(loc, members), n);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
ParseRes<Member<Pattern>> prop = ParseRes.first(src, i + n,
|
||||||
|
ObjectPattern::parseKeyed,
|
||||||
|
ObjectPattern::parseShorthand
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!prop.isSuccess()) return prop.chainError(src.loc(i + n), "Expected a member in object pattern");
|
||||||
|
n += prop.n;
|
||||||
|
|
||||||
|
members.add(prop.result);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (src.is(i + n, "}")) {
|
||||||
|
n++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
else ParseRes.error(src.loc(i + n), "Expected a comma or a closing brace.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return ParseRes.res(new ObjectPattern(loc, members), n);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,47 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.patterns;
|
||||||
|
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||||
|
import me.topchetoeu.jscript.common.parsing.Source;
|
||||||
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
|
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents all nodes that can be a destructors (note that all destructors are assign targets, too)
|
||||||
|
*/
|
||||||
|
public interface Pattern extends PatternLike {
|
||||||
|
Location loc();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the destructor has to declare
|
||||||
|
* @param target
|
||||||
|
*/
|
||||||
|
void destructDeclResolve(CompileResult target);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a declaration-like is being destructed
|
||||||
|
* @param decl The variable type the destructor must declare, if it is a named pne
|
||||||
|
*/
|
||||||
|
void destruct(CompileResult target, DeclarationType decl, boolean shouldDeclare);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run when destructing a declaration without an initializer
|
||||||
|
*/
|
||||||
|
void declare(CompileResult target, DeclarationType decl);
|
||||||
|
|
||||||
|
public static ParseRes<Pattern> parse(Source src, int i, boolean withDefault) {
|
||||||
|
return withDefault ?
|
||||||
|
ParseRes.first(src, i,
|
||||||
|
AssignPattern::parse,
|
||||||
|
ObjectPattern::parse,
|
||||||
|
VariableNode::parse
|
||||||
|
) :
|
||||||
|
ParseRes.first(src, i,
|
||||||
|
ObjectPattern::parse,
|
||||||
|
VariableNode::parse
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override default Pattern toPattern() { return this; }
|
||||||
|
}
|
@ -0,0 +1,5 @@
|
|||||||
|
package me.topchetoeu.jscript.compilation.patterns;
|
||||||
|
|
||||||
|
public interface PatternLike {
|
||||||
|
Pattern toPattern();
|
||||||
|
}
|
@ -4,6 +4,7 @@ import java.util.HashMap;
|
|||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.parsing.Location;
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
|
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
|
||||||
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
||||||
|
|
||||||
public class Scope {
|
public class Scope {
|
||||||
@ -41,15 +42,6 @@ public class Scope {
|
|||||||
return var;
|
return var;
|
||||||
}
|
}
|
||||||
|
|
||||||
// private final int parentVarOffset() {
|
|
||||||
// if (parent != null) return parent.variableOffset();
|
|
||||||
// else return 0;
|
|
||||||
// }
|
|
||||||
// private final int parentCapOffset() {
|
|
||||||
// if (parent != null) return parent.capturedOffset();
|
|
||||||
// else return localsCount();
|
|
||||||
// }
|
|
||||||
|
|
||||||
protected final SyntaxException alreadyDefinedErr(Location loc, String name) {
|
protected final SyntaxException alreadyDefinedErr(Location loc, String name) {
|
||||||
return new SyntaxException(loc, String.format("Identifier '%s' has already been declared", name));
|
return new SyntaxException(loc, String.format("Identifier '%s' has already been declared", name));
|
||||||
}
|
}
|
||||||
@ -141,9 +133,6 @@ public class Scope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
|
|
||||||
// if (parent != null) return parent.variableOffset() + variables.size();
|
|
||||||
// else return variables.size();
|
|
||||||
}
|
}
|
||||||
public final int capturablesOffset() {
|
public final int capturablesOffset() {
|
||||||
var res = 0;
|
var res = 0;
|
||||||
@ -154,8 +143,11 @@ public class Scope {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return res;
|
return res;
|
||||||
// if (parent != null) return parent.capturedOffset() + captured.size();
|
}
|
||||||
// else return localsCount() + captured.size();
|
|
||||||
|
public final Variable define(DeclarationType type, String name, Location loc) {
|
||||||
|
if (type.strict) return defineStrict(new Variable(name, type.readonly), loc);
|
||||||
|
else return define(new Variable(name, type.readonly), loc);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int localsCount() {
|
public int localsCount() {
|
||||||
|
@ -1,199 +0,0 @@
|
|||||||
package me.topchetoeu.jscript.compilation.values;
|
|
||||||
|
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Instruction;
|
|
||||||
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.CompileResult;
|
|
||||||
import me.topchetoeu.jscript.compilation.Node;
|
|
||||||
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
|
|
||||||
import me.topchetoeu.jscript.compilation.destructing.Destructor;
|
|
||||||
|
|
||||||
|
|
||||||
public class ObjectDestructorNode extends Node implements Destructor {
|
|
||||||
public final LinkedHashMap<String, Destructor> destructors;
|
|
||||||
public final VariableNode restDestructor;
|
|
||||||
|
|
||||||
private void compileRestObjBuilder(CompileResult target, int srcDupN) {
|
|
||||||
var subtarget = target.subtarget();
|
|
||||||
var src = subtarget.scope.defineTemp();
|
|
||||||
var dst = subtarget.scope.defineTemp();
|
|
||||||
|
|
||||||
target.add(Instruction.loadObj());
|
|
||||||
target.add(_i -> src.index().toSet(true));
|
|
||||||
target.add(_i -> dst.index().toSet(destructors.size() > 0));
|
|
||||||
|
|
||||||
target.add(Instruction.keys(true, true));
|
|
||||||
var start = target.size();
|
|
||||||
|
|
||||||
target.add(Instruction.dup());
|
|
||||||
var mid = target.temp();
|
|
||||||
|
|
||||||
target.add(_i -> src.index().toGet());
|
|
||||||
target.add(Instruction.dup(1, 1));
|
|
||||||
target.add(Instruction.loadMember());
|
|
||||||
|
|
||||||
target.add(_i -> dst.index().toGet());
|
|
||||||
target.add(Instruction.dup(1, 1));
|
|
||||||
target.add(Instruction.storeMember());
|
|
||||||
|
|
||||||
target.add(Instruction.discard());
|
|
||||||
var end = target.size();
|
|
||||||
target.add(Instruction.jmp(start - end));
|
|
||||||
target.set(mid, Instruction.jmpIfNot(end - mid + 1));
|
|
||||||
|
|
||||||
target.add(Instruction.discard());
|
|
||||||
|
|
||||||
target.add(Instruction.dup(srcDupN, 1));
|
|
||||||
|
|
||||||
target.scope.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public void destructDeclResolve(CompileResult target) {
|
|
||||||
for (var el : destructors.values()) {
|
|
||||||
el.destructDeclResolve(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (restDestructor != null) restDestructor.destructDeclResolve(target);
|
|
||||||
}
|
|
||||||
@Override public void destructArg(CompileResult target) {
|
|
||||||
if (restDestructor != null) compileRestObjBuilder(target, destructors.size() * 2);
|
|
||||||
else if (destructors.size() > 0) target.add(Instruction.dup(destructors.size(), 0));
|
|
||||||
|
|
||||||
for (var el : destructors.entrySet()) {
|
|
||||||
if (restDestructor != null) {
|
|
||||||
target.add(Instruction.pushValue(el.getKey()));
|
|
||||||
target.add(Instruction.delete());
|
|
||||||
}
|
|
||||||
|
|
||||||
target.add(Instruction.loadMember(el.getKey()));
|
|
||||||
el.getValue().destructArg(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (restDestructor != null) restDestructor.destructArg(target);
|
|
||||||
|
|
||||||
target.add(Instruction.discard());
|
|
||||||
}
|
|
||||||
@Override public void afterAssign(CompileResult target, DeclarationType decl) {
|
|
||||||
if (restDestructor != null) compileRestObjBuilder(target, destructors.size() * 2);
|
|
||||||
else if (destructors.size() > 0) target.add(Instruction.dup(destructors.size(), 0));
|
|
||||||
|
|
||||||
for (var el : destructors.entrySet()) {
|
|
||||||
if (restDestructor != null) {
|
|
||||||
target.add(Instruction.pushValue(el.getKey()));
|
|
||||||
target.add(Instruction.delete());
|
|
||||||
}
|
|
||||||
|
|
||||||
target.add(Instruction.loadMember(el.getKey()));
|
|
||||||
el.getValue().afterAssign(target, decl);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (restDestructor != null) restDestructor.afterAssign(target, decl);
|
|
||||||
|
|
||||||
target.add(Instruction.discard());
|
|
||||||
}
|
|
||||||
@Override public void destructAssign(CompileResult target, boolean pollute) {
|
|
||||||
if (restDestructor != null) compileRestObjBuilder(target, destructors.size() * 2);
|
|
||||||
else if (destructors.size() > 0) target.add(Instruction.dup(destructors.size(), 0));
|
|
||||||
|
|
||||||
for (var el : destructors.entrySet()) {
|
|
||||||
if (restDestructor != null) {
|
|
||||||
target.add(Instruction.pushValue(el.getKey()));
|
|
||||||
target.add(Instruction.delete());
|
|
||||||
}
|
|
||||||
|
|
||||||
target.add(Instruction.loadMember(el.getKey()));
|
|
||||||
el.getValue().destructAssign(target, pollute);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (restDestructor != null) restDestructor.destructAssign(target, pollute);
|
|
||||||
|
|
||||||
if (!pollute) target.add(Instruction.discard());
|
|
||||||
}
|
|
||||||
|
|
||||||
public ObjectDestructorNode(Location loc, Map<String, Destructor> map, VariableNode rest) {
|
|
||||||
super(loc);
|
|
||||||
this.destructors = new LinkedHashMap<>(map);
|
|
||||||
this.restDestructor = rest;
|
|
||||||
}
|
|
||||||
public ObjectDestructorNode(Location loc, Map<String, Destructor> map) {
|
|
||||||
super(loc);
|
|
||||||
this.destructors = new LinkedHashMap<>(map);
|
|
||||||
this.restDestructor = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ParseRes<String> parsePropName(Source src, int i) {
|
|
||||||
var n = Parsing.skipEmpty(src, i);
|
|
||||||
|
|
||||||
var res = ParseRes.first(src, i + n,
|
|
||||||
Parsing::parseIdentifier,
|
|
||||||
Parsing::parseString,
|
|
||||||
(s, j) -> Parsing.parseNumber(s, j, false)
|
|
||||||
);
|
|
||||||
if (!res.isSuccess()) return res.chainError();
|
|
||||||
n += res.n;
|
|
||||||
|
|
||||||
if (!src.is(i + n, ":")) return ParseRes.error(src.loc(i + n), "Expected a colon");
|
|
||||||
n++;
|
|
||||||
|
|
||||||
return ParseRes.res(res.result.toString(), n);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ParseRes<ObjectDestructorNode> parse(Source src, int i) {
|
|
||||||
var n = Parsing.skipEmpty(src, i);
|
|
||||||
var loc = src.loc(i + n);
|
|
||||||
|
|
||||||
if (!src.is(i + n, "{")) return ParseRes.failed();
|
|
||||||
n++;
|
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
|
||||||
|
|
||||||
var destructors = new LinkedHashMap<String, Destructor>();
|
|
||||||
|
|
||||||
if (src.is(i + n, "}")) {
|
|
||||||
n++;
|
|
||||||
return ParseRes.res(new ObjectDestructorNode(loc, destructors), n);
|
|
||||||
}
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
|
||||||
|
|
||||||
// if (src.is(i, null))
|
|
||||||
|
|
||||||
var name = parsePropName(src, i + n);
|
|
||||||
if (!name.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a field name");
|
|
||||||
n += name.n;
|
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
|
||||||
|
|
||||||
var destructor = Destructor.parse(src, i + n);
|
|
||||||
if (!destructor.isSuccess()) return destructor.chainError(src.loc(i + n), "Expected a value in array list");
|
|
||||||
n += destructor.n;
|
|
||||||
|
|
||||||
destructors.put(name.result, destructor.result);
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
else if (src.is(i + n, "}")) {
|
|
||||||
n++;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
else ParseRes.error(src.loc(i + n), "Expected a comma or a closing brace.");
|
|
||||||
}
|
|
||||||
|
|
||||||
return ParseRes.res(new ObjectDestructorNode(loc, destructors), n);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -1,10 +1,10 @@
|
|||||||
package me.topchetoeu.jscript.compilation.values;
|
package me.topchetoeu.jscript.compilation.values;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.LinkedList;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Instruction;
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
|
import me.topchetoeu.jscript.common.Instruction.BreakpointType;
|
||||||
import me.topchetoeu.jscript.common.parsing.Location;
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||||
@ -12,79 +12,51 @@ import me.topchetoeu.jscript.common.parsing.Source;
|
|||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
import me.topchetoeu.jscript.compilation.CompoundNode;
|
import me.topchetoeu.jscript.compilation.CompoundNode;
|
||||||
import me.topchetoeu.jscript.compilation.FunctionNode;
|
import me.topchetoeu.jscript.compilation.FunctionNode;
|
||||||
import me.topchetoeu.jscript.compilation.FunctionValueNode;
|
|
||||||
import me.topchetoeu.jscript.compilation.JavaScript;
|
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||||
import me.topchetoeu.jscript.compilation.Node;
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
import me.topchetoeu.jscript.compilation.destructing.Destructor;
|
import me.topchetoeu.jscript.compilation.Parameters;
|
||||||
|
import me.topchetoeu.jscript.compilation.patterns.AssignTarget;
|
||||||
|
import me.topchetoeu.jscript.compilation.patterns.AssignTargetLike;
|
||||||
|
import me.topchetoeu.jscript.compilation.patterns.ObjectAssignable;
|
||||||
|
import me.topchetoeu.jscript.compilation.patterns.Pattern;
|
||||||
|
import me.topchetoeu.jscript.compilation.patterns.ObjectDestructor.Member;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.constants.NumberNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.constants.StringNode;
|
||||||
|
import me.topchetoeu.jscript.compilation.values.operations.AssignNode;
|
||||||
|
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
||||||
|
|
||||||
|
public class ObjectNode extends Node implements AssignTargetLike {
|
||||||
|
public static class PropertyMemberNode extends FunctionNode {
|
||||||
|
public final Node key;
|
||||||
|
public final Pattern argument;
|
||||||
|
|
||||||
public class ObjectNode extends Node implements Destructor {
|
@Override public String name() {
|
||||||
public static class ObjProp {
|
if (key instanceof StringNode str) {
|
||||||
public final String name;
|
if (isGetter()) return "get " + str.value;
|
||||||
public final String access;
|
else return "set " + str.value;
|
||||||
public final FunctionValueNode func;
|
|
||||||
|
|
||||||
public ObjProp(String name, String access, FunctionValueNode func) {
|
|
||||||
this.name = name;
|
|
||||||
this.access = access;
|
|
||||||
this.func = func;
|
|
||||||
}
|
}
|
||||||
|
else return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public final Map<String, Node> map;
|
public boolean isGetter() { return argument == null; }
|
||||||
public final Map<String, FunctionNode> getters;
|
public boolean isSetter() { return argument != null; }
|
||||||
public final Map<String, FunctionNode> setters;
|
|
||||||
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
|
||||||
target.add(Instruction.loadObj());
|
key.compile(target, true);
|
||||||
|
|
||||||
for (var el : map.entrySet()) {
|
var id = target.addChild(compileBody(target, name, null));
|
||||||
target.add(Instruction.dup());
|
target.add(_i -> Instruction.loadFunc(id, true, false, false, name, captures(id, target)));
|
||||||
var val = el.getValue();
|
|
||||||
FunctionNode.compileWithName(val, target, true, el.getKey().toString());
|
target.add(Instruction.defProp(isSetter()));
|
||||||
target.add(Instruction.storeMember(el.getKey()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var keys = new ArrayList<Object>();
|
public PropertyMemberNode(Location loc, Location end, Node key, Pattern argument, CompoundNode body) {
|
||||||
keys.addAll(getters.keySet());
|
super(loc, end, argument == null ? new Parameters(List.of()) : new Parameters(List.of(argument)), body);
|
||||||
keys.addAll(setters.keySet());
|
this.key = key;
|
||||||
|
this.argument = argument;
|
||||||
for (var key : keys) {
|
|
||||||
target.add(Instruction.pushValue((String)key));
|
|
||||||
|
|
||||||
if (getters.containsKey(key)) getters.get(key).compile(target, true);
|
|
||||||
else target.add(Instruction.pushUndefined());
|
|
||||||
|
|
||||||
if (setters.containsKey(key)) setters.get(key).compile(target, true);
|
|
||||||
else target.add(Instruction.pushUndefined());
|
|
||||||
|
|
||||||
target.add(Instruction.defProp());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pollute) target.add(Instruction.discard());
|
public static ParseRes<PropertyMemberNode> parse(Source src, int i) {
|
||||||
}
|
|
||||||
|
|
||||||
public ObjectNode(Location loc, Map<String, Node> map, Map<String, FunctionNode> getters, Map<String, FunctionNode> setters) {
|
|
||||||
super(loc);
|
|
||||||
this.map = map;
|
|
||||||
this.getters = getters;
|
|
||||||
this.setters = setters;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static ParseRes<String> parsePropName(Source src, int i) {
|
|
||||||
var n = Parsing.skipEmpty(src, i);
|
|
||||||
|
|
||||||
var res = ParseRes.first(src, i + n,
|
|
||||||
Parsing::parseIdentifier,
|
|
||||||
Parsing::parseString,
|
|
||||||
(s, j) -> Parsing.parseNumber(s, j, false)
|
|
||||||
);
|
|
||||||
n += res.n;
|
|
||||||
|
|
||||||
if (!res.isSuccess()) return res.chainError();
|
|
||||||
return ParseRes.res(res.result.toString(), n);
|
|
||||||
}
|
|
||||||
private static ParseRes<ObjectNode.ObjProp> parseObjectProp(Source src, int i) {
|
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
@ -97,7 +69,55 @@ public class ObjectNode extends Node implements Destructor {
|
|||||||
if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected a property name after '" + access + "'");
|
if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected a property name after '" + access + "'");
|
||||||
n += name.n;
|
n += name.n;
|
||||||
|
|
||||||
var params = JavaScript.parseParameters(src, i + n);
|
var params = Parameters.parseParameters(src, i + n);
|
||||||
|
if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected an argument list");
|
||||||
|
if (access.result.equals("get") && params.result.params.size() != 0) return ParseRes.error(src.loc(i + n), "Getter must not have any parameters");
|
||||||
|
if (access.result.equals("set") && params.result.params.size() != 1) return ParseRes.error(src.loc(i + n), "Setter must have exactly one parameter");
|
||||||
|
if (params.result.rest != null) return ParseRes.error(params.result.rest.loc(), "Property members may not have rest arguments");
|
||||||
|
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 property accessor.");
|
||||||
|
n += body.n;
|
||||||
|
|
||||||
|
var end = src.loc(i + n - 1);
|
||||||
|
|
||||||
|
return ParseRes.res(new PropertyMemberNode(
|
||||||
|
loc, end, name.result, access.result.equals("get") ? null : params.result.params.get(0), body.result
|
||||||
|
), n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static class MethodMemberNode extends FunctionNode {
|
||||||
|
public final Node key;
|
||||||
|
|
||||||
|
@Override public String name() {
|
||||||
|
if (key instanceof StringNode str) return str.value;
|
||||||
|
else return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) {
|
||||||
|
key.compile(target, true);
|
||||||
|
|
||||||
|
var id = target.addChild(compileBody(target, name, null));
|
||||||
|
target.add(_i -> Instruction.loadFunc(id, true, false, false, name, captures(id, target)));
|
||||||
|
|
||||||
|
target.add(Instruction.defField());
|
||||||
|
}
|
||||||
|
|
||||||
|
public MethodMemberNode(Location loc, Location end, Node key, Parameters params, CompoundNode body) {
|
||||||
|
super(loc, end, params, body);
|
||||||
|
this.key = key;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<MethodMemberNode> parse(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
|
var name = parsePropName(src, i + n);
|
||||||
|
if (!name.isSuccess()) return name.chainError();
|
||||||
|
n += name.n;
|
||||||
|
|
||||||
|
var params = Parameters.parseParameters(src, i + n);
|
||||||
if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected an argument list");
|
if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected an argument list");
|
||||||
n += params.n;
|
n += params.n;
|
||||||
|
|
||||||
@ -107,11 +127,194 @@ public class ObjectNode extends Node implements Destructor {
|
|||||||
|
|
||||||
var end = src.loc(i + n - 1);
|
var end = src.loc(i + n - 1);
|
||||||
|
|
||||||
return ParseRes.res(new ObjProp(
|
return ParseRes.res(new MethodMemberNode(
|
||||||
name.result, access.result,
|
loc, end, name.result, params.result, body.result
|
||||||
new FunctionValueNode(loc, end, params.result, body.result, access + " " + name.result.toString())
|
|
||||||
), n);
|
), n);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
public static class FieldMemberNode extends Node {
|
||||||
|
public final Node key;
|
||||||
|
public final Node value;
|
||||||
|
|
||||||
|
@Override public void compile(CompileResult target, boolean pollute, BreakpointType bp) {
|
||||||
|
key.compile(target, true);
|
||||||
|
|
||||||
|
if (value == null) target.add(Instruction.pushUndefined());
|
||||||
|
else value.compile(target, true);
|
||||||
|
|
||||||
|
target.add(Instruction.defField());
|
||||||
|
}
|
||||||
|
|
||||||
|
public FieldMemberNode(Location loc, Node key, Node value) {
|
||||||
|
super(loc);
|
||||||
|
this.key = key;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<FieldMemberNode> parseObject(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
|
var name = parsePropName(src, i + n);
|
||||||
|
if (!name.isSuccess()) return name.chainError();
|
||||||
|
n += name.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (!src.is(i + n, ":")) return ParseRes.failed();
|
||||||
|
n++;
|
||||||
|
|
||||||
|
var value = JavaScript.parseExpression(src, i + n, 2);
|
||||||
|
if (!value.isSuccess()) return value.chainError(src.loc(i + n), "Expected a value");
|
||||||
|
n += value.n;
|
||||||
|
|
||||||
|
return ParseRes.res(new FieldMemberNode(loc, name.result, value.result), n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<FieldMemberNode> parseShorthand(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
|
var var = VariableNode.parse(src, i + n);
|
||||||
|
if (!var.isSuccess()) return var.chainError();
|
||||||
|
n += var.n;
|
||||||
|
|
||||||
|
if (!src.is(i + n, "=")) return ParseRes.res(new FieldMemberNode(loc, new StringNode(loc, var.result.name), var.result), n);
|
||||||
|
var equalsLoc = src.loc(i + n);
|
||||||
|
n++;
|
||||||
|
|
||||||
|
var value = JavaScript.parseExpression(src, i + n, 2);
|
||||||
|
if (!value.isSuccess()) return value.chainError(src.loc(i + n), "Expected a shorthand initializer");
|
||||||
|
n += value.n;
|
||||||
|
|
||||||
|
return ParseRes.res(new FieldMemberNode(loc, new StringNode(loc, var.result.name), new AssignNode(equalsLoc, var.result, value.result)), n);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ParseRes<FieldMemberNode> parseClass(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
|
var name = parsePropName(src, i + n);
|
||||||
|
if (!name.isSuccess()) return name.chainError();
|
||||||
|
n += name.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (!src.is(i + n, "=")) {
|
||||||
|
var end = JavaScript.parseStatement(src, i + n);
|
||||||
|
if (!end.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected an end of statement or a field initializer");
|
||||||
|
n += end.n;
|
||||||
|
|
||||||
|
return ParseRes.res(new FieldMemberNode(loc, name.result, null), n);
|
||||||
|
}
|
||||||
|
n++;
|
||||||
|
|
||||||
|
var value = JavaScript.parseExpression(src, i + n, 2);
|
||||||
|
if (!value.isSuccess()) return value.chainError(src.loc(i + n), "Expected a value");
|
||||||
|
n += value.n;
|
||||||
|
|
||||||
|
return ParseRes.res(new FieldMemberNode(loc, name.result, value.result), n);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public final List<Node> members;
|
||||||
|
|
||||||
|
// private void compileRestObjBuilder(CompileResult target, int srcDupN) {
|
||||||
|
// var subtarget = target.subtarget();
|
||||||
|
// var src = subtarget.scope.defineTemp();
|
||||||
|
// var dst = subtarget.scope.defineTemp();
|
||||||
|
|
||||||
|
// target.add(Instruction.loadObj());
|
||||||
|
// target.add(_i -> src.index().toSet(true));
|
||||||
|
// target.add(_i -> dst.index().toSet(destructors.size() > 0));
|
||||||
|
|
||||||
|
// target.add(Instruction.keys(true, true));
|
||||||
|
// var start = target.size();
|
||||||
|
|
||||||
|
// target.add(Instruction.dup());
|
||||||
|
// var mid = target.temp();
|
||||||
|
|
||||||
|
// target.add(_i -> src.index().toGet());
|
||||||
|
// target.add(Instruction.dup(1, 1));
|
||||||
|
// target.add(Instruction.loadMember());
|
||||||
|
|
||||||
|
// target.add(_i -> dst.index().toGet());
|
||||||
|
// target.add(Instruction.dup(1, 1));
|
||||||
|
// target.add(Instruction.storeMember());
|
||||||
|
|
||||||
|
// target.add(Instruction.discard());
|
||||||
|
// var end = target.size();
|
||||||
|
// target.add(Instruction.jmp(start - end));
|
||||||
|
// target.set(mid, Instruction.jmpIfNot(end - mid + 1));
|
||||||
|
|
||||||
|
// target.add(Instruction.discard());
|
||||||
|
|
||||||
|
// target.add(Instruction.dup(srcDupN, 1));
|
||||||
|
|
||||||
|
// target.scope.end();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// @Override public void destruct(CompileResult target, DeclarationType decl) {
|
||||||
|
// if (getters.size() > 0) throw new SyntaxException(getters.values().iterator().next().loc(), "Unexpected getter in destructor");
|
||||||
|
// if (setters.size() > 0) throw new SyntaxException(setters.values().iterator().next().loc(), "Unexpected setter in destructor");
|
||||||
|
// }
|
||||||
|
|
||||||
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
|
target.add(Instruction.loadObj());
|
||||||
|
|
||||||
|
for (var el : members) {
|
||||||
|
target.add(Instruction.dup());
|
||||||
|
el.compile(target, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public AssignTarget toAssignTarget() {
|
||||||
|
var newMembers = new LinkedList<Member<AssignTarget>>();
|
||||||
|
|
||||||
|
for (var el : members) {
|
||||||
|
if (el instanceof FieldMemberNode field) {
|
||||||
|
if (field.value instanceof AssignTargetLike target) newMembers.add(new Member<>(field.key, target.toAssignTarget()));
|
||||||
|
else throw new SyntaxException(field.value.loc(), "Expected an assignable in deconstructor");
|
||||||
|
}
|
||||||
|
else throw new SyntaxException(el.loc(), "Unexpected member in deconstructor");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ObjectAssignable(loc(), newMembers);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ObjectNode(Location loc, List<Node> map) {
|
||||||
|
super(loc);
|
||||||
|
this.members = map;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ParseRes<Node> parseComputePropName(Source src, int i) {
|
||||||
|
var n = Parsing.skipEmpty(src, i);
|
||||||
|
if (!src.is(i + n, "[")) return ParseRes.failed();
|
||||||
|
n++;
|
||||||
|
|
||||||
|
var val = JavaScript.parseExpression(src, i, 0);
|
||||||
|
if (!val.isSuccess()) return val.chainError(src.loc(i + n), "Expected an expression in compute property");
|
||||||
|
n += val.n;
|
||||||
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
|
if (!src.is(i + n, "]")) return ParseRes.error(src.loc(i + n), "Expected a closing bracket after compute property");
|
||||||
|
n++;
|
||||||
|
|
||||||
|
return ParseRes.res(val.result, n);
|
||||||
|
}
|
||||||
|
public static ParseRes<Node> parsePropName(Source src, int i) {
|
||||||
|
return ParseRes.first(src, i,
|
||||||
|
(s, j) -> {
|
||||||
|
var m = Parsing.skipEmpty(s, j);
|
||||||
|
var l = s.loc(j + m);
|
||||||
|
|
||||||
|
var r = Parsing.parseIdentifier(s, j + m);
|
||||||
|
if (r.isSuccess()) return ParseRes.res(new StringNode(l, r.result), r.n);
|
||||||
|
else return r.chainError();
|
||||||
|
},
|
||||||
|
StringNode::parse,
|
||||||
|
NumberNode::parse,
|
||||||
|
ObjectNode::parseComputePropName
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public static ParseRes<ObjectNode> parse(Source src, int i) {
|
public static ParseRes<ObjectNode> parse(Source src, int i) {
|
||||||
var n = Parsing.skipEmpty(src, i);
|
var n = Parsing.skipEmpty(src, i);
|
||||||
@ -121,39 +324,23 @@ public class ObjectNode extends Node implements Destructor {
|
|||||||
n++;
|
n++;
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
|
||||||
var values = new LinkedHashMap<String, Node>();
|
var members = new LinkedList<Node>();
|
||||||
var getters = new LinkedHashMap<String, FunctionNode>();
|
|
||||||
var setters = new LinkedHashMap<String, FunctionNode>();
|
|
||||||
|
|
||||||
if (src.is(i + n, "}")) {
|
if (src.is(i + n, "}")) {
|
||||||
n++;
|
n++;
|
||||||
return ParseRes.res(new ObjectNode(loc, values, getters, setters), n);
|
return ParseRes.res(new ObjectNode(loc, members), n);
|
||||||
}
|
}
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
var prop = parseObjectProp(src, i + n);
|
ParseRes<Node> prop = ParseRes.first(src, i + n,
|
||||||
|
MethodMemberNode::parse,
|
||||||
if (prop.isSuccess()) {
|
PropertyMemberNode::parse,
|
||||||
|
FieldMemberNode::parseObject
|
||||||
|
);
|
||||||
|
if (!prop.isSuccess()) return prop.chainError(src.loc(i + n), "Expected a member in object literal");
|
||||||
n += prop.n;
|
n += prop.n;
|
||||||
|
|
||||||
if (prop.result.access.equals("set")) setters.put(prop.result.name, prop.result.func);
|
members.add(prop.result);
|
||||||
else getters.put(prop.result.name, prop.result.func);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
var name = parsePropName(src, i + n);
|
|
||||||
if (!name.isSuccess()) return prop.chainError(src.loc(i + n), "Expected a field name");
|
|
||||||
n += name.n;
|
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
|
||||||
|
|
||||||
if (!src.is(i + n, ":")) return ParseRes.error(src.loc(i + n), "Expected a colon");
|
|
||||||
n++;
|
|
||||||
|
|
||||||
var valRes = JavaScript.parseExpression(src, i + n, 2);
|
|
||||||
if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after property key");
|
|
||||||
n += valRes.n;
|
|
||||||
|
|
||||||
values.put(name.result, valRes.result);
|
|
||||||
}
|
|
||||||
|
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
if (src.is(i + n, ",")) {
|
if (src.is(i + n, ",")) {
|
||||||
@ -174,7 +361,6 @@ public class ObjectNode extends Node implements Destructor {
|
|||||||
else ParseRes.error(src.loc(i + n), "Expected a comma or a closing brace.");
|
else ParseRes.error(src.loc(i + n), "Expected a comma or a closing brace.");
|
||||||
}
|
}
|
||||||
|
|
||||||
return ParseRes.res(new ObjectNode(loc, values, getters, setters), n);
|
return ParseRes.res(new ObjectNode(loc, members), n);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,53 +1,73 @@
|
|||||||
package me.topchetoeu.jscript.compilation.values;
|
package me.topchetoeu.jscript.compilation.values;
|
||||||
|
|
||||||
import java.util.function.IntFunction;
|
import java.util.function.IntFunction;
|
||||||
import java.util.function.Supplier;
|
|
||||||
|
|
||||||
import me.topchetoeu.jscript.common.Instruction;
|
import me.topchetoeu.jscript.common.Instruction;
|
||||||
import me.topchetoeu.jscript.common.parsing.Location;
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||||
import me.topchetoeu.jscript.common.parsing.Source;
|
import me.topchetoeu.jscript.common.parsing.Source;
|
||||||
import me.topchetoeu.jscript.compilation.AssignableNode;
|
|
||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
import me.topchetoeu.jscript.compilation.JavaScript;
|
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||||
import me.topchetoeu.jscript.compilation.Node;
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
|
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
|
||||||
|
import me.topchetoeu.jscript.compilation.patterns.ChangeTarget;
|
||||||
|
import me.topchetoeu.jscript.compilation.patterns.Pattern;
|
||||||
import me.topchetoeu.jscript.compilation.scope.Variable;
|
import me.topchetoeu.jscript.compilation.scope.Variable;
|
||||||
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
||||||
|
|
||||||
public class VariableNode extends Node implements AssignTarget {
|
public class VariableNode extends Node implements Pattern, ChangeTarget {
|
||||||
public final String name;
|
public final String name;
|
||||||
|
|
||||||
@Override public String assignName() { return name; }
|
public String assignName() { return name; }
|
||||||
|
|
||||||
@Override public void compileBeforeAssign(CompileResult target, boolean operator) {
|
// @Override public void compileBeforeAssign(CompileResult target, boolean operator) {
|
||||||
if (operator) {
|
// if (operator) {
|
||||||
|
// target.add(VariableNode.toGet(target, loc(), name));
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// @Override public void compileAfterAssign(CompileResult target, boolean operator, boolean pollute) {
|
||||||
|
// target.add(VariableNode.toSet(target, loc(), name, pollute, false));
|
||||||
|
// }
|
||||||
|
|
||||||
|
@Override public void beforeChange(CompileResult target) {
|
||||||
target.add(VariableNode.toGet(target, loc(), name));
|
target.add(VariableNode.toGet(target, loc(), name));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
@Override public void compileAfterAssign(CompileResult target, boolean operator, boolean pollute) {
|
|
||||||
target.add(VariableNode.toSet(target, loc(), name, pollute, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public void destructArg(CompileResult target) {
|
// @Override public void destructArg(CompileResult target) {
|
||||||
target.add(_i -> target.scope.define(new Variable(name, false), loc()).index().toSet(false));
|
// target.add(_i -> target.scope.define(new Variable(name, false), loc()).index().toSet(false));
|
||||||
}
|
// }
|
||||||
@Override public void destructDeclResolve(CompileResult target) {
|
@Override public void destructDeclResolve(CompileResult target) {
|
||||||
target.scope.define(new Variable(name, false), loc());
|
target.scope.define(new Variable(name, false), loc());
|
||||||
}
|
}
|
||||||
@Override public void afterAssign(CompileResult target, DeclarationType decl) {
|
|
||||||
if (decl.strict) {
|
@Override public void afterAssign(CompileResult target, boolean pollute) {
|
||||||
var v = target.scope.defineStrict(new Variable(name, decl.readonly), loc());
|
target.add(VariableNode.toSet(target, loc(), name, pollute, false));
|
||||||
target.add(_i -> v.index().toSet(false));
|
}
|
||||||
|
|
||||||
|
@Override public void declare(CompileResult target, DeclarationType decl) {
|
||||||
|
if (decl != null) {
|
||||||
|
if (decl.strict) target.scope.defineStrict(new Variable(name, decl.readonly), loc());
|
||||||
|
else target.scope.define(new Variable(name, decl.readonly), loc());
|
||||||
|
}
|
||||||
|
else target.add(_i -> {
|
||||||
|
var i = target.scope.get(name, false);
|
||||||
|
|
||||||
|
if (i == null) return Instruction.globDef(name);
|
||||||
|
else return Instruction.nop();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void destruct(CompileResult target, DeclarationType decl, boolean shouldDeclare) {
|
||||||
|
if (!shouldDeclare || decl == null) {
|
||||||
|
target.add(VariableNode.toSet(target, loc(), name, false, shouldDeclare));
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
target.add(VariableNode.toSet(target, loc(), name, false, true));
|
if (decl == DeclarationType.VAR && target.scope.has(name, false)) throw new SyntaxException(loc(), "Duplicate parameter name not allowed");
|
||||||
|
var v = target.scope.define(decl, name, loc());
|
||||||
|
target.add(_i -> v.index().toSet(false));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@Override public void destructAssign(CompileResult target, boolean pollute) {
|
|
||||||
target.add(VariableNode.toSet(target, loc(), name, pollute, false));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
var i = target.scope.get(name, false);
|
var i = target.scope.get(name, false);
|
||||||
@ -55,7 +75,7 @@ public class VariableNode extends Node implements AssignTarget {
|
|||||||
if (i == null) {
|
if (i == null) {
|
||||||
target.add(_i -> {
|
target.add(_i -> {
|
||||||
if (target.scope.has(name, false)) return Instruction.throwSyntax(loc(), String.format("Cannot access '%s' before initialization", name));
|
if (target.scope.has(name, false)) return Instruction.throwSyntax(loc(), String.format("Cannot access '%s' before initialization", name));
|
||||||
return Instruction.globGet(name);
|
return Instruction.globGet(name, false);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!pollute) target.add(Instruction.discard());
|
if (!pollute) target.add(Instruction.discard());
|
||||||
@ -63,17 +83,17 @@ public class VariableNode extends Node implements AssignTarget {
|
|||||||
else if (pollute) target.add(_i -> i.index().toGet());
|
else if (pollute) target.add(_i -> i.index().toGet());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IntFunction<Instruction> toGet(CompileResult target, Location loc, String name, Supplier<Instruction> onGlobal) {
|
public static IntFunction<Instruction> toGet(CompileResult target, Location loc, String name, boolean forceGet) {
|
||||||
var i = target.scope.get(name, false);
|
var i = target.scope.get(name, false);
|
||||||
|
|
||||||
if (i == null) return _i -> {
|
if (i == null) return _i -> {
|
||||||
if (target.scope.has(name, false)) return Instruction.throwSyntax(loc, String.format("Cannot access '%s' before initialization", name));
|
if (target.scope.has(name, false)) return Instruction.throwSyntax(loc, String.format("Cannot access '%s' before initialization", name));
|
||||||
else return onGlobal.get();
|
else return Instruction.globGet(name, forceGet);
|
||||||
};
|
};
|
||||||
else return _i -> i.index().toGet();
|
else return _i -> i.index().toGet();
|
||||||
}
|
}
|
||||||
public static IntFunction<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));
|
return toGet(target, loc, name, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -5,45 +5,26 @@ import me.topchetoeu.jscript.common.Operation;
|
|||||||
import me.topchetoeu.jscript.common.parsing.Location;
|
import me.topchetoeu.jscript.common.parsing.Location;
|
||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
import me.topchetoeu.jscript.compilation.Node;
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
|
import me.topchetoeu.jscript.compilation.patterns.AssignTarget;
|
||||||
import me.topchetoeu.jscript.compilation.destructing.AssignTarget;
|
|
||||||
import me.topchetoeu.jscript.compilation.destructing.Destructor;
|
|
||||||
import me.topchetoeu.jscript.compilation.scope.Variable;
|
|
||||||
import me.topchetoeu.jscript.compilation.values.VariableNode;
|
|
||||||
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
||||||
|
|
||||||
public class AssignNode extends Node implements Destructor {
|
public class AssignNode extends Node implements AssignTarget {
|
||||||
public final AssignTarget assignable;
|
public final AssignTarget assignable;
|
||||||
public final Node value;
|
public final Node value;
|
||||||
|
|
||||||
@Override public void destructDeclResolve(CompileResult target) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
if (!(assignable instanceof VariableNode var)) {
|
if (assignable instanceof AssignNode other) throw new SyntaxException(other.loc(), "Assign deconstructor not allowed here");
|
||||||
throw new SyntaxException(loc(), "Assign target in declaration destructor must be a variable");
|
|
||||||
|
assignable.beforeAssign(target);
|
||||||
|
value.compile(target, true);
|
||||||
|
assignable.afterAssign(target, pollute);
|
||||||
}
|
}
|
||||||
|
|
||||||
target.scope.define(new Variable(var.name, false), var.loc());
|
@Override public void afterAssign(CompileResult target, boolean pollute) {
|
||||||
}
|
if (assignable instanceof AssignNode other) throw new SyntaxException(other.loc(), "Double assign deconstructor not allowed");
|
||||||
|
|
||||||
@Override public void destructArg(CompileResult target) {
|
if (pollute) target.add(Instruction.dup(2, 0));
|
||||||
if (!(assignable instanceof VariableNode var)) {
|
else target.add(Instruction.dup());
|
||||||
throw new SyntaxException(loc(), "Assign target in declaration destructor must be a variable");
|
|
||||||
}
|
|
||||||
|
|
||||||
var v = target.scope.define(new Variable(var.name, false), var.loc());
|
|
||||||
afterAssign(target, null, false);
|
|
||||||
target.add(_i -> v.index().toSet(false));
|
|
||||||
}
|
|
||||||
@Override public void afterAssign(CompileResult target, DeclarationType decl, boolean pollute) {
|
|
||||||
if (decl != null && decl.strict) {
|
|
||||||
if (!(assignable instanceof VariableNode var)) {
|
|
||||||
throw new SyntaxException(loc(), "Assign target in declaration destructor must be a variable");
|
|
||||||
}
|
|
||||||
target.scope.define(new Variable(var.name, decl.strict), var.loc());
|
|
||||||
}
|
|
||||||
|
|
||||||
assignable.beforeAssign(target, decl);
|
|
||||||
|
|
||||||
target.add(Instruction.dup());
|
|
||||||
target.add(Instruction.pushUndefined());
|
target.add(Instruction.pushUndefined());
|
||||||
target.add(Instruction.operation(Operation.EQUALS));
|
target.add(Instruction.operation(Operation.EQUALS));
|
||||||
var start = target.temp();
|
var start = target.temp();
|
||||||
@ -51,15 +32,10 @@ public class AssignNode extends Node implements Destructor {
|
|||||||
|
|
||||||
value.compile(target, true);
|
value.compile(target, true);
|
||||||
|
|
||||||
target.set(start, Instruction.jmp(target.size() - start));
|
target.set(start, Instruction.jmpIfNot(target.size() - start));
|
||||||
|
|
||||||
assignable.afterAssign(target, decl, pollute);
|
assignable.assign(target, false);
|
||||||
}
|
if (!pollute) target.add(Instruction.discard());
|
||||||
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
|
||||||
assignable.beforeAssign(target, null);
|
|
||||||
value.compile(target, true);
|
|
||||||
assignable.afterAssign(target, null, pollute);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public AssignNode(Location loc, AssignTarget assignable, Node value) {
|
public AssignNode(Location loc, AssignTarget assignable, Node value) {
|
||||||
|
@ -38,7 +38,7 @@ public class CallNode extends Node {
|
|||||||
|
|
||||||
shouldParen = true;
|
shouldParen = true;
|
||||||
|
|
||||||
if (obj.getters.size() > 0 || obj.setters.size() > 0 || obj.map.size() > 0) res = "{}";
|
if (obj.members.size() > 0) res = "{}";
|
||||||
else res = "{(intermediate value)}";
|
else res = "{(intermediate value)}";
|
||||||
}
|
}
|
||||||
else if (func instanceof StringNode) {
|
else if (func instanceof StringNode) {
|
||||||
|
@ -9,24 +9,24 @@ import me.topchetoeu.jscript.common.parsing.Source;
|
|||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
import me.topchetoeu.jscript.compilation.JavaScript;
|
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||||
import me.topchetoeu.jscript.compilation.Node;
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
import me.topchetoeu.jscript.compilation.destructing.ChangeTarget;
|
import me.topchetoeu.jscript.compilation.patterns.ChangeTarget;
|
||||||
import me.topchetoeu.jscript.compilation.values.constants.NumberNode;
|
import me.topchetoeu.jscript.compilation.values.constants.NumberNode;
|
||||||
|
|
||||||
public class ChangeNode extends Node {
|
public class ChangeNode extends Node {
|
||||||
public final ChangeTarget assignable;
|
public final ChangeTarget changable;
|
||||||
public final Node value;
|
public final Node value;
|
||||||
public final Operation op;
|
public final Operation op;
|
||||||
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
assignable.beforeChange(target);
|
changable.beforeChange(target);
|
||||||
value.compile(target, true);
|
value.compile(target, true);
|
||||||
target.add(Instruction.operation(op));
|
target.add(Instruction.operation(op));
|
||||||
assignable.afterChange(target, pollute);
|
changable.afterAssign(target, pollute);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ChangeNode(Location loc, ChangeTarget assignable, Node value, Operation op) {
|
public ChangeNode(Location loc, ChangeTarget changable, Node value, Operation op) {
|
||||||
super(loc);
|
super(loc);
|
||||||
this.assignable = assignable;
|
this.changable = changable;
|
||||||
this.value = value;
|
this.value = value;
|
||||||
this.op = op;
|
this.op = op;
|
||||||
}
|
}
|
||||||
|
@ -6,39 +6,21 @@ import me.topchetoeu.jscript.common.parsing.Location;
|
|||||||
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||||
import me.topchetoeu.jscript.common.parsing.Source;
|
import me.topchetoeu.jscript.common.parsing.Source;
|
||||||
import me.topchetoeu.jscript.compilation.AssignableNode;
|
|
||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
import me.topchetoeu.jscript.compilation.JavaScript;
|
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||||
import me.topchetoeu.jscript.compilation.Node;
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType;
|
import me.topchetoeu.jscript.compilation.patterns.ChangeTarget;
|
||||||
import me.topchetoeu.jscript.compilation.destructing.AssignTarget;
|
|
||||||
import me.topchetoeu.jscript.compilation.destructing.ChangeTarget;
|
|
||||||
import me.topchetoeu.jscript.compilation.values.constants.NumberNode;
|
import me.topchetoeu.jscript.compilation.values.constants.NumberNode;
|
||||||
import me.topchetoeu.jscript.compilation.values.constants.StringNode;
|
import me.topchetoeu.jscript.compilation.values.constants.StringNode;
|
||||||
import me.topchetoeu.jscript.runtime.exceptions.SyntaxException;
|
|
||||||
|
|
||||||
public class IndexNode extends Node implements ChangeTarget {
|
public class IndexNode extends Node implements ChangeTarget {
|
||||||
public final Node object;
|
public final Node object;
|
||||||
public final Node index;
|
public final Node index;
|
||||||
|
|
||||||
@Override public void destructDeclResolve(CompileResult target) {
|
@Override public void beforeAssign(CompileResult target) {
|
||||||
throw new SyntaxException(loc(), "Unexpected index in destructor");
|
|
||||||
}
|
|
||||||
@Override public void destructArg(CompileResult target) {
|
|
||||||
throw new SyntaxException(loc(), "Unexpected index in destructor");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public void beforeAssign(CompileResult target, DeclarationType decl) {
|
|
||||||
if (decl != null) throw new SyntaxException(loc(), "Unexpected index in destructor");
|
|
||||||
object.compile(target, true);
|
object.compile(target, true);
|
||||||
|
|
||||||
if (index instanceof NumberNode num && (int)num.value == num.value) return;
|
indexStorePushKey(target, index);
|
||||||
if (index instanceof StringNode) return;
|
|
||||||
index.compile(target, true);
|
|
||||||
|
|
||||||
target.add(Instruction.dup(1, 1));
|
|
||||||
target.add(Instruction.dup(1, 1));
|
|
||||||
target.add(Instruction.loadMember());
|
|
||||||
}
|
}
|
||||||
@Override public void beforeChange(CompileResult target) {
|
@Override public void beforeChange(CompileResult target) {
|
||||||
object.compile(target, true);
|
object.compile(target, true);
|
||||||
@ -59,26 +41,16 @@ public class IndexNode extends Node implements ChangeTarget {
|
|||||||
target.add(Instruction.loadMember());
|
target.add(Instruction.loadMember());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@Override public void afterAssign(CompileResult target, boolean op, boolean pollute) {
|
|
||||||
if (index instanceof NumberNode num && (int)num.value == num.value) {
|
|
||||||
target.add(Instruction.storeMember((int)num.value, pollute));
|
|
||||||
}
|
|
||||||
else if (index instanceof StringNode str) {
|
|
||||||
target.add(Instruction.storeMember(str.value, pollute));
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
target.add(Instruction.storeMember(pollute));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override public void afterAssign(CompileResult target, DeclarationType decl) {
|
@Override public void assign(CompileResult target, boolean pollute) {
|
||||||
throw new SyntaxException(loc(), "Illegal index in declaration destruction context");
|
|
||||||
}
|
|
||||||
@Override public void destructAssign(CompileResult target, boolean pollute) {
|
|
||||||
object.compile(target, true);
|
object.compile(target, true);
|
||||||
target.add(Instruction.dup(1, 1));
|
target.add(Instruction.dup(1, 1));
|
||||||
compileAfterAssign(target, false, false);
|
indexStorePushKey(target, index);
|
||||||
if (!pollute) target.add(Instruction.discard());
|
indexStore(target, index, pollute);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override public void afterAssign(CompileResult target, boolean pollute) {
|
||||||
|
indexStore(target, index, pollute);
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Override public Node toAssign(Node val, Operation operation) {
|
// @Override public Node toAssign(Node val, Operation operation) {
|
||||||
@ -147,4 +119,35 @@ public class IndexNode extends Node implements ChangeTarget {
|
|||||||
|
|
||||||
return ParseRes.res(new IndexNode(loc, prev, new StringNode(loc, literal.result)), n);
|
return ParseRes.res(new IndexNode(loc, prev, new StringNode(loc, literal.result)), n);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void indexStorePushKey(CompileResult target, Node index) {
|
||||||
|
if (index instanceof NumberNode num && (int)num.value == num.value) return;
|
||||||
|
if (index instanceof StringNode) return;
|
||||||
|
index.compile(target, true);
|
||||||
|
}
|
||||||
|
public static void indexStore(CompileResult target, Node index, boolean pollute) {
|
||||||
|
if (index instanceof NumberNode num && (int)num.value == num.value) {
|
||||||
|
target.add(Instruction.storeMember((int)num.value, pollute));
|
||||||
|
}
|
||||||
|
else if (index instanceof StringNode str) {
|
||||||
|
target.add(Instruction.storeMember(str.value, pollute));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
target.add(Instruction.storeMember(pollute));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public static void indexLoad(CompileResult target, Node index, boolean pollute) {
|
||||||
|
if (index instanceof NumberNode num && (int)num.value == num.value) {
|
||||||
|
target.add(Instruction.loadMember((int)num.value));
|
||||||
|
}
|
||||||
|
else if (index instanceof StringNode str) {
|
||||||
|
target.add(Instruction.loadMember(str.value));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
index.compile(target, true);
|
||||||
|
target.add(Instruction.loadMember());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pollute) target.add(Instruction.discard());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,10 +11,11 @@ import me.topchetoeu.jscript.common.parsing.Location;
|
|||||||
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||||
import me.topchetoeu.jscript.common.parsing.Source;
|
import me.topchetoeu.jscript.common.parsing.Source;
|
||||||
import me.topchetoeu.jscript.compilation.AssignableNode;
|
|
||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
import me.topchetoeu.jscript.compilation.JavaScript;
|
import me.topchetoeu.jscript.compilation.JavaScript;
|
||||||
import me.topchetoeu.jscript.compilation.Node;
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
import me.topchetoeu.jscript.compilation.patterns.AssignTargetLike;
|
||||||
|
import me.topchetoeu.jscript.compilation.patterns.ChangeTarget;
|
||||||
|
|
||||||
public class OperationNode extends Node {
|
public class OperationNode extends Node {
|
||||||
private static interface OperatorFactory {
|
private static interface OperatorFactory {
|
||||||
@ -54,13 +55,22 @@ public class OperationNode extends Node {
|
|||||||
@Override public ParseRes<Node> construct(Source src, int i, Node prev) {
|
@Override public ParseRes<Node> construct(Source src, int i, Node prev) {
|
||||||
var loc = src.loc(i);
|
var loc = src.loc(i);
|
||||||
|
|
||||||
if (!(prev instanceof AssignTarget)) return ParseRes.error(loc, String.format("Expected an assignable expression before '%s'", token));
|
if (operation == null) {
|
||||||
|
if (!(prev instanceof AssignTargetLike target)) return ParseRes.error(loc, String.format("Expected an assignable expression before '%s'", token));
|
||||||
|
|
||||||
var other = JavaScript.parseExpression(src, i, precedence);
|
var other = JavaScript.parseExpression(src, i, precedence);
|
||||||
if (!other.isSuccess()) return other.chainError(src.loc(i + other.n), String.format("Expected a value after '%s'", token));
|
if (!other.isSuccess()) return other.chainError(src.loc(i + other.n), String.format("Expected a value after '%s'", token));
|
||||||
|
|
||||||
if (operation == null) return ParseRes.res(new AssignNode(loc, ((AssignTarget)prev), other.result), other.n);
|
return ParseRes.res(new AssignNode(loc, target.toAssignTarget(), other.result), other.n);
|
||||||
else return ParseRes.res(new ChangeNode(loc, ((AssignTarget)prev), other.result, operation), other.n);
|
}
|
||||||
|
else {
|
||||||
|
if (!(prev instanceof ChangeTarget target)) return ParseRes.error(loc, String.format("Expected a changeable expression before '%s'", token));
|
||||||
|
|
||||||
|
var other = JavaScript.parseExpression(src, i, precedence);
|
||||||
|
if (!other.isSuccess()) return other.chainError(src.loc(i + other.n), String.format("Expected a value after '%s'", token));
|
||||||
|
|
||||||
|
return ParseRes.res(new ChangeNode(loc, target, other.result, operation), other.n);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public AssignmentOperatorFactory(String token, int precedence, Operation operation) {
|
public AssignmentOperatorFactory(String token, int precedence, Operation operation) {
|
||||||
@ -209,7 +219,7 @@ public class OperationNode extends Node {
|
|||||||
var factory = factories.get(token);
|
var factory = factories.get(token);
|
||||||
|
|
||||||
if (!src.is(i + n, token)) continue;
|
if (!src.is(i + n, token)) continue;
|
||||||
if (factory.precedence() < precedence) ParseRes.failed();
|
if (factory.precedence() < precedence) return ParseRes.failed();
|
||||||
|
|
||||||
n += token.length();
|
n += token.length();
|
||||||
n += Parsing.skipEmpty(src, i + n);
|
n += Parsing.skipEmpty(src, i + n);
|
||||||
|
@ -6,9 +6,9 @@ import me.topchetoeu.jscript.common.parsing.Location;
|
|||||||
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
import me.topchetoeu.jscript.common.parsing.ParseRes;
|
||||||
import me.topchetoeu.jscript.common.parsing.Parsing;
|
import me.topchetoeu.jscript.common.parsing.Parsing;
|
||||||
import me.topchetoeu.jscript.common.parsing.Source;
|
import me.topchetoeu.jscript.common.parsing.Source;
|
||||||
import me.topchetoeu.jscript.compilation.AssignableNode;
|
|
||||||
import me.topchetoeu.jscript.compilation.CompileResult;
|
import me.topchetoeu.jscript.compilation.CompileResult;
|
||||||
import me.topchetoeu.jscript.compilation.Node;
|
import me.topchetoeu.jscript.compilation.Node;
|
||||||
|
import me.topchetoeu.jscript.compilation.patterns.ChangeTarget;
|
||||||
import me.topchetoeu.jscript.compilation.values.constants.NumberNode;
|
import me.topchetoeu.jscript.compilation.values.constants.NumberNode;
|
||||||
|
|
||||||
public class PostfixNode extends ChangeNode {
|
public class PostfixNode extends ChangeNode {
|
||||||
@ -21,7 +21,7 @@ public class PostfixNode extends ChangeNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public PostfixNode(Location loc, AssignTarget value, double addAmount) {
|
public PostfixNode(Location loc, ChangeTarget value, double addAmount) {
|
||||||
super(loc, value, new NumberNode(loc, -addAmount), Operation.SUBTRACT);
|
super(loc, value, new NumberNode(loc, -addAmount), Operation.SUBTRACT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,10 +32,10 @@ public class PostfixNode extends ChangeNode {
|
|||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
if (!src.is(i + n, "++")) return ParseRes.failed();
|
if (!src.is(i + n, "++")) return ParseRes.failed();
|
||||||
if (!(prev instanceof AssignTarget)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator.");
|
if (!(prev instanceof ChangeTarget)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator.");
|
||||||
n += 2;
|
n += 2;
|
||||||
|
|
||||||
return ParseRes.res(new PostfixNode(loc, (AssignTarget)prev, 1), n);
|
return ParseRes.res(new PostfixNode(loc, (ChangeTarget)prev, 1), n);
|
||||||
}
|
}
|
||||||
public static ParseRes<ChangeNode> parsePostfixDecrease(Source src, int i, Node prev, int precedence) {
|
public static ParseRes<ChangeNode> parsePostfixDecrease(Source src, int i, Node prev, int precedence) {
|
||||||
if (precedence > 15) return ParseRes.failed();
|
if (precedence > 15) return ParseRes.failed();
|
||||||
@ -44,9 +44,9 @@ public class PostfixNode extends ChangeNode {
|
|||||||
var loc = src.loc(i + n);
|
var loc = src.loc(i + n);
|
||||||
|
|
||||||
if (!src.is(i + n, "--")) return ParseRes.failed();
|
if (!src.is(i + n, "--")) return ParseRes.failed();
|
||||||
if (!(prev instanceof AssignTarget)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator.");
|
if (!(prev instanceof ChangeTarget)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator.");
|
||||||
n += 2;
|
n += 2;
|
||||||
|
|
||||||
return ParseRes.res(new PostfixNode(loc, (AssignTarget)prev, -1), n);
|
return ParseRes.res(new PostfixNode(loc, (ChangeTarget)prev, -1), n);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,15 +16,15 @@ public class TypeofNode extends Node {
|
|||||||
|
|
||||||
@Override public void compile(CompileResult target, boolean pollute) {
|
@Override public void compile(CompileResult target, boolean pollute) {
|
||||||
if (value instanceof VariableNode varNode) {
|
if (value instanceof VariableNode varNode) {
|
||||||
target.add(VariableNode.toGet(target, varNode.loc(), varNode.name, () -> Instruction.typeof(varNode.name)));
|
target.add(VariableNode.toGet(target, varNode.loc(), varNode.name, true));
|
||||||
if (!pollute) target.add(Instruction.discard());
|
if (pollute) target.add(Instruction.typeof());
|
||||||
|
else target.add(Instruction.discard());
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
value.compile(target, pollute);
|
value.compile(target, pollute);
|
||||||
target.add(Instruction.typeof());
|
if (pollute) target.add(Instruction.typeof());
|
||||||
if (!pollute) target.add(Instruction.discard());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public TypeofNode(Location loc, Node value) {
|
public TypeofNode(Location loc, Node value) {
|
||||||
|
@ -233,6 +233,37 @@ const Object = function(value) {
|
|||||||
|
|
||||||
defineField(Object, "prototype", false, false, false, setPrototype({}, null));
|
defineField(Object, "prototype", false, false, false, setPrototype({}, null));
|
||||||
|
|
||||||
|
defineField(Object, "defineProperty", true, false, true, (obj, key, desc) => {
|
||||||
|
if (typeof obj !== "object" || obj === null) {
|
||||||
|
print(obj);
|
||||||
|
print(typeof obj);
|
||||||
|
throw new TypeError("Object.defineProperty called on non-object");
|
||||||
|
}
|
||||||
|
if (typeof desc !== "object" || desc === null) throw new TypeError("Property description must be an object: " + desc);
|
||||||
|
|
||||||
|
if ("get" in desc || "set" in desc) {
|
||||||
|
let get = desc.get, set = desc.set;
|
||||||
|
|
||||||
|
print(typeof get);
|
||||||
|
|
||||||
|
if (get !== undefined && typeof get !== "function") throw new TypeError("Getter must be a function: " + get);
|
||||||
|
if (set !== undefined && typeof set !== "function") throw new TypeError("Setter must be a function: " + set);
|
||||||
|
|
||||||
|
if ("value" in desc || "writable" in desc) {
|
||||||
|
throw new TypeError("Invalid property descriptor. Cannot both specify accessors and a value or writable attribute");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!defineProperty(obj, key, desc.enumerable, desc.configurable, get, set)) {
|
||||||
|
throw new TypeError("Cannot redefine property: " + key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!defineField(obj, key, desc.writable, desc.enumerable, desc.configurable, desc.value)) {
|
||||||
|
throw new TypeError("Cannot redefine property: " + key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return obj;
|
||||||
|
});
|
||||||
|
|
||||||
defineField(Object.prototype, "toString", true, false, true, function() {
|
defineField(Object.prototype, "toString", true, false, true, function() {
|
||||||
if (this !== null && this !== undefined && (Symbol.toStringTag in this)) return "[object " + this[Symbol.toStringTag] + "]";
|
if (this !== null && this !== undefined && (Symbol.toStringTag in this)) return "[object " + this[Symbol.toStringTag] + "]";
|
||||||
else if (typeof this === "number" || this instanceof Number) return "[object Number]";
|
else if (typeof this === "number" || this instanceof Number) return "[object Number]";
|
||||||
@ -246,7 +277,7 @@ defineField(Object.prototype, "valueOf", true, false, true, function() {
|
|||||||
return this;
|
return this;
|
||||||
});
|
});
|
||||||
|
|
||||||
target.Boolean = Boolean;
|
target.Object = Object;
|
||||||
|
|
||||||
const Function = function() {
|
const Function = function() {
|
||||||
const parts = ["return function annonymous("];
|
const parts = ["return function annonymous("];
|
||||||
|
Loading…
Reference in New Issue
Block a user