diff --git a/src/main/java/me/topchetoeu/jscript/common/Instruction.java b/src/main/java/me/topchetoeu/jscript/common/Instruction.java index b53cd49..fa441f5 100644 --- a/src/main/java/me/topchetoeu/jscript/common/Instruction.java +++ b/src/main/java/me/topchetoeu/jscript/common/Instruction.java @@ -64,8 +64,10 @@ public class Instruction { GLOB_SET(0x61), GLOB_DEF(0x62), - STACK_ALLOC(0x70), - STACK_REALLOC(0x71); + // CAP_INIT(0x70), + VAR_INIT(0x71), + CAP_FREE(0x72), + VAR_FREE(0x73); private static final HashMap types = new HashMap<>(); public final int numeric; @@ -409,11 +411,11 @@ public class Instruction { return new Instruction(Type.DUP, count, offset); } - public static Instruction storeVar(int i) { - return new Instruction(Type.STORE_VAR, i, false); - } - public static Instruction storeVar(int i, boolean keep) { - return new Instruction(Type.STORE_VAR, i, keep); + // public static Instruction storeVar(int i) { + // return new Instruction(Type.STORE_VAR, i, false); + // } + public static Instruction storeVar(int i, boolean keep, boolean initialize) { + return new Instruction(Type.STORE_VAR, i, keep, initialize); } public static Instruction storeMember() { @@ -463,12 +465,21 @@ public class Instruction { return new Instruction(Type.OPERATION, op); } - public static Instruction stackAlloc(int start, int n) { - return new Instruction(Type.STACK_ALLOC, start, start + n); + public static Instruction capFree(int i) { + return new Instruction(Type.CAP_FREE, i); } - public static Instruction stackRealloc(int start, int n) { - return new Instruction(Type.STACK_REALLOC, start, start + n); + public static Instruction varFree(int i) { + return new Instruction(Type.VAR_FREE, i); } + public static Instruction varInit(int i, boolean force) { + return new Instruction(Type.VAR_INIT, i, force); + } + // public static Instruction stackAlloc(int start, int n) { + // return new Instruction(Type.STACK_ALLOC, start, start + n); + // } + // public static Instruction stackRealloc(int start, int n) { + // return new Instruction(Type.STACK_REALLOC, start, start + n); + // } @Override public String toString() { var res = type.toString(); diff --git a/src/main/java/me/topchetoeu/jscript/compilation/ClassNode.java b/src/main/java/me/topchetoeu/jscript/compilation/ClassNode.java new file mode 100644 index 0000000..9b2ba98 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/ClassNode.java @@ -0,0 +1,158 @@ +package me.topchetoeu.jscript.compilation; + +import java.util.ArrayList; +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; +import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.compilation.members.FieldMemberNode; +import me.topchetoeu.jscript.compilation.members.MethodMemberNode; +import me.topchetoeu.jscript.compilation.members.PropertyMemberNode; + +public abstract class ClassNode extends FunctionNode { + public static final class ClassBody { + public final List staticMembers; + public final List protoFields; + public final List protoMembers; + public final Parameters constructorParameters; + public final CompoundNode constructorBody; + + public ClassBody( + List staticMembers, List protoFields, List protoMembers, + Parameters constructorParameters, CompoundNode constructorBody + ) { + this.staticMembers = staticMembers; + this.protoFields = protoFields; + this.protoMembers = protoMembers; + this.constructorParameters = constructorParameters; + this.constructorBody = constructorBody; + } + } + + public final ClassBody body; + public final String name; + + @Override public String name() { return name; } + + public void compileStatic(CompileResult target) { + for (var member : body.staticMembers) member.compile(target, true); + } + public void compilePrototype(CompileResult target) { + if (body.protoMembers.size() > 0) { + target.add(Instruction.dup()); + target.add(Instruction.loadMember("prototype")); + + for (var i = 0; i < body.protoMembers.size() - 1; i++) { + body.protoMembers.get(i).compile(target, true); + } + + body.protoMembers.get(body.protoMembers.size() - 1).compile(target, false); + } + } + + @Override protected void compilePreBody(CompileResult target) { + for (var member : body.protoFields) { + target.add(Instruction.loadThis()); + member.compile(target, false); + } + } + + @Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) { + var id = target.addChild(compileBody(target, name, null)); + target.add(_i -> Instruction.loadFunc(id, false, true, false, name, captures(id, target))); + compileStatic(target); + compilePrototype(target); + } + + public ClassNode(Location loc, Location end, String name, ClassBody body) { + super(loc, end, body.constructorParameters, body.constructorBody); + + this.name = name; + this.body = body; + } + + public static ParseRes parseMember(Source src, int i) { + return ParseRes.first(src, i, + PropertyMemberNode::parse, + FieldMemberNode::parseClass, + MethodMemberNode::parse + ); + } + + public static ParseRes parseBody(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 fields = new LinkedList(); + var members = new LinkedList(); + var statics = new LinkedList(); + + var params = new Parameters(new ArrayList<>()); + var body = new CompoundNode(loc, false); + var hasConstr = false; + + if (src.is(i + n, "}")) { + n++; + return ParseRes.res(new ClassBody(statics, fields, members, params, body), n); + } + + while (true) { + ParseRes prop = parseMember(src, i + n); + + if (prop.isSuccess()) { + n += prop.n; + + if (prop.result instanceof FieldMemberNode field) fields.add(field); + else if (prop.result instanceof MethodMemberNode method && method.name().equals("constructor")) { + if (hasConstr) return ParseRes.error(loc, "A class may only have one constructor"); + + params = method.params; + body = method.body; + hasConstr = true; + } + else members.add(prop.result); + } + else if (Parsing.isIdentifier(src, i + n, "static")) { + n += 6; + + var staticProp = parseMember(src, i + n); + if (!staticProp.isSuccess()) { + if (prop.isError()) return prop.chainError(); + else return staticProp.chainError(src.loc(i + n), "Expected a member after 'static' keyword"); + } + n += staticProp.n; + + statics.add(staticProp.result); + } + else { + var end = JavaScript.parseStatementEnd(src, i + n); + if (end.isSuccess()) n += end.n; + else return ParseRes.error(src.loc(i + n), "Expected a member, end of statement or a closing colon"); + } + + n += Parsing.skipEmpty(src, i + n); + + 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 ClassBody(statics, fields, members, params, body), n); + } + + // public FunctionStatementNode(Location loc, Location end, Parameters params, CompoundNode body, String name) { + // super(loc, end, params, body); + // this.name = name; + // } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/ClassStatementNode.java b/src/main/java/me/topchetoeu/jscript/compilation/ClassStatementNode.java new file mode 100644 index 0000000..eaeb32c --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/ClassStatementNode.java @@ -0,0 +1,40 @@ +package me.topchetoeu.jscript.compilation; + +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; + +public class ClassStatementNode extends ClassNode { + @Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) { + super.compile(target, pollute, name, bp); + var i = target.scope.define(DeclarationType.LET, name(), loc()); + target.add(_i -> i.index().toInit()); + if (pollute) target.add(Instruction.pushUndefined()); + } + + public ClassStatementNode(Location loc, Location end, String name, ClassBody body) { + super(loc, end, name, body); + } + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!Parsing.isIdentifier(src, i + n, "class")) return ParseRes.failed(); + n += 5; + + var name = Parsing.parseIdentifier(src, i + n); + if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected a class name"); + n += name.n; + + var body = parseBody(src, i + n); + if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a class body"); + n += body.n; + + return ParseRes.res(new ClassStatementNode(loc, src.loc(i + n), name.result, body.result), n); + } +} diff --git a/src/main/java/me/topchetoeu/jscript/compilation/CompileResult.java b/src/main/java/me/topchetoeu/jscript/compilation/CompileResult.java index 60e86e8..04711d5 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/CompileResult.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/CompileResult.java @@ -78,6 +78,31 @@ public final class CompileResult { setLocationAndDebug(instructions.size() - 1, loc, type); } + public void beginScope() { + // for (var cap : scope.capturables()) { + // add(_i -> Instruction.capInit(cap.index().index)); + // } + } + public void reallocScope() { + for (var cap : scope.capturables()) { + add(_i -> cap.index().toGet()); + add(_i -> Instruction.capFree(cap.index().index)); + add(_i -> cap.index().toInit()); + } + + scope.end(); + } + public void endScope() { + for (var cap : scope.capturables()) { + add(_i -> Instruction.capFree(cap.index().index)); + } + for (var var : scope.locals()) { + add(_i -> Instruction.varFree(var.index().index)); + } + + scope.end(); + } + public int addChild(CompileResult res) { this.children.add(res); return this.children.size() - 1; diff --git a/src/main/java/me/topchetoeu/jscript/compilation/CompoundNode.java b/src/main/java/me/topchetoeu/jscript/compilation/CompoundNode.java index f32edbf..b99a75f 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/CompoundNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/CompoundNode.java @@ -25,10 +25,7 @@ public class CompoundNode extends Node { List statements = new ArrayList(); var subtarget = hasScope ? target.subtarget() : target; - if (hasScope) { - subtarget.add(i -> Instruction.stackAlloc(subtarget.scope.capturablesOffset(), subtarget.scope.allocCount())); - subtarget.scope.singleEntry = singleEntry; - } + if (hasScope) subtarget.beginScope(); for (var stm : this.statements) { if (stm instanceof FunctionStatementNode func) { @@ -46,7 +43,7 @@ public class CompoundNode extends Node { else stm.compile(subtarget, polluted = pollute, BreakpointType.STEP_OVER); } - if (hasScope) subtarget.scope.end(); + if (hasScope) subtarget.endScope(); if (!polluted && pollute) { target.add(Instruction.pushUndefined()); diff --git a/src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java b/src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java index e37a9cd..270ea0d 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/FunctionNode.java @@ -22,6 +22,8 @@ public abstract class FunctionNode extends Node { return ((FunctionScope)target.children.get(id).scope).getCaptureIndices(); } + protected void compilePreBody(CompileResult target) { } + public final CompileResult compileBody(Environment env, FunctionScope scope, boolean lastReturn, String _name, String selfName) { var name = this.name() != null ? this.name() : _name; @@ -30,6 +32,8 @@ public abstract class FunctionNode extends Node { .remove(LabelContext.CONTINUE_CTX); return new CompileResult(env, scope, params.params.size(), target -> { + compilePreBody(target); + if (params.params.size() > 0) { target.add(Instruction.loadArgs(true)); if (params.params.size() > 1) target.add(Instruction.dup(params.params.size() - 1, 0)); @@ -50,7 +54,7 @@ public abstract class FunctionNode extends Node { var i = scope.defineSpecial(new Variable(selfName, true), end); target.add(Instruction.loadCallee()); - target.add(_i -> i.index().toSet(false)); + target.add(_i -> i.index().toInit()); } body.resolve(target); diff --git a/src/main/java/me/topchetoeu/jscript/compilation/FunctionStatementNode.java b/src/main/java/me/topchetoeu/jscript/compilation/FunctionStatementNode.java index 550730a..6de7691 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/FunctionStatementNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/FunctionStatementNode.java @@ -18,7 +18,8 @@ public class FunctionStatementNode extends FunctionNode { @Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) { var id = target.addChild(compileBody(target, name, null)); target.add(_i -> Instruction.loadFunc(id, true, true, false, name, captures(id, target))); - target.add(VariableNode.toSet(target, end, this.name, pollute, true)); + target.add(VariableNode.toInit(target, end, this.name)); + if (pollute) target.add(Instruction.pushUndefined()); } public FunctionStatementNode(Location loc, Location end, Parameters params, CompoundNode body, String name) { diff --git a/src/main/java/me/topchetoeu/jscript/compilation/JavaScript.java b/src/main/java/me/topchetoeu/jscript/compilation/JavaScript.java index f11542b..cc0441d 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/JavaScript.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/JavaScript.java @@ -28,6 +28,7 @@ import me.topchetoeu.jscript.compilation.control.WhileNode; import me.topchetoeu.jscript.compilation.scope.FunctionScope; import me.topchetoeu.jscript.compilation.values.ArgumentsNode; import me.topchetoeu.jscript.compilation.values.ArrayNode; +import me.topchetoeu.jscript.compilation.values.ClassValueNode; import me.topchetoeu.jscript.compilation.values.ObjectNode; import me.topchetoeu.jscript.compilation.values.RegexNode; import me.topchetoeu.jscript.compilation.values.ThisNode; @@ -63,7 +64,7 @@ public final class JavaScript { "finally", "for", "do", "while", "switch", "case", "default", "new", "function", "var", "return", "throw", "typeof", "delete", "break", "continue", "debugger", "implements", "interface", "package", "private", - "protected", "public", "static", "arguments" + "protected", "public", "static", "arguments", "class" )); public static ParseRes parseParens(Source src, int i) { @@ -88,6 +89,7 @@ public final class JavaScript { return ParseRes.first(src, i, (s, j) -> statement ? ParseRes.failed() : ObjectNode.parse(s, j), (s, j) -> statement ? ParseRes.failed() : FunctionNode.parseFunction(s, j, false), + (s, j) -> statement ? ParseRes.failed() : ClassValueNode.parse(s, j), JavaScript::parseLiteral, StringNode::parse, RegexNode::parse, @@ -96,7 +98,7 @@ public final class JavaScript { ChangeNode::parsePrefixIncrease, OperationNode::parsePrefix, ArrayNode::parse, - FunctionArrowNode::parse, + (s, j) -> statement ? ParseRes.failed() : FunctionArrowNode.parse(s, j), JavaScript::parseParens, CallNode::parseNew, TypeofNode::parse, @@ -188,6 +190,7 @@ public final class JavaScript { if (Parsing.isIdentifier(src, i + n, "with")) return ParseRes.error(src.loc(i + n), "'with' statements are not allowed."); ParseRes res = ParseRes.first(src, i + n, + ClassStatementNode::parse, VariableDeclareNode::parse, ReturnNode::parse, ThrowNode::parse, diff --git a/src/main/java/me/topchetoeu/jscript/compilation/VariableDeclareNode.java b/src/main/java/me/topchetoeu/jscript/compilation/VariableDeclareNode.java index d762e8e..85e3d0f 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/VariableDeclareNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/VariableDeclareNode.java @@ -37,8 +37,8 @@ public class VariableDeclareNode extends Node { @Override public void compile(CompileResult target, boolean pollute) { for (var entry : values) { if (entry.value == null) { - if (declType == DeclarationType.VAR) entry.destructor.declare(target, null); - else entry.destructor.declare(target, declType); + if (declType == DeclarationType.VAR) entry.destructor.declare(target, null, false); + else entry.destructor.declare(target, declType, false); } else { entry.value.compile(target, true); 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 b331267..454cfb3 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/ForInNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/ForInNode.java @@ -12,24 +12,20 @@ import me.topchetoeu.jscript.compilation.DeferredIntSupplier; import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.LabelContext; 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; +import me.topchetoeu.jscript.compilation.patterns.Binding; public class ForInNode extends Node { - public final String varName; - public final DeclarationType declType; + public final Binding binding; public final Node object, body; public final String label; - public final Location varLocation; @Override public void resolve(CompileResult target) { body.resolve(target); - if (declType != null && !declType.strict) target.scope.define(new Variable(varName, false), loc()); + binding.resolve(target); } @Override public void compile(CompileResult target, boolean pollute) { - if (declType != null && declType.strict) target.scope.defineStrict(new Variable(varName, declType.readonly), varLocation); + binding.declareLateInit(target); object.compile(target, true, BreakpointType.STEP_OVER); target.add(Instruction.keys(false, true)); @@ -38,8 +34,8 @@ public class ForInNode extends Node { target.add(Instruction.dup()); int mid = target.temp(); - target.add(Instruction.loadMember("value")).setLocation(varLocation); - target.add(VariableNode.toSet(target, loc(), varName, pollute, declType != null && declType.strict)); + target.add(Instruction.loadMember("value")).setLocation(binding.loc()); + binding.assign(target, false); target.setLocationAndDebug(object.loc(), BreakpointType.STEP_OVER); var end = new DeferredIntSupplier(); @@ -56,12 +52,10 @@ public class ForInNode extends Node { if (pollute) target.add(Instruction.pushUndefined()); } - public ForInNode(Location loc, Location varLocation, String label, DeclarationType declType, String varName, Node object, Node body) { + public ForInNode(Location loc, String label, Binding binding, Node object, Node body) { super(loc); - this.varLocation = varLocation; this.label = label; - this.declType = declType; - this.varName = varName; + this.binding = binding; this.object = object; this.body = body; } @@ -82,13 +76,9 @@ public class ForInNode extends Node { n++; n += Parsing.skipEmpty(src, i + n); - var declType = JavaScript.parseDeclarationType(src, i + n); - n += declType.n; - - var name = Parsing.parseIdentifier(src, i + n); - if (!name.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a variable name for for-in loop"); - var nameLoc = src.loc(i + n); - n += name.n; + var binding = Binding.parse(src, i + n); + if (!binding.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a binding in for-in loop"); + n += binding.n; n += Parsing.skipEmpty(src, i + n); if (!Parsing.isIdentifier(src, i + n, "in")) return ParseRes.error(src.loc(i + n), "Expected 'in' keyword after variable declaration"); @@ -106,6 +96,6 @@ public class ForInNode extends Node { if (!bodyRes.isSuccess()) return bodyRes.chainError(src.loc(i + n), "Expected a for-in body"); n += bodyRes.n; - return ParseRes.res(new ForInNode(loc, nameLoc, label.result, declType.result, name.result, obj.result, bodyRes.result), n); + return ParseRes.res(new ForInNode(loc, label.result, binding.result, obj.result, bodyRes.result), n); } } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/ForNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/ForNode.java index 933ec14..2b78601 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/ForNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/ForNode.java @@ -26,7 +26,7 @@ public class ForNode extends Node { @Override public void compile(CompileResult target, boolean pollute) { var subtarget = target.subtarget(); subtarget.scope.singleEntry = false; - subtarget.add(i -> Instruction.stackAlloc(subtarget.scope.capturablesOffset(), subtarget.scope.allocCount())); + subtarget.beginScope(); declaration.compile(subtarget, false, BreakpointType.STEP_OVER); @@ -40,7 +40,7 @@ public class ForNode extends Node { CompoundNode.compileMultiEntry(body, subtarget, false, BreakpointType.STEP_OVER); LabelContext.popLoop(subtarget.env, label); - subtarget.add(_i -> Instruction.stackRealloc(subtarget.scope.capturablesOffset(), subtarget.scope.allocCount())); + subtarget.reallocScope(); CompoundNode.compileMultiEntry(assignment, subtarget, false, BreakpointType.STEP_OVER); int endI = subtarget.size(); @@ -51,7 +51,7 @@ public class ForNode extends Node { subtarget.set(mid, Instruction.jmpIfNot(endI - mid + 1)); if (pollute) subtarget.add(Instruction.pushUndefined()); - subtarget.scope.end(); + subtarget.endScope(); } public ForNode(Location loc, String label, Node declaration, Node condition, Node assignment, Node body) { diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/ForOfNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/ForOfNode.java index b675e7b..6438c9f 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/ForOfNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/ForOfNode.java @@ -12,24 +12,20 @@ import me.topchetoeu.jscript.compilation.DeferredIntSupplier; import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.LabelContext; 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; +import me.topchetoeu.jscript.compilation.patterns.Binding; public class ForOfNode extends Node { - public final String varName; - public final DeclarationType declType; + public final Binding binding; public final Node iterable, body; public final String label; - public final Location varLocation; @Override public void resolve(CompileResult target) { body.resolve(target); - if (declType != null && !declType.strict) target.scope.define(new Variable(varName, false), varLocation); + binding.resolve(target); } @Override public void compile(CompileResult target, boolean pollute) { - if (declType != null && declType.strict) target.scope.defineStrict(new Variable(varName, declType.readonly), varLocation); + binding.declareLateInit(target); iterable.compile(target, true, BreakpointType.STEP_OVER); target.add(Instruction.dup()); @@ -46,8 +42,8 @@ public class ForOfNode extends Node { target.add(Instruction.loadMember("done")).setLocation(iterable.loc()); int mid = target.temp(); - target.add(Instruction.loadMember("value")).setLocation(varLocation); - target.add(VariableNode.toSet(target, varLocation, varName, false, declType != null && declType.strict)); + target.add(Instruction.loadMember("value")).setLocation(binding.loc); + binding.assign(target, false); var end = new DeferredIntSupplier(); @@ -65,12 +61,10 @@ public class ForOfNode extends Node { if (pollute) target.add(Instruction.pushUndefined()); } - public ForOfNode(Location loc, Location varLocation, String label, DeclarationType declType, String varName, Node object, Node body) { + public ForOfNode(Location loc, String label, Binding binding, Node object, Node body) { super(loc); - this.varLocation = varLocation; this.label = label; - this.declType = declType; - this.varName = varName; + this.binding = binding; this.iterable = object; this.body = body; } @@ -91,13 +85,9 @@ public class ForOfNode extends Node { n++; n += Parsing.skipEmpty(src, i + n); - var declType = JavaScript.parseDeclarationType(src, i + n); - n += declType.n; - - var name = Parsing.parseIdentifier(src, i + n); - if (!name.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a variable name for for-of loop"); - var nameLoc = src.loc(i + n); - n += name.n; + var binding = Binding.parse(src, i + n); + if (!binding.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a binding in for-of loop"); + n += binding.n; n += Parsing.skipEmpty(src, i + n); if (!Parsing.isIdentifier(src, i + n, "of")) return ParseRes.error(src.loc(i + n), "Expected 'of' keyword after variable declaration"); @@ -115,6 +105,6 @@ public class ForOfNode extends Node { if (!bodyRes.isSuccess()) return bodyRes.chainError(src.loc(i + n), "Expected a for-of body"); n += bodyRes.n; - return ParseRes.res(new ForOfNode(loc, nameLoc, label.result, declType.result, name.result, obj.result, bodyRes.result), n); + return ParseRes.res(new ForOfNode(loc, label.result, binding.result, obj.result, bodyRes.result), n); } } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/SwitchNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/SwitchNode.java index e8a729b..ee00222 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/SwitchNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/SwitchNode.java @@ -44,7 +44,7 @@ public class SwitchNode extends Node { value.compile(target, true, BreakpointType.STEP_OVER); var subtarget = target.subtarget(); - subtarget.add(_i -> Instruction.stackAlloc(subtarget.scope.capturablesOffset(), subtarget.scope.allocCount())); + subtarget.beginScope(); // TODO: create a jump map for (var ccase : cases) { @@ -64,7 +64,7 @@ public class SwitchNode extends Node { } LabelContext.getBreak(target.env).pop(label); - subtarget.scope.end(); + subtarget.endScope(); int endI = subtarget.size(); end.set(endI); diff --git a/src/main/java/me/topchetoeu/jscript/compilation/control/TryNode.java b/src/main/java/me/topchetoeu/jscript/compilation/control/TryNode.java index fffb14e..18700b3 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/control/TryNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/control/TryNode.java @@ -43,13 +43,14 @@ public class TryNode extends Node { if (captureName != null) { var subtarget = target.subtarget(); - subtarget.add(i -> Instruction.stackAlloc(subtarget.scope.capturablesOffset(), subtarget.scope.allocCount())); + subtarget.beginScope(); subtarget.scope.singleEntry = true; var catchVar = subtarget.scope.defineStrict(new Variable(captureName, false), catchBody.loc()); subtarget.add(Instruction.loadError()); - subtarget.add(_i -> catchVar.index().toSet(false)); + subtarget.add(catchVar.index().toInit()); catchBody.compile(subtarget, false); + subtarget.endScope(); subtarget.scope.end(); } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/members/AssignShorthandNode.java b/src/main/java/me/topchetoeu/jscript/compilation/members/AssignShorthandNode.java new file mode 100644 index 0000000..5fc491c --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/members/AssignShorthandNode.java @@ -0,0 +1,54 @@ +package me.topchetoeu.jscript.compilation.members; + +import me.topchetoeu.jscript.common.SyntaxException; +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.patterns.AssignTarget; +import me.topchetoeu.jscript.compilation.values.VariableNode; +import me.topchetoeu.jscript.compilation.values.constants.StringNode; +import me.topchetoeu.jscript.compilation.values.operations.AssignNode; + +public class AssignShorthandNode extends Node { + public final Node key; + public final AssignTarget target; + public final Node value; + + @Override public void compile(CompileResult target, boolean pollute) { + throw new SyntaxException(loc(), "Unexpected assign shorthand in non-destructor context"); + } + + public AssignShorthandNode(Location loc, Node key, AssignTarget target, Node value) { + super(loc); + this.key = key; + this.target = target; + this.value = value; + } + + public AssignTarget target() { + return new AssignNode(loc(), target, value); + } + + public static ParseRes parse(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; + 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 shorthand initializer"); + n += value.n; + + return ParseRes.res(new AssignShorthandNode(loc, new StringNode(loc, var.result.name), var.result, value.result), n); + } +} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/compilation/members/FieldMemberNode.java b/src/main/java/me/topchetoeu/jscript/compilation/members/FieldMemberNode.java new file mode 100644 index 0000000..9706722 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/members/FieldMemberNode.java @@ -0,0 +1,89 @@ +package me.topchetoeu.jscript.compilation.members; + +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.JavaScript; +import me.topchetoeu.jscript.compilation.Node; +import me.topchetoeu.jscript.compilation.values.ObjectNode; +import me.topchetoeu.jscript.compilation.values.VariableNode; +import me.topchetoeu.jscript.compilation.values.constants.StringNode; + +public class FieldMemberNode extends Node { + public final Node key; + public final Node value; + + @Override public void compile(CompileResult target, boolean pollute) { + if (pollute) target.add(Instruction.dup()); + 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 = ObjectNode.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; + + return ParseRes.res(new FieldMemberNode(loc, new StringNode(loc, var.result.name), var.result), n); + } + + public static ParseRes parseClass(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + var name = ObjectNode.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); + } +} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/compilation/members/MethodMemberNode.java b/src/main/java/me/topchetoeu/jscript/compilation/members/MethodMemberNode.java new file mode 100644 index 0000000..8636c3d --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/members/MethodMemberNode.java @@ -0,0 +1,62 @@ +package me.topchetoeu.jscript.compilation.members; + +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.CompileResult; +import me.topchetoeu.jscript.compilation.CompoundNode; +import me.topchetoeu.jscript.compilation.FunctionNode; +import me.topchetoeu.jscript.compilation.Node; +import me.topchetoeu.jscript.compilation.Parameters; +import me.topchetoeu.jscript.compilation.values.ObjectNode; +import me.topchetoeu.jscript.compilation.values.constants.StringNode; + +public 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) { + if (pollute) target.add(Instruction.dup()); + 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 = ObjectNode.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); + } +} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/compilation/members/PropertyMemberNode.java b/src/main/java/me/topchetoeu/jscript/compilation/members/PropertyMemberNode.java new file mode 100644 index 0000000..6e49e62 --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/members/PropertyMemberNode.java @@ -0,0 +1,81 @@ +package me.topchetoeu.jscript.compilation.members; + +import java.util.Arrays; + +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.CompileResult; +import me.topchetoeu.jscript.compilation.CompoundNode; +import me.topchetoeu.jscript.compilation.FunctionNode; +import me.topchetoeu.jscript.compilation.Node; +import me.topchetoeu.jscript.compilation.Parameters; +import me.topchetoeu.jscript.compilation.patterns.Pattern; +import me.topchetoeu.jscript.compilation.values.ObjectNode; +import me.topchetoeu.jscript.compilation.values.constants.StringNode; + +public class PropertyMemberNode extends FunctionNode { + public final Node key; + public final Pattern argument; + + @Override public String name() { + if (key instanceof StringNode str) { + if (isGetter()) return "get " + str.value; + else return "set " + str.value; + } + else return null; + } + + public boolean isGetter() { return argument == null; } + public boolean isSetter() { return argument != null; } + + @Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) { + if (pollute) target.add(Instruction.dup()); + 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(Arrays.asList()) : new Parameters(Arrays.asList(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 = ObjectNode.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); + } +} \ No newline at end of file diff --git a/src/main/java/me/topchetoeu/jscript/compilation/patterns/AssignPattern.java b/src/main/java/me/topchetoeu/jscript/compilation/patterns/AssignPattern.java index c4d1c11..e6ea21a 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/patterns/AssignPattern.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/patterns/AssignPattern.java @@ -14,19 +14,17 @@ import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType; public class AssignPattern implements Pattern { public final Location loc; - public final Pattern assignable; + public final AssignTarget assignable; public final Node value; @Override public Location loc() { return loc; } @Override public void destructDeclResolve(CompileResult target) { - assignable.destructDeclResolve(target); + if (!(assignable instanceof Pattern p)) throw new SyntaxException(assignable.loc(), "Unexpected non-pattern in destruct context"); + p.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) { + private void common(CompileResult target) { target.add(Instruction.dup()); target.add(Instruction.pushUndefined()); target.add(Instruction.operation(Operation.EQUALS)); @@ -36,8 +34,27 @@ public class AssignPattern implements Pattern { value.compile(target, true); target.set(start, Instruction.jmpIfNot(target.size() - start)); + } - assignable.destruct(target, decl, shouldDeclare); + @Override public void declare(CompileResult target, DeclarationType decl, boolean lateInitializer) { + if (lateInitializer) { + if (assignable instanceof Pattern p) p.declare(target, decl, lateInitializer); + else throw new SyntaxException(assignable.loc(), "Unexpected non-pattern in destruct context"); + } + else throw new SyntaxException(loc(), "Expected an assignment value for destructor declaration"); + } + @Override public void destruct(CompileResult target, DeclarationType decl, boolean shouldDeclare) { + if (!(assignable instanceof Pattern p)) throw new SyntaxException(assignable.loc(), "Unexpected non-pattern in destruct context"); + common(target); + p.destruct(target, decl, shouldDeclare); + } + + @Override public void beforeAssign(CompileResult target) { + assignable.beforeAssign(target); + } + @Override public void afterAssign(CompileResult target, boolean pollute) { + common(target); + assignable.afterAssign(target, false); } public AssignPattern(Location loc, Pattern assignable, Node value) { diff --git a/src/main/java/me/topchetoeu/jscript/compilation/patterns/Binding.java b/src/main/java/me/topchetoeu/jscript/compilation/patterns/Binding.java new file mode 100644 index 0000000..bf7524f --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/patterns/Binding.java @@ -0,0 +1,80 @@ +package me.topchetoeu.jscript.compilation.patterns; + +import me.topchetoeu.jscript.common.SyntaxException; +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.JavaScript.DeclarationType; + +public class Binding implements Pattern { + public final Location loc; + public final DeclarationType type; + public final AssignTarget assignable; + + @Override public Location loc() { return loc; } + + @Override public void destructDeclResolve(CompileResult target) { + if (type != null && !type.strict) { + if (!(assignable instanceof Pattern p)) throw new SyntaxException(assignable.loc(), "Unexpected non-pattern in destruct context"); + p.destructDeclResolve(target); + } + } + + @Override public void destruct(CompileResult target, DeclarationType decl, boolean shouldDeclare) { + if (!(assignable instanceof Pattern p)) throw new SyntaxException(assignable.loc(), "Unexpected non-pattern in destruct context"); + p.destruct(target, decl, shouldDeclare); + } + @Override public void declare(CompileResult target, DeclarationType decl, boolean lateInitializer) { + if (!(assignable instanceof Pattern p)) throw new SyntaxException(assignable.loc(), "Unexpected non-pattern in destruct context"); + p.declare(target, decl, lateInitializer); + } + + public void resolve(CompileResult target) { + if (type != null) destructDeclResolve(target); + } + + public void declare(CompileResult target, boolean hasInit) { + if (type != null) destructVar(target, type, hasInit); + } + public void declareLateInit(CompileResult target) { + if (type != null) declare(target, type, true); + } + + @Override public void afterAssign(CompileResult target, boolean pollute) { + assignable.assign(target, pollute); + } + + public Binding(Location loc, DeclarationType type, AssignTarget assignable) { + this.loc = loc; + this.type = type; + this.assignable = assignable; + } + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + var declType = JavaScript.parseDeclarationType(src, i + n); + if (!declType.isSuccess()) { + var res = JavaScript.parseExpression(src, i + n, 13); + if (res.isSuccess() && res.result instanceof AssignTargetLike target) { + n += res.n; + return ParseRes.res(new Binding(loc, null, target.toAssignTarget()), n); + } + else return ParseRes.failed(); + } + else { + n += declType.n; + n += Parsing.skipEmpty(src, i + n); + + var res = Pattern.parse(src, i + n, false); + if (!res.isSuccess()) return ParseRes.failed(); + n += res.n; + + return ParseRes.res(new Binding(loc, declType.result, res.result), 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 deleted file mode 100644 index f3abc5e..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/patterns/ObjectAssignable.java +++ /dev/null @@ -1,16 +0,0 @@ -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 deleted file mode 100644 index de7b51f..0000000 --- a/src/main/java/me/topchetoeu/jscript/compilation/patterns/ObjectDestructor.java +++ /dev/null @@ -1,44 +0,0 @@ -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 index ca395fa..65e508d 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/patterns/ObjectPattern.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/patterns/ObjectPattern.java @@ -2,36 +2,79 @@ package me.topchetoeu.jscript.compilation.patterns; import java.util.LinkedList; import java.util.List; +import java.util.function.Consumer; +import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.SyntaxException; 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.values.ObjectNode; import me.topchetoeu.jscript.compilation.values.VariableNode; import me.topchetoeu.jscript.compilation.values.constants.StringNode; +import me.topchetoeu.jscript.compilation.values.operations.IndexNode; + +public class ObjectPattern extends Node implements Pattern { + public static final class Member { + public final Node key; + public final AssignTarget consumable; + + public Member(Node key, AssignTarget consumer) { + this.key = key; + this.consumable = consumer; + } + } + + public final List members; + + 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 class ObjectPattern extends ObjectDestructor implements Pattern { @Override public void destructDeclResolve(CompileResult target) { - consume(t -> t.destructDeclResolve(target)); + for (var t : members) { + if (t.consumable instanceof Pattern p) p.destructDeclResolve(target); + else throw new SyntaxException(t.consumable.loc(), "Unexpected non-pattern in destruct context"); + } } @Override public void destruct(CompileResult target, DeclarationType decl, boolean shouldDeclare) { - compile(target, t -> t.destruct(target, decl, shouldDeclare), false); + compile(target, t -> { + if (t instanceof Pattern p) p.destruct(target, decl, shouldDeclare); + else throw new SyntaxException(t.loc(), "Unexpected non-pattern in destruct context"); + }, false); } - @Override public void declare(CompileResult target, DeclarationType decl) { - throw new SyntaxException(loc(), "Object pattern must be initialized"); + @Override public void afterAssign(CompileResult target, boolean pollute) { + compile(target, t -> t.assign(target, false), pollute); } - public ObjectPattern(Location loc, List> members) { - super(loc, members); + @Override public void declare(CompileResult target, DeclarationType decl, boolean lateInitializer) { + if (lateInitializer) { + for (var t : members) { + if (t.consumable instanceof Pattern p) p.declare(target, decl, lateInitializer); + else throw new SyntaxException(t.consumable.loc(), "Unexpected non-pattern in destruct context"); + } + } + else throw new SyntaxException(loc(), "Object pattern must be initialized"); } - private static ParseRes> parseShorthand(Source src, int i) { + public ObjectPattern(Location loc, List members) { + super(loc); + this.members = members; + } + + private static ParseRes parseShorthand(Source src, int i) { ParseRes res = ParseRes.first(src, i, AssignPattern::parse, VariableNode::parse @@ -40,17 +83,17 @@ public class ObjectPattern extends ObjectDestructor implements Pattern 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); + 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 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) { + private static ParseRes parseKeyed(Source src, int i) { var n = Parsing.skipEmpty(src, i); var key = ObjectNode.parsePropName(src, i + n); @@ -65,7 +108,7 @@ public class ObjectPattern extends ObjectDestructor implements Pattern 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); + return ParseRes.res(new Member(key.result, res.result), n); } public static ParseRes parse(Source src, int i) { @@ -76,7 +119,7 @@ public class ObjectPattern extends ObjectDestructor implements Pattern n++; n += Parsing.skipEmpty(src, i + n); - var members = new LinkedList>(); + var members = new LinkedList(); if (src.is(i + n, "}")) { n++; @@ -84,7 +127,7 @@ public class ObjectPattern extends ObjectDestructor implements Pattern } while (true) { - ParseRes> prop = ParseRes.first(src, i + n, + ParseRes prop = ParseRes.first(src, i + n, ObjectPattern::parseKeyed, ObjectPattern::parseShorthand ); diff --git a/src/main/java/me/topchetoeu/jscript/compilation/patterns/Pattern.java b/src/main/java/me/topchetoeu/jscript/compilation/patterns/Pattern.java index 44a4ad6..07d07fb 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/patterns/Pattern.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/patterns/Pattern.java @@ -10,7 +10,7 @@ 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 { +public interface Pattern extends AssignTarget { Location loc(); /** @@ -28,7 +28,21 @@ public interface Pattern { /** * Run when destructing a declaration without an initializer */ - void declare(CompileResult target, DeclarationType decl); + void declare(CompileResult target, DeclarationType decl, boolean lateInitializer); + + public default void destructArg(CompileResult target, DeclarationType decl) { + destruct(target, decl, false); + } + public default void destructVar(CompileResult target, DeclarationType decl, boolean hasInitializer) { + if (hasInitializer) { + if (decl == null || !decl.strict) destruct(target, null, true); + else destruct(target, decl, true); + } + else { + if (decl == null || !decl.strict) declare(target, null, false); + else declare(target, decl, false); + } + } public static ParseRes parse(Source src, int i, boolean withDefault) { return withDefault ? diff --git a/src/main/java/me/topchetoeu/jscript/compilation/scope/FunctionScope.java b/src/main/java/me/topchetoeu/jscript/compilation/scope/FunctionScope.java index 5daf8aa..c5319bc 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/scope/FunctionScope.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/scope/FunctionScope.java @@ -36,7 +36,7 @@ public class FunctionScope extends Scope { } else { functionVarMap.put(var.name, var); - return variables.add(var); + return locals.add(var); } } public Variable defineSpecial(Variable var, Location loc) { @@ -44,7 +44,7 @@ public class FunctionScope extends Scope { if (strictVarMap.containsKey(var.name)) throw alreadyDefinedErr(loc, var.name); specialVarMap.put(var.name, var); - return variables.add(var); + return locals.add(var); } @Override public Variable get(String name, boolean capture) { 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 e848e40..bed8f93 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/scope/Scope.java @@ -10,8 +10,8 @@ import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType; public class Scope { protected final HashMap strictVarMap = new HashMap<>(); - protected final VariableList variables = new VariableList(VariableIndex.IndexType.LOCALS, this::variableOffset); - protected final VariableList captured = new VariableList(VariableIndex.IndexType.CAPTURABLES, this::capturablesOffset); + protected final VariableList locals = new VariableList(VariableIndex.IndexType.LOCALS, this::variableOffset); + protected final VariableList capturables = new VariableList(VariableIndex.IndexType.CAPTURABLES, this::capturablesOffset); private boolean ended = false; private boolean finished = false; @@ -29,7 +29,7 @@ public class Scope { protected void transferCaptured(Variable var) { if (!singleEntry) { - this.captured.add(var); + this.capturables.add(var); } else if (parent != null) { parent.transferCaptured(var); @@ -60,7 +60,7 @@ public class Scope { */ public Variable defineTemp() { checkNotEnded(); - return this.variables.add(new Variable("", false)); + return this.locals.add(new Variable("", false)); } /** @@ -96,7 +96,7 @@ public class Scope { if (hasNonStrict(var.name)) throw alreadyDefinedErr(loc, var.name); strictVarMap.put(var.name, var); - return variables.add(var); + return locals.add(var); } /** * Gets the index supplier of the given variable name, or null if it is a global @@ -129,7 +129,7 @@ public class Scope { var res = 0; for (var curr = parent; curr != null; curr = curr.parent) { - res += parent.variables.size(); + res += parent.locals.size(); } return res; @@ -138,7 +138,7 @@ public class Scope { var res = 0; for (var curr = this; curr != null; curr = curr.parent) { - if (curr != this) res += parent.captured.size(); + if (curr != this) res += parent.capturables.size(); if (curr.parent == null) res += curr.localsCount(); } @@ -157,19 +157,26 @@ public class Scope { if (res < childN) res = childN; } - return res + variables.size(); + return res + locals.size(); } public int capturesCount() { return 0; } public int allocCount() { - var res = captured.size(); + var res = capturables.size(); return res; } public int capturablesCount() { - var res = captured.size(); + var res = capturables.size(); for (var child : children) res += child.capturablesCount(); return res; } + public Iterable capturables() { + return capturables.all(); + } + public Iterable locals() { + return locals.all(); + } + /** * Ends this scope. This will make it possible for another child to take its place */ @@ -187,8 +194,8 @@ public class Scope { } protected void onFinish() { - this.variables.freeze(); - this.captured.freeze(); + this.locals.freeze(); + this.capturables.freeze(); } /** diff --git a/src/main/java/me/topchetoeu/jscript/compilation/scope/VariableIndex.java b/src/main/java/me/topchetoeu/jscript/compilation/scope/VariableIndex.java index 3e04a05..374d7b1 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/scope/VariableIndex.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/scope/VariableIndex.java @@ -30,9 +30,25 @@ public final class VariableIndex { } public final Instruction toSet(boolean keep) { switch (type) { - case CAPTURES: return Instruction.storeVar(~index, keep); - case CAPTURABLES: return Instruction.storeVar(index, keep); - case LOCALS: return Instruction.storeVar(index, keep); + case CAPTURES: return Instruction.storeVar(index, keep, false); + case CAPTURABLES: return Instruction.storeVar(index, keep, false); + case LOCALS: return Instruction.storeVar(index, keep, false); + default: throw new UnsupportedOperationException("Unknown index type " + type); + } + } + public final Instruction toInit() { + switch (type) { + case CAPTURES: throw new UnsupportedOperationException("Unknown index type " + type); + case CAPTURABLES: return Instruction.storeVar(index, false, true); + case LOCALS: return Instruction.storeVar(index, false, true); + default: throw new UnsupportedOperationException("Unknown index type " + type); + } + } + public final Instruction toUndefinedInit(boolean force) { + switch (type) { + case CAPTURES: throw new UnsupportedOperationException("Unknown index type " + type); + case CAPTURABLES: return Instruction.varInit(index, force); + case LOCALS: return Instruction.varInit(index, force); default: throw new UnsupportedOperationException("Unknown index type " + type); } } diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/ClassValueNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/ClassValueNode.java new file mode 100644 index 0000000..cf4434e --- /dev/null +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/ClassValueNode.java @@ -0,0 +1,30 @@ +package me.topchetoeu.jscript.compilation.values; + +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.ClassNode; + +public class ClassValueNode extends ClassNode { + public ClassValueNode(Location loc, Location end, String name, ClassBody body) { + super(loc, end, name, body); + } + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!Parsing.isIdentifier(src, i + n, "class")) return ParseRes.failed(); + n += 5; + + var name = Parsing.parseIdentifier(src, i + n); + n += name.n; + + var body = parseBody(src, i + n); + if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a class body"); + n += body.n; + + return ParseRes.res(new ClassValueNode(loc, src.loc(i + n), name.result, body.result), 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 02b770f..eee8f17 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/ObjectNode.java @@ -1,252 +1,28 @@ package me.topchetoeu.jscript.compilation.values; -import java.util.Arrays; import java.util.LinkedList; import java.util.List; import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.SyntaxException; -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.CompileResult; -import me.topchetoeu.jscript.compilation.CompoundNode; -import me.topchetoeu.jscript.compilation.FunctionNode; import me.topchetoeu.jscript.compilation.JavaScript; import me.topchetoeu.jscript.compilation.Node; -import me.topchetoeu.jscript.compilation.Parameters; +import me.topchetoeu.jscript.compilation.members.AssignShorthandNode; +import me.topchetoeu.jscript.compilation.members.FieldMemberNode; +import me.topchetoeu.jscript.compilation.members.MethodMemberNode; +import me.topchetoeu.jscript.compilation.members.PropertyMemberNode; 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.patterns.ObjectPattern; import me.topchetoeu.jscript.compilation.values.constants.NumberNode; import me.topchetoeu.jscript.compilation.values.constants.StringNode; -import me.topchetoeu.jscript.compilation.values.operations.AssignNode; public class ObjectNode extends Node implements AssignTargetLike { - public static class PropertyMemberNode extends FunctionNode { - public final Node key; - public final Pattern argument; - - @Override public String name() { - if (key instanceof StringNode str) { - if (isGetter()) return "get " + str.value; - else return "set " + str.value; - } - else return null; - } - - 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(Arrays.asList()) : new Parameters(Arrays.asList(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) { - 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; - - return ParseRes.res(new FieldMemberNode(loc, new StringNode(loc, var.result.name), var.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 static class AssignShorthandNode extends Node { - public final Node key; - public final AssignTarget target; - public final Node value; - - @Override public void compile(CompileResult target, boolean pollute) { - throw new SyntaxException(loc(), "Unexpected assign shorthand in non-destructor context"); - } - - public AssignShorthandNode(Location loc, Node key, AssignTarget target, Node value) { - super(loc); - this.key = key; - this.target = target; - this.value = value; - } - - public AssignTarget target() { - return new AssignNode(loc(), target, value); - } - - public static ParseRes parse(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; - 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 shorthand initializer"); - n += value.n; - - return ParseRes.res(new AssignShorthandNode(loc, new StringNode(loc, var.result.name), var.result, value.result), n); - } - } - public final List members; // TODO: Implement spreading into object @@ -288,26 +64,22 @@ public class ObjectNode extends Node implements AssignTargetLike { @Override public void compile(CompileResult target, boolean pollute) { target.add(Instruction.loadObj()); - - for (var el : members) { - target.add(Instruction.dup()); - el.compile(target, false); - } + for (var el : members) el.compile(target, true); } @Override public AssignTarget toAssignTarget() { - var newMembers = new LinkedList>(); + 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())); + if (field.value instanceof AssignTargetLike target) newMembers.add(new ObjectPattern.Member(field.key, target.toAssignTarget())); else throw new SyntaxException(field.value.loc(), "Expected an assignable in deconstructor"); } - else if (el instanceof AssignShorthandNode shorthand) newMembers.add(new Member<>(shorthand.key, shorthand.target())); + else if (el instanceof AssignShorthandNode shorthand) newMembers.add(new ObjectPattern.Member(shorthand.key, shorthand.target())); else throw new SyntaxException(el.loc(), "Unexpected member in deconstructor"); } - return new ObjectAssignable(loc(), newMembers); + return new ObjectPattern(loc(), newMembers); } public ObjectNode(Location loc, List map) { 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 17f8bcf..ae32c9c 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/VariableNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/VariableNode.java @@ -26,17 +26,18 @@ public class VariableNode extends Node implements Pattern, ChangeTarget { } @Override public void destructDeclResolve(CompileResult target) { - target.scope.define(new Variable(name, false), loc()); + var i = target.scope.define(new Variable(name, false), loc()); + if (i != null) target.add(_i -> i.index().toUndefinedInit(false)); } @Override public void afterAssign(CompileResult target, boolean pollute) { - target.add(VariableNode.toSet(target, loc(), name, pollute, false)); + target.add(VariableNode.toSet(target, loc(), name, pollute)); } - @Override public void declare(CompileResult target, DeclarationType decl) { + @Override public void declare(CompileResult target, DeclarationType decl, boolean lateInitializer) { 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()); + var i = target.scope.define(decl, name, loc()); + target.add(_i -> i.index().toUndefinedInit(decl.strict)); } else target.add(_i -> { var i = target.scope.get(name, false); @@ -48,52 +49,60 @@ public class VariableNode extends Node implements Pattern, ChangeTarget { @Override public void destruct(CompileResult target, DeclarationType decl, boolean shouldDeclare) { if (!shouldDeclare || decl == null) { - target.add(VariableNode.toSet(target, loc(), name, false, shouldDeclare)); + if (shouldDeclare) target.add(VariableNode.toInit(target, loc(), name)); + else target.add(VariableNode.toInit(target, loc(), name)); } else { 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)); + target.add(_i -> v.index().toInit()); } } @Override public void compile(CompileResult target, boolean pollute) { - var i = target.scope.get(name, false); - - 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, false); - }); - - if (!pollute) target.add(Instruction.discard()); - } - else if (pollute) target.add(_i -> i.index().toGet()); + target.add(toGet(target, loc(), name, true, false)); } - public static IntFunction toGet(CompileResult target, Location loc, String name, boolean forceGet) { - var i = target.scope.get(name, false); + public static IntFunction toGet(CompileResult target, Location loc, String name, boolean keep, boolean forceGet) { + var oldI = 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 Instruction.globGet(name, forceGet); + if (oldI != null) { + if (keep) return _i -> oldI.index().toGet(); + else return _i -> Instruction.nop(); + } + else return _i -> { + var newI = target.scope.get(name, false); + + if (newI == null) return Instruction.globGet(name, forceGet); + else if (keep) return newI.index().toGet(); + else return Instruction.nop(); }; - else return _i -> i.index().toGet(); } public static IntFunction toGet(CompileResult target, Location loc, String name) { - return toGet(target, loc, name, false); + return toGet(target, loc, name, true, false); } + public static IntFunction toInit(CompileResult target, Location loc, String name) { + var oldI = target.scope.get(name, false); - public static IntFunction toSet(CompileResult target, Location loc, String name, boolean keep, boolean define) { - var i = target.scope.get(name, false); + if (oldI != null) return _i -> oldI.index().toInit(); + else return _i -> { + 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 Instruction.globSet(name, keep, define); + if (i == null) return Instruction.globSet(name, false, true); + else return i.index().toInit(); + }; + } + public static IntFunction toSet(CompileResult target, Location loc, String name, boolean keep) { + var oldI = target.scope.get(name, false); + + if (oldI != null) return _i -> oldI.index().toSet(keep); + else return _i -> { + var i = target.scope.get(name, false); + + if (i == null) return Instruction.globSet(name, keep, false); + else return i.index().toSet(keep); }; - else if (!define && i.readonly) return _i -> Instruction.throwSyntax(new SyntaxException(loc, "Assignment to constant variable")); - else return _i -> i.index().toSet(keep); } public VariableNode(Location loc, String name) { 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 19b3798..ba89965 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,7 +16,7 @@ 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, true)); + target.add(VariableNode.toGet(target, varNode.loc(), varNode.name, true, true)); if (pollute) target.add(Instruction.typeof()); else target.add(Instruction.discard()); diff --git a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/VariableAssignNode.java b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/VariableAssignNode.java index 940a27c..9f11b5b 100644 --- a/src/main/java/me/topchetoeu/jscript/compilation/values/operations/VariableAssignNode.java +++ b/src/main/java/me/topchetoeu/jscript/compilation/values/operations/VariableAssignNode.java @@ -18,11 +18,11 @@ public class VariableAssignNode extends Node { target.add(VariableNode.toGet(target, loc(), name)); FunctionNode.compileWithName(value, target, true, name); target.add(Instruction.operation(operation)); - target.add(VariableNode.toSet(target, loc(), name, pollute, false)); + target.add(VariableNode.toSet(target, loc(), name, pollute)); } else { FunctionNode.compileWithName(value, target, true, name); - target.add(VariableNode.toSet(target, loc(), name, pollute, false)); + target.add(VariableNode.toSet(target, loc(), name, pollute)); } } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/Frame.java b/src/main/java/me/topchetoeu/jscript/runtime/Frame.java index 4fc615a..17e5a2c 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/Frame.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/Frame.java @@ -1,6 +1,5 @@ package me.topchetoeu.jscript.runtime; -import java.util.Arrays; import java.util.Stack; import java.util.concurrent.CancellationException; @@ -361,15 +360,7 @@ public final class Frame { this.argsVal = new ArgumentsValue(this, args); this.captures = func.captures; - var i = 0; - this.locals = new Value[func.body.localsN]; - Arrays.fill(locals, Value.UNDEFINED); - this.capturables = new Value[func.body.capturablesN][1]; - - for (i = 0; i < func.body.capturablesN; i++) { - this.capturables[i][0] = Value.UNDEFINED; - } } } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java b/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java index 1fa9ecf..a019369 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/InstructionRunner.java @@ -90,7 +90,7 @@ public class InstructionRunner { var members = new ArrayList<>(val.getMembers(env, instr.get(0), instr.get(1))); Collections.reverse(members); - frame.push(null); + frame.push(Value.UNDEFINED); for (var el : members) { var obj = new ObjectValue(); @@ -147,7 +147,9 @@ public class InstructionRunner { private static Value execLoadVar(Environment env, Instruction instr, Frame frame) { int i = instr.get(0); - frame.push(frame.getVar(i)); + var res = frame.getVar(i); + if (res == null) throw EngineException.ofSyntax("Uninitialized variable"); + frame.push(res); frame.codePtr++; return null; @@ -285,6 +287,7 @@ public class InstructionRunner { var val = (boolean)instr.get(1) ? frame.peek() : frame.pop(); int i = instr.get(0); + if (!(boolean)instr.get(2) && frame.getVar(i) == null) throw EngineException.ofSyntax("Uninitialized variable"); frame.setVar(i, val); frame.codePtr++; @@ -516,21 +519,21 @@ public class InstructionRunner { return null; } - private static Value execStackAlloc(Environment env, Instruction instr, Frame frame) { - int offset = instr.get(0); - int n = instr.get(1); - - for (var i = offset; i < n; i++) frame.capturables[i] = new Value[] { Value.UNDEFINED }; + private static Value execVarInit(Environment env, Instruction instr, Frame frame) { + if ((boolean)instr.get(1) || frame.getVar(instr.get(0)) == null) { + frame.setVar(instr.get(0), Value.UNDEFINED); + } frame.codePtr++; return null; } - private static Value execStackRealloc(Environment env, Instruction instr, Frame frame) { - int offset = instr.get(0); - int n = instr.get(1); - - for (var i = offset; i < n; i++) frame.capturables[i] = new Value[] { frame.capturables[i][0] }; - + private static Value execVarFree(Environment env, Instruction instr, Frame frame) { + frame.locals[(int)instr.get(0)] = null; + frame.codePtr++; + return null; + } + private static Value execCapFree(Environment env, Instruction instr, Frame frame) { + frame.capturables[(int)instr.get(0) - frame.locals.length] = new Value[1]; frame.codePtr++; return null; } @@ -592,8 +595,9 @@ public class InstructionRunner { case GLOB_GET: return exexGlobGet(env, instr, frame); case GLOB_SET: return exexGlobSet(env, instr, frame); - case STACK_ALLOC: return execStackAlloc(env, instr, frame); - case STACK_REALLOC: return execStackRealloc(env, instr, frame); + case VAR_INIT: return execVarInit(env, instr, frame); + case VAR_FREE: return execVarFree(env, instr, frame); + case CAP_FREE: return execCapFree(env, instr, frame); default: throw EngineException.ofSyntax("Invalid instruction " + instr.type.name() + "."); } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java b/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java index 6dc1b3a..5518ec7 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/SimpleRepl.java @@ -254,11 +254,6 @@ public class SimpleRepl { res.defineOwnMember(env, "parse", new NativeFunction(args -> { return JSONConverter.toJs(JSON.parse(null, args.get(0).toString(env))); })); - res.defineOwnMember(env, "setConstructable", new NativeFunction(args -> { - var func = (FunctionValue)args.get(0); - func.enableNew = args.get(1).toBoolean(); - return Value.UNDEFINED; - })); res.defineOwnMember(env, "invokeType", new NativeFunction(args -> { if (((ArgumentsValue)args.get(0)).frame.isNew) return StringValue.of("new"); else return StringValue.of("call"); diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java b/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java index 0e654b9..0604cdd 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/Value.java @@ -200,7 +200,7 @@ public abstract class Value { public final Value getMemberOrNull(Environment env, KeyCache key) { for (Value obj = this; obj != null; obj = obj.getPrototype(env)) { var member = obj.getOwnMember(env, key); - if (member != null) return member.get(env, obj); + if (member != null) return member.get(env, this); } return null; @@ -240,7 +240,7 @@ public abstract class Value { for (Value obj = this; obj != null; obj = obj.getPrototype(env)) { var member = obj.getOwnMember(env, key); if (member != null && (member instanceof PropertyMember || obj == this)) { - if (member.set(env, val, obj)) { + if (member.set(env, val, this)) { if (val instanceof FunctionValue) ((FunctionValue)val).setName(key.toString(env)); return true; } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/functions/FunctionValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/functions/FunctionValue.java index 9714be9..7e49161 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/functions/FunctionValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/functions/FunctionValue.java @@ -1,6 +1,7 @@ package me.topchetoeu.jscript.runtime.values.functions; import me.topchetoeu.jscript.common.environment.Environment; +import me.topchetoeu.jscript.runtime.exceptions.EngineException; import me.topchetoeu.jscript.runtime.values.KeyCache; import me.topchetoeu.jscript.runtime.values.Member; import me.topchetoeu.jscript.runtime.values.Value; @@ -50,7 +51,10 @@ public abstract class FunctionValue extends ObjectValue { @Override public String toString() { return String.format("function %s(...)", name); } @Override public Value call(Environment ext, boolean isNew, String name, Value thisArg, Value ...args) { if (isNew && !enableNew) super.call(ext, isNew, name, thisArg, args); - if (!isNew && !enableCall) super.call(ext, isNew, name, thisArg, args); + if (!isNew && !enableCall) { + if (name == null || name.equals("")) name = "(intermediate value)"; + throw EngineException.ofType(name + " is not invokable"); + } return onCall(ext, isNew, name, thisArg, args); } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayLikeValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayLikeValue.java index e7f70bd..4b39653 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayLikeValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayLikeValue.java @@ -55,6 +55,7 @@ public abstract class ArrayLikeValue extends ObjectValue { @Override public Member getOwnMember(Environment env, KeyCache key) { var res = super.getOwnMember(env, key); if (res != null) return res; + if (key.isSymbol()) return null; var num = key.toNumber(env); var i = key.toInt(env); @@ -67,18 +68,21 @@ public abstract class ArrayLikeValue extends ObjectValue { if (!(member instanceof FieldMember) || super.getOwnMember(env, key) != null) return super.defineOwnMember(env, key, member); if (!getState().writable) return false; - var num = key.toNumber(env); - var i = key.toInt(env); + if (!key.isSymbol()) { + var num = key.toNumber(env); + var i = key.toInt(env); - if (i == num) { - if (!getState().extendable && !has(i)) return false; - if (set(env, i, ((FieldMember)member).get(env, this))) return true; + if (i == num) { + if (!getState().extendable && !has(i)) return false; + if (set(env, i, ((FieldMember)member).get(env, this))) return true; + } } return super.defineOwnMember(env, key, member); } @Override public boolean deleteOwnMember(Environment env, KeyCache key) { if (!super.deleteOwnMember(env, key)) return false; + if (key.isSymbol()) return true; var num = key.toNumber(env); var i = key.toInt(env); diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayValue.java index e855c4e..39f7db7 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ArrayValue.java @@ -54,7 +54,7 @@ public class ArrayValue extends ArrayLikeValue implements Iterable { @Override public boolean remove(int i) { if (i < 0 || i >= values.length) return true; values[i] = null; - return false; + return true; } public void shrink(int n) { diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ObjectValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ObjectValue.java index 888ca35..c492bbe 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ObjectValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/objects/ObjectValue.java @@ -72,7 +72,10 @@ public class ObjectValue extends Value { @Override public final void freeze() { state = State.FROZEN; } @Override public Member getOwnMember(Environment env, KeyCache key) { - if (symbolMembers.size() > 0 && key.isSymbol()) return symbolMembers.get(key.toSymbol()); + if (key.isSymbol()) { + if (symbolMembers.size() > 0) return symbolMembers.get(key.toSymbol()); + else return null; + } else if (members.size() > 0) return members.get(key.toString(env)); else return null; } diff --git a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/StringValue.java b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/StringValue.java index 7409021..c715c51 100644 --- a/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/StringValue.java +++ b/src/main/java/me/topchetoeu/jscript/runtime/values/primitives/StringValue.java @@ -40,16 +40,19 @@ public final class StringValue extends PrimitiveValue { @Override public ObjectValue getPrototype(Environment env) { return env.get(STRING_PROTO); } @Override public Member getOwnMember(Environment env, KeyCache key) { - var num = key.toNumber(env); - var i = key.toInt(env); + if (!key.isSymbol()) { + var num = key.toNumber(env); + var i = key.toInt(env); - if (i == num && i >= 0 && i < value.length()) { - return FieldMember.of(this, new StringValue(value.charAt(i) + ""), false, true, false); + if (i == num && i >= 0 && i < value.length()) { + return FieldMember.of(this, new StringValue(value.charAt(i) + ""), false, true, false); + } + else if (key.toString(env).equals("length")) { + return FieldMember.of(this, NumberValue.of(value.length()), false, false, false); + } } - else if (key.toString(env).equals("length")) { - return FieldMember.of(this, NumberValue.of(value.length()), false, false, false); - } - else return super.getOwnMember(env, key); + + return super.getOwnMember(env, key); } @Override public Set getOwnMembers(Environment env, boolean onlyEnumerable) { diff --git a/src/main/resources/lib/index.js b/src/main/resources/lib/index.js index 28185d9..b7711eb 100644 --- a/src/main/resources/lib/index.js +++ b/src/main/resources/lib/index.js @@ -1,3 +1,5 @@ +// return; + const target = arguments[0]; const primordials = arguments[1]; @@ -58,8 +60,7 @@ const undefined = ({}).definitelyDefined; target.undefined = undefined; -const unwrapThis = (self, type, constr, name, arg, defaultVal) => { - if (arg == null) arg = "this"; +const unwrapThis = (self, type, constr, name, arg = "this", defaultVal) => { if (typeof self === type) return self; if (self instanceof constr && valueKey in self) self = self[valueKey]; if (typeof self === type) return self; @@ -70,14 +71,31 @@ const unwrapThis = (self, type, constr, name, arg, defaultVal) => { const wrapIndex = (i, len) => {}; -const Symbol = (name = "") => symbol.makeSymbol(name); +class Symbol { + get description() { + return symbol.getSymbolDescriptor(unwrapThis(this, "symbol", Symbol, "Symbol.prototype.description")); + } + toString() { + return "Symbol(" + unwrapThis(this, "symbol", Symbol, "Symbol.prototype.toString").description + ")"; + } + valueOf() { + return unwrapThis(this, "symbol", Symbol, "Symbol.prototype.valueOf"); + } -defineField(Symbol, "for", true, false, true, function(name) { - return symbol.getSymbol(name + ""); -}); -defineField(Symbol, "keyFor", true, false, true, function(value) { - return symbol.getSymbolKey(unwrapThis(value, "symbol", Symbol, "Symbol.keyFor")); -}); + constructor(name = "") { + return symbol.makeSymbol(name); + } + + static for(name) { + return symbol.getSymbol(name + ""); + } + static keyFor(value) { + return symbol.getSymbolKey(unwrapThis(value, "symbol", Symbol, "Symbol.keyFor")); + } +} + +setCallable(Symbol, true); +setConstructable(Symbol, false); defineField(Symbol, "asyncIterator", false, false, false, Symbol("Symbol.asyncIterator")); defineField(Symbol, "iterator", false, false, false, Symbol("Symbol.iterator")); @@ -87,61 +105,60 @@ defineField(Symbol, "replace", false, false, false, Symbol("Symbol.replace")); defineField(Symbol, "search", false, false, false, Symbol("Symbol.search")); defineField(Symbol, "split", false, false, false, Symbol("Symbol.split")); defineField(Symbol, "toStringTag", false, false, false, Symbol("Symbol.toStringTag")); -defineField(Symbol, "prototype", false, false, false, {}); - -defineProperty(Symbol.prototype, "description", false, true, function () { - return symbol.getSymbolDescription(unwrapThis(this, "symbol", Symbol, "Symbol.prototype.description")); -}, undefined); -defineField(Symbol.prototype, "toString", true, false, true, function() { - return "Symbol(" + unwrapThis(this, "symbol", Symbol, "Symbol.prototype.toString").description + ")"; -}); -defineField(Symbol.prototype, "valueOf", true, false, true, function() { - return unwrapThis(this, "symbol", Symbol, "Symbol.prototype.valueOf"); -}); +Symbol(); target.Symbol = Symbol; -const Number = function(value) { - if (invokeType(arguments) === "call") { - if (arguments.length === 0) return 0; - else return +value; +class Number { + toString() { + return "" + unwrapThis(this, "number", Number, "Number.prototype.toString"); + } + valueOf() { + return unwrapThis(this, "number", Number, "Number.prototype.toString"); } - this[valueKey] = target.Number(value); -}; + constructor(value) { + if (invokeType(arguments) === "call") { + if (arguments.length === 0) return 0; + else return +value; + } -defineField(Number, "isFinite", true, false, true, function(value) { - value = unwrapThis(value, "number", Number, "Number.isFinite", "value", undefined); + this[valueKey] = target.Number(value); + } - if (value === undefined || value !== value) return false; - if (value === Infinity || value === -Infinity) return false; + static isFinite(value) { + value = unwrapThis(value, "number", Number, "Number.isFinite", "value", undefined); - return true; -}); -defineField(Number, "isInteger", true, false, true, function(value) { - value = unwrapThis(value, "number", Number, "Number.isInteger", "value", undefined); - if (value === undefined) return false; - return number.parseInt(value) === value; -}); -defineField(Number, "isNaN", true, false, true, function(value) { - return number.isNaN(value); -}); -defineField(Number, "isSafeInteger", true, false, true, function(value) { - value = unwrapThis(value, "number", Number, "Number.isSafeInteger", "value", undefined); - if (value === undefined || number.parseInt(value) !== value) return false; - return value >= -9007199254740991 && value <= 9007199254740991; -}); -defineField(Number, "parseFloat", true, false, true, function(value) { - value = 0 + value; - return number.parseFloat(value); -}); -defineField(Number, "parseInt", true, false, true, function(value, radix) { - value = 0 + value; - radix = +radix; - if (number.isNaN(radix)) radix = 10; + if (value === undefined || value !== value) return false; + if (value === Infinity || value === -Infinity) return false; - return number.parseInt(value, radix); -}); + return true; + } + static isInteger(value) { + value = unwrapThis(value, "number", Number, "Number.isInteger", "value", undefined); + if (value === undefined) return false; + return number.parseInt(value) === value; + } + static isNaN(value) { + return number.isNaN(value); + } + static isSafeInteger(value) { + value = unwrapThis(value, "number", Number, "Number.isSafeInteger", "value", undefined); + if (value === undefined || number.parseInt(value) !== value) return false; + return value >= -9007199254740991 && value <= 9007199254740991; + } + static parseFloat(value) { + value = 0 + value; + return number.parseFloat(value); + } + static parseInt(value, radix) { + value = 0 + value; + radix = +radix; + if (number.isNaN(radix)) radix = 10; + + return number.parseInt(value, radix); + } +} defineField(Number, "EPSILON", false, false, false, 2.220446049250313e-16); defineField(Number, "MIN_SAFE_INTEGER", false, false, false, -9007199254740991); @@ -151,267 +168,288 @@ defineField(Number, "NEGATIVE_INFINITY", false, false, false, -number.Infinity); defineField(Number, "NaN", false, false, false, number.NaN); defineField(Number, "MAX_VALUE", false, false, false, 1.7976931348623157e+308); defineField(Number, "MIN_VALUE", false, false, false, 5e-324); -defineField(Number, "prototype", false, false, false, {}); - -defineField(Number.prototype, "toString", true, false, true); -defineField(Number.prototype, "toString", true, false, true, function() { - return "" + unwrapThis(this, "number", Number, "Number.prototype.toString"); -}); -defineField(Number.prototype, "valueOf", true, false, true, function() { - return unwrapThis(this, "number", Number, "Number.prototype.toString"); -}); +setCallable(Number, true); target.Number = Number; target.parseInt = Number.parseInt; target.parseFloat = Number.parseFloat; target.NaN = Number.NaN; target.Infinity = Number.POSITIVE_INFINITY; -const String = function(value) { - if (invokeType(arguments) === "call") { - if (arguments.length === 0) return ""; - else return value + ""; +class String { + at(index) { + throw "Not implemented :/"; + return unwrapThis(this, "string", String, "String.prototype.at")[index]; + } + toString() { + return unwrapThis(this, "string", String, "String.prototype.toString"); + } + valueOf() { + return unwrapThis(this, "string", String, "String.prototype.valueOf"); } - this[valueKey] = String(value); -}; + constructor(value) { + if (invokeType(arguments) === "call") { + if (arguments.length === 0) return ""; + else return value + ""; + } -defineField(String, "fromCharCode", true, false, true, function() { - const res = []; - res[arguments.length] = 0; - - for (let i = 0; i < arguments.length; i++) { - res[i] = fromCharCode(+arguments[i]); + this[valueKey] = String(value); } - return stringBuild(res); -}); -defineField(String, "fromCodePoint", true, false, true, function() { - const res = []; - res[arguments.length] = 0; + static fromCharCode() { + const res = []; + res[arguments.length] = 0; - for (let i = 0; i < arguments.length; i++) { - res[i] = fromCodePoint(+arguments[i]); + for (let i = 0; i < arguments.length; i++) { + res[i] = fromCharCode(+arguments[i]); + } + + return stringBuild(res); } + static fromCodePoint() { + const res = []; + res[arguments.length] = 0; - return stringBuild(res); -}); + for (let i = 0; i < arguments.length; i++) { + res[i] = fromCodePoint(+arguments[i]); + } -defineField(String, "prototype", false, false, false, {}); - -defineField(String.prototype, "at", true, false, true, function(index) { - throw "Not implemented :/"; - return unwrapThis(this, "string", String, "String.prototype.at")[index]; -}); -defineField(String.prototype, "toString", true, false, true, function() { - return unwrapThis(this, "string", String, "String.prototype.toString"); -}); -defineField(String.prototype, "valueOf", true, false, true, function() { - return unwrapThis(this, "string", String, "String.prototype.valueOf"); -}); + return stringBuild(res); + } +} +setCallable(String, true); target.String = String; -const Boolean = function(value) { - if (invokeType(arguments) === "call") { - if (arguments.length === 0) return false; - else return !!value; +class Boolean { + toString() { + return "" + unwrapThis(this, "boolean", Boolean, "Boolean.prototype.toString"); + } + valueOf() { + return unwrapThis(this, "boolean", Boolean, "Boolean.prototype.valueOf"); } - this[valueKey] = Boolean(value); -}; + constructor(value) { + if (invokeType(arguments) === "call") { + if (arguments.length === 0) return false; + else return !!value; + } -defineField(Boolean, "prototype", false, false, false, {}); - -defineField(Boolean.prototype, "toString", true, false, true, function() { - return "" + unwrapThis(this, "boolean", Boolean, "Boolean.prototype.toString"); -}); -defineField(Boolean.prototype, "valueOf", true, false, true, function() { - return unwrapThis(this, "boolean", Boolean, "Boolean.prototype.valueOf"); -}); + this[valueKey] = Boolean(value); + } +} +setCallable(Boolean, true); target.Boolean = Boolean; -const Object = function(value) { - if (typeof value === 'object' && value !== null) return value; +class Object { + toString() { + print("2"); + if (this !== null && this !== undefined && (Symbol.toStringTag in this)) return "[object " + this[Symbol.toStringTag] + "]"; + else if (typeof this === "number" || this instanceof Number) return "[object Number]"; + else if (typeof this === "symbol" || this instanceof Symbol) return "[object Symbol]"; + else if (typeof this === "string" || this instanceof String) return "[object String]"; + else if (typeof this === "boolean" || this instanceof Boolean) return "[object Boolean]"; + else if (typeof this === "function") return "[object Function]"; + else return "[object Object]"; + } + valueOf() { + print("1"); + return this; + } - if (typeof value === 'string') return new String(value); - if (typeof value === 'number') return new Number(value); - if (typeof value === 'boolean') return new Boolean(value); - if (typeof value === 'symbol') { - const res = {}; - setPrototype(res, Symbol.prototype); - res[valueKey] = value; + constructor(value) { + if (typeof value === 'object' && value !== null) return value; + + if (typeof value === 'string') return new String(value); + if (typeof value === 'number') return new Number(value); + if (typeof value === 'boolean') return new Boolean(value); + if (typeof value === 'symbol') { + const res = {}; + setPrototype(res, Symbol.prototype); + res[valueKey] = value; + return res; + } + + const target = this; + // TODO: use new.target.prototype as proto + if (target == null || typeof target !== 'object') target = {}; + + this[valueKey] = Object(value); + } + + static defineProperty(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; + } +} + +setCallable(Object, true); +setPrototype(Object.prototype, null); +target.Object = Object; + +class Function { + toString() { + if (this.name !== "") return "function " + this.name + "(...) { ... }"; + else return "function (...) { ... }"; + } + + constructor() { + const parts = ["return function annonymous("]; + + for (let i = 0; i < arguments.length - 1; i++) { + if (i > 0) parts[parts.length] = ","; + parts[parts.length] = arguments[i]; + } + parts[parts.length] = "){\n"; + parts[parts.length] = String(arguments[arguments.length - 1]); + parts[parts.length] = "\n}"; + + const res = compile(stringBuild(parts))(); return res; } - const target = this; - if (target == null || typeof target !== 'object') target = {}; + static compile(src = "", { globals = [], wrap = false } = {}) { + const parts = []; - this[valueKey] = Object(value); -}; + if (wrap) parts[parts.length] = "return (function() {\n"; + if (globals.length > 0) { + parts[parts.length] = "var "; -defineField(Object, "prototype", false, false, false, setPrototype({}, null)); + for (let i = 0; i < globals.length; i++) { + if (i > 0) parts[parts.length] = ","; + parts[parts.length] = globals[i]; + } -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); + parts[parts.length] = ";((g=arguments[0])=>{"; - if ("get" in desc || "set" in desc) { - let get = desc.get, set = desc.set; + for (let i = 0; i < globals.length; i++) { + const name = globals[i]; + parts[parts.length] = name + "=g[" + json.stringify(name) + "];"; + } - 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"); + parts[parts.length] = "})()\n"; } - 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); + parts[parts.length] = src; + if (wrap) parts[parts.length] = "\n})(arguments[0])"; + + const res = compile(stringBuild(parts)); + return res; } +} - 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]"; - else if (typeof this === "symbol" || this instanceof Symbol) return "[object Symbol]"; - else if (typeof this === "string" || this instanceof String) return "[object String]"; - else if (typeof this === "boolean" || this instanceof Boolean) return "[object Boolean]"; - else if (typeof this === "function") return "[object Function]"; - else return "[object Object]"; -}); -defineField(Object.prototype, "valueOf", true, false, true, function() { - return this; -}); - -target.Object = Object; - -const Function = function() { - const parts = ["return function annonymous("]; - - for (let i = 0; i < arguments.length - 1; i++) { - if (i > 0) parts[parts.length] = ","; - parts[parts.length] = arguments[i]; - } - parts[parts.length] = "){\n"; - parts[parts.length] = String(arguments[arguments.length - 1]); - parts[parts.length] = "\n}"; - - const res = compile(stringBuild(parts))(); - return res; -}; - -defineField(Function, "compile", true, false, true, (src = "", options = {}) => { - if (options.globals == null) options.globals = []; - if (options.wrap == null) options.wrap = true; - - const parts = []; - - if (options.wrap) parts[parts.length] = "return (function() {\n"; - if (options.globals.length > 0) { - parts[parts.length] = "var "; - - for (let i = 0; i < options.globals.length; i++) { - if (i > 0) parts[parts.length] = ","; - parts[parts.length] = options.globals[i]; - } - - parts[parts.length] = ";((g=arguments[0])=>{"; - - for (let i = 0; i < options.globals.length; i++) { - const name = options.globals[i]; - parts[parts.length] = name + "=g[" + json.stringify(name) + "];"; - } - - parts[parts.length] = "})()\n"; - } - - parts[parts.length] = src; - if (options.wrap) parts[parts.length] = "\n})(arguments[0])"; - - const res = compile(stringBuild(parts)); - return res; -}); -defineField(Function, "prototype", false, false, false, setPrototype({}, null)); - -defineField(Function.prototype, "toString", true, false, true, function() { - if (this.name !== "") return "function " + this.name + "(...) { ... }"; - else return "function (...) { ... }"; -}); -defineField(Function.prototype, "valueOf", true, false, true, function() { - return this; -}); - +setCallable(Function, true); target.Function = Function; -// setIntrinsic("spread_obj", target.spread_obj = (target, obj) => { -// if (obj === null || obj === undefined) return; -// const members = getOwnMembers(obj, true); -// const symbols = getOwnSymbolMembers(obj, true); +class Array { + constructor(len) { + if (arguments.length === 1 && typeof len === "number") { + const res = []; + res.length = len; + return res; + } + // TODO: Implement spreading + else throw new Error("Spreading not implemented"); + } +} -// for (let i = 0; i < members.length; i++) { -// const member = members[i]; -// target[member] = obj[member]; -// } +setCallable(Array, true); +target.Array = Array; -// for (let i = 0; i < symbols.length; i++) { -// const member = symbols[i]; -// target[member] = obj[member]; -// } -// }); -// setIntrinsic("apply", target.spread_call = (func, self, args) => { -// return invoke(func, self, args); -// }); -// setIntrinsic("apply", target.spread_new = (func, args) => { -// return invoke(func, null, args); -// }); +class Error { + toString() { + let res = this.name || "Error"; + + const msg = this.message; + if (msg) res += ": " + msg; + + return res; + } + + constructor (msg = "") { + if (invokeType(arguments) === "call") return new Error(msg); + this.message = msg + ""; + } +} -const Error = function(msg = "") { - if (invokeType(arguments) === "call") return new Error(msg); - this.message = msg + ""; -}; defineField(Error.prototype, "name", true, false, true, "Error"); defineField(Error.prototype, "message", true, false, true, ""); -defineField(Error.prototype, "toString", true, false, true, function toString() { - let res = this.name || "Error"; - - const msg = this.message; - if (msg) res += ": " + msg; - - return res; -}); - +setCallable(Error, true); target.Error = Error; -const SyntaxError = function(msg = "") { - if (invokeType(arguments) === "call") return new SyntaxError(msg); - this.message = msg + ""; -}; -defineField(SyntaxError.prototype, "name", true, false, true, "SyntaxError"); +class SyntaxError { + constructor (msg = "") { + if (invokeType(arguments) === "call") return new SyntaxError(msg); + this.message = msg + ""; + } +} +defineField(SyntaxError.prototype, "name", true, false, true, "SyntaxError"); setPrototype(SyntaxError, Error); setPrototype(SyntaxError.prototype, Error.prototype); - +setCallable(SyntaxError, true); target.SyntaxError = SyntaxError; +class TypeError { + constructor (msg = "") { + if (invokeType(arguments) === "call") return new TypeError(msg); + this.message = msg + ""; + } +} + +defineField(TypeError.prototype, "name", true, false, true, "TypeError"); +setPrototype(TypeError, Error); +setPrototype(TypeError.prototype, Error.prototype); +setCallable(TypeError, true); +target.TypeError = TypeError; + +class RangeError { + constructor (msg = "") { + if (invokeType(arguments) === "call") return new RangeError(msg); + this.message = msg + ""; + } +} + +defineField(RangeError.prototype, "name", true, false, true, "RangeError"); +setPrototype(RangeError, Error); +setPrototype(RangeError.prototype, Error.prototype); +setCallable(RangeError, true); +target.RangeError = RangeError; + setGlobalPrototype("string", String.prototype); setGlobalPrototype("number", Number.prototype); setGlobalPrototype("boolean", Boolean.prototype); setGlobalPrototype("symbol", Symbol.prototype); setGlobalPrototype("object", Object.prototype); +setGlobalPrototype("array", Array.prototype); setGlobalPrototype("function", Function.prototype); setGlobalPrototype("error", Error.prototype); setGlobalPrototype("syntax", SyntaxError.prototype);