diff --git a/src/java/me/topchetoeu/jscript/common/json/JSON.java b/src/java/me/topchetoeu/jscript/common/json/JSON.java index b81062b..34704d6 100644 --- a/src/java/me/topchetoeu/jscript/common/json/JSON.java +++ b/src/java/me/topchetoeu/jscript/common/json/JSON.java @@ -8,6 +8,7 @@ import me.topchetoeu.jscript.common.ParseRes; import me.topchetoeu.jscript.compilation.parsing.Operator; import me.topchetoeu.jscript.compilation.parsing.Parsing; import me.topchetoeu.jscript.compilation.parsing.Token; +import me.topchetoeu.jscript.compilation.values.ConstantStatement; import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; public class JSON { @@ -15,7 +16,7 @@ public class JSON { return Parsing.parseIdentifier(tokens, i); } public static ParseRes parseString(Filename filename, List tokens, int i) { - var res = Parsing.parseString(filename, tokens, i); + var res = ConstantStatement.parseString(filename, tokens, i); if (res.isSuccess()) return ParseRes.res((String)res.result.value, res.n); else return res.transform(); } @@ -23,7 +24,7 @@ public class JSON { var minus = Parsing.isOperator(tokens, i, Operator.SUBTRACT); if (minus) i++; - var res = Parsing.parseNumber(filename, tokens, i); + var res = ConstantStatement.parseNumber(filename, tokens, i); if (res.isSuccess()) return ParseRes.res((minus ? -1 : 1) * (Double)res.result.value, res.n + (minus ? 1 : 0)); else return res.transform(); } diff --git a/src/java/me/topchetoeu/jscript/compilation/AssignableStatement.java b/src/java/me/topchetoeu/jscript/compilation/AssignableStatement.java index 9602c2e..c4c17d2 100644 --- a/src/java/me/topchetoeu/jscript/compilation/AssignableStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/AssignableStatement.java @@ -1,7 +1,15 @@ package me.topchetoeu.jscript.compilation; +import java.util.List; + +import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.Location; import me.topchetoeu.jscript.common.Operation; +import me.topchetoeu.jscript.common.ParseRes; +import me.topchetoeu.jscript.common.ParseRes.State; +import me.topchetoeu.jscript.compilation.parsing.Operator; +import me.topchetoeu.jscript.compilation.parsing.Parsing; +import me.topchetoeu.jscript.compilation.parsing.Token; public abstract class AssignableStatement extends Statement { public abstract Statement toAssign(Statement val, Operation operation); @@ -9,4 +17,42 @@ public abstract class AssignableStatement extends Statement { protected AssignableStatement(Location loc) { super(loc); } + + public static ParseRes parse(Filename filename, List tokens, int i, Statement prev, int precedence) { + var loc = Parsing.getLoc(filename, tokens, i); + int n = 0; + + if (precedence > 2) return ParseRes.failed(); + + var opRes = Parsing.parseOperator(tokens, i + n++); + if (opRes.state != State.SUCCESS) return ParseRes.failed(); + + var op = opRes.result; + if (!op.isAssign()) return ParseRes.failed(); + + if (!(prev instanceof AssignableStatement)) { + return ParseRes.error(loc, "Invalid expression on left hand side of assign operator."); + } + + var res = Parsing.parseValue(filename, tokens, i + n, 2); + if (!res.isSuccess()) return ParseRes.error(loc, String.format("Expected value after assignment operator '%s'.", op.readable), res); + n += res.n; + + Operation operation = null; + + if (op == Operator.ASSIGN_ADD) operation = Operation.ADD; + if (op == Operator.ASSIGN_SUBTRACT) operation = Operation.SUBTRACT; + if (op == Operator.ASSIGN_MULTIPLY) operation = Operation.MULTIPLY; + if (op == Operator.ASSIGN_DIVIDE) operation = Operation.DIVIDE; + if (op == Operator.ASSIGN_MODULO) operation = Operation.MODULO; + if (op == Operator.ASSIGN_OR) operation = Operation.OR; + if (op == Operator.ASSIGN_XOR) operation = Operation.XOR; + if (op == Operator.ASSIGN_AND) operation = Operation.AND; + if (op == Operator.ASSIGN_SHIFT_LEFT) operation = Operation.SHIFT_LEFT; + if (op == Operator.ASSIGN_SHIFT_RIGHT) operation = Operation.SHIFT_RIGHT; + if (op == Operator.ASSIGN_USHIFT_RIGHT) operation = Operation.USHIFT_RIGHT; + + return ParseRes.res(((AssignableStatement)prev).toAssign(res.result, operation), n); + } + } diff --git a/src/java/me/topchetoeu/jscript/compilation/CompoundStatement.java b/src/java/me/topchetoeu/jscript/compilation/CompoundStatement.java index 06eed66..62fa7a9 100644 --- a/src/java/me/topchetoeu/jscript/compilation/CompoundStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/CompoundStatement.java @@ -1,11 +1,17 @@ package me.topchetoeu.jscript.compilation; +import java.util.ArrayList; import java.util.List; import java.util.Vector; +import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.ParseRes; import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.compilation.parsing.Operator; +import me.topchetoeu.jscript.compilation.parsing.Parsing; +import me.topchetoeu.jscript.compilation.parsing.Token; import me.topchetoeu.jscript.compilation.values.FunctionStatement; public class CompoundStatement extends Statement { @@ -61,4 +67,46 @@ public class CompoundStatement extends Statement { this.separateFuncs = separateFuncs; this.statements = statements; } + + public static ParseRes parseComma(Filename filename, List tokens, int i, Statement prev, int precedence) { + var loc = Parsing.getLoc(filename, tokens, i); + var n = 0; + + if (precedence > 1) return ParseRes.failed(); + if (!Parsing.isOperator(tokens, i + n++, Operator.COMMA)) return ParseRes.failed(); + + var res = Parsing.parseValue(filename, tokens, i + n, 2); + if (!res.isSuccess()) return ParseRes.error(loc, "Expected a value after the comma.", res); + n += res.n; + + return ParseRes.res(new CompoundStatement(loc, false, prev, res.result), n); + } + public static ParseRes parse(Filename filename, List tokens, int i) { + var loc = Parsing.getLoc(filename, tokens, i); + int n = 0; + if (!Parsing.isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed(); + + var statements = new ArrayList(); + + while (true) { + if (Parsing.isOperator(tokens, i + n, Operator.BRACE_CLOSE)) { + n++; + break; + } + if (Parsing.isOperator(tokens, i + n, Operator.SEMICOLON)) { + n++; + continue; + } + + var res = Parsing.parseStatement(filename, tokens, i + n); + if (!res.isSuccess()) { + return ParseRes.error(Parsing.getLoc(filename, tokens, i), "Expected a statement.", res); + } + n += res.n; + + statements.add(res.result); + } + + return ParseRes.res(new CompoundStatement(loc, true, statements.toArray(Statement[]::new)).setEnd(Parsing.getLoc(filename, tokens, i + n - 1)), n); + } } diff --git a/src/java/me/topchetoeu/jscript/compilation/VariableDeclareStatement.java b/src/java/me/topchetoeu/jscript/compilation/VariableDeclareStatement.java index 1f4b6a4..25d16f1 100644 --- a/src/java/me/topchetoeu/jscript/compilation/VariableDeclareStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/VariableDeclareStatement.java @@ -1,10 +1,16 @@ package me.topchetoeu.jscript.compilation; +import java.util.ArrayList; import java.util.List; +import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.ParseRes; import me.topchetoeu.jscript.common.Instruction.BreakpointType; +import me.topchetoeu.jscript.compilation.parsing.Operator; +import me.topchetoeu.jscript.compilation.parsing.Parsing; +import me.topchetoeu.jscript.compilation.parsing.Token; import me.topchetoeu.jscript.compilation.values.FunctionStatement; public class VariableDeclareStatement extends Statement { @@ -49,4 +55,49 @@ public class VariableDeclareStatement extends Statement { super(loc); this.values = values; } + + public static ParseRes parse(Filename filename, List tokens, int i) { + var loc = Parsing.getLoc(filename, tokens, i); + int n = 0; + if (!Parsing.isIdentifier(tokens, i + n++, "var")) return ParseRes.failed(); + + var res = new ArrayList(); + + if (Parsing.isStatementEnd(tokens, i + n)) { + if (Parsing.isOperator(tokens, i + n, Operator.SEMICOLON)) return ParseRes.res(new VariableDeclareStatement(loc, res), 2); + else return ParseRes.res(new VariableDeclareStatement(loc, res), 1); + } + + while (true) { + var nameLoc = Parsing.getLoc(filename, tokens, i + n); + var nameRes = Parsing.parseIdentifier(tokens, i + n++); + if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a variable name."); + + if (!Parsing.checkVarName(nameRes.result)) { + return ParseRes.error(loc, String.format("Unexpected identifier '%s'.", nameRes.result)); + } + + Statement val = null; + + if (Parsing.isOperator(tokens, i + n, Operator.ASSIGN)) { + n++; + var valRes = Parsing.parseValue(filename, tokens, i + n, 2); + if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after '='.", valRes); + n += valRes.n; + val = valRes.result; + } + + res.add(new Pair(nameRes.result, val, nameLoc)); + + if (Parsing.isOperator(tokens, i + n, Operator.COMMA)) { + n++; + continue; + } + else if (Parsing.isStatementEnd(tokens, i + n)) { + if (Parsing.isOperator(tokens, i + n, Operator.SEMICOLON)) return ParseRes.res(new VariableDeclareStatement(loc, res), n + 1); + else return ParseRes.res(new VariableDeclareStatement(loc, res), n); + } + else return ParseRes.error(loc, "Expected a comma or end of statement."); + } + } } diff --git a/src/java/me/topchetoeu/jscript/compilation/control/BreakStatement.java b/src/java/me/topchetoeu/jscript/compilation/control/BreakStatement.java index eafae48..4c39bc7 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/BreakStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/BreakStatement.java @@ -1,15 +1,21 @@ package me.topchetoeu.jscript.compilation.control; +import java.util.List; + +import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.ParseRes; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.parsing.Operator; +import me.topchetoeu.jscript.compilation.parsing.Parsing; +import me.topchetoeu.jscript.compilation.parsing.Token; public class BreakStatement extends Statement { public final String label; - @Override - public void compile(CompileResult target, boolean pollute) { + @Override public void compile(CompileResult target, boolean pollute) { target.add(Instruction.nop("break", label)); if (pollute) target.add(Instruction.pushUndefined()); } @@ -18,4 +24,23 @@ public class BreakStatement extends Statement { super(loc); this.label = label; } + + public static ParseRes parseBreak(Filename filename, List tokens, int i) { + if (!Parsing.isIdentifier(tokens, i, "break")) return ParseRes.failed(); + + if (Parsing.isStatementEnd(tokens, i + 1)) { + if (Parsing.isOperator(tokens, i + 1, Operator.SEMICOLON)) return ParseRes.res(new BreakStatement(Parsing.getLoc(filename, tokens, i), null), 2); + else return ParseRes.res(new BreakStatement(Parsing.getLoc(filename, tokens, i), null), 1); + } + + var labelRes = Parsing.parseIdentifier(tokens, i + 1); + if (labelRes.isFailed()) return ParseRes.error(Parsing.getLoc(filename, tokens, i), "Expected a label name or an end of statement."); + var label = labelRes.result; + + if (Parsing.isStatementEnd(tokens, i + 2)) { + if (Parsing.isOperator(tokens, i + 2, Operator.SEMICOLON)) return ParseRes.res(new BreakStatement(Parsing.getLoc(filename, tokens, i), label), 3); + else return ParseRes.res(new BreakStatement(Parsing.getLoc(filename, tokens, i), label), 2); + } + else return ParseRes.error(Parsing.getLoc(filename, tokens, i), "Expected an end of statement."); + } } diff --git a/src/java/me/topchetoeu/jscript/compilation/control/ContinueStatement.java b/src/java/me/topchetoeu/jscript/compilation/control/ContinueStatement.java index dfc3abd..2513892 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/ContinueStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/ContinueStatement.java @@ -1,15 +1,21 @@ package me.topchetoeu.jscript.compilation.control; +import java.util.List; + +import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.ParseRes; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.parsing.Operator; +import me.topchetoeu.jscript.compilation.parsing.Parsing; +import me.topchetoeu.jscript.compilation.parsing.Token; public class ContinueStatement extends Statement { public final String label; - @Override - public void compile(CompileResult target, boolean pollute) { + @Override public void compile(CompileResult target, boolean pollute) { target.add(Instruction.nop("cont", label)); if (pollute) target.add(Instruction.pushUndefined()); } @@ -18,4 +24,23 @@ public class ContinueStatement extends Statement { super(loc); this.label = label; } + + public static ParseRes parseContinue(Filename filename, List tokens, int i) { + if (!Parsing.isIdentifier(tokens, i, "continue")) return ParseRes.failed(); + + if (Parsing.isStatementEnd(tokens, i + 1)) { + if (Parsing.isOperator(tokens, i + 1, Operator.SEMICOLON)) return ParseRes.res(new ContinueStatement(Parsing.getLoc(filename, tokens, i), null), 2); + else return ParseRes.res(new ContinueStatement(Parsing.getLoc(filename, tokens, i), null), 1); + } + + var labelRes = Parsing.parseIdentifier(tokens, i + 1); + if (labelRes.isFailed()) return ParseRes.error(Parsing.getLoc(filename, tokens, i), "Expected a label name or an end of statement."); + var label = labelRes.result; + + if (Parsing.isStatementEnd(tokens, i + 2)) { + if (Parsing.isOperator(tokens, i + 2, Operator.SEMICOLON)) return ParseRes.res(new ContinueStatement(Parsing.getLoc(filename, tokens, i), label), 3); + else return ParseRes.res(new ContinueStatement(Parsing.getLoc(filename, tokens, i), label), 2); + } + else return ParseRes.error(Parsing.getLoc(filename, tokens, i), "Expected an end of statement."); + } } diff --git a/src/java/me/topchetoeu/jscript/compilation/control/DebugStatement.java b/src/java/me/topchetoeu/jscript/compilation/control/DebugStatement.java index 20d23ef..bf1c352 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/DebugStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/DebugStatement.java @@ -1,13 +1,19 @@ package me.topchetoeu.jscript.compilation.control; +import java.util.List; + +import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.ParseRes; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.parsing.Operator; +import me.topchetoeu.jscript.compilation.parsing.Parsing; +import me.topchetoeu.jscript.compilation.parsing.Token; public class DebugStatement extends Statement { - @Override - public void compile(CompileResult target, boolean pollute) { + @Override public void compile(CompileResult target, boolean pollute) { target.add(Instruction.debug()); if (pollute) target.add(Instruction.pushUndefined()); } @@ -15,4 +21,15 @@ public class DebugStatement extends Statement { public DebugStatement(Location loc) { super(loc); } + + public static ParseRes parse(Filename filename, List tokens, int i) { + if (!Parsing.isIdentifier(tokens, i, "debugger")) return ParseRes.failed(); + + if (Parsing.isStatementEnd(tokens, i + 1)) { + if (Parsing.isOperator(tokens, i + 1, Operator.SEMICOLON)) return ParseRes.res(new DebugStatement(Parsing.getLoc(filename, tokens, i)), 2); + else return ParseRes.res(new DebugStatement(Parsing.getLoc(filename, tokens, i)), 1); + } + else return ParseRes.error(Parsing.getLoc(filename, tokens, i), "Expected an end of statement."); + } + } diff --git a/src/java/me/topchetoeu/jscript/compilation/control/DeleteStatement.java b/src/java/me/topchetoeu/jscript/compilation/control/DeleteStatement.java index 9355182..cdc35db 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/DeleteStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/DeleteStatement.java @@ -1,9 +1,18 @@ package me.topchetoeu.jscript.compilation.control; +import java.util.List; + +import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.ParseRes; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.parsing.Parsing; +import me.topchetoeu.jscript.compilation.parsing.Token; +import me.topchetoeu.jscript.compilation.values.ConstantStatement; +import me.topchetoeu.jscript.compilation.values.IndexStatement; +import me.topchetoeu.jscript.compilation.values.VariableStatement; public class DeleteStatement extends Statement { public final Statement key; @@ -18,6 +27,25 @@ public class DeleteStatement extends Statement { if (pollute) target.add(Instruction.pushValue(true)); } + public static ParseRes parseDelete(Filename filename, List tokens, int i) { + var loc = Parsing.getLoc(filename, tokens, i); + int n = 0; + if (!Parsing.isIdentifier(tokens, i + n++, "delete")) return ParseRes.failed(); + + var valRes = Parsing.parseValue(filename, tokens, i + n, 15); + if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'delete'.", valRes); + n += valRes.n; + + if (valRes.result instanceof IndexStatement) { + var index = (IndexStatement)valRes.result; + return ParseRes.res(new DeleteStatement(loc, index.index, index.object), n); + } + else if (valRes.result instanceof VariableStatement) { + return ParseRes.error(loc, "A variable may not be deleted."); + } + else return ParseRes.res(new ConstantStatement(loc, true), n); + } + public DeleteStatement(Location loc, Statement key, Statement value) { super(loc); this.key = key; diff --git a/src/java/me/topchetoeu/jscript/compilation/control/DoWhileStatement.java b/src/java/me/topchetoeu/jscript/compilation/control/DoWhileStatement.java index 2417902..456b487 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/DoWhileStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/DoWhileStatement.java @@ -1,10 +1,17 @@ package me.topchetoeu.jscript.compilation.control; +import java.util.List; + +import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.ParseRes; import me.topchetoeu.jscript.common.Instruction.BreakpointType; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.parsing.Operator; +import me.topchetoeu.jscript.compilation.parsing.Parsing; +import me.topchetoeu.jscript.compilation.parsing.Token; public class DoWhileStatement extends Statement { public final Statement condition, body; @@ -33,4 +40,35 @@ public class DoWhileStatement extends Statement { this.condition = condition; this.body = body; } + + public static ParseRes parseDoWhile(Filename filename, List tokens, int i) { + var loc = Parsing.getLoc(filename, tokens, i); + int n = 0; + + var labelRes = WhileStatement.parseLabel(tokens, i + n); + n += labelRes.n; + + if (!Parsing.isIdentifier(tokens, i + n++, "do")) return ParseRes.failed(); + var bodyRes = Parsing.parseStatement(filename, tokens, i + n); + if (!bodyRes.isSuccess()) return ParseRes.error(loc, "Expected a do-while body.", bodyRes); + n += bodyRes.n; + + if (!Parsing.isIdentifier(tokens, i + n++, "while")) return ParseRes.error(loc, "Expected 'while' keyword."); + if (!Parsing.isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.error(loc, "Expected a open paren after 'while'."); + + var condRes = Parsing.parseValue(filename, tokens, i + n, 0); + if (!condRes.isSuccess()) return ParseRes.error(loc, "Expected a while condition.", condRes); + n += condRes.n; + + if (!Parsing.isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after while condition."); + + var res = ParseRes.res(new DoWhileStatement(loc, labelRes.result, condRes.result, bodyRes.result), n); + + if (Parsing.isStatementEnd(tokens, i + n)) { + if (Parsing.isOperator(tokens, i + n, Operator.SEMICOLON)) return res.addN(1); + else return res; + } + else return ParseRes.error(Parsing.getLoc(filename, tokens, i), "Expected a semicolon."); + } + } diff --git a/src/java/me/topchetoeu/jscript/compilation/control/ForInStatement.java b/src/java/me/topchetoeu/jscript/compilation/control/ForInStatement.java index 216509a..d4d162f 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/ForInStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/ForInStatement.java @@ -1,11 +1,18 @@ package me.topchetoeu.jscript.compilation.control; +import java.util.List; + +import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Location; import me.topchetoeu.jscript.common.Operation; +import me.topchetoeu.jscript.common.ParseRes; import me.topchetoeu.jscript.common.Instruction.BreakpointType; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.parsing.Operator; +import me.topchetoeu.jscript.compilation.parsing.Parsing; +import me.topchetoeu.jscript.compilation.parsing.Token; public class ForInStatement extends Statement { public final String varName; @@ -14,14 +21,12 @@ public class ForInStatement extends Statement { public final String label; public final Location varLocation; - @Override - public void declare(CompileResult target) { + @Override public void declare(CompileResult target) { body.declare(target); if (isDeclaration) target.scope.define(varName); } - @Override - public void compile(CompileResult target, boolean pollute) { + @Override public void compile(CompileResult target, boolean pollute) { var key = target.scope.getKey(varName); if (key instanceof String) target.add(Instruction.makeVar((String)key)); @@ -66,4 +71,60 @@ public class ForInStatement extends Statement { this.object = object; this.body = body; } + + public static ParseRes parse(Filename filename, List tokens, int i) { + var loc = Parsing.getLoc(filename, tokens, i); + int n = 0; + + var labelRes = WhileStatement.parseLabel(tokens, i + n); + var isDecl = false; + n += labelRes.n; + + if (!Parsing.isIdentifier(tokens, i + n++, "for")) return ParseRes.failed(); + if (!Parsing.isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.error(loc, "Expected a open paren after 'for'."); + + if (Parsing.isIdentifier(tokens, i + n, "var")) { + isDecl = true; + n++; + } + + var nameRes = Parsing.parseIdentifier(tokens, i + n); + if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a variable name for 'for' loop."); + var nameLoc = Parsing.getLoc(filename, tokens, i + n); + n += nameRes.n; + + Statement varVal = null; + + if (Parsing.isOperator(tokens, i + n, Operator.ASSIGN)) { + n++; + + var valRes = Parsing.parseValue(filename, tokens, i + n, 2); + if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after '='.", valRes); + n += nameRes.n; + + varVal = valRes.result; + } + + if (!Parsing.isIdentifier(tokens, i + n++, "in")) { + if (varVal == null) { + if (nameRes.result.equals("const")) return ParseRes.error(loc, "'const' declarations are not supported."); + else if (nameRes.result.equals("let")) return ParseRes.error(loc, "'let' declarations are not supported."); + } + return ParseRes.error(loc, "Expected 'in' keyword after variable declaration."); + } + + var objRes = Parsing.parseValue(filename, tokens, i + n, 0); + if (!objRes.isSuccess()) return ParseRes.error(loc, "Expected a value.", objRes); + n += objRes.n; + + if (!Parsing.isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after for."); + + + var bodyRes = Parsing.parseStatement(filename, tokens, i + n); + if (!bodyRes.isSuccess()) return ParseRes.error(loc, "Expected a for body.", bodyRes); + n += bodyRes.n; + + return ParseRes.res(new ForInStatement(loc, nameLoc, labelRes.result, isDecl, nameRes.result, varVal, objRes.result, bodyRes.result), n); + } + } diff --git a/src/java/me/topchetoeu/jscript/compilation/control/ForOfStatement.java b/src/java/me/topchetoeu/jscript/compilation/control/ForOfStatement.java index fba5fc7..a304252 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/ForOfStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/ForOfStatement.java @@ -1,10 +1,17 @@ package me.topchetoeu.jscript.compilation.control; +import java.util.List; + +import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.ParseRes; import me.topchetoeu.jscript.common.Instruction.BreakpointType; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.parsing.Operator; +import me.topchetoeu.jscript.compilation.parsing.Parsing; +import me.topchetoeu.jscript.compilation.parsing.Token; public class ForOfStatement extends Statement { public final String varName; @@ -70,4 +77,45 @@ public class ForOfStatement extends Statement { this.iterable = object; this.body = body; } + + public static ParseRes parse(Filename filename, List tokens, int i) { + var loc = Parsing.getLoc(filename, tokens, i); + int n = 0; + + var labelRes = WhileStatement.parseLabel(tokens, i + n); + var isDecl = false; + n += labelRes.n; + + if (!Parsing.isIdentifier(tokens, i + n++, "for")) return ParseRes.failed(); + if (!Parsing.isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.error(loc, "Expected a open paren after 'for'."); + + if (Parsing.isIdentifier(tokens, i + n, "var")) { + isDecl = true; + n++; + } + + var nameRes = Parsing.parseIdentifier(tokens, i + n); + if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a variable name for 'for' loop."); + var nameLoc = Parsing.getLoc(filename, tokens, i + n); + n += nameRes.n; + + if (!Parsing.isIdentifier(tokens, i + n++, "of")) { + if (nameRes.result.equals("const")) return ParseRes.error(loc, "'const' declarations are not supported."); + else if (nameRes.result.equals("let")) return ParseRes.error(loc, "'let' declarations are not supported."); + else return ParseRes.error(loc, "Expected 'of' keyword after variable declaration."); + } + + var objRes = Parsing.parseValue(filename, tokens, i + n, 0); + if (!objRes.isSuccess()) return ParseRes.error(loc, "Expected a value.", objRes); + n += objRes.n; + + if (!Parsing.isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after for."); + + + var bodyRes = Parsing.parseStatement(filename, tokens, i + n); + if (!bodyRes.isSuccess()) return ParseRes.error(loc, "Expected a for body.", bodyRes); + n += bodyRes.n; + + return ParseRes.res(new ForOfStatement(loc, nameLoc, labelRes.result, isDecl, nameRes.result, objRes.result, bodyRes.result), n); + } } diff --git a/src/java/me/topchetoeu/jscript/compilation/control/ForStatement.java b/src/java/me/topchetoeu/jscript/compilation/control/ForStatement.java index 9a59613..e97a073 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/ForStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/ForStatement.java @@ -1,10 +1,20 @@ package me.topchetoeu.jscript.compilation.control; +import java.util.List; + +import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.ParseRes; import me.topchetoeu.jscript.common.Instruction.BreakpointType; import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.CompoundStatement; import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.VariableDeclareStatement; +import me.topchetoeu.jscript.compilation.parsing.Operator; +import me.topchetoeu.jscript.compilation.parsing.Parsing; +import me.topchetoeu.jscript.compilation.parsing.Token; +import me.topchetoeu.jscript.compilation.values.ConstantStatement; public class ForStatement extends Statement { public final Statement declaration, assignment, condition, body; @@ -42,4 +52,62 @@ public class ForStatement extends Statement { this.assignment = assignment; this.body = body; } + + public static ParseRes parse(Filename filename, List tokens, int i) { + var loc = Parsing.getLoc(filename, tokens, i); + int n = 0; + + var labelRes = WhileStatement.parseLabel(tokens, i + n); + n += labelRes.n; + + if (!Parsing.isIdentifier(tokens, i + n++, "for")) return ParseRes.failed(); + if (!Parsing.isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.error(loc, "Expected a open paren after 'for'."); + + Statement decl, cond, inc; + + if (Parsing.isOperator(tokens, i + n, Operator.SEMICOLON)) { + n++; + decl = new CompoundStatement(loc, false); + } + else { + var declRes = ParseRes.any( + VariableDeclareStatement.parse(filename, tokens, i + n), + Parsing.parseValueStatement(filename, tokens, i + n) + ); + if (!declRes.isSuccess()) return ParseRes.error(loc, "Expected a declaration or an expression.", declRes); + n += declRes.n; + decl = declRes.result; + } + + if (Parsing.isOperator(tokens, i + n, Operator.SEMICOLON)) { + n++; + cond = new ConstantStatement(loc, 1); + } + else { + var condRes = Parsing.parseValue(filename, tokens, i + n, 0); + if (!condRes.isSuccess()) return ParseRes.error(loc, "Expected a condition.", condRes); + n += condRes.n; + if (!Parsing.isOperator(tokens, i + n++, Operator.SEMICOLON)) return ParseRes.error(loc, "Expected a semicolon.", condRes); + cond = condRes.result; + } + + if (Parsing.isOperator(tokens, i + n, Operator.PAREN_CLOSE)) { + n++; + inc = new CompoundStatement(loc, false); + } + else { + var incRes = Parsing.parseValue(filename, tokens, i + n, 0); + if (!incRes.isSuccess()) return ParseRes.error(loc, "Expected a condition.", incRes); + n += incRes.n; + inc = incRes.result; + if (!Parsing.isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after for."); + } + + + var res = Parsing.parseStatement(filename, tokens, i + n); + if (!res.isSuccess()) return ParseRes.error(loc, "Expected a for body.", res); + n += res.n; + + return ParseRes.res(new ForStatement(loc, labelRes.result, decl, cond, inc, res.result), n); + } } diff --git a/src/java/me/topchetoeu/jscript/compilation/control/IfStatement.java b/src/java/me/topchetoeu/jscript/compilation/control/IfStatement.java index ba37671..8dd2034 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/IfStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/IfStatement.java @@ -1,10 +1,17 @@ package me.topchetoeu.jscript.compilation.control; +import java.util.List; + +import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.ParseRes; import me.topchetoeu.jscript.common.Instruction.BreakpointType; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.parsing.Operator; +import me.topchetoeu.jscript.compilation.parsing.Parsing; +import me.topchetoeu.jscript.compilation.parsing.Token; public class IfStatement extends Statement { public final Statement condition, body, elseBody; @@ -45,4 +52,51 @@ public class IfStatement extends Statement { this.body = body; this.elseBody = elseBody; } + + public static ParseRes parseTernary(Filename filename, List tokens, int i, Statement prev, int precedence) { + var loc = Parsing.getLoc(filename, tokens, i); + var n = 0; + + if (precedence > 2) return ParseRes.failed(); + if (!Parsing.isOperator(tokens, i + n++, Operator.QUESTION)) return ParseRes.failed(); + + var a = Parsing.parseValue(filename, tokens, i + n, 2); + if (!a.isSuccess()) return ParseRes.error(loc, "Expected a value after the ternary operator.", a); + n += a.n; + + if (!Parsing.isOperator(tokens, i + n++, Operator.COLON)) return ParseRes.failed(); + + var b = Parsing.parseValue(filename, tokens, i + n, 2); + if (!b.isSuccess()) return ParseRes.error(loc, "Expected a second value after the ternary operator.", b); + n += b.n; + + return ParseRes.res(new IfStatement(loc, prev, a.result, b.result), n); + } + public static ParseRes parse(Filename filename, List tokens, int i) { + var loc = Parsing.getLoc(filename, tokens, i); + int n = 0; + + if (!Parsing.isIdentifier(tokens, i + n++, "if")) return ParseRes.failed(); + if (!Parsing.isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.error(loc, "Expected a open paren after 'if'."); + + var condRes = Parsing.parseValue(filename, tokens, i + n, 0); + if (!condRes.isSuccess()) return ParseRes.error(loc, "Expected an if condition.", condRes); + n += condRes.n; + + if (!Parsing.isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after if condition."); + + var res = Parsing.parseStatement(filename, tokens, i + n); + if (!res.isSuccess()) return ParseRes.error(loc, "Expected an if body.", res); + n += res.n; + + if (!Parsing.isIdentifier(tokens, i + n, "else")) return ParseRes.res(new IfStatement(loc, condRes.result, res.result, null), n); + n++; + + var elseRes = Parsing.parseStatement(filename, tokens, i + n); + if (!elseRes.isSuccess()) return ParseRes.error(loc, "Expected an else body.", elseRes); + n += elseRes.n; + + return ParseRes.res(new IfStatement(loc, condRes.result, res.result, elseRes.result), n); + } + } diff --git a/src/java/me/topchetoeu/jscript/compilation/control/ReturnStatement.java b/src/java/me/topchetoeu/jscript/compilation/control/ReturnStatement.java index 4e915fd..a9e0f5c 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/ReturnStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/ReturnStatement.java @@ -1,9 +1,16 @@ package me.topchetoeu.jscript.compilation.control; +import java.util.List; + +import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.ParseRes; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.parsing.Operator; +import me.topchetoeu.jscript.compilation.parsing.Parsing; +import me.topchetoeu.jscript.compilation.parsing.Token; public class ReturnStatement extends Statement { public final Statement value; @@ -19,4 +26,27 @@ public class ReturnStatement extends Statement { super(loc); this.value = value; } + + public static ParseRes parseReturn(Filename filename, List tokens, int i) { + var loc = Parsing.getLoc(filename, tokens, i); + int n = 0; + if (!Parsing.isIdentifier(tokens, i + n++, "return")) return ParseRes.failed(); + + if (Parsing.isStatementEnd(tokens, i + n)) { + if (Parsing.isOperator(tokens, i + n, Operator.SEMICOLON)) return ParseRes.res(new ReturnStatement(loc, null), 2); + else return ParseRes.res(new ReturnStatement(loc, null), 1); + } + + var valRes = Parsing.parseValue(filename, tokens, i + n, 0); + n += valRes.n; + if (valRes.isError()) return ParseRes.error(loc, "Expected a return value.", valRes); + + var res = ParseRes.res(new ReturnStatement(loc, valRes.result), n); + + if (Parsing.isStatementEnd(tokens, i + n)) { + if (Parsing.isOperator(tokens, i + n, Operator.SEMICOLON)) return res.addN(1); + else return res; + } + else return ParseRes.error(Parsing.getLoc(filename, tokens, i), "Expected an end of statement.", valRes); + } } diff --git a/src/java/me/topchetoeu/jscript/compilation/control/SwitchStatement.java b/src/java/me/topchetoeu/jscript/compilation/control/SwitchStatement.java index bf4988c..1883bd8 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/SwitchStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/SwitchStatement.java @@ -1,14 +1,22 @@ package me.topchetoeu.jscript.compilation.control; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; +import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Location; import me.topchetoeu.jscript.common.Operation; +import me.topchetoeu.jscript.common.ParseRes; import me.topchetoeu.jscript.common.Instruction.BreakpointType; import me.topchetoeu.jscript.common.Instruction.Type; import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.CompoundStatement; import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.parsing.Operator; +import me.topchetoeu.jscript.compilation.parsing.Parsing; +import me.topchetoeu.jscript.compilation.parsing.Token; public class SwitchStatement extends Statement { public static class SwitchCase { @@ -26,13 +34,11 @@ public class SwitchStatement extends Statement { public final Statement[] body; public final int defaultI; - @Override - public void declare(CompileResult target) { + @Override public void declare(CompileResult target) { for (var stm : body) stm.declare(target); } - @Override - public void compile(CompileResult target, boolean pollute) { + @Override public void compile(CompileResult target, boolean pollute) { var caseToStatement = new HashMap(); var statementToIndex = new HashMap(); @@ -80,4 +86,85 @@ public class SwitchStatement extends Statement { this.cases = cases; this.body = body; } + + private static ParseRes parseSwitchCase(Filename filename, List tokens, int i) { + var loc = Parsing.getLoc(filename, tokens, i); + int n = 0; + + if (!Parsing.isIdentifier(tokens, i + n++, "case")) return ParseRes.failed(); + + var valRes = Parsing.parseValue(filename, tokens, i + n, 0); + if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'case'.", valRes); + n += valRes.n; + + if (!Parsing.isOperator(tokens, i + n++, Operator.COLON)) return ParseRes.error(loc, "Expected colons after 'case' value."); + + return ParseRes.res(valRes.result, n); + } + private static ParseRes parseDefaultCase(List tokens, int i) { + if (!Parsing.isIdentifier(tokens, i, "default")) return ParseRes.failed(); + if (!Parsing.isOperator(tokens, i + 1, Operator.COLON)) return ParseRes.error(Parsing.getLoc(null, tokens, i), "Expected colons after 'default'."); + + return ParseRes.res(null, 2); + } + public static ParseRes parse(Filename filename, List tokens, int i) { + var loc = Parsing.getLoc(filename, tokens, i); + int n = 0; + + if (!Parsing.isIdentifier(tokens, i + n++, "switch")) return ParseRes.failed(); + if (!Parsing.isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.error(loc, "Expected a open paren after 'switch'."); + + var valRes = Parsing.parseValue(filename, tokens, i + n, 0); + if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a switch value.", valRes); + n += valRes.n; + + if (!Parsing.isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after switch value."); + if (!Parsing.isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.error(loc, "Expected an opening brace after switch value."); + + var statements = new ArrayList(); + var cases = new ArrayList(); + var defaultI = -1; + + while (true) { + if (Parsing.isOperator(tokens, i + n, Operator.BRACE_CLOSE)) { + n++; + break; + } + if (Parsing.isOperator(tokens, i + n, Operator.SEMICOLON)) { + n++; + continue; + } + + var defaultRes = SwitchStatement.parseDefaultCase(tokens, i + n); + var caseRes = SwitchStatement.parseSwitchCase(filename, tokens, i + n); + + if (defaultRes.isSuccess()) { + defaultI = statements.size(); + n += defaultRes.n; + } + else if (caseRes.isSuccess()) { + cases.add(new SwitchCase(caseRes.result, statements.size())); + n += caseRes.n; + } + else if (defaultRes.isError()) return defaultRes.transform(); + else if (caseRes.isError()) return defaultRes.transform(); + else { + var res = ParseRes.any( + Parsing.parseStatement(filename, tokens, i + n), + CompoundStatement.parse(filename, tokens, i + n) + ); + if (!res.isSuccess()) { + return ParseRes.error(Parsing.getLoc(filename, tokens, i), "Expected a statement.", res); + } + n += res.n; + statements.add(res.result); + } + } + + return ParseRes.res(new SwitchStatement( + loc, valRes.result, defaultI, + cases.toArray(SwitchCase[]::new), + statements.toArray(Statement[]::new) + ), n); + } } diff --git a/src/java/me/topchetoeu/jscript/compilation/control/ThrowStatement.java b/src/java/me/topchetoeu/jscript/compilation/control/ThrowStatement.java index 156f7ae..3e19c1f 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/ThrowStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/ThrowStatement.java @@ -1,9 +1,16 @@ package me.topchetoeu.jscript.compilation.control; +import java.util.List; + +import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.ParseRes; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.parsing.Operator; +import me.topchetoeu.jscript.compilation.parsing.Parsing; +import me.topchetoeu.jscript.compilation.parsing.Token; public class ThrowStatement extends Statement { public final Statement value; @@ -18,4 +25,22 @@ public class ThrowStatement extends Statement { super(loc); this.value = value; } + + public static ParseRes parseThrow(Filename filename, List tokens, int i) { + var loc = Parsing.getLoc(filename, tokens, i); + int n = 0; + if (!Parsing.isIdentifier(tokens, i + n++, "throw")) return ParseRes.failed(); + + var valRes = Parsing.parseValue(filename, tokens, i + n, 0); + n += valRes.n; + if (valRes.isError()) return ParseRes.error(loc, "Expected a throw value.", valRes); + + var res = ParseRes.res(new ThrowStatement(loc, valRes.result), n); + + if (Parsing.isStatementEnd(tokens, i + n)) { + if (Parsing.isOperator(tokens, i + n, Operator.SEMICOLON)) return res.addN(1); + else return res; + } + else return ParseRes.error(Parsing.getLoc(filename, tokens, i), "Expected an end of statement.", valRes); + } } diff --git a/src/java/me/topchetoeu/jscript/compilation/control/TryStatement.java b/src/java/me/topchetoeu/jscript/compilation/control/TryStatement.java index 41ce48f..5854df2 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/TryStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/TryStatement.java @@ -1,10 +1,17 @@ package me.topchetoeu.jscript.compilation.control; +import java.util.List; + +import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.ParseRes; import me.topchetoeu.jscript.common.Instruction.BreakpointType; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.parsing.Operator; +import me.topchetoeu.jscript.compilation.parsing.Parsing; +import me.topchetoeu.jscript.compilation.parsing.Token; public class TryStatement extends Statement { public final Statement tryBody; @@ -12,15 +19,13 @@ public class TryStatement extends Statement { public final Statement finallyBody; public final String name; - @Override - public void declare(CompileResult target) { + @Override public void declare(CompileResult target) { tryBody.declare(target); if (catchBody != null) catchBody.declare(target); if (finallyBody != null) finallyBody.declare(target); } - @Override - public void compile(CompileResult target, boolean pollute, BreakpointType bpt) { + @Override public void compile(CompileResult target, boolean pollute, BreakpointType bpt) { int replace = target.temp(); int start = replace + 1, catchStart = -1, finallyStart = -1; @@ -55,4 +60,45 @@ public class TryStatement extends Statement { this.finallyBody = finallyBody; this.name = name; } + + public static ParseRes parse(Filename filename, List tokens, int i) { + var loc = Parsing.getLoc(filename, tokens, i); + int n = 0; + + if (!Parsing.isIdentifier(tokens, i + n++, "try")) return ParseRes.failed(); + + var res = Parsing.parseStatement(filename, tokens, i + n); + if (!res.isSuccess()) return ParseRes.error(loc, "Expected an if body.", res); + n += res.n; + + String name = null; + Statement catchBody = null, finallyBody = null; + + + if (Parsing.isIdentifier(tokens, i + n, "catch")) { + n++; + if (Parsing.isOperator(tokens, i + n, Operator.PAREN_OPEN)) { + n++; + var nameRes = Parsing.parseIdentifier(tokens, i + n++); + if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a catch variable name."); + name = nameRes.result; + if (!Parsing.isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after catch variable name."); + } + + var catchRes = Parsing.parseStatement(filename, tokens, i + n); + if (!catchRes.isSuccess()) return ParseRes.error(loc, "Expected a catch body.", catchRes); + n += catchRes.n; + catchBody = catchRes.result; + } + + if (Parsing.isIdentifier(tokens, i + n, "finally")) { + n++; + var finallyRes = Parsing.parseStatement(filename, tokens, i + n); + if (!finallyRes.isSuccess()) return ParseRes.error(loc, "Expected a finally body.", finallyRes); + n += finallyRes.n; + finallyBody = finallyRes.result; + } + + return ParseRes.res(new TryStatement(loc, res.result, catchBody, finallyBody, name), n); + } } diff --git a/src/java/me/topchetoeu/jscript/compilation/control/WhileStatement.java b/src/java/me/topchetoeu/jscript/compilation/control/WhileStatement.java index 459e4ef..ed9cc06 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/WhileStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/WhileStatement.java @@ -1,11 +1,18 @@ package me.topchetoeu.jscript.compilation.control; +import java.util.List; + +import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.ParseRes; import me.topchetoeu.jscript.common.Instruction.BreakpointType; import me.topchetoeu.jscript.common.Instruction.Type; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.parsing.Operator; +import me.topchetoeu.jscript.compilation.parsing.Parsing; +import me.topchetoeu.jscript.compilation.parsing.Token; public class WhileStatement extends Statement { public final Statement condition, body; @@ -31,6 +38,14 @@ public class WhileStatement extends Statement { if (pollute) target.add(Instruction.pushUndefined()); } + public static ParseRes parseLabel(List tokens, int i) { + int n = 0; + + var nameRes = Parsing.parseIdentifier(tokens, i + n++); + if (!Parsing.isOperator(tokens, i + n++, Operator.COLON)) return ParseRes.failed(); + + return ParseRes.res(nameRes.result, n); + } public WhileStatement(Location loc, String label, Statement condition, Statement body) { super(loc); this.label = label; @@ -49,4 +64,27 @@ public class WhileStatement extends Statement { } } } + + public static ParseRes parseWhile(Filename filename, List tokens, int i) { + var loc = Parsing.getLoc(filename, tokens, i); + int n = 0; + + var labelRes = WhileStatement.parseLabel(tokens, i + n); + n += labelRes.n; + + if (!Parsing.isIdentifier(tokens, i + n++, "while")) return ParseRes.failed(); + if (!Parsing.isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.error(loc, "Expected a open paren after 'while'."); + + var condRes = Parsing.parseValue(filename, tokens, i + n, 0); + if (!condRes.isSuccess()) return ParseRes.error(loc, "Expected a while condition.", condRes); + n += condRes.n; + + if (!Parsing.isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after while condition."); + + var res = Parsing.parseStatement(filename, tokens, i + n); + if (!res.isSuccess()) return ParseRes.error(loc, "Expected a while body.", res); + n += res.n; + + return ParseRes.res(new WhileStatement(loc, labelRes.result, condRes.result, res.result), n); + } } diff --git a/src/java/me/topchetoeu/jscript/compilation/parsing/Parsing.java b/src/java/me/topchetoeu/jscript/compilation/parsing/Parsing.java index 84a467f..321ec1a 100644 --- a/src/java/me/topchetoeu/jscript/compilation/parsing/Parsing.java +++ b/src/java/me/topchetoeu/jscript/compilation/parsing/Parsing.java @@ -1,33 +1,30 @@ package me.topchetoeu.jscript.compilation.parsing; +import java.lang.invoke.CallSite; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedHashMap; import java.util.List; import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.common.Operation; import me.topchetoeu.jscript.common.ParseRes; -import me.topchetoeu.jscript.common.ParseRes.State; import me.topchetoeu.jscript.compilation.*; -import me.topchetoeu.jscript.compilation.VariableDeclareStatement.Pair; import me.topchetoeu.jscript.compilation.control.*; -import me.topchetoeu.jscript.compilation.control.SwitchStatement.SwitchCase; import me.topchetoeu.jscript.compilation.scope.LocalScopeRecord; import me.topchetoeu.jscript.compilation.values.*; import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; // TODO: this has to be rewritten +// @SourceFile public class Parsing { public static interface Parser { ParseRes parse(Filename filename, List tokens, int i); } - private static class ObjProp { + public static class ObjProp { public final String name; public final String access; public final FunctionStatement func; @@ -139,6 +136,7 @@ public class Parsing { // This method is so long because we're tokenizing the string using an iterative approach // instead of a recursive descent parser. This is mainly done for performance reasons. + // preformance reasons my ass private static ArrayList splitTokens(Filename filename, String raw) { var tokens = new ArrayList(); var currToken = new StringBuilder(64); @@ -402,7 +400,7 @@ public class Parsing { return -1; } - private static boolean inBounds(List tokens, int i) { + public static boolean inBounds(List tokens, int i) { return i >= 0 && i < tokens.size(); } @@ -658,80 +656,6 @@ public class Parsing { return !reserved.contains(name); } - public static ParseRes parseString(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - if (inBounds(tokens, i)) { - if (tokens.get(i).isString()) { - return ParseRes.res(new ConstantStatement(loc, tokens.get(i).string()), 1); - } - else return ParseRes.failed(); - } - else return ParseRes.failed(); - } - public static ParseRes parseNumber(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - if (inBounds(tokens, i)) { - if (tokens.get(i).isNumber()) { - return ParseRes.res(new ConstantStatement(loc, tokens.get(i).number()), 1); - } - else return ParseRes.failed(); - } - else return ParseRes.failed(); - } - public static ParseRes parseRegex(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - if (inBounds(tokens, i)) { - if (tokens.get(i).isRegex()) { - var val = tokens.get(i).regex(); - var index = val.lastIndexOf('/'); - var first = val.substring(1, index); - var second = val.substring(index + 1); - return ParseRes.res(new RegexStatement(loc, first, second), 1); - } - else return ParseRes.failed(); - } - return ParseRes.failed(); - } - - public static ParseRes parseArray(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; - if (!isOperator(tokens, i + n++, Operator.BRACKET_OPEN)) return ParseRes.failed(); - - var values = new ArrayList(); - - // Java allows labels, so labels were used - loop: while (true) { - if (isOperator(tokens, i + n, Operator.BRACKET_CLOSE)) { - n++; - break; - } - - while (isOperator(tokens, i + n, Operator.COMMA)) { - n++; - values.add(null); - if (isOperator(tokens, i + n, Operator.BRACKET_CLOSE)) { - n++; - break loop; - } - } - - var res = parseValue(filename, tokens, i + n, 2); - if (!res.isSuccess()) return ParseRes.error(loc, "Expected an array element.", res); - else n += res.n; - - values.add(res.result); - - if (isOperator(tokens, i + n, Operator.COMMA)) n++; - else if (isOperator(tokens, i + n, Operator.BRACKET_CLOSE)) { - n++; - break; - } - } - - return ParseRes.res(new ArrayStatement(loc, values.toArray(Statement[]::new)), n); - } - public static ParseRes> parseParamList(Filename filename, List tokens, int i) { var loc = getLoc(filename, tokens, i); int n = 0; @@ -764,244 +688,6 @@ public class Parsing { return ParseRes.res(args, n); } - public static ParseRes parsePropName(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - - if (inBounds(tokens, i)) { - var token = tokens.get(i); - - if (token.isNumber() || token.isIdentifier() || token.isString()) return ParseRes.res(token.rawValue, 1); - else return ParseRes.error(loc, "Expected identifier, string or number literal."); - } - else return ParseRes.failed(); - } - public static ParseRes parseObjectProp(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; - - var accessRes = parseIdentifier(tokens, i + n++); - if (!accessRes.isSuccess()) return ParseRes.failed(); - var access = accessRes.result; - if (!access.equals("get") && !access.equals("set")) return ParseRes.failed(); - - var nameRes = parsePropName(filename, tokens, i + n); - if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a property name after '" + access + "'."); - var name = nameRes.result; - n += nameRes.n; - - var argsRes = parseParamList(filename, tokens, i + n); - if (!argsRes.isSuccess()) return ParseRes.error(loc, "Expected an argument list.", argsRes); - n += argsRes.n; - - var res = parseCompound(filename, tokens, i + n); - if (!res.isSuccess()) return ParseRes.error(loc, "Expected a compound statement for property accessor.", res); - n += res.n; - - var end = getLoc(filename, tokens, i + n - 1); - - return ParseRes.res(new ObjProp( - name, access, - new FunctionStatement(loc, end, access + " " + name.toString(), argsRes.result.toArray(String[]::new), false, res.result) - ), n); - } - public static ParseRes parseObject(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; - if (!isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed(); - - var values = new LinkedHashMap(); - var getters = new LinkedHashMap(); - var setters = new LinkedHashMap(); - - if (isOperator(tokens, i + n, Operator.BRACE_CLOSE)) { - n++; - return ParseRes.res(new ObjectStatement(loc, values, getters, setters), n); - } - - while (true) { - var propRes = parseObjectProp(filename, tokens, i + n); - - if (propRes.isSuccess()) { - n += propRes.n; - if (propRes.result.access.equals("set")) { - setters.put(propRes.result.name, propRes.result.func); - } - else { - getters.put(propRes.result.name, propRes.result.func); - } - } - else { - var nameRes = parsePropName(filename, tokens, i + n); - if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a field name.", propRes); - n += nameRes.n; - - if (!isOperator(tokens, i + n++, Operator.COLON)) return ParseRes.error(loc, "Expected a colon."); - - var valRes = parseValue(filename, tokens, i + n, 2); - if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value in array list.", valRes); - n += valRes.n; - - values.put(nameRes.result, valRes.result); - } - - if (isOperator(tokens, i + n, Operator.COMMA)) { - n++; - if (isOperator(tokens, i + n, Operator.BRACE_CLOSE)) { - n++; - break; - } - continue; - } - else if (isOperator(tokens, i + n, Operator.BRACE_CLOSE)) { - n++; - break; - } - else ParseRes.error(loc, "Expected a comma or a closing brace."); - } - - return ParseRes.res(new ObjectStatement(loc, values, getters, setters), n); - } - public static ParseRes parseNew(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - var n = 0; - if (!isIdentifier(tokens, i + n++, "new")) return ParseRes.failed(); - - var valRes = parseValue(filename, tokens, i + n, 18); - n += valRes.n; - if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'new' keyword.", valRes); - var callRes = parseCall(filename, tokens, i + n, valRes.result, 0); - n += callRes.n; - if (callRes.isError()) return callRes.transform(); - else if (callRes.isFailed()) return ParseRes.res(new CallStatement(loc, true, valRes.result), n); - var call = (CallStatement)callRes.result; - - return ParseRes.res(new CallStatement(loc, true, call.func, call.args), n); - } - public static ParseRes parseTypeof(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - var n = 0; - if (!isIdentifier(tokens, i + n++, "typeof")) return ParseRes.failed(); - - var valRes = parseValue(filename, tokens, i + n, 15); - if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'typeof' keyword.", valRes); - n += valRes.n; - - return ParseRes.res(new TypeofStatement(loc, valRes.result), n); - } - public static ParseRes parseVoid(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - var n = 0; - if (!isIdentifier(tokens, i + n++, "void")) return ParseRes.failed(); - - var valRes = parseValue(filename, tokens, i + n, 14); - if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'void' keyword.", valRes); - n += valRes.n; - - return ParseRes.res(new DiscardStatement(loc, valRes.result), n); - } - public static ParseRes parseDelete(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; - if (!isIdentifier(tokens, i + n++, "delete")) return ParseRes.failed(); - - var valRes = parseValue(filename, tokens, i + n, 15); - if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'delete'.", valRes); - n += valRes.n; - - if (valRes.result instanceof IndexStatement) { - var index = (IndexStatement)valRes.result; - return ParseRes.res(new DeleteStatement(loc, index.index, index.object), n); - } - else if (valRes.result instanceof VariableStatement) { - return ParseRes.error(loc, "A variable may not be deleted."); - } - else { - return ParseRes.res(new ConstantStatement(loc, true), n); - } - } - - public static ParseRes parseFunction(Filename filename, List tokens, int i, boolean statement) { - var loc = getLoc(filename, tokens, i); - int n = 0; - - if (!isIdentifier(tokens, i + n++, "function")) return ParseRes.failed(); - - var nameRes = parseIdentifier(tokens, i + n); - if (!nameRes.isSuccess() && statement) return ParseRes.error(loc, "A statement function requires a name, one is not present."); - var name = nameRes.result; - n += nameRes.n; - - if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.error(loc, "Expected a parameter list."); - - var args = new ArrayList(); - - if (isOperator(tokens, i + n, Operator.PAREN_CLOSE)) { - n++; - } - else { - while (true) { - var argRes = parseIdentifier(tokens, i + n); - if (argRes.isSuccess()) { - args.add(argRes.result); - n++; - if (isOperator(tokens, i + n, Operator.COMMA)) { - n++; - } - if (isOperator(tokens, i + n, Operator.PAREN_CLOSE)) { - n++; - break; - } - } - else return ParseRes.error(loc, "Expected an argument, comma or a closing brace."); - } - } - - var res = parseCompound(filename, tokens, i + n); - n += res.n; - var end = getLoc(filename, tokens, i + n - 1); - - if (res.isSuccess()) return ParseRes.res(new FunctionStatement(loc, end, name, args.toArray(String[]::new), statement, res.result), n); - else return ParseRes.error(loc, "Expected a compound statement for function.", res); - } - - public static ParseRes parseUnary(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; - - var opState = parseOperator(tokens, i + n++); - if (!opState.isSuccess()) return ParseRes.failed(); - var op = opState.result; - - Operation operation = null; - - if (op == Operator.ADD) operation = Operation.POS; - else if (op == Operator.SUBTRACT) operation = Operation.NEG; - else if (op == Operator.INVERSE) operation = Operation.INVERSE; - else if (op == Operator.NOT) operation = Operation.NOT; - else return ParseRes.failed(); - - var res = parseValue(filename, tokens, n + i, 14); - - if (res.isSuccess()) return ParseRes.res(new OperationStatement(loc, operation, res.result), n + res.n); - else return ParseRes.error(loc, String.format("Expected a value after the unary operator '%s'.", op.readable), res); - } - public static ParseRes parsePrefixChange(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; - - var opState = parseOperator(tokens, i + n++); - if (!opState.isSuccess()) return ParseRes.failed(); - - int change = 0; - - if (opState.result == Operator.INCREASE) change = 1; - else if (opState.result == Operator.DECREASE) change = -1; - else return ParseRes.failed(); - - var res = parseValue(filename, tokens, i + n, 15); - if (!(res.result instanceof AssignableStatement)) return ParseRes.error(loc, "Expected assignable value after prefix operator."); - return ParseRes.res(new ChangeStatement(loc, (AssignableStatement)res.result, change, false), n + res.n); - } public static ParseRes parseParens(Filename filename, List tokens, int i) { int n = 0; if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.failed(); @@ -1018,269 +704,44 @@ public class Parsing { var res = new ArrayList>(); if (!statement) { - res.add(parseObject(filename, tokens, i)); - res.add(parseFunction(filename, tokens, i, false)); + res.add(ObjectStatement.parse(filename, tokens, i)); + res.add(FunctionStatement.parseFunction(filename, tokens, i, false)); } res.addAll(List.of( - parseVariable(filename, tokens, i), + VariableStatement.parseVariable(filename, tokens, i), parseLiteral(filename, tokens, i), - parseString(filename, tokens, i), - parseRegex(filename, tokens, i), - parseNumber(filename, tokens, i), - parseUnary(filename, tokens, i), - parseArray(filename, tokens, i), - parsePrefixChange(filename, tokens, i), + ConstantStatement.parseString(filename, tokens, i), + RegexStatement.parse(filename, tokens, i), + ConstantStatement.parseNumber(filename, tokens, i), + OperationStatement.parseUnary(filename, tokens, i), + ArrayStatement.parse(filename, tokens, i), + ChangeStatement.parsePrefix(filename, tokens, i), parseParens(filename, tokens, i), - parseNew(filename, tokens, i), - parseTypeof(filename, tokens, i), - parseVoid(filename, tokens, i), - parseDelete(filename, tokens, i) + CallStatement.parseNew(filename, tokens, i), + TypeofStatement.parse(filename, tokens, i), + DiscardStatement.parse(filename, tokens, i), + DeleteStatement.parseDelete(filename, tokens, i) )); return ParseRes.any(res); } - public static ParseRes parseVariable(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - var literal = parseIdentifier(tokens, i); - - if (!literal.isSuccess()) return ParseRes.failed(); - - if (!checkVarName(literal.result)) { - if (literal.result.equals("await")) return ParseRes.error(loc, "'await' expressions are not supported."); - if (literal.result.equals("const")) return ParseRes.error(loc, "'const' declarations are not supported."); - if (literal.result.equals("let")) return ParseRes.error(loc, "'let' declarations are not supported."); - return ParseRes.error(loc, String.format("Unexpected identifier '%s'.", literal.result)); - } - - return ParseRes.res(new VariableStatement(loc, literal.result), 1); - } public static ParseRes parseLiteral(Filename filename, List tokens, int i) { var loc = getLoc(filename, tokens, i); var id = parseIdentifier(tokens, i); if (!id.isSuccess()) return id.transform(); - if (id.result.equals("true")) { - return ParseRes.res(new ConstantStatement(loc, true), 1); - } - if (id.result.equals("false")) { - return ParseRes.res(new ConstantStatement(loc, false), 1); - } - if (id.result.equals("undefined")) { - return ParseRes.res(ConstantStatement.ofUndefined(loc), 1); - } - if (id.result.equals("null")) { - return ParseRes.res(ConstantStatement.ofNull(loc), 1); - } - if (id.result.equals("this")) { - return ParseRes.res(new VariableIndexStatement(loc, 0), 1); - } - if (id.result.equals("arguments")) { - return ParseRes.res(new VariableIndexStatement(loc, 1), 1); - } - if (id.result.equals("globalThis") || id.result.equals("window") || id.result.equals("self")) { - return ParseRes.res(new GlobalThisStatement(loc), 1); - } + if (id.result.equals("true")) return ParseRes.res(new ConstantStatement(loc, true), 1); + if (id.result.equals("false")) return ParseRes.res(new ConstantStatement(loc, false), 1); + if (id.result.equals("undefined")) return ParseRes.res(ConstantStatement.ofUndefined(loc), 1); + if (id.result.equals("null")) return ParseRes.res(ConstantStatement.ofNull(loc), 1); + if (id.result.equals("this")) return ParseRes.res(new VariableIndexStatement(loc, 0), 1); + if (id.result.equals("arguments")) return ParseRes.res(new VariableIndexStatement(loc, 1), 1); + if (id.result.equals("globalThis")) return ParseRes.res(new GlobalThisStatement(loc), 1); + return ParseRes.failed(); } - public static ParseRes parseMember(Filename filename, List tokens, int i, Statement prev, int precedence) { - var loc = getLoc(filename, tokens, i); - var n = 0; - - if (precedence > 18) return ParseRes.failed(); - - if (!isOperator(tokens, i + n++, Operator.DOT)) return ParseRes.failed(); - - var literal = parseIdentifier(tokens, i + n++); - if (!literal.isSuccess()) return ParseRes.error(loc, "Expected an identifier after member access."); - - return ParseRes.res(new IndexStatement(loc, prev, new ConstantStatement(loc, literal.result)), n); - } - public static ParseRes parseIndex(Filename filename, List tokens, int i, Statement prev, int precedence) { - var loc = getLoc(filename, tokens, i); - var n = 0; - - if (precedence > 18) return ParseRes.failed(); - - if (!isOperator(tokens, i + n++, Operator.BRACKET_OPEN)) return ParseRes.failed(); - - var valRes = parseValue(filename, tokens, i + n, 0); - if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value in index expression.", valRes); - n += valRes.n; - - if (!isOperator(tokens, i + n++, Operator.BRACKET_CLOSE)) return ParseRes.error(loc, "Expected a closing bracket."); - - return ParseRes.res(new IndexStatement(loc, prev, valRes.result), n); - } - public static ParseRes parseAssign(Filename filename, List tokens, int i, Statement prev, int precedence) { - var loc = getLoc(filename, tokens, i); - int n = 0 ; - - if (precedence > 2) return ParseRes.failed(); - - var opRes = parseOperator(tokens, i + n++); - if (opRes.state != State.SUCCESS) return ParseRes.failed(); - - var op = opRes.result; - if (!op.isAssign()) return ParseRes.failed(); - - if (!(prev instanceof AssignableStatement)) { - return ParseRes.error(loc, "Invalid expression on left hand side of assign operator."); - } - - var res = parseValue(filename, tokens, i + n, 2); - if (!res.isSuccess()) return ParseRes.error(loc, String.format("Expected value after assignment operator '%s'.", op.readable), res); - n += res.n; - - Operation operation = null; - - if (op == Operator.ASSIGN_ADD) operation = Operation.ADD; - if (op == Operator.ASSIGN_SUBTRACT) operation = Operation.SUBTRACT; - if (op == Operator.ASSIGN_MULTIPLY) operation = Operation.MULTIPLY; - if (op == Operator.ASSIGN_DIVIDE) operation = Operation.DIVIDE; - if (op == Operator.ASSIGN_MODULO) operation = Operation.MODULO; - if (op == Operator.ASSIGN_OR) operation = Operation.OR; - if (op == Operator.ASSIGN_XOR) operation = Operation.XOR; - if (op == Operator.ASSIGN_AND) operation = Operation.AND; - if (op == Operator.ASSIGN_SHIFT_LEFT) operation = Operation.SHIFT_LEFT; - if (op == Operator.ASSIGN_SHIFT_RIGHT) operation = Operation.SHIFT_RIGHT; - if (op == Operator.ASSIGN_USHIFT_RIGHT) operation = Operation.USHIFT_RIGHT; - - return ParseRes.res(((AssignableStatement)prev).toAssign(res.result, operation), n); - } - public static ParseRes parseCall(Filename filename, List tokens, int i, Statement prev, int precedence) { - var loc = getLoc(filename, tokens, i); - var n = 0; - - if (precedence > 17) return ParseRes.failed(); - if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.failed(); - - var args = new ArrayList(); - boolean prevArg = false; - - while (true) { - var argRes = parseValue(filename, tokens, i + n, 2); - if (argRes.isSuccess()) { - args.add(argRes.result); - n += argRes.n; - prevArg = true; - } - else if (argRes.isError()) return argRes.transform(); - else if (prevArg && isOperator(tokens, i + n, Operator.COMMA)) { - prevArg = false; - n++; - } - else if (isOperator(tokens, i + n, Operator.PAREN_CLOSE)) { - n++; - break; - } - else return ParseRes.error(getLoc(filename, tokens, i + n), prevArg ? "Expected a comma or a closing paren." : "Expected an expression or a closing paren."); - } - - return ParseRes.res(new CallStatement(loc, false, prev, args.toArray(Statement[]::new)), n); - } - public static ParseRes parsePostfixChange(Filename filename, List tokens, int i, Statement prev, int precedence) { - var loc = getLoc(filename, tokens, i); - int n = 0; - - if (precedence > 15) return ParseRes.failed(); - - var opState = parseOperator(tokens, i + n++); - if (!opState.isSuccess()) return ParseRes.failed(); - - int change = 0; - - if (opState.result == Operator.INCREASE) change = 1; - else if (opState.result == Operator.DECREASE) change = -1; - else return ParseRes.failed(); - - if (!(prev instanceof AssignableStatement)) return ParseRes.error(loc, "Expected assignable value before suffix operator."); - return ParseRes.res(new ChangeStatement(loc, (AssignableStatement)prev, change, true), n); - } - public static ParseRes parseInstanceof(Filename filename, List tokens, int i, Statement prev, int precedence) { - var loc = getLoc(filename, tokens, i); - int n = 0; - - if (precedence > 9) return ParseRes.failed(); - if (!isIdentifier(tokens, i + n++, "instanceof")) return ParseRes.failed(); - - var valRes = parseValue(filename, tokens, i + n, 10); - if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'instanceof'.", valRes); - n += valRes.n; - - return ParseRes.res(new OperationStatement(loc, Operation.INSTANCEOF, prev, valRes.result), n); - } - public static ParseRes parseIn(Filename filename, List tokens, int i, Statement prev, int precedence) { - var loc = getLoc(filename, tokens, i); - int n = 0; - - if (precedence > 9) return ParseRes.failed(); - if (!isIdentifier(tokens, i + n++, "in")) return ParseRes.failed(); - - var valRes = parseValue(filename, tokens, i + n, 10); - if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'in'.", valRes); - n += valRes.n; - - return ParseRes.res(new OperationStatement(loc, Operation.IN, prev, valRes.result), n); - } - public static ParseRes parseComma(Filename filename, List tokens, int i, Statement prev, int precedence) { - var loc = getLoc(filename, tokens, i); - var n = 0; - - if (precedence > 1) return ParseRes.failed(); - if (!isOperator(tokens, i + n++, Operator.COMMA)) return ParseRes.failed(); - - var res = parseValue(filename, tokens, i + n, 2); - if (!res.isSuccess()) return ParseRes.error(loc, "Expected a value after the comma.", res); - n += res.n; - - return ParseRes.res(new CompoundStatement(loc, false, prev, res.result), n); - } - public static ParseRes parseTernary(Filename filename, List tokens, int i, Statement prev, int precedence) { - var loc = getLoc(filename, tokens, i); - var n = 0; - - if (precedence > 2) return ParseRes.failed(); - if (!isOperator(tokens, i + n++, Operator.QUESTION)) return ParseRes.failed(); - - var a = parseValue(filename, tokens, i + n, 2); - if (!a.isSuccess()) return ParseRes.error(loc, "Expected a value after the ternary operator.", a); - n += a.n; - - if (!isOperator(tokens, i + n++, Operator.COLON)) return ParseRes.failed(); - - var b = parseValue(filename, tokens, i + n, 2); - if (!b.isSuccess()) return ParseRes.error(loc, "Expected a second value after the ternary operator.", b); - n += b.n; - - return ParseRes.res(new IfStatement(loc, prev, a.result, b.result), n); - } - public static ParseRes parseOperator(Filename filename, List tokens, int i, Statement prev, int precedence) { - var loc = getLoc(filename, tokens, i); - var n = 0; - - var opRes = parseOperator(tokens, i + n++); - if (!opRes.isSuccess()) return ParseRes.failed(); - var op = opRes.result; - - if (op.precedence < precedence) return ParseRes.failed(); - if (op.isAssign()) return parseAssign(filename, tokens, i + n - 1, prev, precedence); - - var res = parseValue(filename, tokens, i + n, op.precedence + (op.reverse ? 0 : 1)); - if (!res.isSuccess()) return ParseRes.error(loc, String.format("Expected a value after the '%s' operator.", op.readable), res); - n += res.n; - - if (op == Operator.LAZY_AND) { - return ParseRes.res(new LazyAndStatement(loc, prev, res.result), n); - } - if (op == Operator.LAZY_OR) { - return ParseRes.res(new LazyOrStatement(loc, prev, res.result), n); - } - - return ParseRes.res(new OperationStatement(loc, op.operation, prev, res.result), n); - } - public static ParseRes parseValue(Filename filename, List tokens, int i, int precedence, boolean statement) { Statement prev = null; int n = 0; @@ -1297,15 +758,15 @@ public class Parsing { } else { var res = ParseRes.any( - parseOperator(filename, tokens, i + n, prev, precedence), - parseMember(filename, tokens, i + n, prev, precedence), - parseIndex(filename, tokens, i + n, prev, precedence), - parseCall(filename, tokens, i + n, prev, precedence), - parsePostfixChange(filename, tokens, i + n, prev, precedence), - parseInstanceof(filename, tokens, i + n, prev, precedence), - parseIn(filename, tokens, i + n, prev, precedence), - parseComma(filename, tokens, i + n, prev, precedence), - parseTernary(filename, tokens, i + n, prev, precedence) + OperationStatement.parseOperator(filename, tokens, i + n, prev, precedence), + IndexStatement.parseMember(filename, tokens, i + n, prev, precedence), + IndexStatement.parseIndex(filename, tokens, i + n, prev, precedence), + CallStatement.parseCall(filename, tokens, i + n, prev, precedence), + ChangeStatement.parsePostfix(filename, tokens, i + n, prev, precedence), + OperationStatement.parseInstanceof(filename, tokens, i + n, prev, precedence), + OperationStatement.parseIn(filename, tokens, i + n, prev, precedence), + CompoundStatement.parseComma(filename, tokens, i + n, prev, precedence), + IfStatement.parseTernary(filename, tokens, i + n, prev, precedence) ); if (res.isSuccess()) { @@ -1330,7 +791,6 @@ public class Parsing { var valRes = parseValue(filename, tokens, i, 0, true); if (!valRes.isSuccess()) return valRes.transform(); - // valRes.result.setLoc(loc); var res = ParseRes.res(valRes.result, valRes.n); if (isStatementEnd(tokens, i + res.n)) { @@ -1342,543 +802,26 @@ public class Parsing { } else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.", res); } - public static ParseRes parseVariableDeclare(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; - if (!isIdentifier(tokens, i + n++, "var")) return ParseRes.failed(); - - var res = new ArrayList(); - - if (isStatementEnd(tokens, i + n)) { - if (isOperator(tokens, i + n, Operator.SEMICOLON)) return ParseRes.res(new VariableDeclareStatement(loc, res), 2); - else return ParseRes.res(new VariableDeclareStatement(loc, res), 1); - } - - while (true) { - var nameLoc = getLoc(filename, tokens, i + n); - var nameRes = parseIdentifier(tokens, i + n++); - if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a variable name."); - - if (!checkVarName(nameRes.result)) { - return ParseRes.error(loc, String.format("Unexpected identifier '%s'.", nameRes.result)); - } - - Statement val = null; - - if (isOperator(tokens, i + n, Operator.ASSIGN)) { - n++; - var valRes = parseValue(filename, tokens, i + n, 2); - if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after '='.", valRes); - n += valRes.n; - val = valRes.result; - } - - res.add(new Pair(nameRes.result, val, nameLoc)); - - if (isOperator(tokens, i + n, Operator.COMMA)) { - n++; - continue; - } - else if (isStatementEnd(tokens, i + n)) { - if (isOperator(tokens, i + n, Operator.SEMICOLON)) return ParseRes.res(new VariableDeclareStatement(loc, res), n + 1); - else return ParseRes.res(new VariableDeclareStatement(loc, res), n); - } - else return ParseRes.error(loc, "Expected a comma or end of statement."); - } - } - - public static ParseRes parseReturn(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; - if (!isIdentifier(tokens, i + n++, "return")) return ParseRes.failed(); - - if (isStatementEnd(tokens, i + n)) { - if (isOperator(tokens, i + n, Operator.SEMICOLON)) return ParseRes.res(new ReturnStatement(loc, null), 2); - else return ParseRes.res(new ReturnStatement(loc, null), 1); - } - - var valRes = parseValue(filename, tokens, i + n, 0); - n += valRes.n; - if (valRes.isError()) return ParseRes.error(loc, "Expected a return value.", valRes); - - var res = ParseRes.res(new ReturnStatement(loc, valRes.result), n); - - if (isStatementEnd(tokens, i + n)) { - if (isOperator(tokens, i + n, Operator.SEMICOLON)) return res.addN(1); - else return res; - } - else - return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.", valRes); - } - public static ParseRes parseThrow(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; - if (!isIdentifier(tokens, i + n++, "throw")) return ParseRes.failed(); - - var valRes = parseValue(filename, tokens, i + n, 0); - n += valRes.n; - if (valRes.isError()) return ParseRes.error(loc, "Expected a throw value.", valRes); - - var res = ParseRes.res(new ThrowStatement(loc, valRes.result), n); - - if (isStatementEnd(tokens, i + n)) { - if (isOperator(tokens, i + n, Operator.SEMICOLON)) return res.addN(1); - else return res; - } - else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.", valRes); - } - - public static ParseRes parseBreak(Filename filename, List tokens, int i) { - if (!isIdentifier(tokens, i, "break")) return ParseRes.failed(); - - if (isStatementEnd(tokens, i + 1)) { - if (isOperator(tokens, i + 1, Operator.SEMICOLON)) return ParseRes.res(new BreakStatement(getLoc(filename, tokens, i), null), 2); - else return ParseRes.res(new BreakStatement(getLoc(filename, tokens, i), null), 1); - } - - var labelRes = parseIdentifier(tokens, i + 1); - if (labelRes.isFailed()) return ParseRes.error(getLoc(filename, tokens, i), "Expected a label name or an end of statement."); - var label = labelRes.result; - - if (isStatementEnd(tokens, i + 2)) { - if (isOperator(tokens, i + 2, Operator.SEMICOLON)) return ParseRes.res(new BreakStatement(getLoc(filename, tokens, i), label), 3); - else return ParseRes.res(new BreakStatement(getLoc(filename, tokens, i), label), 2); - } - else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement."); - } - public static ParseRes parseContinue(Filename filename, List tokens, int i) { - if (!isIdentifier(tokens, i, "continue")) return ParseRes.failed(); - - if (isStatementEnd(tokens, i + 1)) { - if (isOperator(tokens, i + 1, Operator.SEMICOLON)) return ParseRes.res(new ContinueStatement(getLoc(filename, tokens, i), null), 2); - else return ParseRes.res(new ContinueStatement(getLoc(filename, tokens, i), null), 1); - } - - var labelRes = parseIdentifier(tokens, i + 1); - if (labelRes.isFailed()) return ParseRes.error(getLoc(filename, tokens, i), "Expected a label name or an end of statement."); - var label = labelRes.result; - - if (isStatementEnd(tokens, i + 2)) { - if (isOperator(tokens, i + 2, Operator.SEMICOLON)) return ParseRes.res(new ContinueStatement(getLoc(filename, tokens, i), label), 3); - else return ParseRes.res(new ContinueStatement(getLoc(filename, tokens, i), label), 2); - } - else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement."); - } - public static ParseRes parseDebug(Filename filename, List tokens, int i) { - if (!isIdentifier(tokens, i, "debugger")) return ParseRes.failed(); - - if (isStatementEnd(tokens, i + 1)) { - if (isOperator(tokens, i + 1, Operator.SEMICOLON)) return ParseRes.res(new DebugStatement(getLoc(filename, tokens, i)), 2); - else return ParseRes.res(new DebugStatement(getLoc(filename, tokens, i)), 1); - } - else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement."); - } - - public static ParseRes parseCompound(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; - if (!isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed(); - - var statements = new ArrayList(); - - while (true) { - if (isOperator(tokens, i + n, Operator.BRACE_CLOSE)) { - n++; - break; - } - if (isOperator(tokens, i + n, Operator.SEMICOLON)) { - n++; - continue; - } - - var res = parseStatement(filename, tokens, i + n); - if (!res.isSuccess()) { - return ParseRes.error(getLoc(filename, tokens, i), "Expected a statement.", res); - } - n += res.n; - - statements.add(res.result); - } - - return ParseRes.res(new CompoundStatement(loc, true, statements.toArray(Statement[]::new)).setEnd(getLoc(filename, tokens, i + n - 1)), n); - } - public static ParseRes parseLabel(List tokens, int i) { - int n = 0; - - var nameRes = parseIdentifier(tokens, i + n++); - if (!isOperator(tokens, i + n++, Operator.COLON)) return ParseRes.failed(); - - return ParseRes.res(nameRes.result, n); - } - public static ParseRes parseIf(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; - - if (!isIdentifier(tokens, i + n++, "if")) return ParseRes.failed(); - if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.error(loc, "Expected a open paren after 'if'."); - - var condRes = parseValue(filename, tokens, i + n, 0); - if (!condRes.isSuccess()) return ParseRes.error(loc, "Expected an if condition.", condRes); - n += condRes.n; - - if (!isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after if condition."); - - var res = parseStatement(filename, tokens, i + n); - if (!res.isSuccess()) return ParseRes.error(loc, "Expected an if body.", res); - n += res.n; - - if (!isIdentifier(tokens, i + n, "else")) return ParseRes.res(new IfStatement(loc, condRes.result, res.result, null), n); - n++; - - var elseRes = parseStatement(filename, tokens, i + n); - if (!elseRes.isSuccess()) return ParseRes.error(loc, "Expected an else body.", elseRes); - n += elseRes.n; - - return ParseRes.res(new IfStatement(loc, condRes.result, res.result, elseRes.result), n); - } - public static ParseRes parseWhile(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; - - var labelRes = parseLabel(tokens, i + n); - n += labelRes.n; - - if (!isIdentifier(tokens, i + n++, "while")) return ParseRes.failed(); - if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.error(loc, "Expected a open paren after 'while'."); - - var condRes = parseValue(filename, tokens, i + n, 0); - if (!condRes.isSuccess()) return ParseRes.error(loc, "Expected a while condition.", condRes); - n += condRes.n; - - if (!isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after while condition."); - - var res = parseStatement(filename, tokens, i + n); - if (!res.isSuccess()) return ParseRes.error(loc, "Expected a while body.", res); - n += res.n; - - return ParseRes.res(new WhileStatement(loc, labelRes.result, condRes.result, res.result), n); - } - public static ParseRes parseSwitchCase(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; - - if (!isIdentifier(tokens, i + n++, "case")) return ParseRes.failed(); - - var valRes = parseValue(filename, tokens, i + n, 0); - if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'case'.", valRes); - n += valRes.n; - - if (!isOperator(tokens, i + n++, Operator.COLON)) return ParseRes.error(loc, "Expected colons after 'case' value."); - - return ParseRes.res(valRes.result, n); - } - public static ParseRes parseDefaultCase(List tokens, int i) { - if (!isIdentifier(tokens, i, "default")) return ParseRes.failed(); - if (!isOperator(tokens, i + 1, Operator.COLON)) return ParseRes.error(getLoc(null, tokens, i), "Expected colons after 'default'."); - - return ParseRes.res(null, 2); - } - public static ParseRes parseSwitch(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; - - if (!isIdentifier(tokens, i + n++, "switch")) return ParseRes.failed(); - if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.error(loc, "Expected a open paren after 'switch'."); - - var valRes = parseValue(filename, tokens, i + n, 0); - if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a switch value.", valRes); - n += valRes.n; - - if (!isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after switch value."); - if (!isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.error(loc, "Expected an opening brace after switch value."); - - var statements = new ArrayList(); - var cases = new ArrayList(); - var defaultI = -1; - - while (true) { - if (isOperator(tokens, i + n, Operator.BRACE_CLOSE)) { - n++; - break; - } - if (isOperator(tokens, i + n, Operator.SEMICOLON)) { - n++; - continue; - } - - var defaultRes = parseDefaultCase(tokens, i + n); - var caseRes = parseSwitchCase(filename, tokens, i + n); - - if (defaultRes.isSuccess()) { - defaultI = statements.size(); - n += defaultRes.n; - } - else if (caseRes.isSuccess()) { - cases.add(new SwitchCase(caseRes.result, statements.size())); - n += caseRes.n; - } - else if (defaultRes.isError()) return defaultRes.transform(); - else if (caseRes.isError()) return defaultRes.transform(); - else { - var res = ParseRes.any( - parseStatement(filename, tokens, i + n), - parseCompound(filename, tokens, i + n) - ); - if (!res.isSuccess()) { - return ParseRes.error(getLoc(filename, tokens, i), "Expected a statement.", res); - } - n += res.n; - statements.add(res.result); - } - } - - return ParseRes.res(new SwitchStatement( - loc, valRes.result, defaultI, - cases.toArray(SwitchCase[]::new), - statements.toArray(Statement[]::new) - ), n); - } - public static ParseRes parseDoWhile(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; - - var labelRes = parseLabel(tokens, i + n); - n += labelRes.n; - - if (!isIdentifier(tokens, i + n++, "do")) return ParseRes.failed(); - var bodyRes = parseStatement(filename, tokens, i + n); - if (!bodyRes.isSuccess()) return ParseRes.error(loc, "Expected a do-while body.", bodyRes); - n += bodyRes.n; - - if (!isIdentifier(tokens, i + n++, "while")) return ParseRes.error(loc, "Expected 'while' keyword."); - if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.error(loc, "Expected a open paren after 'while'."); - - var condRes = parseValue(filename, tokens, i + n, 0); - if (!condRes.isSuccess()) return ParseRes.error(loc, "Expected a while condition.", condRes); - n += condRes.n; - - if (!isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after while condition."); - - var res = ParseRes.res(new DoWhileStatement(loc, labelRes.result, condRes.result, bodyRes.result), n); - - if (isStatementEnd(tokens, i + n)) { - if (isOperator(tokens, i + n, Operator.SEMICOLON)) return res.addN(1); - else return res; - } - else return ParseRes.error(getLoc(filename, tokens, i), "Expected a semicolon."); - } - public static ParseRes parseFor(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; - - var labelRes = parseLabel(tokens, i + n); - n += labelRes.n; - - if (!isIdentifier(tokens, i + n++, "for")) return ParseRes.failed(); - if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.error(loc, "Expected a open paren after 'for'."); - - Statement decl, cond, inc; - - if (isOperator(tokens, i + n, Operator.SEMICOLON)) { - n++; - decl = new CompoundStatement(loc, false); - } - else { - var declRes = ParseRes.any( - parseVariableDeclare(filename, tokens, i + n), - parseValueStatement(filename, tokens, i + n) - ); - if (!declRes.isSuccess()) return ParseRes.error(loc, "Expected a declaration or an expression.", declRes); - n += declRes.n; - decl = declRes.result; - } - - if (isOperator(tokens, i + n, Operator.SEMICOLON)) { - n++; - cond = new ConstantStatement(loc, 1); - } - else { - var condRes = parseValue(filename, tokens, i + n, 0); - if (!condRes.isSuccess()) return ParseRes.error(loc, "Expected a condition.", condRes); - n += condRes.n; - if (!isOperator(tokens, i + n++, Operator.SEMICOLON)) return ParseRes.error(loc, "Expected a semicolon.", condRes); - cond = condRes.result; - } - - if (isOperator(tokens, i + n, Operator.PAREN_CLOSE)) { - n++; - inc = new CompoundStatement(loc, false); - } - else { - var incRes = parseValue(filename, tokens, i + n, 0); - if (!incRes.isSuccess()) return ParseRes.error(loc, "Expected a condition.", incRes); - n += incRes.n; - inc = incRes.result; - if (!isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after for."); - } - - - var res = parseStatement(filename, tokens, i + n); - if (!res.isSuccess()) return ParseRes.error(loc, "Expected a for body.", res); - n += res.n; - - return ParseRes.res(new ForStatement(loc, labelRes.result, decl, cond, inc, res.result), n); - } - public static ParseRes parseForIn(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; - - var labelRes = parseLabel(tokens, i + n); - var isDecl = false; - n += labelRes.n; - - if (!isIdentifier(tokens, i + n++, "for")) return ParseRes.failed(); - if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.error(loc, "Expected a open paren after 'for'."); - - if (isIdentifier(tokens, i + n, "var")) { - isDecl = true; - n++; - } - - var nameRes = parseIdentifier(tokens, i + n); - if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a variable name for 'for' loop."); - var nameLoc = getLoc(filename, tokens, i + n); - n += nameRes.n; - - Statement varVal = null; - - if (isOperator(tokens, i + n, Operator.ASSIGN)) { - n++; - - var valRes = parseValue(filename, tokens, i + n, 2); - if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after '='.", valRes); - n += nameRes.n; - - varVal = valRes.result; - } - - if (!isIdentifier(tokens, i + n++, "in")) { - if (varVal == null) { - if (nameRes.result.equals("const")) return ParseRes.error(loc, "'const' declarations are not supported."); - else if (nameRes.result.equals("let")) return ParseRes.error(loc, "'let' declarations are not supported."); - } - return ParseRes.error(loc, "Expected 'in' keyword after variable declaration."); - } - - var objRes = parseValue(filename, tokens, i + n, 0); - if (!objRes.isSuccess()) return ParseRes.error(loc, "Expected a value.", objRes); - n += objRes.n; - - if (!isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after for."); - - - var bodyRes = parseStatement(filename, tokens, i + n); - if (!bodyRes.isSuccess()) return ParseRes.error(loc, "Expected a for body.", bodyRes); - n += bodyRes.n; - - return ParseRes.res(new ForInStatement(loc, nameLoc, labelRes.result, isDecl, nameRes.result, varVal, objRes.result, bodyRes.result), n); - } - public static ParseRes parseForOf(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; - - var labelRes = parseLabel(tokens, i + n); - var isDecl = false; - n += labelRes.n; - - if (!isIdentifier(tokens, i + n++, "for")) return ParseRes.failed(); - if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.error(loc, "Expected a open paren after 'for'."); - - if (isIdentifier(tokens, i + n, "var")) { - isDecl = true; - n++; - } - - var nameRes = parseIdentifier(tokens, i + n); - if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a variable name for 'for' loop."); - var nameLoc = getLoc(filename, tokens, i + n); - n += nameRes.n; - - if (!isIdentifier(tokens, i + n++, "of")) { - if (nameRes.result.equals("const")) return ParseRes.error(loc, "'const' declarations are not supported."); - else if (nameRes.result.equals("let")) return ParseRes.error(loc, "'let' declarations are not supported."); - else return ParseRes.error(loc, "Expected 'of' keyword after variable declaration."); - } - - var objRes = parseValue(filename, tokens, i + n, 0); - if (!objRes.isSuccess()) return ParseRes.error(loc, "Expected a value.", objRes); - n += objRes.n; - - if (!isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after for."); - - - var bodyRes = parseStatement(filename, tokens, i + n); - if (!bodyRes.isSuccess()) return ParseRes.error(loc, "Expected a for body.", bodyRes); - n += bodyRes.n; - - return ParseRes.res(new ForOfStatement(loc, nameLoc, labelRes.result, isDecl, nameRes.result, objRes.result, bodyRes.result), n); - } - public static ParseRes parseCatch(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; - - if (!isIdentifier(tokens, i + n++, "try")) return ParseRes.failed(); - - var res = parseStatement(filename, tokens, i + n); - if (!res.isSuccess()) return ParseRes.error(loc, "Expected an if body.", res); - n += res.n; - - String name = null; - Statement catchBody = null, finallyBody = null; - - - if (isIdentifier(tokens, i + n, "catch")) { - n++; - if (isOperator(tokens, i + n, Operator.PAREN_OPEN)) { - n++; - var nameRes = parseIdentifier(tokens, i + n++); - if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a catch variable name."); - name = nameRes.result; - if (!isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after catch variable name."); - } - - var catchRes = parseStatement(filename, tokens, i + n); - if (!catchRes.isSuccess()) return ParseRes.error(loc, "Expected a catch body.", catchRes); - n += catchRes.n; - catchBody = catchRes.result; - } - - if (isIdentifier(tokens, i + n, "finally")) { - n++; - var finallyRes = parseStatement(filename, tokens, i + n); - if (!finallyRes.isSuccess()) return ParseRes.error(loc, "Expected a finally body.", finallyRes); - n += finallyRes.n; - finallyBody = finallyRes.result; - } - - return ParseRes.res(new TryStatement(loc, res.result, catchBody, finallyBody, name), n); - } - public static ParseRes parseStatement(Filename filename, List tokens, int i) { if (isOperator(tokens, i, Operator.SEMICOLON)) return ParseRes.res(new CompoundStatement(getLoc(filename, tokens, i), false), 1); if (isIdentifier(tokens, i, "with")) return ParseRes.error(getLoc(filename, tokens, i), "'with' statements are not allowed."); return ParseRes.any( - parseVariableDeclare(filename, tokens, i), - parseReturn(filename, tokens, i), - parseThrow(filename, tokens, i), - parseContinue(filename, tokens, i), - parseBreak(filename, tokens, i), - parseDebug(filename, tokens, i), - parseIf(filename, tokens, i), - parseWhile(filename, tokens, i), - parseSwitch(filename, tokens, i), - parseFor(filename, tokens, i), - parseForIn(filename, tokens, i), - parseForOf(filename, tokens, i), - parseDoWhile(filename, tokens, i), - parseCatch(filename, tokens, i), - parseCompound(filename, tokens, i), - parseFunction(filename, tokens, i, true), + VariableDeclareStatement.parse(filename, tokens, i), + ReturnStatement.parseReturn(filename, tokens, i), + ThrowStatement.parseThrow(filename, tokens, i), + ContinueStatement.parseContinue(filename, tokens, i), + BreakStatement.parseBreak(filename, tokens, i), + DebugStatement.parse(filename, tokens, i), + IfStatement.parse(filename, tokens, i), + WhileStatement.parseWhile(filename, tokens, i), + SwitchStatement.parse(filename, tokens, i), + ForStatement.parse(filename, tokens, i), + ForInStatement.parse(filename, tokens, i), + ForOfStatement.parse(filename, tokens, i), + DoWhileStatement.parseDoWhile(filename, tokens, i), + TryStatement.parse(filename, tokens, i), + CompoundStatement.parse(filename, tokens, i), + FunctionStatement.parseFunction(filename, tokens, i, true), parseValueStatement(filename, tokens, i) ); } diff --git a/src/java/me/topchetoeu/jscript/compilation/values/ArrayStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/ArrayStatement.java index e24e50b..8ffbf4d 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/ArrayStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/ArrayStatement.java @@ -1,9 +1,17 @@ package me.topchetoeu.jscript.compilation.values; +import java.util.ArrayList; +import java.util.List; + +import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.ParseRes; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.parsing.Operator; +import me.topchetoeu.jscript.compilation.parsing.Parsing; +import me.topchetoeu.jscript.compilation.parsing.Token; public class ArrayStatement extends Statement { public final Statement[] statements; @@ -37,4 +45,42 @@ public class ArrayStatement extends Statement { super(loc); this.statements = statements; } + + public static ParseRes parse(Filename filename, List tokens, int i) { + var loc = Parsing.getLoc(filename, tokens, i); + int n = 0; + if (!Parsing.isOperator(tokens, i + n++, Operator.BRACKET_OPEN)) return ParseRes.failed(); + + var values = new ArrayList(); + + loop: while (true) { + if (Parsing.isOperator(tokens, i + n, Operator.BRACKET_CLOSE)) { + n++; + break; + } + + while (Parsing.isOperator(tokens, i + n, Operator.COMMA)) { + n++; + values.add(null); + if (Parsing.isOperator(tokens, i + n, Operator.BRACKET_CLOSE)) { + n++; + break loop; + } + } + + var res = Parsing.parseValue(filename, tokens, i + n, 2); + if (!res.isSuccess()) return ParseRes.error(loc, "Expected an array element.", res); + else n += res.n; + + values.add(res.result); + + if (Parsing.isOperator(tokens, i + n, Operator.COMMA)) n++; + else if (Parsing.isOperator(tokens, i + n, Operator.BRACKET_CLOSE)) { + n++; + break; + } + } + + return ParseRes.res(new ArrayStatement(loc, values.toArray(Statement[]::new)), n); + } } diff --git a/src/java/me/topchetoeu/jscript/compilation/values/CallStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/CallStatement.java index 412eacc..e8a82d6 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/CallStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/CallStatement.java @@ -1,18 +1,25 @@ package me.topchetoeu.jscript.compilation.values; +import java.util.ArrayList; +import java.util.List; + +import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.ParseRes; import me.topchetoeu.jscript.common.Instruction.BreakpointType; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.parsing.Operator; +import me.topchetoeu.jscript.compilation.parsing.Parsing; +import me.topchetoeu.jscript.compilation.parsing.Token; public class CallStatement extends Statement { public final Statement func; public final Statement[] args; public final boolean isNew; - @Override - public void compile(CompileResult target, boolean pollute, BreakpointType type) { + @Override public void compile(CompileResult target, boolean pollute, BreakpointType type) { if (isNew) func.compile(target, true); else if (func instanceof IndexStatement) { ((IndexStatement)func).compile(target, true, true); @@ -29,8 +36,7 @@ public class CallStatement extends Statement { if (!pollute) target.add(Instruction.discard()); } - @Override - public void compile(CompileResult target, boolean pollute) { + @Override public void compile(CompileResult target, boolean pollute) { compile(target, pollute, BreakpointType.STEP_IN); } @@ -40,4 +46,52 @@ public class CallStatement extends Statement { this.func = func; this.args = args; } + + public static ParseRes parseCall(Filename filename, List tokens, int i, Statement prev, int precedence) { + var loc = Parsing.getLoc(filename, tokens, i); + var n = 0; + + if (precedence > 17) return ParseRes.failed(); + if (!Parsing.isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.failed(); + + var args = new ArrayList(); + boolean prevArg = false; + + while (true) { + var argRes = Parsing.parseValue(filename, tokens, i + n, 2); + if (argRes.isSuccess()) { + args.add(argRes.result); + n += argRes.n; + prevArg = true; + } + else if (argRes.isError()) return argRes.transform(); + else if (prevArg && Parsing.isOperator(tokens, i + n, Operator.COMMA)) { + prevArg = false; + n++; + } + else if (Parsing.isOperator(tokens, i + n, Operator.PAREN_CLOSE)) { + n++; + break; + } + else return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), prevArg ? "Expected a comma or a closing paren." : "Expected an expression or a closing paren."); + } + + return ParseRes.res(new CallStatement(loc, false, prev, args.toArray(Statement[]::new)), n); + } + public static ParseRes parseNew(Filename filename, List tokens, int i) { + var loc = Parsing.getLoc(filename, tokens, i); + var n = 0; + if (!Parsing.isIdentifier(tokens, i + n++, "new")) return ParseRes.failed(); + + var valRes = Parsing.parseValue(filename, tokens, i + n, 18); + n += valRes.n; + if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'new' keyword.", valRes); + var callRes = CallStatement.parseCall(filename, tokens, i + n, valRes.result, 0); + n += callRes.n; + if (callRes.isError()) return callRes.transform(); + else if (callRes.isFailed()) return ParseRes.res(new CallStatement(loc, true, valRes.result), n); + var call = (CallStatement)callRes.result; + + return ParseRes.res(new CallStatement(loc, true, call.func, call.args), n); + } } diff --git a/src/java/me/topchetoeu/jscript/compilation/values/ChangeStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/ChangeStatement.java index 26ae605..0531b4d 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/ChangeStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/ChangeStatement.java @@ -1,19 +1,25 @@ package me.topchetoeu.jscript.compilation.values; +import java.util.List; + +import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Location; import me.topchetoeu.jscript.common.Operation; +import me.topchetoeu.jscript.common.ParseRes; import me.topchetoeu.jscript.compilation.AssignableStatement; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.parsing.Operator; +import me.topchetoeu.jscript.compilation.parsing.Parsing; +import me.topchetoeu.jscript.compilation.parsing.Token; public class ChangeStatement extends Statement { public final AssignableStatement value; public final double addAmount; public final boolean postfix; - @Override - public void compile(CompileResult target, boolean pollute) { + @Override public void compile(CompileResult target, boolean pollute) { value.toAssign(new ConstantStatement(loc(), -addAmount), Operation.SUBTRACT).compile(target, true); if (!pollute) target.add(Instruction.discard()); else if (postfix) { @@ -28,4 +34,40 @@ public class ChangeStatement extends Statement { this.addAmount = addAmount; this.postfix = postfix; } + + public static ParseRes parsePrefix(Filename filename, List tokens, int i) { + var loc = Parsing.getLoc(filename, tokens, i); + int n = 0; + + var opState = Parsing.parseOperator(tokens, i + n++); + if (!opState.isSuccess()) return ParseRes.failed(); + + int change = 0; + + if (opState.result == Operator.INCREASE) change = 1; + else if (opState.result == Operator.DECREASE) change = -1; + else return ParseRes.failed(); + + var res = Parsing.parseValue(filename, tokens, i + n, 15); + if (!(res.result instanceof AssignableStatement)) return ParseRes.error(loc, "Expected assignable value after prefix operator."); + return ParseRes.res(new ChangeStatement(loc, (AssignableStatement)res.result, change, false), n + res.n); + } + public static ParseRes parsePostfix(Filename filename, List tokens, int i, Statement prev, int precedence) { + var loc = Parsing.getLoc(filename, tokens, i); + int n = 0; + + if (precedence > 15) return ParseRes.failed(); + + var opState = Parsing.parseOperator(tokens, i + n++); + if (!opState.isSuccess()) return ParseRes.failed(); + + int change = 0; + + if (opState.result == Operator.INCREASE) change = 1; + else if (opState.result == Operator.DECREASE) change = -1; + else return ParseRes.failed(); + + if (!(prev instanceof AssignableStatement)) return ParseRes.error(loc, "Expected assignable value before suffix operator."); + return ParseRes.res(new ChangeStatement(loc, (AssignableStatement)prev, change, true), n); + } } diff --git a/src/java/me/topchetoeu/jscript/compilation/values/ConstantStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/ConstantStatement.java index 2def204..60a8503 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/ConstantStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/ConstantStatement.java @@ -1,9 +1,15 @@ package me.topchetoeu.jscript.compilation.values; +import java.util.List; + +import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.ParseRes; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.parsing.Parsing; +import me.topchetoeu.jscript.compilation.parsing.Token; public class ConstantStatement extends Statement { public final Object value; @@ -44,4 +50,25 @@ public class ConstantStatement extends Statement { public static ConstantStatement ofNull(Location loc) { return new ConstantStatement(loc, null, true); } + + public static ParseRes parseNumber(Filename filename, List tokens, int i) { + var loc = Parsing.getLoc(filename, tokens, i); + if (Parsing.inBounds(tokens, i)) { + if (tokens.get(i).isNumber()) { + return ParseRes.res(new ConstantStatement(loc, tokens.get(i).number()), 1); + } + else return ParseRes.failed(); + } + else return ParseRes.failed(); + } + public static ParseRes parseString(Filename filename, List tokens, int i) { + var loc = Parsing.getLoc(filename, tokens, i); + if (Parsing.inBounds(tokens, i)) { + if (tokens.get(i).isString()) { + return ParseRes.res(new ConstantStatement(loc, tokens.get(i).string()), 1); + } + else return ParseRes.failed(); + } + else return ParseRes.failed(); + } } diff --git a/src/java/me/topchetoeu/jscript/compilation/values/DiscardStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/DiscardStatement.java index 40ad0cd..cd13dd8 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/DiscardStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/DiscardStatement.java @@ -1,9 +1,15 @@ package me.topchetoeu.jscript.compilation.values; +import java.util.List; + +import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.ParseRes; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.parsing.Parsing; +import me.topchetoeu.jscript.compilation.parsing.Token; public class DiscardStatement extends Statement { public final Statement value; @@ -20,4 +26,16 @@ public class DiscardStatement extends Statement { super(loc); this.value = val; } + + public static ParseRes parse(Filename filename, List tokens, int i) { + var loc = Parsing.getLoc(filename, tokens, i); + var n = 0; + if (!Parsing.isIdentifier(tokens, i + n++, "void")) return ParseRes.failed(); + + var valRes = Parsing.parseValue(filename, tokens, i + n, 14); + if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'void' keyword.", valRes); + n += valRes.n; + + return ParseRes.res(new DiscardStatement(loc, valRes.result), n); + } } diff --git a/src/java/me/topchetoeu/jscript/compilation/values/FunctionStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/FunctionStatement.java index db7e331..a54d082 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/FunctionStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/FunctionStatement.java @@ -1,12 +1,20 @@ package me.topchetoeu.jscript.compilation.values; +import java.util.ArrayList; +import java.util.List; + +import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.ParseRes; import me.topchetoeu.jscript.common.Instruction.BreakpointType; import me.topchetoeu.jscript.common.Instruction.Type; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.CompoundStatement; import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.parsing.Operator; +import me.topchetoeu.jscript.compilation.parsing.Parsing; +import me.topchetoeu.jscript.compilation.parsing.Token; import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; public class FunctionStatement extends Statement { @@ -124,4 +132,48 @@ public class FunctionStatement extends Statement { if (stm instanceof FunctionStatement) ((FunctionStatement)stm).compile(target, pollute, name, bp); else stm.compile(target, pollute, bp); } + + public static ParseRes parseFunction(Filename filename, List tokens, int i, boolean statement) { + var loc = Parsing.getLoc(filename, tokens, i); + int n = 0; + + if (!Parsing.isIdentifier(tokens, i + n++, "function")) return ParseRes.failed(); + + var nameRes = Parsing.parseIdentifier(tokens, i + n); + if (!nameRes.isSuccess() && statement) return ParseRes.error(loc, "A statement function requires a name, one is not present."); + var name = nameRes.result; + n += nameRes.n; + + if (!Parsing.isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.error(loc, "Expected a parameter list."); + + var args = new ArrayList(); + + if (Parsing.isOperator(tokens, i + n, Operator.PAREN_CLOSE)) { + n++; + } + else { + while (true) { + var argRes = Parsing.parseIdentifier(tokens, i + n); + if (argRes.isSuccess()) { + args.add(argRes.result); + n++; + if (Parsing.isOperator(tokens, i + n, Operator.COMMA)) { + n++; + } + if (Parsing.isOperator(tokens, i + n, Operator.PAREN_CLOSE)) { + n++; + break; + } + } + else return ParseRes.error(loc, "Expected an argument, comma or a closing brace."); + } + } + + var res = CompoundStatement.parse(filename, tokens, i + n); + n += res.n; + var end = Parsing.getLoc(filename, tokens, i + n - 1); + + if (res.isSuccess()) return ParseRes.res(new FunctionStatement(loc, end, name, args.toArray(String[]::new), statement, res.result), n); + else return ParseRes.error(loc, "Expected a compound statement for function.", res); + } } diff --git a/src/java/me/topchetoeu/jscript/compilation/values/IndexStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/IndexStatement.java index 58315dc..29dcbd3 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/IndexStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/IndexStatement.java @@ -1,12 +1,19 @@ package me.topchetoeu.jscript.compilation.values; +import java.util.List; + +import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Location; import me.topchetoeu.jscript.common.Operation; +import me.topchetoeu.jscript.common.ParseRes; import me.topchetoeu.jscript.common.Instruction.BreakpointType; import me.topchetoeu.jscript.compilation.AssignableStatement; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.parsing.Operator; +import me.topchetoeu.jscript.compilation.parsing.Parsing; +import me.topchetoeu.jscript.compilation.parsing.Token; public class IndexStatement extends AssignableStatement { public final Statement object; @@ -34,4 +41,34 @@ public class IndexStatement extends AssignableStatement { this.object = object; this.index = index; } + + public static ParseRes parseIndex(Filename filename, List tokens, int i, Statement prev, int precedence) { + var loc = Parsing.getLoc(filename, tokens, i); + var n = 0; + + if (precedence > 18) return ParseRes.failed(); + + if (!Parsing.isOperator(tokens, i + n++, Operator.BRACKET_OPEN)) return ParseRes.failed(); + + var valRes = Parsing.parseValue(filename, tokens, i + n, 0); + if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value in index expression.", valRes); + n += valRes.n; + + if (!Parsing.isOperator(tokens, i + n++, Operator.BRACKET_CLOSE)) return ParseRes.error(loc, "Expected a closing bracket."); + + return ParseRes.res(new IndexStatement(loc, prev, valRes.result), n); + } + public static ParseRes parseMember(Filename filename, List tokens, int i, Statement prev, int precedence) { + var loc = Parsing.getLoc(filename, tokens, i); + var n = 0; + + if (precedence > 18) return ParseRes.failed(); + + if (!Parsing.isOperator(tokens, i + n++, Operator.DOT)) return ParseRes.failed(); + + var literal = Parsing.parseIdentifier(tokens, i + n++); + if (!literal.isSuccess()) return ParseRes.error(loc, "Expected an identifier after member access."); + + return ParseRes.res(new IndexStatement(loc, prev, new ConstantStatement(loc, literal.result)), n); + } } diff --git a/src/java/me/topchetoeu/jscript/compilation/values/ObjectStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/ObjectStatement.java index f5af390..b98e84e 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/ObjectStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/ObjectStatement.java @@ -1,12 +1,21 @@ package me.topchetoeu.jscript.compilation.values; import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; +import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.ParseRes; import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.CompoundStatement; import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.parsing.Operator; +import me.topchetoeu.jscript.compilation.parsing.Parsing; +import me.topchetoeu.jscript.compilation.parsing.Parsing.ObjProp; +import me.topchetoeu.jscript.compilation.parsing.Token; public class ObjectStatement extends Statement { public final Map map; @@ -58,4 +67,104 @@ public class ObjectStatement extends Statement { this.getters = getters; this.setters = setters; } + + private static ParseRes parsePropName(Filename filename, List tokens, int i) { + var loc = Parsing.getLoc(filename, tokens, i); + + if (Parsing.inBounds(tokens, i)) { + var token = tokens.get(i); + + if (token.isNumber() || token.isIdentifier() || token.isString()) return ParseRes.res(token.rawValue, 1); + else return ParseRes.error(loc, "Expected identifier, string or number literal."); + } + else return ParseRes.failed(); + } + private static ParseRes parseObjectProp(Filename filename, List tokens, int i) { + var loc =Parsing. getLoc(filename, tokens, i); + int n = 0; + + var accessRes = Parsing.parseIdentifier(tokens, i + n++); + if (!accessRes.isSuccess()) return ParseRes.failed(); + var access = accessRes.result; + if (!access.equals("get") && !access.equals("set")) return ParseRes.failed(); + + var nameRes = parsePropName(filename, tokens, i + n); + if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a property name after '" + access + "'."); + var name = nameRes.result; + n += nameRes.n; + + var argsRes = Parsing.parseParamList(filename, tokens, i + n); + if (!argsRes.isSuccess()) return ParseRes.error(loc, "Expected an argument list.", argsRes); + n += argsRes.n; + + var res = CompoundStatement.parse(filename, tokens, i + n); + if (!res.isSuccess()) return ParseRes.error(loc, "Expected a compound statement for property accessor.", res); + n += res.n; + + var end = Parsing.getLoc(filename, tokens, i + n - 1); + + return ParseRes.res(new ObjProp( + name, access, + new FunctionStatement(loc, end, access + " " + name.toString(), argsRes.result.toArray(String[]::new), false, res.result) + ), n); + } + + public static ParseRes parse(Filename filename, List tokens, int i) { + var loc = Parsing.getLoc(filename, tokens, i); + int n = 0; + if (!Parsing.isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed(); + + var values = new LinkedHashMap(); + var getters = new LinkedHashMap(); + var setters = new LinkedHashMap(); + + if (Parsing.isOperator(tokens, i + n, Operator.BRACE_CLOSE)) { + n++; + return ParseRes.res(new ObjectStatement(loc, values, getters, setters), n); + } + + while (true) { + var propRes = parseObjectProp(filename, tokens, i + n); + + if (propRes.isSuccess()) { + n += propRes.n; + if (propRes.result.access.equals("set")) { + setters.put(propRes.result.name, propRes.result.func); + } + else { + getters.put(propRes.result.name, propRes.result.func); + } + } + else { + var nameRes = parsePropName(filename, tokens, i + n); + if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a field name.", propRes); + n += nameRes.n; + + if (!Parsing.isOperator(tokens, i + n++, Operator.COLON)) return ParseRes.error(loc, "Expected a colon."); + + var valRes = Parsing.parseValue(filename, tokens, i + n, 2); + if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value in array list.", valRes); + n += valRes.n; + + values.put(nameRes.result, valRes.result); + } + + if (Parsing.isOperator(tokens, i + n, Operator.COMMA)) { + n++; + if (Parsing.isOperator(tokens, i + n, Operator.BRACE_CLOSE)) { + n++; + break; + } + continue; + } + else if (Parsing.isOperator(tokens, i + n, Operator.BRACE_CLOSE)) { + n++; + break; + } + else ParseRes.error(loc, "Expected a comma or a closing brace."); + } + + return ParseRes.res(new ObjectStatement(loc, values, getters, setters), n); + } + } diff --git a/src/java/me/topchetoeu/jscript/compilation/values/OperationStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/OperationStatement.java index cf66133..55d373d 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/OperationStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/OperationStatement.java @@ -1,10 +1,18 @@ package me.topchetoeu.jscript.compilation.values; +import java.util.List; + +import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Location; import me.topchetoeu.jscript.common.Operation; +import me.topchetoeu.jscript.common.ParseRes; +import me.topchetoeu.jscript.compilation.AssignableStatement; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.parsing.Operator; +import me.topchetoeu.jscript.compilation.parsing.Parsing; +import me.topchetoeu.jscript.compilation.parsing.Token; public class OperationStatement extends Statement { public final Statement[] args; @@ -18,8 +26,7 @@ public class OperationStatement extends Statement { return true; } - @Override - public void compile(CompileResult target, boolean pollute) { + @Override public void compile(CompileResult target, boolean pollute) { for (var arg : args) { arg.compile(target, true); } @@ -33,4 +40,76 @@ public class OperationStatement extends Statement { this.operation = operation; this.args = args; } + + public static ParseRes parseUnary(Filename filename, List tokens, int i) { + var loc = Parsing.getLoc(filename, tokens, i); + int n = 0; + + var opState = Parsing.parseOperator(tokens, i + n++); + if (!opState.isSuccess()) return ParseRes.failed(); + var op = opState.result; + + Operation operation = null; + + if (op == Operator.ADD) operation = Operation.POS; + else if (op == Operator.SUBTRACT) operation = Operation.NEG; + else if (op == Operator.INVERSE) operation = Operation.INVERSE; + else if (op == Operator.NOT) operation = Operation.NOT; + else return ParseRes.failed(); + + var res = Parsing.parseValue(filename, tokens, n + i, 14); + + if (res.isSuccess()) return ParseRes.res(new OperationStatement(loc, operation, res.result), n + res.n); + else return ParseRes.error(loc, String.format("Expected a value after the unary operator '%s'.", op.readable), res); + } + public static ParseRes parseInstanceof(Filename filename, List tokens, int i, Statement prev, int precedence) { + var loc = Parsing.getLoc(filename, tokens, i); + int n = 0; + + if (precedence > 9) return ParseRes.failed(); + if (!Parsing.isIdentifier(tokens, i + n++, "instanceof")) return ParseRes.failed(); + + var valRes = Parsing.parseValue(filename, tokens, i + n, 10); + if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'instanceof'.", valRes); + n += valRes.n; + + return ParseRes.res(new OperationStatement(loc, Operation.INSTANCEOF, prev, valRes.result), n); + } + public static ParseRes parseIn(Filename filename, List tokens, int i, Statement prev, int precedence) { + var loc = Parsing.getLoc(filename, tokens, i); + int n = 0; + + if (precedence > 9) return ParseRes.failed(); + if (!Parsing.isIdentifier(tokens, i + n++, "in")) return ParseRes.failed(); + + var valRes = Parsing.parseValue(filename, tokens, i + n, 10); + if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'in'.", valRes); + n += valRes.n; + + return ParseRes.res(new OperationStatement(loc, Operation.IN, prev, valRes.result), n); + } + public static ParseRes parseOperator(Filename filename, List tokens, int i, Statement prev, int precedence) { + var loc = Parsing.getLoc(filename, tokens, i); + var n = 0; + + var opRes = Parsing.parseOperator(tokens, i + n++); + if (!opRes.isSuccess()) return ParseRes.failed(); + var op = opRes.result; + + if (op.precedence < precedence) return ParseRes.failed(); + if (op.isAssign()) return AssignableStatement.parse(filename, tokens, i + n - 1, prev, precedence); + + var res = Parsing.parseValue(filename, tokens, i + n, op.precedence + (op.reverse ? 0 : 1)); + if (!res.isSuccess()) return ParseRes.error(loc, String.format("Expected a value after the '%s' operator.", op.readable), res); + n += res.n; + + if (op == Operator.LAZY_AND) { + return ParseRes.res(new LazyAndStatement(loc, prev, res.result), n); + } + if (op == Operator.LAZY_OR) { + return ParseRes.res(new LazyOrStatement(loc, prev, res.result), n); + } + + return ParseRes.res(new OperationStatement(loc, op.operation, prev, res.result), n); + } } diff --git a/src/java/me/topchetoeu/jscript/compilation/values/RegexStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/RegexStatement.java index fe2bdb8..b7a753a 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/RegexStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/RegexStatement.java @@ -1,9 +1,15 @@ package me.topchetoeu.jscript.compilation.values; +import java.util.List; + +import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.ParseRes; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.parsing.Parsing; +import me.topchetoeu.jscript.compilation.parsing.Token; public class RegexStatement extends Statement { public final String pattern, flags; @@ -17,6 +23,21 @@ public class RegexStatement extends Statement { if (!pollute) target.add(Instruction.discard()); } + public static ParseRes parse(Filename filename, List tokens, int i) { + var loc = Parsing.getLoc(filename, tokens, i); + if (Parsing.inBounds(tokens, i)) { + if (tokens.get(i).isRegex()) { + var val = tokens.get(i).regex(); + var index = val.lastIndexOf('/'); + var first = val.substring(1, index); + var second = val.substring(index + 1); + return ParseRes.res(new RegexStatement(loc, first, second), 1); + } + else return ParseRes.failed(); + } + return ParseRes.failed(); + } + public RegexStatement(Location loc, String pattern, String flags) { super(loc); this.pattern = pattern; diff --git a/src/java/me/topchetoeu/jscript/compilation/values/TypeofStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/TypeofStatement.java index 11ab78d..267c748 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/TypeofStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/TypeofStatement.java @@ -1,9 +1,15 @@ package me.topchetoeu.jscript.compilation.values; +import java.util.List; + +import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.common.ParseRes; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.parsing.Parsing; +import me.topchetoeu.jscript.compilation.parsing.Token; public class TypeofStatement extends Statement { public final Statement value; @@ -29,4 +35,16 @@ public class TypeofStatement extends Statement { super(loc); this.value = value; } + + public static ParseRes parse(Filename filename, List tokens, int i) { + var loc = Parsing.getLoc(filename, tokens, i); + var n = 0; + if (!Parsing.isIdentifier(tokens, i + n++, "typeof")) return ParseRes.failed(); + + var valRes = Parsing.parseValue(filename, tokens, i + n, 15); + if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'typeof' keyword.", valRes); + n += valRes.n; + + return ParseRes.res(new TypeofStatement(loc, valRes.result), n); + } } diff --git a/src/java/me/topchetoeu/jscript/compilation/values/VariableStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/VariableStatement.java index 1ae3d2d..6349c40 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/VariableStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/VariableStatement.java @@ -1,11 +1,17 @@ package me.topchetoeu.jscript.compilation.values; +import java.util.List; + +import me.topchetoeu.jscript.common.Filename; import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Location; import me.topchetoeu.jscript.common.Operation; +import me.topchetoeu.jscript.common.ParseRes; import me.topchetoeu.jscript.compilation.AssignableStatement; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.parsing.Parsing; +import me.topchetoeu.jscript.compilation.parsing.Token; public class VariableStatement extends AssignableStatement { public final String name; @@ -28,4 +34,20 @@ public class VariableStatement extends AssignableStatement { super(loc); this.name = name; } + + public static ParseRes parseVariable(Filename filename, List tokens, int i) { + var loc = Parsing.getLoc(filename, tokens, i); + var literal = Parsing.parseIdentifier(tokens, i); + + if (!literal.isSuccess()) return ParseRes.failed(); + + if (!Parsing.checkVarName(literal.result)) { + if (literal.result.equals("await")) return ParseRes.error(loc, "'await' expressions are not supported."); + if (literal.result.equals("const")) return ParseRes.error(loc, "'const' declarations are not supported."); + if (literal.result.equals("let")) return ParseRes.error(loc, "'let' declarations are not supported."); + return ParseRes.error(loc, String.format("Unexpected identifier '%s'.", literal.result)); + } + + return ParseRes.res(new VariableStatement(loc, literal.result), 1); + } } diff --git a/src/java/me/topchetoeu/jscript/runtime/Frame.java b/src/java/me/topchetoeu/jscript/runtime/Frame.java index 02f1360..c2a40c7 100644 --- a/src/java/me/topchetoeu/jscript/runtime/Frame.java +++ b/src/java/me/topchetoeu/jscript/runtime/Frame.java @@ -1,5 +1,6 @@ package me.topchetoeu.jscript.runtime; +import java.util.Arrays; import java.util.LinkedHashMap; import java.util.Map; import java.util.Stack; @@ -19,6 +20,7 @@ import me.topchetoeu.jscript.runtime.values.functions.CodeFunction; import me.topchetoeu.jscript.runtime.values.objects.ArrayValue; import me.topchetoeu.jscript.runtime.values.objects.ObjectValue; import me.topchetoeu.jscript.runtime.values.objects.ScopeValue; +import me.topchetoeu.jscript.runtime.values.primitives.VoidValue; public class Frame { public static final Key KEY = new Key<>(); @@ -124,7 +126,7 @@ public class Frame { else return stack[stackPtr - 1 - offset]; } public Value pop() { - if (stackPtr == 0) return null; + if (stackPtr == 0) return VoidValue.UNDEFINED; return stack[--stackPtr]; } public Value[] take(int n) { @@ -135,6 +137,7 @@ public class Frame { int copyN = stackPtr - srcI; Value[] res = new Value[n]; + Arrays.fill(res, VoidValue.UNDEFINED); System.arraycopy(stack, srcI, res, dstI, copyN); stackPtr -= copyN; diff --git a/src/java/me/topchetoeu/jscript/runtime/SimpleRepl.java b/src/java/me/topchetoeu/jscript/runtime/SimpleRepl.java index 6cf802b..5253aa3 100644 --- a/src/java/me/topchetoeu/jscript/runtime/SimpleRepl.java +++ b/src/java/me/topchetoeu/jscript/runtime/SimpleRepl.java @@ -16,6 +16,7 @@ import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; import me.topchetoeu.jscript.runtime.scope.GlobalScope; import me.topchetoeu.jscript.runtime.values.Value; import me.topchetoeu.jscript.runtime.values.functions.NativeFunction; +import me.topchetoeu.jscript.runtime.values.primitives.VoidValue; public class SimpleRepl { static Thread engineTask, debugTask; @@ -48,7 +49,7 @@ public class SimpleRepl { if (raw == null) break; var func = Compiler.compile(environment, new Filename("jscript", "repl/" + i + ".js"), raw); - var res = engine.pushMsg(false, environment, func, null).await(); + var res = engine.pushMsg(false, environment, func, VoidValue.UNDEFINED).await(); System.err.println(res.toReadable(environment)); } catch (EngineException | SyntaxException e) { System.err.println(Value.errorToReadable(e, null)); }