diff --git a/src/java/me/topchetoeu/jscript/common/FunctionBody.java b/src/java/me/topchetoeu/jscript/common/FunctionBody.java index 3b91614..f1e2033 100644 --- a/src/java/me/topchetoeu/jscript/common/FunctionBody.java +++ b/src/java/me/topchetoeu/jscript/common/FunctionBody.java @@ -3,12 +3,11 @@ package me.topchetoeu.jscript.common; public class FunctionBody { public final FunctionBody[] children; public final Instruction[] instructions; - public final int localsN, capturesN, argsN, length; + public final int localsN, capturesN, length; - public FunctionBody(int localsN, int capturesN, int length, int argsN, Instruction[] instructions, FunctionBody[] children) { + public FunctionBody(int localsN, int capturesN, int length, Instruction[] instructions, FunctionBody[] children) { this.children = children; this.length = length; - this.argsN = argsN; this.localsN = localsN; this.capturesN = capturesN; this.instructions = instructions; diff --git a/src/java/me/topchetoeu/jscript/common/Instruction.java b/src/java/me/topchetoeu/jscript/common/Instruction.java index 2e7f5b2..d8354be 100644 --- a/src/java/me/topchetoeu/jscript/common/Instruction.java +++ b/src/java/me/topchetoeu/jscript/common/Instruction.java @@ -1,16 +1,15 @@ package me.topchetoeu.jscript.common; -// import java.io.DataInputStream; -// import java.io.DataOutputStream; -// import java.io.IOException; import java.util.HashMap; +import java.util.function.IntFunction; +import java.util.function.IntSupplier; import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; public class Instruction { public static enum Type { - NOP(0x00), - RETURN(0x01), + RETURN(0x00), + NOP(0x01), THROW(0x02), THROW_SYNTAX(0x03), DELETE(0x04), @@ -41,10 +40,18 @@ public class Instruction { LOAD_VAR(0x40), LOAD_MEMBER(0x41), - LOAD_ARGS(0x42), - LOAD_THIS(0x43), + LOAD_MEMBER_INT(0x42), + LOAD_MEMBER_STR(0x43), + + LOAD_ARGS(0x44), + LOAD_REST_ARGS(0x45), + LOAD_CALLEE(0x46), + LOAD_THIS(0x47), + STORE_VAR(0x48), STORE_MEMBER(0x49), + STORE_MEMBER_INT(0x4A), + STORE_MEMBER_STR(0x4B), DEF_PROP(0x50), KEYS(0x51), @@ -286,6 +293,7 @@ public class Instruction { public static Instruction callNew(int argn) { return new Instruction(Type.CALL_NEW, argn, ""); } + public static Instruction jmp(int offset) { return new Instruction(Type.JMP, offset); } @@ -296,6 +304,17 @@ public class Instruction { return new Instruction(Type.JMP_IFN, offset); } + public static IntFunction jmp(IntSupplier pos) { + return i -> new Instruction(Type.JMP, pos.getAsInt() - i); + } + public static IntFunction jmpIf(IntSupplier pos) { + return i -> new Instruction(Type.JMP_IF, pos.getAsInt() - i); + } + public static IntFunction jmpIfNot(IntSupplier pos) { + return i -> new Instruction(Type.JMP_IFN, pos.getAsInt() - i); + } + + public static Instruction pushUndefined() { return new Instruction(Type.PUSH_UNDEFINED); } @@ -313,7 +332,7 @@ public class Instruction { } public static Instruction globDef(String name) { - return new Instruction(Type.GLOB_GET, name); + return new Instruction(Type.GLOB_DEF, name); } public static Instruction globGet(String name) { @@ -332,6 +351,12 @@ public class Instruction { public static Instruction loadArgs() { return new Instruction(Type.LOAD_ARGS); } + public static Instruction loadRestArgs(int offset) { + return new Instruction(Type.LOAD_REST_ARGS, offset); + } + public static Instruction loadCallee() { + return new Instruction(Type.LOAD_CALLEE); + } public static Instruction loadGlob() { return new Instruction(Type.LOAD_GLOB); } @@ -341,17 +366,26 @@ public class Instruction { public static Instruction loadMember() { return new Instruction(Type.LOAD_MEMBER); } + public static Instruction loadMember(int member) { + return new Instruction(Type.LOAD_MEMBER_INT, member); + } + public static Instruction loadMember(String member) { + return new Instruction(Type.LOAD_MEMBER_STR, member); + } public static Instruction loadRegex(String pattern, String flags) { return new Instruction(Type.LOAD_REGEX, pattern, flags); } - public static Instruction loadFunc(int id, String name, int[] captures) { + public static Instruction loadFunc(int id, boolean callable, boolean constructible, boolean captureThis, String name, int[] captures) { if (name == null) name = ""; - var args = new Object[2 + captures.length]; + var args = new Object[5 + captures.length]; args[0] = id; args[1] = name; - for (var i = 0; i < captures.length; i++) args[i + 2] = captures[i]; + args[2] = callable; + args[3] = constructible; + args[4] = captureThis; + for (var i = 0; i < captures.length; i++) args[i + 5] = captures[i]; return new Instruction(Type.LOAD_FUNC, args); } public static Instruction loadObj() { @@ -373,12 +407,28 @@ public class Instruction { public static Instruction storeVar(int i, boolean keep) { return new Instruction(Type.STORE_VAR, i, keep); } + public static Instruction storeMember() { return new Instruction(Type.STORE_MEMBER, false); } public static Instruction storeMember(boolean keep) { return new Instruction(Type.STORE_MEMBER, keep); } + + public static Instruction storeMember(String key) { + return new Instruction(Type.STORE_MEMBER_STR, key, false); + } + public static Instruction storeMember(String key, boolean keep) { + return new Instruction(Type.STORE_MEMBER_STR, key, keep); + } + + public static Instruction storeMember(int key) { + return new Instruction(Type.STORE_MEMBER_INT, key, false); + } + public static Instruction storeMember(int key, boolean keep) { + return new Instruction(Type.STORE_MEMBER_STR, key, keep); + } + public static Instruction discard() { return new Instruction(Type.DISCARD); } diff --git a/src/java/me/topchetoeu/jscript/compilation/CompoundNode.java b/src/java/me/topchetoeu/jscript/compilation/CompoundNode.java index 0a03de1..19466c4 100644 --- a/src/java/me/topchetoeu/jscript/compilation/CompoundNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/CompoundNode.java @@ -22,7 +22,7 @@ public class CompoundNode extends Node { public void compile(CompileResult target, boolean pollute, boolean alloc, BreakpointType type) { List statements = new ArrayList(); - var subtarget = target.subtarget(); + var subtarget = alloc ? target.subtarget() : target; if (alloc) subtarget.add(i -> Instruction.stackAlloc(subtarget.scope.allocCount())); for (var stm : this.statements) { @@ -41,8 +41,10 @@ public class CompoundNode extends Node { else stm.compile(subtarget, polluted = pollute, BreakpointType.STEP_OVER); } - subtarget.scope.end(); - if (alloc) subtarget.add(Instruction.stackFree(subtarget.scope.allocCount())); + if (alloc) { + subtarget.scope.end(); + subtarget.add(Instruction.stackFree(subtarget.scope.allocCount())); + } if (!polluted && pollute) { target.add(Instruction.pushUndefined()); diff --git a/src/java/me/topchetoeu/jscript/compilation/FunctionArrowNode.java b/src/java/me/topchetoeu/jscript/compilation/FunctionArrowNode.java new file mode 100644 index 0000000..e6f72fa --- /dev/null +++ b/src/java/me/topchetoeu/jscript/compilation/FunctionArrowNode.java @@ -0,0 +1,73 @@ +package me.topchetoeu.jscript.compilation; + +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.control.ReturnNode; + +public class FunctionArrowNode extends FunctionNode { + @Override public String name() { return null; } + + @Override protected void compileLoadFunc(CompileResult target, int id, int[] captures, String name) { + target.add(Instruction.loadFunc(id, true, false, true, null, captures)); + } + + @Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) { + compile(target, pollute, false, name, null, bp); + } + + public FunctionArrowNode(Location loc, Location end, Parameters params, Node body) { + super(loc, end, params, expToBody(body)); + } + + private static final CompoundNode expToBody(Node node) { + if (node instanceof CompoundNode res) return res; + else return new CompoundNode(node.loc(), new ReturnNode(node.loc(), node)); + } + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + Parameters params; + + if (src.is(i + n, "(")) { + var paramsRes = JavaScript.parseParameters(src, i + n); + if (!paramsRes.isSuccess()) return paramsRes.chainError(); + n += paramsRes.n; + n += Parsing.skipEmpty(src, i + n); + + params = paramsRes.result; + } + else { + var singleParam = Parsing.parseIdentifier(src, i + n); + if (!singleParam.isSuccess()) return ParseRes.failed(); + + var paramLoc = src.loc(i + n); + n += singleParam.n; + n += Parsing.skipEmpty(src, i + n); + + params = new Parameters(List.of(new Parameter(paramLoc, singleParam.result, null))); + } + + if (!src.is(i + n, "=>")) return ParseRes.failed(); + n += 2; + + ParseRes body = ParseRes.first(src, i + n, + (s, j) -> JavaScript.parseExpression(s, j, 2), + CompoundNode::parse + ); + if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected an expression or a compount statement after '=>'"); + n += body.n; + + return ParseRes.res(new FunctionArrowNode( + loc, src.loc(i + n - 1), + params, body.result + ), n); + } +} diff --git a/src/java/me/topchetoeu/jscript/compilation/FunctionNode.java b/src/java/me/topchetoeu/jscript/compilation/FunctionNode.java index 66c88e4..d725326 100644 --- a/src/java/me/topchetoeu/jscript/compilation/FunctionNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/FunctionNode.java @@ -35,11 +35,11 @@ public abstract class FunctionNode extends Node { // } // } - protected void compileLoadFunc(CompileResult target, int[] captures, String name) { - target.add(Instruction.loadFunc(target.children.size(), name, captures)); + protected void compileLoadFunc(CompileResult target, int id, int[] captures, String name) { + target.add(Instruction.loadFunc(id, true, true, false, name, captures)); } - private CompileResult compileBody(CompileResult target, String name, boolean storeSelf, boolean pollute, BreakpointType bp) { + private CompileResult compileBody(CompileResult target, boolean hasArgs, String name, String selfName, boolean pollute, BreakpointType bp) { var env = target.env.child() .remove(LabelContext.BREAK_CTX) .remove(LabelContext.CONTINUE_CTX); @@ -47,43 +47,74 @@ public abstract class FunctionNode extends Node { var funcScope = new FunctionScope(target.scope); var subtarget = new CompileResult(env, new LocalScope(funcScope)); - for (var param : params.params) { - // TODO: Implement default values - // TODO: Implement argument location - if (funcScope.hasArg(param.name)) throw new SyntaxException(param.loc, "Duplicate parameter name not allowed"); - var i = funcScope.defineParam(param.name, param.loc); + subtarget.length = params.params.size(); - if (param.node != null) { - var end = new DeferredIntSupplier(); + if (hasArgs || params.params.size() > 0) subtarget.add(Instruction.loadArgs()); - subtarget.add(_i -> Instruction.loadVar(i.index())); - subtarget.add(Instruction.pushUndefined()); - subtarget.add(Instruction.operation(Operation.EQUALS)); - subtarget.add(_i -> Instruction.jmpIfNot(end.getAsInt() - _i)); - param.node.compile(subtarget, pollute); - subtarget.add(_i -> Instruction.storeVar(i.index())); + if (hasArgs) { + var argsVar = funcScope.defineParam("arguments", true, loc()); + subtarget.add(_i -> Instruction.storeVar(argsVar.index(), params.params.size() > 0)); + } - end.set(subtarget.size()); + if (params.params.size() > 0) { + if (params.params.size() > 1) subtarget.add(Instruction.dup(params.params.size() - 1)); + var i = 0; + + for (var param : params.params) { + if (funcScope.hasArg(param.name)) throw new SyntaxException(param.loc, "Duplicate parameter name not allowed"); + if (!JavaScript.checkVarName(param.name)) { + throw new SyntaxException(param.loc, String.format("Unexpected identifier '%s'", param.name)); + } + var varI = funcScope.defineParam(param.name, false, param.loc); + + subtarget.add(Instruction.loadMember(i++)); + + if (param.node != null) { + var end = new DeferredIntSupplier(); + + subtarget.add(Instruction.dup()); + subtarget.add(Instruction.pushUndefined()); + subtarget.add(Instruction.operation(Operation.EQUALS)); + subtarget.add(Instruction.jmpIfNot(end)); + subtarget.add(Instruction.discard()); + param.node.compile(subtarget, pollute); + + end.set(subtarget.size()); + } + + subtarget.add(Instruction.storeVar(varI.index())); } } + if (params.restName != null) { + if (funcScope.hasArg(params.restName)) throw new SyntaxException(params.restLocation, "Duplicate parameter name not allowed"); + var restVar = funcScope.defineParam(params.restName, true, params.restLocation); + subtarget.add(Instruction.loadRestArgs(params.params.size())); + subtarget.add(_i -> Instruction.storeVar(restVar.index())); + } + + if (selfName != null && !funcScope.hasArg(name)) { + var i = funcScope.defineParam(selfName, true, end); + + subtarget.add(Instruction.loadCallee()); + subtarget.add(_i -> Instruction.storeVar(i.index(), false)); + } + body.resolve(subtarget); body.compile(subtarget, false, false, BreakpointType.NONE); - subtarget.length = params.length; - subtarget.assignN = params.params.size(); subtarget.scope.end(); funcScope.end(); - if (pollute) compileLoadFunc(target, funcScope.getCaptureIndices(), name); + if (pollute) compileLoadFunc(target, target.children.size(), funcScope.getCaptureIndices(), name); return target.addChild(subtarget); } - public void compile(CompileResult target, boolean pollute, boolean storeSelf, String name, BreakpointType bp) { + public void compile(CompileResult target, boolean pollute, boolean hasArgs, String name, String selfName, BreakpointType bp) { if (this.name() != null) name = this.name(); - compileBody(target, name, storeSelf, pollute, bp); + compileBody(target, hasArgs, name, selfName, pollute, bp); } public abstract void compile(CompileResult target, boolean pollute, String name, BreakpointType bp); public void compile(CompileResult target, boolean pollute, String name) { diff --git a/src/java/me/topchetoeu/jscript/compilation/FunctionStatementNode.java b/src/java/me/topchetoeu/jscript/compilation/FunctionStatementNode.java index 183fcd9..9d0398a 100644 --- a/src/java/me/topchetoeu/jscript/compilation/FunctionStatementNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/FunctionStatementNode.java @@ -14,7 +14,7 @@ public class FunctionStatementNode extends FunctionNode { } @Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) { - compile(target, true, false, this.name, bp); + compile(target, true, true, name, this.name, bp); target.add(VariableNode.toSet(target, end, this.name, pollute, true)); } diff --git a/src/java/me/topchetoeu/jscript/compilation/FunctionValueNode.java b/src/java/me/topchetoeu/jscript/compilation/FunctionValueNode.java index 89f96dd..cfe0bf8 100644 --- a/src/java/me/topchetoeu/jscript/compilation/FunctionValueNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/FunctionValueNode.java @@ -9,7 +9,7 @@ public class FunctionValueNode extends FunctionNode { @Override public String name() { return name; } @Override public void compile(CompileResult target, boolean pollute, String name, BreakpointType bp) { - compile(target, pollute, true, name, bp); + compile(target, pollute, true, name, null, bp); } public FunctionValueNode(Location loc, Location end, Parameters params, CompoundNode body, String name) { diff --git a/src/java/me/topchetoeu/jscript/compilation/JavaScript.java b/src/java/me/topchetoeu/jscript/compilation/JavaScript.java index 7b58aae..c5574d6 100644 --- a/src/java/me/topchetoeu/jscript/compilation/JavaScript.java +++ b/src/java/me/topchetoeu/jscript/compilation/JavaScript.java @@ -26,9 +26,7 @@ import me.topchetoeu.jscript.compilation.control.TryNode; import me.topchetoeu.jscript.compilation.control.WhileNode; import me.topchetoeu.jscript.compilation.scope.GlobalScope; import me.topchetoeu.jscript.compilation.scope.LocalScope; -import me.topchetoeu.jscript.compilation.values.ArgumentsNode; import me.topchetoeu.jscript.compilation.values.ArrayNode; -import me.topchetoeu.jscript.compilation.values.GlobalThisNode; import me.topchetoeu.jscript.compilation.values.ObjectNode; import me.topchetoeu.jscript.compilation.values.RegexNode; import me.topchetoeu.jscript.compilation.values.ThisNode; @@ -45,7 +43,20 @@ import me.topchetoeu.jscript.compilation.values.operations.OperationNode; import me.topchetoeu.jscript.compilation.values.operations.TypeofNode; import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; -public class JavaScript { +public final class JavaScript { + public static enum DeclarationType { + VAR(false, false), + CONST(true, true), + LET(true, false); + + public final boolean strict, readonly; + + private DeclarationType(boolean strict, boolean readonly) { + this.strict = strict; + this.readonly = readonly; + } + } + static final Set reserved = Set.of( "true", "false", "void", "null", "this", "if", "else", "try", "catch", "finally", "for", "do", "while", "switch", "case", "default", "new", @@ -84,6 +95,7 @@ public class JavaScript { ChangeNode::parsePrefixIncrease, OperationNode::parsePrefix, ArrayNode::parse, + FunctionArrowNode::parse, JavaScript::parseParens, CallNode::parseNew, TypeofNode::parse, @@ -103,11 +115,11 @@ public class JavaScript { if (id.result.equals("true")) return ParseRes.res(new BoolNode(loc, true), n); if (id.result.equals("false")) return ParseRes.res(new BoolNode(loc, false), n); - if (id.result.equals("undefined")) return ParseRes.res(new DiscardNode(loc, null), n); + // if (id.result.equals("undefined")) return ParseRes.res(new DiscardNode(loc, null), n); if (id.result.equals("null")) return ParseRes.res(new NullNode(loc), n); if (id.result.equals("this")) return ParseRes.res(new ThisNode(loc), n); - if (id.result.equals("arguments")) return ParseRes.res(new ArgumentsNode(loc), n); - if (id.result.equals("globalThis")) return ParseRes.res(new GlobalThisNode(loc), n); + // if (id.result.equals("arguments")) return ParseRes.res(new ArgumentsNode(loc), n); + // if (id.result.equals("globalThis")) return ParseRes.res(new GlobalThisNode(loc), n); return ParseRes.failed(); } @@ -228,10 +240,25 @@ public class JavaScript { while (true) { n += Parsing.skipEmpty(src, i + n); + if (src.is(i + n, "...")) { + n += 3; + var restLoc = src.loc(i); + + var restName = Parsing.parseIdentifier(src, i + n); + if (!restName.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a rest parameter"); + n += restName.n; + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected an end of parameters list after rest parameter"); + n++; + + return ParseRes.res(new Parameters(params, restName.result, restLoc), n); + } + var paramLoc = src.loc(i); var name = Parsing.parseIdentifier(src, i + n); - if (!name.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected an argument or a closing brace"); + if (!name.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a parameter or a closing brace"); n += name.n; n += Parsing.skipEmpty(src, i + n); @@ -239,7 +266,7 @@ public class JavaScript { n++; var val = parseExpression(src, i + n, 2); - if (!val.isSuccess()) return openParen.chainError(src.loc(i + n), "Expected a default value"); + if (!val.isSuccess()) return openParen.chainError(src.loc(i + n), "Expected a parameter default value"); n += val.n; n += Parsing.skipEmpty(src, i + n); @@ -251,6 +278,7 @@ public class JavaScript { n++; n += Parsing.skipEmpty(src, i + n); } + if (src.is(i + n, ")")) { n++; break; @@ -261,6 +289,17 @@ public class JavaScript { return ParseRes.res(new Parameters(params), n); } + public static ParseRes parseDeclarationType(Source src, int i) { + var res = Parsing.parseIdentifier(src, i); + if (!res.isSuccess()) return res.chainError(); + + if (res.result.equals("var")) return ParseRes.res(DeclarationType.VAR, res.n); + if (res.result.equals("let")) return ParseRes.res(DeclarationType.LET, res.n); + if (res.result.equals("const")) return ParseRes.res(DeclarationType.CONST, res.n); + + return ParseRes.failed(); + } + public static Node[] parse(Environment env, Filename filename, String raw) { var src = new Source(env, filename, raw); var list = new ArrayList(); @@ -289,17 +328,20 @@ public class JavaScript { public static CompileResult compile(Environment env, Node ...statements) { var target = new CompileResult(env, new LocalScope(new GlobalScope())); var stm = new CompoundNode(null, statements); + var argsI = target.scope.defineStrict("arguments", true, null); + target.add(Instruction.loadArgs()); + target.add(_i -> Instruction.storeVar(argsI.index())); - try { + // try { stm.resolve(target); stm.compile(target, true, false, BreakpointType.NONE); // FunctionNode.checkBreakAndCont(target, 0); - } - catch (SyntaxException e) { - target = new CompileResult(env, new LocalScope(new GlobalScope())); + // } + // catch (SyntaxException e) { + // target = new CompileResult(env, new LocalScope(new GlobalScope())); - target.add(Instruction.throwSyntax(e)).setLocation(stm.loc()); - } + // target.add(Instruction.throwSyntax(e)).setLocation(stm.loc()); + // } return target; } diff --git a/src/java/me/topchetoeu/jscript/compilation/Parameters.java b/src/java/me/topchetoeu/jscript/compilation/Parameters.java index c104a38..307170f 100644 --- a/src/java/me/topchetoeu/jscript/compilation/Parameters.java +++ b/src/java/me/topchetoeu/jscript/compilation/Parameters.java @@ -1,16 +1,16 @@ package me.topchetoeu.jscript.compilation; -import java.util.HashSet; import java.util.List; -import java.util.Set; + +import me.topchetoeu.jscript.common.parsing.Location; public final class Parameters { public final int length; public final List params; - public final Set names; + public final String restName; + public final Location restLocation; - public Parameters(List params) { - this.names = new HashSet<>(); + public Parameters(List params, String restName, Location restLocation) { var len = params.size(); for (var i = params.size() - 1; i >= 0; i--) { @@ -18,11 +18,12 @@ public final class Parameters { len--; } - for (var param : params) { - this.names.add(param.name); - } - this.params = params; this.length = len; + this.restName = restName; + this.restLocation = restLocation; + } + public Parameters(List params) { + this(params, null, null); } } diff --git a/src/java/me/topchetoeu/jscript/compilation/VariableDeclareNode.java b/src/java/me/topchetoeu/jscript/compilation/VariableDeclareNode.java index 69b91f8..24ec36b 100644 --- a/src/java/me/topchetoeu/jscript/compilation/VariableDeclareNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/VariableDeclareNode.java @@ -9,6 +9,7 @@ import me.topchetoeu.jscript.common.parsing.Location; import me.topchetoeu.jscript.common.parsing.ParseRes; import me.topchetoeu.jscript.common.parsing.Parsing; import me.topchetoeu.jscript.common.parsing.Source; +import me.topchetoeu.jscript.compilation.JavaScript.DeclarationType; import me.topchetoeu.jscript.compilation.values.VariableNode; public class VariableDeclareNode extends Node { @@ -25,51 +26,56 @@ public class VariableDeclareNode extends Node { } public final List values; + public final DeclarationType declType; @Override public void resolve(CompileResult target) { - for (var entry : values) { - target.scope.define(entry.name, false, entry.location); + if (!declType.strict) { + for (var entry : values) { + target.scope.define(entry.name, false, entry.location); + } } } + // let a = 10, b = "test"; var c = () => a + b @Override public void compile(CompileResult target, boolean pollute) { for (var entry : values) { if (entry.name == null) continue; + if (declType.strict) target.scope.defineStrict(entry.name, declType.readonly, entry.location); if (entry.value != null) { FunctionNode.compileWithName(entry.value, target, true, entry.name, BreakpointType.STEP_OVER); target.add(VariableNode.toSet(target, entry.location, entry.name, false, true)); } - else { - target.add(_i -> { - var i = target.scope.get(entry.name, true); + else target.add(_i -> { + var i = target.scope.get(entry.name, true); - if (i == null) return Instruction.globDef(entry.name); - else return Instruction.nop(); - }); - } + if (i == null) return Instruction.globDef(entry.name); + else return Instruction.nop(); + }); } if (pollute) target.add(Instruction.pushUndefined()); } - public VariableDeclareNode(Location loc, List values) { + public VariableDeclareNode(Location loc, DeclarationType declType, List values) { super(loc); this.values = values; + this.declType = declType; } 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, "var")) return ParseRes.failed(); - n += 3; + var declType = JavaScript.parseDeclarationType(src, i + n); + if (!declType.isSuccess()) return declType.chainError(); + n += declType.n; var res = new ArrayList(); var end = JavaScript.parseStatementEnd(src, i + n); if (end.isSuccess()) { n += end.n; - return ParseRes.res(new VariableDeclareNode(loc, res), n); + return ParseRes.res(new VariableDeclareNode(loc, declType.result, res), n); } while (true) { @@ -83,6 +89,7 @@ public class VariableDeclareNode extends Node { } Node val = null; + var endN = n; n += Parsing.skipEmpty(src, i + n); if (src.is(i + n, "=")) { @@ -92,6 +99,7 @@ public class VariableDeclareNode extends Node { if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after '='"); n += valRes.n; + endN = n; n += Parsing.skipEmpty(src, i + n); val = valRes.result; } @@ -103,11 +111,11 @@ public class VariableDeclareNode extends Node { continue; } - end = JavaScript.parseStatementEnd(src, i + n); + end = JavaScript.parseStatementEnd(src, i + endN); if (end.isSuccess()) { - n += end.n; - return ParseRes.res(new VariableDeclareNode(loc, res), n); + n += end.n + endN - n; + return ParseRes.res(new VariableDeclareNode(loc, declType.result, res), n); } else return end.chainError(src.loc(i + n), "Expected a comma or end of statement"); } diff --git a/src/java/me/topchetoeu/jscript/compilation/control/ForInNode.java b/src/java/me/topchetoeu/jscript/compilation/control/ForInNode.java index e2c2735..4a8e4d5 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/ForInNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/ForInNode.java @@ -12,21 +12,24 @@ 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.values.VariableNode; public class ForInNode extends Node { public final String varName; - public final boolean isDeclaration; + public final DeclarationType declType; public final Node object, body; public final String label; public final Location varLocation; @Override public void resolve(CompileResult target) { body.resolve(target); - if (isDeclaration) target.scope.define(varName, false, loc()); + if (declType != null && !declType.strict) target.scope.define(varName, false, loc()); } @Override public void compile(CompileResult target, boolean pollute) { + if (declType != null && declType.strict) target.scope.defineStrict(varName, declType.readonly, varLocation); + object.compile(target, true, BreakpointType.STEP_OVER); target.add(Instruction.keys(true)); @@ -36,9 +39,8 @@ public class ForInNode extends Node { target.add(Instruction.operation(Operation.EQUALS)); int mid = target.temp(); - target.add(Instruction.pushValue("value")).setLocation(varLocation); - target.add(Instruction.loadMember()).setLocation(varLocation); - target.add(VariableNode.toSet(target, loc(), varName, pollute, isDeclaration)); + target.add(Instruction.loadMember("value")).setLocation(varLocation); + target.add(VariableNode.toSet(target, loc(), varName, pollute, declType != null && declType.strict)); target.setLocationAndDebug(object.loc(), BreakpointType.STEP_OVER); var end = new DeferredIntSupplier(); @@ -49,19 +51,17 @@ public class ForInNode extends Node { int endI = target.size(); - // WhileNode.replaceBreaks(target, label, mid + 1, end, start, end + 1); - target.add(Instruction.jmp(start - endI)); target.add(Instruction.discard()); target.set(mid, Instruction.jmpIf(endI - mid + 1)); if (pollute) target.add(Instruction.pushUndefined()); } - public ForInNode(Location loc, Location varLocation, String label, boolean isDecl, String varName, Node object, Node body) { + public ForInNode(Location loc, Location varLocation, String label, DeclarationType declType, String varName, Node object, Node body) { super(loc); this.varLocation = varLocation; this.label = label; - this.isDeclaration = isDecl; + this.declType = declType; this.varName = varName; this.object = object; this.body = body; @@ -83,12 +83,8 @@ public class ForInNode extends Node { n++; n += Parsing.skipEmpty(src, i + n); - var isDecl = false; - - if (Parsing.isIdentifier(src, i + n, "var")) { - isDecl = true; - n += 3; - } + 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"); @@ -111,6 +107,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, isDecl, name.result, obj.result, bodyRes.result), n); + return ParseRes.res(new ForInNode(loc, nameLoc, label.result, declType.result, name.result, obj.result, bodyRes.result), n); } } diff --git a/src/java/me/topchetoeu/jscript/compilation/control/ForNode.java b/src/java/me/topchetoeu/jscript/compilation/control/ForNode.java index 8a7c3d3..e0d125c 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/ForNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/ForNode.java @@ -23,28 +23,31 @@ public class ForNode extends Node { body.resolve(target); } @Override public void compile(CompileResult target, boolean pollute) { - declaration.compile(target, false, BreakpointType.STEP_OVER); + var subtarget = target.subtarget(); + subtarget.add(i -> Instruction.stackAlloc(subtarget.scope.allocCount())); - int start = target.size(); - condition.compile(target, true, BreakpointType.STEP_OVER); - int mid = target.temp(); + declaration.compile(subtarget, false, BreakpointType.STEP_OVER); + + int start = subtarget.size(); + condition.compile(subtarget, true, BreakpointType.STEP_OVER); + int mid = subtarget.temp(); var end = new DeferredIntSupplier(); - LabelContext.pushLoop(target.env, loc(), label, end, start); - body.compile(target, false, BreakpointType.STEP_OVER); - LabelContext.popLoop(target.env, label); + LabelContext.pushLoop(subtarget.env, loc(), label, end, start); + body.compile(subtarget, false, BreakpointType.STEP_OVER); + LabelContext.popLoop(subtarget.env, label); - // int beforeAssign = target.size(); - assignment.compile(target, false, BreakpointType.STEP_OVER); - int endI = target.size(); + assignment.compile(subtarget, false, BreakpointType.STEP_OVER); + int endI = subtarget.size(); end.set(endI); - // WhileNode.replaceBreaks(target, label, mid + 1, end, beforeAssign, end + 1); + subtarget.add(Instruction.jmp(start - endI)); + subtarget.set(mid, Instruction.jmpIfNot(endI - mid + 1)); + if (pollute) subtarget.add(Instruction.pushUndefined()); - target.add(Instruction.jmp(start - endI)); - target.set(mid, Instruction.jmpIfNot(endI - mid + 1)); - if (pollute) target.add(Instruction.pushUndefined()); + subtarget.scope.end(); + subtarget.add(Instruction.stackFree(subtarget.scope.allocCount())); } public ForNode(Location loc, String label, Node declaration, Node condition, Node assignment, Node body) { diff --git a/src/java/me/topchetoeu/jscript/compilation/control/ForOfNode.java b/src/java/me/topchetoeu/jscript/compilation/control/ForOfNode.java index 87ffb45..6cc1aa9 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/ForOfNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/ForOfNode.java @@ -11,42 +11,41 @@ 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.values.VariableNode; public class ForOfNode extends Node { public final String varName; - public final boolean isDeclaration; + public final DeclarationType declType; public final Node iterable, body; public final String label; public final Location varLocation; @Override public void resolve(CompileResult target) { body.resolve(target); - if (isDeclaration) target.scope.define(varName, false, varLocation); + if (declType != null && !declType.strict) target.scope.define(varName, false, varLocation); } @Override public void compile(CompileResult target, boolean pollute) { + if (declType != null && declType.strict) target.scope.defineStrict(varName, declType.readonly, varLocation); + iterable.compile(target, true, BreakpointType.STEP_OVER); target.add(Instruction.dup()); target.add(Instruction.loadIntrinsics("it_key")); target.add(Instruction.loadMember()).setLocation(iterable.loc()); - target.add(Instruction.loadMember()).setLocation(iterable.loc()); target.add(Instruction.call(0)).setLocation(iterable.loc()); int start = target.size(); target.add(Instruction.dup()); target.add(Instruction.dup()); - target.add(Instruction.pushValue("next")); - target.add(Instruction.loadMember()).setLocation(iterable.loc()); + target.add(Instruction.loadMember("next")).setLocation(iterable.loc()); target.add(Instruction.call(0)).setLocation(iterable.loc()); target.add(Instruction.dup()); - target.add(Instruction.pushValue("done")); - target.add(Instruction.loadMember()).setLocation(iterable.loc()); + target.add(Instruction.loadMember("done")).setLocation(iterable.loc()); int mid = target.temp(); - target.add(Instruction.pushValue("value")); - target.add(Instruction.loadMember()).setLocation(varLocation); - target.add(VariableNode.toSet(target, varLocation, varName, false, isDeclaration)); + target.add(Instruction.loadMember("value")).setLocation(varLocation); + target.add(VariableNode.toSet(target, varLocation, varName, false, declType != null && declType.strict)); var end = new DeferredIntSupplier(); @@ -57,8 +56,6 @@ public class ForOfNode extends Node { int endI = target.size(); end.set(endI); - // WhileNode.replaceBreaks(target, label, mid + 1, end, start, end + 1); - target.add(Instruction.jmp(start - endI)); target.add(Instruction.discard()); target.add(Instruction.discard()); @@ -66,11 +63,11 @@ public class ForOfNode extends Node { if (pollute) target.add(Instruction.pushUndefined()); } - public ForOfNode(Location loc, Location varLocation, String label, boolean isDecl, String varName, Node object, Node body) { + public ForOfNode(Location loc, Location varLocation, String label, DeclarationType declType, String varName, Node object, Node body) { super(loc); this.varLocation = varLocation; this.label = label; - this.isDeclaration = isDecl; + this.declType = declType; this.varName = varName; this.iterable = object; this.body = body; @@ -92,12 +89,8 @@ public class ForOfNode extends Node { n++; n += Parsing.skipEmpty(src, i + n); - var isDecl = false; - - if (Parsing.isIdentifier(src, i + n, "var")) { - isDecl = true; - n += 3; - } + 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"); @@ -120,6 +113,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, isDecl, name.result, obj.result, bodyRes.result), n); + return ParseRes.res(new ForOfNode(loc, nameLoc, label.result, declType.result, name.result, obj.result, bodyRes.result), n); } } diff --git a/src/java/me/topchetoeu/jscript/compilation/values/VariableNode.java b/src/java/me/topchetoeu/jscript/compilation/values/VariableNode.java index 1ced342..bca18a2 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/VariableNode.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/VariableNode.java @@ -42,7 +42,7 @@ public class VariableNode extends Node implements AssignableNode { if (!pollute) target.add(Instruction.discard()); } else if (pollute) { - target.add(Instruction.loadVar(i.index())); + target.add(_i -> Instruction.loadVar(i.index())); } } @@ -67,8 +67,8 @@ public class VariableNode extends Node implements AssignableNode { if (target.scope.has(name)) throw new SyntaxException(loc, String.format("Cannot access '%s' before initialization", name)); else return Instruction.globSet(name, keep, define); }; + else if (!define && i.readonly) return _i -> Instruction.throwSyntax(new SyntaxException(loc, "Assignment to constant variable")); else return _i -> Instruction.storeVar(i.index(), keep); - } public VariableNode(Location loc, String name) {