From cb82f4cf321fbfc548d738aa3346b211a09293b3 Mon Sep 17 00:00:00 2001 From: TopchetoEU <36534413+TopchetoEU@users.noreply.github.com> Date: Sat, 14 Sep 2024 14:23:46 +0300 Subject: [PATCH] feat: implement patterns --- .../compilation/FunctionArrowNode.java | 9 +- .../jscript/compilation/FunctionNode.java | 48 +-- .../jscript/compilation/JavaScript.java | 65 --- .../jscript/compilation/Parameters.java | 75 +++- .../compilation/VariableDeclareNode.java | 40 +- .../destructing/AssignDestructorNode.java | 69 --- .../compilation/destructing/AssignTarget.java | 4 - .../compilation/destructing/Destructor.java | 38 -- .../destructing/NamedDestructor.java | 20 - .../compilation/patterns/AssignPattern.java | 69 +++ .../compilation/patterns/AssignTarget.java | 27 ++ .../patterns/AssignTargetLike.java | 8 + .../ChangeTarget.java | 3 +- .../compilation/patterns/NamedDestructor.java | 15 + .../patterns/ObjectAssignable.java | 16 + .../patterns/ObjectDestructor.java | 44 ++ .../compilation/patterns/ObjectPattern.java | 118 +++++ .../jscript/compilation/patterns/Pattern.java | 47 ++ .../compilation/patterns/PatternLike.java | 5 + .../jscript/compilation/scope/Scope.java | 20 +- .../values/ObjectDestructorNode.java | 199 --------- .../compilation/values/ObjectNode.java | 402 +++++++++++++----- .../compilation/values/VariableNode.java | 74 ++-- .../values/operations/AssignNode.java | 52 +-- .../values/operations/CallNode.java | 2 +- .../values/operations/ChangeNode.java | 12 +- .../values/operations/IndexNode.java | 79 ++-- .../values/operations/OperationNode.java | 24 +- .../values/operations/PostfixNode.java | 12 +- .../values/operations/TypeofNode.java | 8 +- src/main/resources/lib/index.js | 33 +- 31 files changed, 927 insertions(+), 710 deletions(-) delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/destructing/AssignDestructorNode.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/destructing/AssignTarget.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/destructing/Destructor.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/destructing/NamedDestructor.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/patterns/AssignPattern.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/patterns/AssignTarget.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/patterns/AssignTargetLike.java rename src/main/java/me/topchetoeu/jscript/compilation/{destructing => patterns}/ChangeTarget.java (57%) create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/patterns/NamedDestructor.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/patterns/ObjectAssignable.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/patterns/ObjectDestructor.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/patterns/ObjectPattern.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/patterns/Pattern.java create mode 100644 src/main/java/me/topchetoeu/jscript/compilation/patterns/PatternLike.java delete mode 100644 src/main/java/me/topchetoeu/jscript/compilation/values/ObjectDestructorNode.java diff --git a/src/main/java/me/topchetoeu/jscript/compilation/FunctionArrowNode.java b/src/main/java/me/topchetoeu/jscript/compilation/FunctionArrowNode.java index 036533c..aa5ca8c 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/FunctionArrowNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/FunctionArrowNode.java @@ -9,6 +9,7 @@ import me.topchetoeu.jscript.common.parsing.ParseRes; import me.topchetoeu.jscript.common.parsing.Parsing; import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.compilation.control.ReturnNode; +import me.topchetoeu.jscript.compilation.patterns.Pattern; public class FunctionArrowNode extends FunctionNode { @Override public String name() { return null; } @@ -34,7 +35,7 @@ public class FunctionArrowNode extends FunctionNode { Parameters params; 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(); n += paramsRes.n; n += Parsing.skipEmpty(src, i + n); @@ -42,14 +43,12 @@ public class FunctionArrowNode extends FunctionNode { params = paramsRes.result; } else { - var singleParam = Parsing.parseIdentifier(src, i + n); + var singleParam = Pattern.parse(src, i + n, true); if (!singleParam.isSuccess()) return ParseRes.failed(); - - var paramLoc = src.loc(i + n); n += singleParam.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(); diff --git a/src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java b/src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java index 9c369b8..b8b2bed 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java @@ -1,16 +1,15 @@ package me.topchetoeu.jscript.compilation; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Operation; import me.topchetoeu.jscript.common.Instruction.BreakpointType; import me.topchetoeu.jscript.common.environment.Environment; import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.common.parsing.ParseRes; import me.topchetoeu.jscript.common.parsing.Parsing; import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType; import me.topchetoeu.jscript.compilation.scope.FunctionScope; import me.topchetoeu.jscript.compilation.scope.Variable; -import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; public abstract class FunctionNode extends Node { public final CompoundNode body; @@ -37,36 +36,37 @@ public abstract class FunctionNode extends Node { var i = 0; for (var param : params.params) { - if (scope.has(param.name, false)) throw new SyntaxException(param.loc, "Duplicate parameter name not allowed"); - if (!JavaScript.checkVarName(param.name)) { - throw new SyntaxException(param.loc, String.format("Unexpected identifier '%s'", param.name)); - } - var varI = scope.define(new Variable(param.name, false), param.loc); - target.add(Instruction.loadMember(i++)); + 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) { - var end = new DeferredIntSupplier(); + // if (param.node != null) { + // var end = new DeferredIntSupplier(); - target.add(Instruction.dup()); - target.add(Instruction.pushUndefined()); - target.add(Instruction.operation(Operation.EQUALS)); - target.add(Instruction.jmpIfNot(end)); - target.add(Instruction.discard()); - param.node.compile(target, true); + // target.add(Instruction.dup()); + // target.add(Instruction.pushUndefined()); + // target.add(Instruction.operation(Operation.EQUALS)); + // target.add(Instruction.jmpIfNot(end)); + // target.add(Instruction.discard()); + // param.node.compile(target, true); - end.set(target.size()); - } + // end.set(target.size()); + // } - target.add(_i -> varI.index().toSet(false)); + // target.add(_i -> varI.index().toSet(false)); } } - if (params.restName != null) { - if (scope.has(params.restName, false)) throw new SyntaxException(params.restLocation, "Duplicate parameter name not allowed"); - var restVar = scope.define(new Variable(params.restName, false), params.restLocation); + if (params.rest != null) { 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)) { @@ -131,7 +131,7 @@ public abstract class FunctionNode extends Node { n += name.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"); n += params.n; diff --git a/src/main/java/me/topchetoeu/jscript/compilation/JavaScript.java b/src/main/java/me/topchetoeu/jscript/compilation/JavaScript.java index 082d7ec..cab60c3 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/JavaScript.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/JavaScript.java @@ -222,71 +222,6 @@ public final class JavaScript { return ParseRes.failed(); } - public static ParseRes 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(); - - 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 parseDeclarationType(Source src, int i) { var res = Parsing.parseIdentifier(src, i); if (!res.isSuccess()) return res.chainError(); diff --git a/src/main/java/me/topchetoeu/jscript/compilation/Parameters.java b/src/main/java/me/topchetoeu/jscript/compilation/Parameters.java index 307170f..712a2e8 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/Parameters.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/Parameters.java @@ -1,29 +1,84 @@ package me.topchetoeu.jscript.compilation; +import java.util.ArrayList; 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 int length; - public final List params; - public final String restName; - public final Location restLocation; + public final List params; + public final Pattern rest; - public Parameters(List params, String restName, Location restLocation) { + public Parameters(List params, Pattern rest) { var len = params.size(); for (var i = params.size() - 1; i >= 0; i--) { - if (params.get(i).node == null) break; + if (!(params.get(i) instanceof AssignNode)) break; len--; } this.params = params; this.length = len; - this.restName = restName; - this.restLocation = restLocation; + this.rest = rest; } - public Parameters(List params) { - this(params, null, null); + public Parameters(List params) { + this(params, null); + } + + public static ParseRes 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(); + + 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); } } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/VariableDeclareNode.java b/src/main/java/me/topchetoeu/jscript/compilation/VariableDeclareNode.java index 7584d48..d762e8e 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/VariableDeclareNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/VariableDeclareNode.java @@ -4,23 +4,21 @@ import java.util.ArrayList; import java.util.List; 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.ParseRes; import me.topchetoeu.jscript.common.parsing.Parsing; import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType; -import me.topchetoeu.jscript.compilation.scope.Variable; -import me.topchetoeu.jscript.compilation.values.VariableNode; +import me.topchetoeu.jscript.compilation.patterns.Pattern; public class VariableDeclareNode extends Node { public static class Pair { - public final String name; + public final Pattern destructor; public final Node value; public final Location location; - public Pair(String name, Node value, Location location) { - this.name = name; + public Pair(Pattern destr, Node value, Location location) { + this.destructor = destr; this.value = value; this.location = location; } @@ -32,25 +30,22 @@ public class VariableDeclareNode extends Node { @Override public void resolve(CompileResult target) { if (!declType.strict) { 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) { for (var entry : values) { - if (entry.name == null) continue; - if (declType.strict) target.scope.defineStrict(new Variable(entry.name, declType.readonly), entry.location); - - if (entry.value != null) { - FunctionNode.compileWithName(entry.value, target, true, entry.name, BreakpointType.STEP_OVER); - target.add(VariableNode.toSet(target, entry.location, entry.name, false, true)); + if (entry.value == null) { + if (declType == DeclarationType.VAR) entry.destructor.declare(target, null); + else entry.destructor.declare(target, declType); } - else target.add(_i -> { - var i = target.scope.get(entry.name, false); + else { + entry.value.compile(target, true); - if (i == null) return Instruction.globDef(entry.name); - else return Instruction.nop(); - }); + if (declType == DeclarationType.VAR) entry.destructor.destruct(target, null, true); + else entry.destructor.destruct(target, declType, true); + } } if (pollute) target.add(Instruction.pushUndefined()); @@ -80,13 +75,10 @@ public class VariableDeclareNode extends Node { while (true) { 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)) { - return ParseRes.error(src.loc(i + n), String.format("Unexpected identifier '%s'", name.result)); - } + var name = Pattern.parse(src, i + n, false); + if (!name.isSuccess()) return name.chainError(nameLoc, "Expected a variable name or a destructor"); + n += name.n; Node val = null; var endN = n; diff --git a/src/main/java/me/topchetoeu/jscript/compilation/destructing/AssignDestructorNode.java b/src/main/java/me/topchetoeu/jscript/compilation/destructing/AssignDestructorNode.java deleted file mode 100644 index 755f043..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/destructing/AssignDestructorNode.java +++ /dev/null @@ -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 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); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/destructing/AssignTarget.java b/src/main/java/me/topchetoeu/jscript/compilation/destructing/AssignTarget.java deleted file mode 100644 index 1e60d9f..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/destructing/AssignTarget.java +++ /dev/null @@ -1,4 +0,0 @@ -package me.topchetoeu.jscript.compilation.destructing; - -public interface AssignTarget extends Destructor { -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/destructing/Destructor.java b/src/main/java/me/topchetoeu/jscript/compilation/destructing/Destructor.java deleted file mode 100644 index 264cdde..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/destructing/Destructor.java +++ /dev/null @@ -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 parse(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - - ParseRes 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); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/destructing/NamedDestructor.java b/src/main/java/me/topchetoeu/jscript/compilation/destructing/NamedDestructor.java deleted file mode 100644 index 47b5a4b..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/destructing/NamedDestructor.java +++ /dev/null @@ -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 parse(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - - ParseRes first = ParseRes.first(src, i + n, - AssignDestructorNode::parse, - VariableNode::parse - ); - return first.addN(n); - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/patterns/AssignPattern.java b/src/main/java/me/topchetoeu/jscript/compilation/patterns/AssignPattern.java new file mode 100644 index 0000000..f7fa3b8 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/patterns/AssignPattern.java @@ -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 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); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/patterns/AssignTarget.java b/src/main/java/me/topchetoeu/jscript/compilation/patterns/AssignTarget.java new file mode 100644 index 0000000..f8b4739 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/patterns/AssignTarget.java @@ -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; } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/patterns/AssignTargetLike.java b/src/main/java/me/topchetoeu/jscript/compilation/patterns/AssignTargetLike.java new file mode 100644 index 0000000..ce1dcee --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/patterns/AssignTargetLike.java @@ -0,0 +1,8 @@ +package me.topchetoeu.jscript.compilation.patterns; + +/** + * Represents all nodes that can be assign targets + */ +public interface AssignTargetLike { + AssignTarget toAssignTarget(); +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/destructing/ChangeTarget.java b/src/main/java/me/topchetoeu/jscript/compilation/patterns/ChangeTarget.java similarity index 57% rename from src/main/java/me/topchetoeu/jscript/compilation/destructing/ChangeTarget.java rename to src/main/java/me/topchetoeu/jscript/compilation/patterns/ChangeTarget.java index 0736444..68993af 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/destructing/ChangeTarget.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/patterns/ChangeTarget.java @@ -1,8 +1,7 @@ -package me.topchetoeu.jscript.compilation.destructing; +package me.topchetoeu.jscript.compilation.patterns; import me.topchetoeu.jscript.compilation.CompileResult; public interface ChangeTarget extends AssignTarget { void beforeChange(CompileResult target); - void afterChange(CompileResult target, boolean pollute); } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/patterns/NamedDestructor.java b/src/main/java/me/topchetoeu/jscript/compilation/patterns/NamedDestructor.java new file mode 100644 index 0000000..818471a --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/patterns/NamedDestructor.java @@ -0,0 +1,15 @@ +package me.topchetoeu.jscript.compilation.patterns; + +public interface NamedDestructor extends Pattern { + String name(); + + // public static ParseRes parse(Source src, int i) { + // var n = Parsing.skipEmpty(src, i); + + // ParseRes first = ParseRes.first(src, i + n, + // AssignDestructorNode::parse, + // VariableNode::parse + // ); + // return first.addN(n); + // } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/patterns/ObjectAssignable.java b/src/main/java/me/topchetoeu/jscript/compilation/patterns/ObjectAssignable.java new file mode 100644 index 0000000..f3abc5e --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/patterns/ObjectAssignable.java @@ -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 implements AssignTarget { + @Override public void afterAssign(CompileResult target, boolean pollute) { + compile(target, t -> t.assign(target, false), pollute); + } + + public ObjectAssignable(Location loc, List> members) { + super(loc, members); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/patterns/ObjectDestructor.java b/src/main/java/me/topchetoeu/jscript/compilation/patterns/ObjectDestructor.java new file mode 100644 index 0000000..de7b51f --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/patterns/ObjectDestructor.java @@ -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 extends Node { + public static final class Member { + public final Node key; + public final T consumable; + + public Member(Node key, T consumer) { + this.key = key; + this.consumable = consumer; + } + } + + public final List> members; + + public void consume(Consumer consumer) { + for (var el : members) { + consumer.accept(el.consumable); + } + } + public void compile(CompileResult target, Consumer 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> members) { + super(loc); + this.members = members; + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/patterns/ObjectPattern.java b/src/main/java/me/topchetoeu/jscript/compilation/patterns/ObjectPattern.java new file mode 100644 index 0000000..cd9e120 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/patterns/ObjectPattern.java @@ -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 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> members) { + super(loc, members); + } + + private static ParseRes> parseShorthand(Source src, int i) { + ParseRes 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> 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 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 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>(); + + if (src.is(i + n, "}")) { + n++; + return ParseRes.res(new ObjectPattern(loc, members), n); + } + + while (true) { + ParseRes> 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); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/patterns/Pattern.java b/src/main/java/me/topchetoeu/jscript/compilation/patterns/Pattern.java new file mode 100644 index 0000000..80781af --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/patterns/Pattern.java @@ -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 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; } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/patterns/PatternLike.java b/src/main/java/me/topchetoeu/jscript/compilation/patterns/PatternLike.java new file mode 100644 index 0000000..8df31c1 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/patterns/PatternLike.java @@ -0,0 +1,5 @@ +package me.topchetoeu.jscript.compilation.patterns; + +public interface PatternLike { + Pattern toPattern(); +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java b/src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java index 3e210f7..3d3006a 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java @@ -4,6 +4,7 @@ import java.util.HashMap; import java.util.LinkedList; import me.topchetoeu.jscript.common.parsing.Location; +import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType; import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; public class Scope { @@ -41,15 +42,6 @@ public class Scope { 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) { return new SyntaxException(loc, String.format("Identifier '%s' has already been declared", name)); } @@ -141,9 +133,6 @@ public class Scope { } return res; - - // if (parent != null) return parent.variableOffset() + variables.size(); - // else return variables.size(); } public final int capturablesOffset() { var res = 0; @@ -154,8 +143,11 @@ public class Scope { } 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() { diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectDestructorNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectDestructorNode.java deleted file mode 100644 index 68501f0..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectDestructorNode.java +++ /dev/null @@ -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 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 map, VariableNode rest) { - super(loc); - this.destructors = new LinkedHashMap<>(map); - this.restDestructor = rest; - } - public ObjectDestructorNode(Location loc, Map map) { - super(loc); - this.destructors = new LinkedHashMap<>(map); - this.restDestructor = null; - } - - private static ParseRes 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 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(); - - 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); - } - -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectNode.java index 8e4483e..b959352 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectNode.java @@ -1,10 +1,10 @@ package me.topchetoeu.jscript.compilation.values; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.Map; +import java.util.LinkedList; +import java.util.List; 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.ParseRes; import me.topchetoeu.jscript.common.parsing.Parsing; @@ -12,105 +12,308 @@ import me.topchetoeu.jscript.common.parsing.Source; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.CompoundNode; import me.topchetoeu.jscript.compilation.FunctionNode; -import me.topchetoeu.jscript.compilation.FunctionValueNode; import me.topchetoeu.jscript.compilation.JavaScript; 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 { - public static class ObjProp { - public final String name; - public final String access; - public final FunctionValueNode func; + @Override public String name() { + if (key instanceof StringNode str) { + if (isGetter()) return "get " + str.value; + else return "set " + str.value; + } + else return null; + } - public ObjProp(String name, String access, FunctionValueNode func) { - this.name = name; - this.access = access; - this.func = func; + public boolean isGetter() { return argument == null; } + public boolean isSetter() { return argument != 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.defProp(isSetter())); + } + + public PropertyMemberNode(Location loc, Location end, Node key, Pattern argument, CompoundNode body) { + super(loc, end, argument == null ? new Parameters(List.of()) : new Parameters(List.of(argument)), body); + this.key = key; + this.argument = argument; + } + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + var access = Parsing.parseIdentifier(src, i + n); + if (!access.isSuccess()) return ParseRes.failed(); + if (!access.result.equals("get") && !access.result.equals("set")) return ParseRes.failed(); + n += access.n; + + var name = parsePropName(src, i + n); + if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected a property name after '" + access + "'"); + 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 (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 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"); + 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 MethodMemberNode( + loc, end, name.result, params.result, body.result + ), 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 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 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 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 Map map; - public final Map getters; - public final Map setters; + public final List 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 : map.entrySet()) { + for (var el : members) { target.add(Instruction.dup()); - var val = el.getValue(); - FunctionNode.compileWithName(val, target, true, el.getKey().toString()); - target.add(Instruction.storeMember(el.getKey())); + el.compile(target, false); } - - var keys = new ArrayList(); - keys.addAll(getters.keySet()); - keys.addAll(setters.keySet()); - - 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 ObjectNode(Location loc, Map map, Map getters, Map setters) { + @Override public AssignTarget toAssignTarget() { + var newMembers = new LinkedList>(); + + 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 map) { super(loc); - this.map = map; - this.getters = getters; - this.setters = setters; + this.members = map; } - private static ParseRes parsePropName(Source src, int i) { + private static ParseRes parseComputePropName(Source src, int i) { var n = Parsing.skipEmpty(src, i); + if (!src.is(i + n, "[")) return ParseRes.failed(); + n++; - var res = ParseRes.first(src, i + n, - Parsing::parseIdentifier, - Parsing::parseString, - (s, j) -> Parsing.parseNumber(s, j, false) + 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 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 ); - n += res.n; - - if (!res.isSuccess()) return res.chainError(); - return ParseRes.res(res.result.toString(), n); - } - private static ParseRes parseObjectProp(Source src, int i) { - var n = Parsing.skipEmpty(src, i); - var loc = src.loc(i + n); - - var access = Parsing.parseIdentifier(src, i + n); - if (!access.isSuccess()) return ParseRes.failed(); - if (!access.result.equals("get") && !access.result.equals("set")) return ParseRes.failed(); - n += access.n; - - var name = parsePropName(src, i + n); - if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected a property name after '" + access + "'"); - n += name.n; - - var params = JavaScript.parseParameters(src, i + n); - if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected an argument list"); - n += params.n; - - 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 ObjProp( - name.result, access.result, - new FunctionValueNode(loc, end, params.result, body.result, access + " " + name.result.toString()) - ), n); } public static ParseRes parse(Source src, int i) { @@ -121,39 +324,23 @@ public class ObjectNode extends Node implements Destructor { n++; n += Parsing.skipEmpty(src, i + n); - var values = new LinkedHashMap(); - var getters = new LinkedHashMap(); - var setters = new LinkedHashMap(); + var members = new LinkedList(); if (src.is(i + n, "}")) { n++; - return ParseRes.res(new ObjectNode(loc, values, getters, setters), n); + return ParseRes.res(new ObjectNode(loc, members), n); } while (true) { - var prop = parseObjectProp(src, i + n); + ParseRes prop = ParseRes.first(src, i + n, + MethodMemberNode::parse, + PropertyMemberNode::parse, + FieldMemberNode::parseObject + ); + if (!prop.isSuccess()) return prop.chainError(src.loc(i + n), "Expected a member in object literal"); + n += prop.n; - if (prop.isSuccess()) { - n += prop.n; - - if (prop.result.access.equals("set")) setters.put(prop.result.name, prop.result.func); - 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); - } + members.add(prop.result); n += Parsing.skipEmpty(src, 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."); } - return ParseRes.res(new ObjectNode(loc, values, getters, setters), n); + return ParseRes.res(new ObjectNode(loc, members), n); } - } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/VariableNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/VariableNode.java index c990238..9c2d3d7 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/VariableNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/VariableNode.java @@ -1,53 +1,73 @@ package me.topchetoeu.jscript.compilation.values; import java.util.function.IntFunction; -import java.util.function.Supplier; 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.AssignableNode; 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.compilation.patterns.ChangeTarget; +import me.topchetoeu.jscript.compilation.patterns.Pattern; import me.topchetoeu.jscript.compilation.scope.Variable; 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; - @Override public String assignName() { return name; } + public String assignName() { return name; } - @Override public void compileBeforeAssign(CompileResult target, boolean 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 compileBeforeAssign(CompileResult target, boolean 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)); } - @Override public void destructArg(CompileResult target) { - target.add(_i -> target.scope.define(new Variable(name, false), loc()).index().toSet(false)); - } + // @Override public void destructArg(CompileResult target) { + // target.add(_i -> target.scope.define(new Variable(name, false), loc()).index().toSet(false)); + // } @Override public void destructDeclResolve(CompileResult target) { target.scope.define(new Variable(name, false), loc()); } - @Override public void afterAssign(CompileResult target, DeclarationType decl) { - if (decl.strict) { - var v = target.scope.defineStrict(new Variable(name, decl.readonly), loc()); - target.add(_i -> v.index().toSet(false)); + + @Override public void afterAssign(CompileResult target, boolean pollute) { + target.add(VariableNode.toSet(target, loc(), name, pollute, 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 { - 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) { var i = target.scope.get(name, false); @@ -55,7 +75,7 @@ public class VariableNode extends Node implements AssignTarget { if (i == null) { target.add(_i -> { 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()); @@ -63,18 +83,18 @@ public class VariableNode extends Node implements AssignTarget { else if (pollute) target.add(_i -> i.index().toGet()); } - public static IntFunction toGet(CompileResult target, Location loc, String name, Supplier onGlobal) { + public static IntFunction toGet(CompileResult target, Location loc, String name, boolean forceGet) { var i = target.scope.get(name, false); if (i == null) return _i -> { 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(); } public static IntFunction toGet(CompileResult target, Location loc, String name) { - return toGet(target, loc, name, () -> Instruction.globGet(name)); - } + return toGet(target, loc, name, false); + } public static IntFunction toSet(CompileResult target, Location loc, String name, boolean keep, boolean define) { diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/AssignNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/AssignNode.java index e33bdd2..bea0b41 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/AssignNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/AssignNode.java @@ -5,45 +5,26 @@ import me.topchetoeu.jscript.common.Operation; import me.topchetoeu.jscript.common.parsing.Location; 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.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.compilation.patterns.AssignTarget; 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 Node value; - @Override public void destructDeclResolve(CompileResult target) { - if (!(assignable instanceof VariableNode var)) { - throw new SyntaxException(loc(), "Assign target in declaration destructor must be a variable"); - } + @Override public void compile(CompileResult target, boolean pollute) { + if (assignable instanceof AssignNode other) throw new SyntaxException(other.loc(), "Assign deconstructor not allowed here"); - target.scope.define(new Variable(var.name, false), var.loc()); + assignable.beforeAssign(target); + value.compile(target, true); + assignable.afterAssign(target, pollute); } - @Override public void destructArg(CompileResult target) { - if (!(assignable instanceof VariableNode var)) { - throw new SyntaxException(loc(), "Assign target in declaration destructor must be a variable"); - } + @Override public void afterAssign(CompileResult target, boolean pollute) { + if (assignable instanceof AssignNode other) throw new SyntaxException(other.loc(), "Double assign deconstructor not allowed"); - 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()); + if (pollute) target.add(Instruction.dup(2, 0)); + else target.add(Instruction.dup()); target.add(Instruction.pushUndefined()); target.add(Instruction.operation(Operation.EQUALS)); var start = target.temp(); @@ -51,15 +32,10 @@ public class AssignNode extends Node implements Destructor { 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); - } - - @Override public void compile(CompileResult target, boolean pollute) { - assignable.beforeAssign(target, null); - value.compile(target, true); - assignable.afterAssign(target, null, pollute); + assignable.assign(target, false); + if (!pollute) target.add(Instruction.discard()); } public AssignNode(Location loc, AssignTarget assignable, Node value) { diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/CallNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/CallNode.java index 74f11c0..5aa242b 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/CallNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/CallNode.java @@ -38,7 +38,7 @@ public class CallNode extends Node { 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 if (func instanceof StringNode) { diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/ChangeNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/ChangeNode.java index bf68d55..e5c316c 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/ChangeNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/ChangeNode.java @@ -9,24 +9,24 @@ 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.destructing.ChangeTarget; +import me.topchetoeu.jscript.compilation.patterns.ChangeTarget; import me.topchetoeu.jscript.compilation.values.constants.NumberNode; public class ChangeNode extends Node { - public final ChangeTarget assignable; + public final ChangeTarget changable; public final Node value; public final Operation op; @Override public void compile(CompileResult target, boolean pollute) { - assignable.beforeChange(target); + changable.beforeChange(target); value.compile(target, true); 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); - this.assignable = assignable; + this.changable = changable; this.value = value; this.op = op; } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/IndexNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/IndexNode.java index 0f049bd..6299813 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/IndexNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/IndexNode.java @@ -6,39 +6,21 @@ 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.AssignableNode; 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.compilation.destructing.AssignTarget; -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.StringNode; -import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; public class IndexNode extends Node implements ChangeTarget { public final Node object; public final Node index; - @Override public void destructDeclResolve(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"); + @Override public void beforeAssign(CompileResult target) { object.compile(target, true); - if (index instanceof NumberNode num && (int)num.value == num.value) return; - 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()); + indexStorePushKey(target, index); } @Override public void beforeChange(CompileResult target) { object.compile(target, true); @@ -59,26 +41,16 @@ public class IndexNode extends Node implements ChangeTarget { 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) { - throw new SyntaxException(loc(), "Illegal index in declaration destruction context"); - } - @Override public void destructAssign(CompileResult target, boolean pollute) { + @Override public void assign(CompileResult target, boolean pollute) { object.compile(target, true); target.add(Instruction.dup(1, 1)); - compileAfterAssign(target, false, false); - if (!pollute) target.add(Instruction.discard()); + indexStorePushKey(target, index); + indexStore(target, index, pollute); + } + + @Override public void afterAssign(CompileResult target, boolean pollute) { + indexStore(target, index, pollute); } // @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); } + + 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()); + } } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/OperationNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/OperationNode.java index dcb2335..fa3f77e 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/OperationNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/OperationNode.java @@ -11,10 +11,11 @@ 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.AssignableNode; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.JavaScript; 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 { private static interface OperatorFactory { @@ -54,13 +55,22 @@ public class OperationNode extends Node { @Override public ParseRes construct(Source src, int i, Node prev) { 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); - if (!other.isSuccess()) return other.chainError(src.loc(i + other.n), String.format("Expected a value after '%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)); - if (operation == null) return ParseRes.res(new AssignNode(loc, ((AssignTarget)prev), other.result), other.n); - else return ParseRes.res(new ChangeNode(loc, ((AssignTarget)prev), other.result, operation), other.n); + return ParseRes.res(new AssignNode(loc, target.toAssignTarget(), other.result), 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) { @@ -209,7 +219,7 @@ public class OperationNode extends Node { var factory = factories.get(token); if (!src.is(i + n, token)) continue; - if (factory.precedence() < precedence) ParseRes.failed(); + if (factory.precedence() < precedence) return ParseRes.failed(); n += token.length(); n += Parsing.skipEmpty(src, i + n); diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/PostfixNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/PostfixNode.java index cd3f133..14b5f77 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/PostfixNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/PostfixNode.java @@ -6,9 +6,9 @@ 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.AssignableNode; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.Node; +import me.topchetoeu.jscript.compilation.patterns.ChangeTarget; import me.topchetoeu.jscript.compilation.values.constants.NumberNode; 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); } @@ -32,10 +32,10 @@ public class PostfixNode extends ChangeNode { var loc = src.loc(i + n); 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; - return ParseRes.res(new PostfixNode(loc, (AssignTarget)prev, 1), n); + return ParseRes.res(new PostfixNode(loc, (ChangeTarget)prev, 1), n); } public static ParseRes parsePostfixDecrease(Source src, int i, Node prev, int precedence) { if (precedence > 15) return ParseRes.failed(); @@ -44,9 +44,9 @@ public class PostfixNode extends ChangeNode { var loc = src.loc(i + n); 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; - return ParseRes.res(new PostfixNode(loc, (AssignTarget)prev, -1), n); + return ParseRes.res(new PostfixNode(loc, (ChangeTarget)prev, -1), n); } } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/TypeofNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/TypeofNode.java index 4d3b07d..19b3798 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/TypeofNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/TypeofNode.java @@ -16,15 +16,15 @@ public class TypeofNode extends Node { @Override public void compile(CompileResult target, boolean pollute) { if (value instanceof VariableNode varNode) { - target.add(VariableNode.toGet(target, varNode.loc(), varNode.name, () -> Instruction.typeof(varNode.name))); - if (!pollute) target.add(Instruction.discard()); + target.add(VariableNode.toGet(target, varNode.loc(), varNode.name, true)); + if (pollute) target.add(Instruction.typeof()); + else target.add(Instruction.discard()); return; } value.compile(target, pollute); - target.add(Instruction.typeof()); - if (!pollute) target.add(Instruction.discard()); + if (pollute) target.add(Instruction.typeof()); } public TypeofNode(Location loc, Node value) { diff --git a/src/main/resources/lib/index.js b/src/main/resources/lib/index.js index 550cfa5..ea223fc 100644 --- a/src/main/resources/lib/index.js +++ b/src/main/resources/lib/index.js @@ -233,6 +233,37 @@ const Object = function(value) { 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() { 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]"; @@ -246,7 +277,7 @@ defineField(Object.prototype, "valueOf", true, false, true, function() { return this; }); -target.Boolean = Boolean; +target.Object = Object; const Function = function() { const parts = ["return function annonymous("];