diff --git a/src/main/java/me/topchetoeu/jscript/common/Instruction.java b/src/main/java/me/topchetoeu/jscript/common/Instruction.java index f64c718..f9d75ad 100644 --- a/src/main/java/me/topchetoeu/jscript/common/Instruction.java +++ b/src/main/java/me/topchetoeu/jscript/common/Instruction.java @@ -449,8 +449,8 @@ public class Instruction { return new Instruction(Type.TYPEOF, varName); } - public static Instruction keys(boolean forInFormat) { - return new Instruction(Type.KEYS, forInFormat); + public static Instruction keys(boolean own, boolean onlyEnumerable) { + return new Instruction(Type.KEYS, own, onlyEnumerable); } public static Instruction defProp() { diff --git a/src/main/java/me/topchetoeu/jscript/compilation/AssignableNode.java b/src/main/java/me/topchetoeu/jscript/compilation/AssignableNode.java deleted file mode 100644 index 6dd3333..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/AssignableNode.java +++ /dev/null @@ -1,9 +0,0 @@ -package me.topchetoeu.jscript.compilation; - -public interface AssignableNode { - public void compileBeforeAssign(CompileResult target, boolean operator); - public void compileAfterAssign(CompileResult target, boolean operator, boolean pollute); - public default String assignName() { - return null; - } -} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/ForInNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/ForInNode.java index 59688a5..b331267 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/ForInNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/ForInNode.java @@ -1,7 +1,6 @@ package me.topchetoeu.jscript.compilation.control; import me.topchetoeu.jscript.common.Instruction; -import me.topchetoeu.jscript.common.Operation; import me.topchetoeu.jscript.common.Instruction.BreakpointType; import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.common.parsing.ParseRes; @@ -33,12 +32,10 @@ public class ForInNode extends Node { if (declType != null && declType.strict) target.scope.defineStrict(new Variable(varName, declType.readonly), varLocation); object.compile(target, true, BreakpointType.STEP_OVER); - target.add(Instruction.keys(true)); + target.add(Instruction.keys(false, true)); int start = target.size(); target.add(Instruction.dup()); - target.add(Instruction.pushUndefined()); - target.add(Instruction.operation(Operation.EQUALS)); int mid = target.temp(); target.add(Instruction.loadMember("value")).setLocation(varLocation); @@ -55,7 +52,7 @@ public class ForInNode extends Node { target.add(Instruction.jmp(start - endI)); target.add(Instruction.discard()); - target.set(mid, Instruction.jmpIf(endI - mid + 1)); + target.set(mid, Instruction.jmpIfNot(endI - mid + 1)); if (pollute) target.add(Instruction.pushUndefined()); } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/destructing/AssignDestructorNode.java b/src/main/java/me/topchetoeu/jscript/compilation/destructing/AssignDestructorNode.java new file mode 100644 index 0000000..755f043 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/destructing/AssignDestructorNode.java @@ -0,0 +1,69 @@ +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 new file mode 100644 index 0000000..1e60d9f --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/destructing/AssignTarget.java @@ -0,0 +1,4 @@ +package me.topchetoeu.jscript.compilation.destructing; + +public interface AssignTarget extends Destructor { +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/destructing/ChangeTarget.java b/src/main/java/me/topchetoeu/jscript/compilation/destructing/ChangeTarget.java new file mode 100644 index 0000000..0736444 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/destructing/ChangeTarget.java @@ -0,0 +1,8 @@ +package me.topchetoeu.jscript.compilation.destructing; + +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/destructing/Destructor.java b/src/main/java/me/topchetoeu/jscript/compilation/destructing/Destructor.java new file mode 100644 index 0000000..264cdde --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/destructing/Destructor.java @@ -0,0 +1,38 @@ +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 new file mode 100644 index 0000000..47b5a4b --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/destructing/NamedDestructor.java @@ -0,0 +1,20 @@ +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/scope/Scope.java b/src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java index 4059df0..206e990 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java @@ -61,6 +61,16 @@ public class Scope { if (ended) throw new IllegalStateException("Cannot define in an ended scope"); } + /** + * Defines a nameless variable for holding intermediate temporary values + * + * @throws RuntimeException If the scope is finalized or has an active child + */ + public Variable defineTemp() { + checkNotEnded(); + return this.variables.add(new Variable("", false)); + } + /** * Defines an ES5-style variable * diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectDestructorNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectDestructorNode.java new file mode 100644 index 0000000..68501f0 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectDestructorNode.java @@ -0,0 +1,199 @@ +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 5a5de0e..8e4483e 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectNode.java @@ -15,9 +15,10 @@ 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; -public class ObjectNode extends Node { +public class ObjectNode extends Node implements Destructor { public static class ObjProp { public final String name; public final String access; @@ -148,7 +149,7 @@ public class ObjectNode extends Node { n++; var valRes = JavaScript.parseExpression(src, i + n, 2); - if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value in array list"); + 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); 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 c4b7edb..c990238 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/VariableNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/VariableNode.java @@ -12,9 +12,11 @@ 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.scope.Variable; import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; -public class VariableNode extends Node implements AssignableNode { +public class VariableNode extends Node implements AssignTarget { public final String name; @Override public String assignName() { return name; } @@ -28,6 +30,25 @@ public class VariableNode extends Node implements AssignableNode { target.add(VariableNode.toSet(target, loc(), name, pollute, 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)); + } + else { + target.add(VariableNode.toSet(target, loc(), name, false, true)); + } + } + @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); @@ -81,10 +102,8 @@ public class VariableNode extends Node implements AssignableNode { n += literal.n; if (!JavaScript.checkVarName(literal.result)) { - if (literal.result.equals("await")) return ParseRes.error(src.loc(i + n), "'await' expressions are not supported."); - if (literal.result.equals("const")) return ParseRes.error(src.loc(i + n), "'const' declarations are not supported."); - if (literal.result.equals("let")) return ParseRes.error(src.loc(i + n), "'let' declarations are not supported."); - return ParseRes.error(src.loc(i + n), String.format("Unexpected keyword '%s'.", literal.result)); + if (literal.result.equals("await")) return ParseRes.error(src.loc(i + n), "'await' expressions are not supported"); + return ParseRes.error(src.loc(i + n), String.format("Unexpected keyword '%s'", literal.result)); } return ParseRes.res(new VariableNode(loc, literal.result), n); 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 e80f6f7..e33bdd2 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 @@ -1,21 +1,68 @@ package me.topchetoeu.jscript.compilation.values.operations; +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Operation; import me.topchetoeu.jscript.common.parsing.Location; -import me.topchetoeu.jscript.compilation.AssignableNode; 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.runtime.exceptions.SyntaxException; -public class AssignNode extends Node { - public final AssignableNode assignable; +public class AssignNode extends Node implements Destructor { + public final AssignTarget assignable; public final Node value; - @Override public void compile(CompileResult target, boolean pollute) { - assignable.compileBeforeAssign(target, false); - value.compile(target, true); - assignable.compileAfterAssign(target, false, pollute); + @Override public void destructDeclResolve(CompileResult target) { + 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, false), var.loc()); } - public AssignNode(Location loc, AssignableNode assignable, Node value) { + @Override public void destructArg(CompileResult target) { + if (!(assignable instanceof VariableNode var)) { + 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.operation(Operation.EQUALS)); + var start = target.temp(); + target.add(Instruction.discard()); + + value.compile(target, true); + + target.set(start, Instruction.jmp(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); + } + + public AssignNode(Location loc, AssignTarget assignable, Node value) { super(loc); this.assignable = assignable; this.value = value; 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 e72b937..bf68d55 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 @@ -6,25 +6,25 @@ 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.destructing.ChangeTarget; import me.topchetoeu.jscript.compilation.values.constants.NumberNode; public class ChangeNode extends Node { - public final AssignableNode assignable; + public final ChangeTarget assignable; public final Node value; public final Operation op; @Override public void compile(CompileResult target, boolean pollute) { - assignable.compileBeforeAssign(target, true); + assignable.beforeChange(target); value.compile(target, true); target.add(Instruction.operation(op)); - assignable.compileAfterAssign(target, true, pollute); + assignable.afterChange(target, pollute); } - public ChangeNode(Location loc, AssignableNode assignable, Node value, Operation op) { + public ChangeNode(Location loc, ChangeTarget assignable, Node value, Operation op) { super(loc); this.assignable = assignable; this.value = value; @@ -40,9 +40,9 @@ public class ChangeNode extends Node { var res = JavaScript.parseExpression(src, i + n, 15); if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected assignable value after prefix operator."); - else if (!(res.result instanceof AssignableNode)) return ParseRes.error(src.loc(i + n), "Expected assignable value after prefix operator."); + else if (!(res.result instanceof ChangeTarget)) return ParseRes.error(src.loc(i + n), "Expected assignable value after prefix operator."); - return ParseRes.res(new ChangeNode(loc, (AssignableNode)res.result, new NumberNode(loc, -1), Operation.SUBTRACT), n + res.n); + return ParseRes.res(new ChangeNode(loc, (ChangeTarget)res.result, new NumberNode(loc, -1), Operation.SUBTRACT), n + res.n); } public static ParseRes parsePrefixDecrease(Source src, int i) { var n = Parsing.skipEmpty(src, i); @@ -53,8 +53,8 @@ public class ChangeNode extends Node { var res = JavaScript.parseExpression(src, i + n, 15); if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected assignable value after prefix operator."); - else if (!(res.result instanceof AssignableNode)) return ParseRes.error(src.loc(i + n), "Expected assignable value after prefix operator."); + else if (!(res.result instanceof ChangeTarget)) return ParseRes.error(src.loc(i + n), "Expected assignable value after prefix operator."); - return ParseRes.res(new ChangeNode(loc, (AssignableNode)res.result, new NumberNode(loc, 1), Operation.SUBTRACT), n + res.n); + return ParseRes.res(new ChangeNode(loc, (ChangeTarget)res.result, new NumberNode(loc, 1), Operation.SUBTRACT), n + res.n); } } 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 cba4934..0f049bd 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 @@ -10,39 +10,56 @@ 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.values.constants.NumberNode; import me.topchetoeu.jscript.compilation.values.constants.StringNode; +import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; -public class IndexNode extends Node implements AssignableNode { +public class IndexNode extends Node implements ChangeTarget { public final Node object; public final Node index; - @Override public void compileBeforeAssign(CompileResult target, boolean op) { + @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"); + 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()); + } + @Override public void beforeChange(CompileResult target) { object.compile(target, true); if (index instanceof NumberNode num && (int)num.value == num.value) { - if (op) { - target.add(Instruction.dup()); - target.add(Instruction.loadMember((int)num.value)); - } + target.add(Instruction.dup()); + target.add(Instruction.loadMember((int)num.value)); } else if (index instanceof StringNode str) { - if (op) { - target.add(Instruction.dup()); - target.add(Instruction.loadMember(str.value)); - } + target.add(Instruction.dup()); + target.add(Instruction.loadMember(str.value)); } else { index.compile(target, true); - if (op) { - target.add(Instruction.dup(1, 1)); - target.add(Instruction.dup(1, 1)); - target.add(Instruction.loadMember()); - } + target.add(Instruction.dup(1, 1)); + target.add(Instruction.dup(1, 1)); + target.add(Instruction.loadMember()); } } - @Override public void compileAfterAssign(CompileResult target, boolean op, boolean pollute) { + @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)); } @@ -54,6 +71,16 @@ public class IndexNode extends Node implements AssignableNode { } } + @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) { + object.compile(target, true); + target.add(Instruction.dup(1, 1)); + compileAfterAssign(target, false, false); + if (!pollute) target.add(Instruction.discard()); + } + // @Override public Node toAssign(Node val, Operation operation) { // return new IndexAssignNode(loc(), object, index, val, operation); // } 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 b3f4350..dcb2335 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 @@ -54,13 +54,13 @@ public class OperationNode extends Node { @Override public ParseRes construct(Source src, int i, Node prev) { var loc = src.loc(i); - if (!(prev instanceof AssignableNode)) return ParseRes.error(loc, String.format("Expected an assignable expression before '%s'", token)); + if (!(prev instanceof AssignTarget)) 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)); - if (operation == null) return ParseRes.res(new AssignNode(loc, ((AssignableNode)prev), other.result), other.n); - else return ParseRes.res(new ChangeNode(loc, ((AssignableNode)prev), other.result, operation), other.n); + 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); } public AssignmentOperatorFactory(String token, int precedence, Operation operation) { 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 420ea74..cd3f133 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 @@ -21,7 +21,7 @@ public class PostfixNode extends ChangeNode { } } - public PostfixNode(Location loc, AssignableNode value, double addAmount) { + public PostfixNode(Location loc, AssignTarget 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 AssignableNode)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator."); + if (!(prev instanceof AssignTarget)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator."); n += 2; - return ParseRes.res(new PostfixNode(loc, (AssignableNode)prev, 1), n); + return ParseRes.res(new PostfixNode(loc, (AssignTarget)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 AssignableNode)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator."); + if (!(prev instanceof AssignTarget)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator."); n += 2; - return ParseRes.res(new PostfixNode(loc, (AssignableNode)prev, -1), n); + return ParseRes.res(new PostfixNode(loc, (AssignTarget)prev, -1), n); } }