diff --git a/src/java/me/topchetoeu/jscript/common/Location.java b/src/java/me/topchetoeu/jscript/common/Location.java index e3030b8..2118ee3 100644 --- a/src/java/me/topchetoeu/jscript/common/Location.java +++ b/src/java/me/topchetoeu/jscript/common/Location.java @@ -1,101 +1,108 @@ package me.topchetoeu.jscript.common; import java.util.ArrayList; +import java.util.Objects; -public class Location implements Comparable { - public static final Location INTERNAL = new Location(-1, -1, new Filename("jscript", "native")); - private int line; - private int start; - private Filename filename; +public abstract class Location implements Comparable { + public static final Location INTERNAL = Location.of("jscript://native"); - public int line() { return line; } - public int start() { return start; } - public Filename filename() { return filename; } + public abstract int line(); + public abstract int start(); + public abstract Filename filename(); - @Override - public String toString() { + public final String toString() { var res = new ArrayList(); - if (filename != null) res.add(filename.toString()); - if (line >= 0) res.add(line + ""); - if (start >= 0) res.add(start + ""); + if (filename() != null) res.add(filename().toString()); + if (line() >= 0) res.add(line() + 1 + ""); + if (start() >= 0) res.add(start() + 1 + ""); return String.join(":", res); } - public Location add(int n, boolean clone) { - if (clone) return new Location(line, start + n, filename); - this.start += n; - return this; + public final Location add(int n) { + var self = this; + + return new Location() { + @Override public Filename filename() { return self.filename(); } + @Override public int start() { return self.start() + n; } + @Override public int line() { return self.line(); } + }; } - public Location add(int n) { - return add(n, false); - } - public Location nextLine() { - line++; - start = 0; - return this; - } - public Location clone() { - return new Location(line, start, filename); + public final Location nextLine() { + var self = this; + + return new Location() { + @Override public Filename filename() { return self.filename(); } + @Override public int start() { return 0; } + @Override public int line() { return self.line() + 1; } + }; } - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result + line; - result = prime * result + start; - result = prime * result + ((filename == null) ? 0 : filename.hashCode()); - return result; + @Override public final int hashCode() { + return Objects.hash(line(), start(), filename()); } - - @Override - public boolean equals(Object obj) { + @Override public final boolean equals(Object obj) { if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - Location other = (Location) obj; - if (line != other.line) return false; - if (start != other.start) return false; - if (filename == null && other.filename != null) return false; - else if (!filename.equals(other.filename)) return false; + if (!(obj instanceof Location)) return false; + var other = (Location)obj; + + if (!Objects.equals(this.start(), other.start())) return false; + if (!Objects.equals(this.line(), other.line())) return false; + if (!Objects.equals(this.filename(), other.filename())) return false; + return true; } - @Override - public int compareTo(Location other) { - int a = filename.toString().compareTo(other.filename.toString()); - int b = Integer.compare(line, other.line); - int c = Integer.compare(start, other.start); + @Override public int compareTo(Location other) { + int a = filename().toString().compareTo(other.filename().toString()); + int b = Integer.compare(line(), other.line()); + int c = Integer.compare(start(), other.start()); if (a != 0) return a; if (b != 0) return b; + return c; } - public Location(int line, int start, Filename filename) { - this.line = line; - this.start = start; - this.filename = filename; + public static Location of(Filename filename, int line, int start) { + return new Location() { + @Override public Filename filename() { return filename; } + @Override public int start() { return start; } + @Override public int line() { return line; } + }; } - public static Location parse(String raw) { - int i0 = -1, i1 = -1; - for (var i = raw.length() - 1; i >= 0; i--) { - if (raw.charAt(i) == ':') { - if (i1 == -1) i1 = i; - else if (i0 == -1) { - i0 = i; - break; - } + public static Location of(String raw) { + var i0 = raw.lastIndexOf(':'); + if (i0 < 0) return Location.of(Filename.parse(raw), -1, -1); + + var i1 = raw.lastIndexOf(':', i0); + if (i0 < 0) { + try { + return Location.of(Filename.parse(raw.substring(0, i0)), Integer.parseInt(raw.substring(i0 + 1)), -1); + } + catch (NumberFormatException e) { + return Location.of(Filename.parse(raw), -1, -1); } } - return new Location( - Integer.parseInt(raw.substring(i0 + 1, i1)), - Integer.parseInt(raw.substring(i1 + 1)), - Filename.parse(raw.substring(0, i0)) - ); + int start, line; + + try { + start = Integer.parseInt(raw.substring(i1 + 1)); + } + catch (NumberFormatException e) { + return Location.of(Filename.parse(raw), -1, -1); + } + + try { + line = Integer.parseInt(raw.substring(i0 + 1, i1)); + } + catch (NumberFormatException e) { + return Location.of(Filename.parse(raw.substring(i1 + 1)), start, -1); + } + + return Location.of(Filename.parse(raw.substring(0, i0)), start, line); } } diff --git a/src/java/me/topchetoeu/jscript/common/json/JSON.java b/src/java/me/topchetoeu/jscript/common/json/JSON.java index 34704d6..66d6ed8 100644 --- a/src/java/me/topchetoeu/jscript/common/json/JSON.java +++ b/src/java/me/topchetoeu/jscript/common/json/JSON.java @@ -1,85 +1,85 @@ package me.topchetoeu.jscript.common.json; -import java.util.List; +import java.util.Map; import java.util.stream.Collectors; import me.topchetoeu.jscript.common.Filename; -import me.topchetoeu.jscript.common.ParseRes; -import me.topchetoeu.jscript.compilation.parsing.Operator; +import me.topchetoeu.jscript.compilation.parsing.ParseRes; 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.parsing.Source; import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; public class JSON { - public static ParseRes parseIdentifier(List tokens, int i) { - return Parsing.parseIdentifier(tokens, i); + public static ParseRes parseString(Source src, int i) { + var res = Parsing.parseString(src, i); + if (!res.isSuccess()) return res.chainError(); + return ParseRes.res(JSONElement.string(res.result), res.n); } - public static ParseRes parseString(Filename filename, List tokens, int i) { - var res = ConstantStatement.parseString(filename, tokens, i); - if (res.isSuccess()) return ParseRes.res((String)res.result.value, res.n); - else return res.transform(); - } - public static ParseRes parseNumber(Filename filename, List tokens, int i) { - var minus = Parsing.isOperator(tokens, i, Operator.SUBTRACT); - if (minus) i++; + public static ParseRes parseNumber(Source src, int i) { + var n = Parsing.skipEmpty(src, 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(); + if (src.is(i + n, "-")) { + n++; + + var res = Parsing.parseNumber(src, i); + if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a number after minus"); + n += res.n; + + return ParseRes.res(JSONElement.number(-res.result), n); + } + else { + var res = Parsing.parseNumber(src, i + n).addN(n); + if (!res.isSuccess()) return res.chainError(); + n += res.n; + return ParseRes.res(JSONElement.number(res.result), n); + } } - public static ParseRes parseBool(Filename filename, List tokens, int i) { - var id = parseIdentifier(tokens, i); + public static ParseRes parseLiteral(Source src, int i) { + var id = Parsing.parseIdentifier(src, i); if (!id.isSuccess()) return ParseRes.failed(); - else if (id.result.equals("true")) return ParseRes.res(true, 1); - else if (id.result.equals("false")) return ParseRes.res(false, 1); + else if (id.result.equals("true")) return ParseRes.res(JSONElement.bool(true), id.n); + else if (id.result.equals("false")) return ParseRes.res(JSONElement.bool(false), id.n); + else if (id.result.equals("null")) return ParseRes.res(JSONElement.NULL, id.n); else return ParseRes.failed(); } - public static ParseRes parseValue(Filename filename, List tokens, int i) { - return ParseRes.any( - parseString(filename, tokens, i), - parseNumber(filename, tokens, i), - parseBool(filename, tokens, i), - parseMap(filename, tokens, i), - parseList(filename, tokens, i) + public static ParseRes parseValue(Source src, int i) { + return ParseRes.first(src, i, + JSON::parseString, + JSON::parseNumber, + JSON::parseLiteral, + JSON::parseMap, + JSON::parseList ); } - public static ParseRes parseMap(Filename filename, List tokens, int i) { - int n = 0; - if (!Parsing.isOperator(tokens, i + n++, Operator.BRACE_OPEN)) return ParseRes.failed(); + public static ParseRes parseMap(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + + if (!src.is(i + n, "{")) return ParseRes.failed(); + n++; var values = new JSONMap(); + if (src.is(i + n, "}")) return ParseRes.res(new JSONMap(Map.of()), n + 1); while (true) { - if (Parsing.isOperator(tokens, i + n, Operator.BRACE_CLOSE)) { - n++; - break; - } + var name = parseString(src, i + n); + if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected an index"); + n += name.n; + n += Parsing.skipEmpty(src, i + n); - var name = ParseRes.any( - parseIdentifier(tokens, i + n), - parseString(filename, tokens, i + n), - parseNumber(filename, tokens, i + n) - ); - if (!name.isSuccess()) return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected an index.", name); - else n += name.n; - - if (!Parsing.isOperator(tokens, i + n, Operator.COLON)) { - return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected a colon.", name); - } + if (!src.is(i + n, ":")) return name.chainError(src.loc(i + n), "Expected a colon"); n++; - var res = parseValue(filename, tokens, i + n); - if (!res.isSuccess()) return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected a list element.", res); - else n += res.n; + var res = parseValue(src, i + n); + if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a list element"); + values.put(name.result.toString(), res.result); + n += res.n; + n += Parsing.skipEmpty(src, i + n); - values.put(name.result.toString(), JSONElement.of(res.result)); - - if (Parsing.isOperator(tokens, i + n, Operator.COMMA)) n++; - else if (Parsing.isOperator(tokens, i + n, Operator.BRACE_CLOSE)) { + if (src.is(i + n, ",")) n++; + else if (src.is(i + n, "}")) { n++; break; } @@ -87,26 +87,23 @@ public class JSON { return ParseRes.res(values, n); } - public static ParseRes parseList(Filename filename, List tokens, int i) { - int n = 0; - if (!Parsing.isOperator(tokens, i + n++, Operator.BRACKET_OPEN)) return ParseRes.failed(); + public static ParseRes parseList(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + + if (!src.is(i + n++, "[]")) return ParseRes.failed(); var values = new JSONList(); + if (src.is(i + n, "]")) return ParseRes.res(new JSONList(), n + 1); while (true) { - if (Parsing.isOperator(tokens, i + n, Operator.BRACKET_CLOSE)) { - n++; - break; - } + var res = parseValue(src, i + n); + if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a list element"); + values.add(res.result); + n += res.n; + n += Parsing.skipEmpty(src, i + n); - var res = parseValue(filename, tokens, i + n); - if (!res.isSuccess()) return ParseRes.error(Parsing.getLoc(filename, tokens, i + n), "Expected a list element.", res); - else n += res.n; - - values.add(JSONElement.of(res.result)); - - if (Parsing.isOperator(tokens, i + n, Operator.COMMA)) n++; - else if (Parsing.isOperator(tokens, i + n, Operator.BRACKET_CLOSE)) { + if (src.is(i + n, ",")) n++; + else if (src.is(i + n, "]")) { n++; break; } @@ -116,7 +113,8 @@ public class JSON { } public static JSONElement parse(Filename filename, String raw) { if (filename == null) filename = new Filename("jscript", "json"); - var res = parseValue(filename, Parsing.tokenize(filename, raw), 0); + + var res = parseValue(new Source(filename, raw), 0); if (res.isFailed()) throw new SyntaxException(null, "Invalid JSON given."); else if (res.isError()) throw new SyntaxException(null, res.error); else return JSONElement.of(res.result); diff --git a/src/java/me/topchetoeu/jscript/common/mapping/FunctionMap.java b/src/java/me/topchetoeu/jscript/common/mapping/FunctionMap.java index 153c3a2..d0d5d47 100644 --- a/src/java/me/topchetoeu/jscript/common/mapping/FunctionMap.java +++ b/src/java/me/topchetoeu/jscript/common/mapping/FunctionMap.java @@ -103,7 +103,7 @@ public class FunctionMap { var res = new ArrayList(candidates.size()); for (var candidate : candidates.entrySet()) { - var val = correctBreakpoint(new Location(line, column, candidate.getKey())); + var val = correctBreakpoint(Location.of(candidate.getKey(), line, column)); if (val == null) continue; res.add(val); } diff --git a/src/java/me/topchetoeu/jscript/compilation/AssignableStatement.java b/src/java/me/topchetoeu/jscript/compilation/AssignableStatement.java index c4c17d2..53a557a 100644 --- a/src/java/me/topchetoeu/jscript/compilation/AssignableStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/AssignableStatement.java @@ -1,15 +1,11 @@ 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.ParseRes; import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Token; +import me.topchetoeu.jscript.compilation.parsing.Source; public abstract class AssignableStatement extends Statement { public abstract Statement toAssign(Statement val, Operation operation); @@ -18,41 +14,37 @@ public abstract class AssignableStatement extends Statement { 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; - + public static ParseRes parse(Source src, int i, Statement prev, int precedence) { if (precedence > 2) return ParseRes.failed(); - var opRes = Parsing.parseOperator(tokens, i + n++); - if (opRes.state != State.SUCCESS) return ParseRes.failed(); + var n = Parsing.skipEmpty(src, i); - var op = opRes.result; - if (!op.isAssign()) return ParseRes.failed(); + for (var op : Operator.opsByLength) { + if (!op.assignable || !src.is(i + n, op.readable + "=")) continue; + n += op.readable.length() + 1; - if (!(prev instanceof AssignableStatement)) { - return ParseRes.error(loc, "Invalid expression on left hand side of assign operator."); + if (!(prev instanceof AssignableStatement)) { + return ParseRes.error(src.loc(i + n), "Invalid expression on left hand side of assign operator"); + } + + var res = Parsing.parseValue(src, i + n, 2); + if (!res.isSuccess()) return res.chainError(src.loc(i + n), String.format("Expected a value after the '%s=' operator", op.readable)); + n += res.n; + + return ParseRes.res(((AssignableStatement)prev).toAssign(res.result, op.operation), n); } - 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); + if (!src.is(i + n, "=")) return ParseRes.failed(); + n++; + + if (!(prev instanceof AssignableStatement)) { + return ParseRes.error(src.loc(i + n), "Invalid expression on left hand side of assign operator"); + } + + var res = Parsing.parseValue(src, i + n, 2); + if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a value after the '=' operator"); 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); + return ParseRes.res(((AssignableStatement)prev).toAssign(res.result, null), n); } - } diff --git a/src/java/me/topchetoeu/jscript/compilation/CompoundStatement.java b/src/java/me/topchetoeu/jscript/compilation/CompoundStatement.java index 62fa7a9..5adf30c 100644 --- a/src/java/me/topchetoeu/jscript/compilation/CompoundStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/CompoundStatement.java @@ -4,14 +4,12 @@ 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.ParseRes; import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Token; +import me.topchetoeu.jscript.compilation.parsing.Source; import me.topchetoeu.jscript.compilation.values.FunctionStatement; public class CompoundStatement extends Statement { @@ -68,45 +66,49 @@ public class CompoundStatement extends Statement { 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; - + public static ParseRes parseComma(Source src, int i, Statement prev, int precedence) { 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); + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!src.is(i + n, ",")) return ParseRes.failed(); + n++; + + var res = Parsing.parseValue(src, i + n, 2); + if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a value after the comma"); 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(); + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!src.is(i + n, "{")) return ParseRes.failed(); + n++; var statements = new ArrayList(); while (true) { - if (Parsing.isOperator(tokens, i + n, Operator.BRACE_CLOSE)) { + n += Parsing.skipEmpty(src, i + n); + + if (src.is(i + n, "}")) { n++; break; } - if (Parsing.isOperator(tokens, i + n, Operator.SEMICOLON)) { + if (src.is(i + n, ";")) { 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); - } + var res = Parsing.parseStatement(src, i + n); + if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a statement"); 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); + return ParseRes.res(new CompoundStatement(loc, true, statements.toArray(Statement[]::new)).setEnd(src.loc(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 25d16f1..dd10c58 100644 --- a/src/java/me/topchetoeu/jscript/compilation/VariableDeclareStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/VariableDeclareStatement.java @@ -3,14 +3,12 @@ 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.ParseRes; import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Token; +import me.topchetoeu.jscript.compilation.parsing.Source; import me.topchetoeu.jscript.compilation.values.FunctionStatement; public class VariableDeclareStatement extends Statement { @@ -56,48 +54,59 @@ public class VariableDeclareStatement extends Statement { 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(); + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!Parsing.isIdentifier(src, i + n, "var")) return ParseRes.failed(); + n += 3; var 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); + var end = Parsing.parseStatementEnd(src, i + n); + if (end.isSuccess()) { + n += end.n; + return ParseRes.res(new VariableDeclareStatement(loc, res), n); } 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."); + var nameLoc = src.loc(i + n); + var name = Parsing.parseIdentifier(src, i + n); + if (!name.isSuccess()) return name.chainError(nameLoc, "Expected a variable name"); + n += name.n; - if (!Parsing.checkVarName(nameRes.result)) { - return ParseRes.error(loc, String.format("Unexpected identifier '%s'.", nameRes.result)); + if (!Parsing.checkVarName(name.result)) { + return ParseRes.error(src.loc(i + n), String.format("Unexpected identifier '%s'", name.result)); } Statement val = null; + n += Parsing.skipEmpty(src, i + n); - if (Parsing.isOperator(tokens, i + n, Operator.ASSIGN)) { + if (src.is(i + n, "=")) { n++; - var valRes = Parsing.parseValue(filename, tokens, i + n, 2); - if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after '='.", valRes); + + var valRes = Parsing.parseValue(src, i + n, 2); + if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after '='"); + n += valRes.n; + n += Parsing.skipEmpty(src, i + n); val = valRes.result; } - res.add(new Pair(nameRes.result, val, nameLoc)); + res.add(new Pair(name.result, val, nameLoc)); - if (Parsing.isOperator(tokens, i + n, Operator.COMMA)) { + if (src.is(i + n, ",")) { 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); + + end = Parsing.parseStatementEnd(src, i + n); + + if (end.isSuccess()) { + n += end.n; + return ParseRes.res(new VariableDeclareStatement(loc, res), n); } - else return ParseRes.error(loc, "Expected a comma or end of statement."); + else return end.chainError(src.loc(i + n), "Expected a comma or end of statement"); } } } diff --git a/src/java/me/topchetoeu/jscript/compilation/control/BreakStatement.java b/src/java/me/topchetoeu/jscript/compilation/control/BreakStatement.java index 4c39bc7..5a62233 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/BreakStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/BreakStatement.java @@ -1,16 +1,12 @@ 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.ParseRes; import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Token; +import me.topchetoeu.jscript.compilation.parsing.Source; public class BreakStatement extends Statement { public final String label; @@ -25,22 +21,28 @@ public class BreakStatement extends Statement { this.label = label; } - public static ParseRes parseBreak(Filename filename, List tokens, int i) { - if (!Parsing.isIdentifier(tokens, i, "break")) return ParseRes.failed(); + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); - 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); + if (!Parsing.isIdentifier(src, i + n, "break")) return ParseRes.failed(); + n += 5; + + var end = Parsing.parseStatementEnd(src, i + n); + if (end.isSuccess()) { + n += end.n; + return ParseRes.res(new BreakStatement(loc, null), n); } - 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; + var label = Parsing.parseIdentifier(src, i + n); + if (label.isFailed()) return ParseRes.error(src.loc(i + n), "Expected a label name or an end of statement"); + n += label.n; - 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); + end = Parsing.parseStatementEnd(src, i + n); + if (end.isSuccess()) { + n += end.n; + return ParseRes.res(new BreakStatement(loc, label.result), n); } - else return ParseRes.error(Parsing.getLoc(filename, tokens, i), "Expected an end of statement."); + else return end.chainError(src.loc(i + n), "Expected 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 2513892..281e11c 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/ContinueStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/ContinueStatement.java @@ -1,16 +1,12 @@ 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.ParseRes; import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Token; +import me.topchetoeu.jscript.compilation.parsing.Source; public class ContinueStatement extends Statement { public final String label; @@ -25,22 +21,28 @@ public class ContinueStatement extends Statement { this.label = label; } - public static ParseRes parseContinue(Filename filename, List tokens, int i) { - if (!Parsing.isIdentifier(tokens, i, "continue")) return ParseRes.failed(); + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); - 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); + if (!Parsing.isIdentifier(src, i + n, "continue")) return ParseRes.failed(); + n += 8; + + var end = Parsing.parseStatementEnd(src, i + n); + if (end.isSuccess()) { + n += end.n; + return ParseRes.res(new ContinueStatement(loc, null), n); } - 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; + var label = Parsing.parseIdentifier(src, i + n); + if (label.isFailed()) return ParseRes.error(src.loc(i + n), "Expected a label name or an end of statement"); + n += label.n; - 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); + end = Parsing.parseStatementEnd(src, i + n); + if (end.isSuccess()) { + n += end.n; + return ParseRes.res(new ContinueStatement(loc, label.result), n); } - else return ParseRes.error(Parsing.getLoc(filename, tokens, i), "Expected an end of statement."); + else return end.chainError(src.loc(i + n), "Expected 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 bf1c352..4150118 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/DebugStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/DebugStatement.java @@ -1,16 +1,12 @@ 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.ParseRes; import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Token; +import me.topchetoeu.jscript.compilation.parsing.Source; public class DebugStatement extends Statement { @Override public void compile(CompileResult target, boolean pollute) { @@ -22,14 +18,19 @@ public class DebugStatement extends Statement { super(loc); } - public static ParseRes parse(Filename filename, List tokens, int i) { - if (!Parsing.isIdentifier(tokens, i, "debugger")) return ParseRes.failed(); + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); - 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); + if (!Parsing.isIdentifier(src, i + n, "debugger")) return ParseRes.failed(); + n += 8; + + var end = Parsing.parseStatementEnd(src, i + n); + if (end.isSuccess()) { + n += end.n; + return ParseRes.res(new DebugStatement(loc), n); } - else return ParseRes.error(Parsing.getLoc(filename, tokens, i), "Expected an end of statement."); + else return end.chainError(src.loc(i + n), "Expected 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 cdc35db..52a8c7d 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/DeleteStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/DeleteStatement.java @@ -1,18 +1,15 @@ 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.ParseRes; 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.parsing.Source; import me.topchetoeu.jscript.compilation.values.IndexStatement; import me.topchetoeu.jscript.compilation.values.VariableStatement; +import me.topchetoeu.jscript.compilation.values.constants.BoolStatement; public class DeleteStatement extends Statement { public final Statement key; @@ -27,13 +24,15 @@ 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(); + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); - var valRes = Parsing.parseValue(filename, tokens, i + n, 15); - if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'delete'.", valRes); + if (!Parsing.isIdentifier(src, i + n, "delete")) return ParseRes.failed(); + n += 6; + + var valRes = Parsing.parseValue(src, i + n, 15); + if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'delete'"); n += valRes.n; if (valRes.result instanceof IndexStatement) { @@ -41,9 +40,9 @@ public class DeleteStatement extends Statement { 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."); + return ParseRes.error(src.loc(i + n), "A variable may not be deleted"); } - else return ParseRes.res(new ConstantStatement(loc, true), n); + else return ParseRes.res(new BoolStatement(loc, true), n); } public DeleteStatement(Location loc, Statement key, Statement value) { diff --git a/src/java/me/topchetoeu/jscript/compilation/control/DoWhileStatement.java b/src/java/me/topchetoeu/jscript/compilation/control/DoWhileStatement.java index 456b487..1d64dd8 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/DoWhileStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/DoWhileStatement.java @@ -1,17 +1,13 @@ 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.ParseRes; import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Token; +import me.topchetoeu.jscript.compilation.parsing.Source; public class DoWhileStatement extends Statement { public final Statement condition, body; @@ -41,34 +37,41 @@ public class DoWhileStatement extends Statement { this.body = body; } - public static ParseRes parseDoWhile(Filename filename, List tokens, int i) { - var loc = Parsing.getLoc(filename, tokens, i); - int n = 0; + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); - var labelRes = WhileStatement.parseLabel(tokens, i + n); + var labelRes = WhileStatement.parseLabel(src, 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); + if (!Parsing.isIdentifier(src, i + n, "do")) return ParseRes.failed(); + n += 2; + + var bodyRes = Parsing.parseStatement(src, i + n); + if (!bodyRes.isSuccess()) return bodyRes.chainError(src.loc(i + n), "Expected a do-while body."); 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'."); + if (!Parsing.isIdentifier(src, i + n, "while")) return ParseRes.failed(); + n += 5; + n += Parsing.skipEmpty(src, i + n); - var condRes = Parsing.parseValue(filename, tokens, i + n, 0); - if (!condRes.isSuccess()) return ParseRes.error(loc, "Expected a while condition.", condRes); + if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'while'."); + n++; + + var condRes = Parsing.parseValue(src, i + n, 0); + if (!condRes.isSuccess()) return condRes.chainError(src.loc(i + n), "Expected a do-while condition."); n += condRes.n; + n += Parsing.skipEmpty(src, i + n); - if (!Parsing.isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after while condition."); + if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after do-while condition."); + n++; - 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; + var end = Parsing.parseStatementEnd(src, i + n); + if (end.isSuccess()) { + n += end.n; + return ParseRes.res(new DoWhileStatement(loc, labelRes.result, condRes.result, bodyRes.result), n); } - else return ParseRes.error(Parsing.getLoc(filename, tokens, i), "Expected a semicolon."); + else return end.chainError(src.loc(i + n), "Expected end of statement"); } } diff --git a/src/java/me/topchetoeu/jscript/compilation/control/ForInStatement.java b/src/java/me/topchetoeu/jscript/compilation/control/ForInStatement.java index d4d162f..bbf4e03 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/ForInStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/ForInStatement.java @@ -1,23 +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.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.ParseRes; import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Token; +import me.topchetoeu.jscript.compilation.parsing.Source; public class ForInStatement extends Statement { public final String varName; public final boolean isDeclaration; - public final Statement varValue, object, body; + public final Statement object, body; public final String label; public final Location varLocation; @@ -31,11 +27,6 @@ public class ForInStatement extends Statement { if (key instanceof String) target.add(Instruction.makeVar((String)key)); - if (varValue != null) { - varValue.compile(target, true); - target.add(Instruction.storeVar(target.scope.getKey(varName))); - } - object.compile(target, true, BreakpointType.STEP_OVER); target.add(Instruction.keys(true)); @@ -61,70 +52,56 @@ public class ForInStatement extends Statement { if (pollute) target.add(Instruction.pushUndefined()); } - public ForInStatement(Location loc, Location varLocation, String label, boolean isDecl, String varName, Statement varValue, Statement object, Statement body) { + public ForInStatement(Location loc, Location varLocation, String label, boolean isDecl, String varName, Statement object, Statement body) { super(loc); this.varLocation = varLocation; this.label = label; this.isDeclaration = isDecl; this.varName = varName; - this.varValue = varValue; 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; + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + var label = WhileStatement.parseLabel(src, i + n); + n += label.n; + n += Parsing.skipEmpty(src, i + n); + + if (!Parsing.isIdentifier(src, i + n, "for")) return ParseRes.failed(); + n += 3; + n += Parsing.skipEmpty(src, i + n); - 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")) { + if (Parsing.isIdentifier(src, i + n, "var")) { isDecl = true; - n++; + n += 3; } - 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; + var name = Parsing.parseIdentifier(src, i + n); + if (!name.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a variable name for for-in loop"); + var nameLoc = src.loc(i + n); + n += name.n; + n += Parsing.skipEmpty(src, i + n); - Statement varVal = null; + if (!Parsing.isIdentifier(src, i + n, "in")) return ParseRes.error(src.loc(i + n), "Expected 'in' keyword after variable declaration"); + n += 2; - if (Parsing.isOperator(tokens, i + n, Operator.ASSIGN)) { - n++; + var obj = Parsing.parseValue(src, i + n, 0); + if (!obj.isSuccess()) return obj.chainError(src.loc(i + n), "Expected a value"); + n += obj.n; + n += Parsing.skipEmpty(src, i + 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; + if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren"); + 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); + var bodyRes = Parsing.parseStatement(src, i + n); + if (!bodyRes.isSuccess()) return bodyRes.chainError(src.loc(i + n), "Expected a for-in body"); n += bodyRes.n; - return ParseRes.res(new ForInStatement(loc, nameLoc, labelRes.result, isDecl, nameRes.result, varVal, objRes.result, bodyRes.result), n); + return ParseRes.res(new ForInStatement(loc, nameLoc, label.result, isDecl, name.result, obj.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 a304252..b56c3d9 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/ForOfStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/ForOfStatement.java @@ -1,17 +1,13 @@ 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.ParseRes; import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Token; +import me.topchetoeu.jscript.compilation.parsing.Source; public class ForOfStatement extends Statement { public final String varName; @@ -78,44 +74,46 @@ public class ForOfStatement extends Statement { this.body = body; } - public static ParseRes parse(Filename filename, List tokens, int i) { - var loc = Parsing.getLoc(filename, tokens, i); - int n = 0; + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + var label = WhileStatement.parseLabel(src, i + n); + n += label.n; + n += Parsing.skipEmpty(src, i + n); + + if (!Parsing.isIdentifier(src, i + n, "for")) return ParseRes.failed(); + n += 3; + n += Parsing.skipEmpty(src, i + n); - 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")) { + if (Parsing.isIdentifier(src, i + n, "var")) { isDecl = true; - n++; + n += 3; } - 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; + var name = Parsing.parseIdentifier(src, i + n); + if (!name.isSuccess()) return ParseRes.error(src.loc(i + n), "Expected a variable name for for-of loop"); + var nameLoc = src.loc(i + n); + n += name.n; + n += Parsing.skipEmpty(src, i + 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."); - } + if (!Parsing.isIdentifier(src, i + n, "fo")) return ParseRes.error(src.loc(i + n), "Expected 'of' keyword after variable declaration"); + n += 2; - var objRes = Parsing.parseValue(filename, tokens, i + n, 0); - if (!objRes.isSuccess()) return ParseRes.error(loc, "Expected a value.", objRes); - n += objRes.n; + var obj = Parsing.parseValue(src, i + n, 0); + if (!obj.isSuccess()) return obj.chainError(src.loc(i + n), "Expected a value"); + n += obj.n; + n += Parsing.skipEmpty(src, i + n); - if (!Parsing.isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after for."); - + if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren"); + n++; - var bodyRes = Parsing.parseStatement(filename, tokens, i + n); - if (!bodyRes.isSuccess()) return ParseRes.error(loc, "Expected a for body.", bodyRes); + var bodyRes = Parsing.parseStatement(src, i + n); + if (!bodyRes.isSuccess()) return bodyRes.chainError(src.loc(i + n), "Expected a for-of body"); n += bodyRes.n; - return ParseRes.res(new ForOfStatement(loc, nameLoc, labelRes.result, isDecl, nameRes.result, objRes.result, bodyRes.result), n); + return ParseRes.res(new ForOfStatement(loc, nameLoc, label.result, isDecl, name.result, obj.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 e97a073..8dbfec7 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/ForStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/ForStatement.java @@ -1,20 +1,15 @@ 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.ParseRes; 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.parsing.Source; +import me.topchetoeu.jscript.compilation.values.DiscardStatement; public class ForStatement extends Statement { public final Statement declaration, assignment, condition, body; @@ -53,61 +48,69 @@ public class ForStatement extends Statement { this.body = body; } - public static ParseRes parse(Filename filename, List tokens, int i) { - var loc = Parsing.getLoc(filename, tokens, i); - int n = 0; + private static ParseRes parseSemicolon(Source src, int i) { + var n = Parsing.skipEmpty(src, i); - var labelRes = WhileStatement.parseLabel(tokens, i + n); - n += labelRes.n; + if (!src.is(i + n, ";")) return ParseRes.failed(); + else return ParseRes.res(new DiscardStatement(src.loc(i), null), n + 1); + } + private static ParseRes parseCondition(Source src, int i) { + var n = Parsing.skipEmpty(src, i); - 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); + var res = Parsing.parseValue(src, i + n, 0); + if (!res.isSuccess()) return res.chainError(); n += res.n; + n += Parsing.skipEmpty(src, i + n); - return ParseRes.res(new ForStatement(loc, labelRes.result, decl, cond, inc, res.result), n); + if (!src.is(i + n, ";")) return ParseRes.error(src.loc(i + n), "Expected a semicolon"); + else return ParseRes.res(res.result, n + 1); + } + private static ParseRes parseUpdater(Source src, int i) { + return Parsing.parseValue(src, i, 0); + } + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + var labelRes = WhileStatement.parseLabel(src, i + n); + n += labelRes.n; + n += Parsing.skipEmpty(src, i + n); + + if (!Parsing.isIdentifier(src, i + n, "for")) return ParseRes.failed(); + n += 3; + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'for'"); + n++; + + ParseRes decl = ParseRes.first(src, i + n, + ForStatement::parseSemicolon, + VariableDeclareStatement::parse, + ForStatement::parseCondition + ); + if (!decl.isSuccess()) return decl.chainError(src.loc(i + n), "Expected a declaration or an expression"); + n += decl.n; + + ParseRes cond = ParseRes.first(src, i + n, + ForStatement::parseSemicolon, + ForStatement::parseCondition + ); + if (!cond.isSuccess()) return cond.chainError(src.loc(i + n), "Expected a condition"); + n += cond.n; + + var update = parseUpdater(src, i + n); + if (update.isError()) return update.chainError(); + n += update.n; + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a close paren after for updater"); + n++; + + var body = Parsing.parseStatement(src, i + n); + if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a for body."); + n += body.n; + + return ParseRes.res(new ForStatement(loc, labelRes.result, decl.result, cond.result, update.result, body.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 8dd2034..5c0fcdb 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/IfStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/IfStatement.java @@ -1,17 +1,13 @@ 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.ParseRes; import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Token; +import me.topchetoeu.jscript.compilation.parsing.Source; public class IfStatement extends Statement { public final Statement condition, body, elseBody; @@ -53,47 +49,58 @@ public class IfStatement extends Statement { 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; - + public static ParseRes parseTernary(Source src, int i, Statement prev, int precedence) { 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); + var n = Parsing.skipEmpty(src, i); + + if (!src.is(i + n, "?")) return ParseRes.failed(); + var loc = src.loc(i + n); + n++; + + var a = Parsing.parseValue(src, i + n, 2); + if (!a.isSuccess()) return a.chainError(src.loc(i + n), "Expected a value after the ternary operator."); n += a.n; + n += Parsing.skipEmpty(src, i); - if (!Parsing.isOperator(tokens, i + n++, Operator.COLON)) return ParseRes.failed(); + if (!src.is(i + n, ":")) return ParseRes.failed(); + n++; - 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); + var b = Parsing.parseValue(src, i + n, 2); + if (!b.isSuccess()) return b.chainError(src.loc(i + n), "Expected a second value after the ternary operator."); 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; + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); - 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'."); + if (!Parsing.isIdentifier(src, i + n, "if")) return ParseRes.failed(); + n += 2; + n += Parsing.skipEmpty(src, i + n); - 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); + if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'if'."); n++; - var elseRes = Parsing.parseStatement(filename, tokens, i + n); - if (!elseRes.isSuccess()) return ParseRes.error(loc, "Expected an else body.", elseRes); + var condRes = Parsing.parseValue(src, i + n, 0); + if (!condRes.isSuccess()) return condRes.chainError(src.loc(i + n), "Expected an if condition."); + n += condRes.n; + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after if condition."); + n++; + + var res = Parsing.parseStatement(src, i + n); + if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected an if body."); + n += res.n; + + var elseKw = Parsing.parseIdentifier(src, i + n, "else"); + if (!elseKw.isSuccess()) return ParseRes.res(new IfStatement(loc, condRes.result, res.result, null), n); + n += elseKw.n; + + var elseRes = Parsing.parseStatement(src, i + n); + if (!elseRes.isSuccess()) return elseRes.chainError(src.loc(i + n), "Expected an else body."); 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 a9e0f5c..84dd2fa 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/ReturnStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/ReturnStatement.java @@ -1,16 +1,12 @@ 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.ParseRes; import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Token; +import me.topchetoeu.jscript.compilation.parsing.Source; public class ReturnStatement extends Statement { public final Statement value; @@ -27,26 +23,28 @@ public class ReturnStatement extends Statement { 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(); + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); - 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); + if (!Parsing.isIdentifier(src, i + n, "return")) return ParseRes.failed(); + n += 6; + + var end = Parsing.parseStatementEnd(src, i + n); + if (end.isSuccess()) { + n += end.n; + return ParseRes.res(new ReturnStatement(loc, null), n); } - 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 val = Parsing.parseValue(src, i + n, 0); + if (val.isFailed()) return ParseRes.error(src.loc(i + n), "Expected a value"); + n += val.n; - 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; + end = Parsing.parseStatementEnd(src, i + n); + if (end.isSuccess()) { + n += end.n; + return ParseRes.res(new ReturnStatement(loc, val.result), n); } - else return ParseRes.error(Parsing.getLoc(filename, tokens, i), "Expected an end of statement.", valRes); + else return end.chainError(src.loc(i + n), "Expected end of statement"); } } diff --git a/src/java/me/topchetoeu/jscript/compilation/control/SwitchStatement.java b/src/java/me/topchetoeu/jscript/compilation/control/SwitchStatement.java index 1883bd8..6d44178 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/SwitchStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/SwitchStatement.java @@ -2,21 +2,17 @@ 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.ParseRes; import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Token; +import me.topchetoeu.jscript.compilation.parsing.Source; public class SwitchStatement extends Statement { public static class SwitchCase { @@ -87,78 +83,96 @@ public class SwitchStatement extends Statement { this.body = body; } - private static ParseRes parseSwitchCase(Filename filename, List tokens, int i) { - var loc = Parsing.getLoc(filename, tokens, i); - int n = 0; + private static ParseRes parseSwitchCase(Source src, int i) { + var n = Parsing.skipEmpty(src, i); - if (!Parsing.isIdentifier(tokens, i + n++, "case")) return ParseRes.failed(); + if (!Parsing.isIdentifier(src, i + n, "case")) return ParseRes.failed(); + n += 4; - var valRes = Parsing.parseValue(filename, tokens, i + n, 0); - if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'case'.", valRes); + var valRes = Parsing.parseValue(src, i + n, 0); + if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'case'"); n += valRes.n; - if (!Parsing.isOperator(tokens, i + n++, Operator.COLON)) return ParseRes.error(loc, "Expected colons after 'case' value."); + if (!src.is(i + n, ":")) return ParseRes.error(src.loc(i + n), "Expected colons after 'case' value"); + n++; 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'."); + private static ParseRes parseDefaultCase(Source src, int i) { + var n = Parsing.skipEmpty(src, i); - return ParseRes.res(null, 2); + if (!Parsing.isIdentifier(src, i + n, "default")) return ParseRes.failed(); + n += 7; + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i + n, ":")) return ParseRes.error(src.loc(i + n), "Expected colons after 'default'"); + n++; + + return ParseRes.res(null, n); } - public static ParseRes parse(Filename filename, List tokens, int i) { - var loc = Parsing.getLoc(filename, tokens, i); - int n = 0; + @SuppressWarnings("unused") + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); - 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'."); + if (!Parsing.isIdentifier(src, i + n, "switch")) return ParseRes.failed(); + n += 6; + n += Parsing.skipEmpty(src, i + n); + if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'switch'"); + n++; - var valRes = Parsing.parseValue(filename, tokens, i + n, 0); - if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a switch value.", valRes); + var valRes = Parsing.parseValue(src, i + n, 0); + if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a switch value"); n += valRes.n; + n += Parsing.skipEmpty(src, i + 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."); + if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after switch value"); + n++; + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i + n, "{")) return ParseRes.error(src.loc(i + n), "Expected an opening brace after switch value"); + n++; + n += Parsing.skipEmpty(src, i + n); var statements = new ArrayList(); var cases = new ArrayList(); var defaultI = -1; while (true) { - if (Parsing.isOperator(tokens, i + n, Operator.BRACE_CLOSE)) { + n += Parsing.skipEmpty(src, i + n); + + if (src.is(i + n, "}")) { n++; break; } - if (Parsing.isOperator(tokens, i + n, Operator.SEMICOLON)) { + if (src.is(i + n, ";")) { n++; continue; } - var defaultRes = SwitchStatement.parseDefaultCase(tokens, i + n); - var caseRes = SwitchStatement.parseSwitchCase(filename, tokens, i + n); + ParseRes caseRes = ParseRes.first(src, i + n, + SwitchStatement::parseDefaultCase, + SwitchStatement::parseSwitchCase + ); - if (defaultRes.isSuccess()) { - defaultI = statements.size(); - n += defaultRes.n; - } - else if (caseRes.isSuccess()) { - cases.add(new SwitchCase(caseRes.result, statements.size())); + // Parsing::parseStatement + + if (caseRes.isSuccess()) { n += caseRes.n; + + if (caseRes.result == null) defaultI = statements.size(); + else cases.add(new SwitchCase(caseRes.result, statements.size())); + continue; } - 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); + if (caseRes.isError()) return caseRes.chainError(); + + var stm = Parsing.parseStatement(src, i + n); + if (stm.isSuccess()) { + n += stm.n; + statements.add(stm.result); + continue; } + else stm.chainError(src.loc(i + n), "Expected a statement, 'case' or 'default'"); } return ParseRes.res(new SwitchStatement( diff --git a/src/java/me/topchetoeu/jscript/compilation/control/ThrowStatement.java b/src/java/me/topchetoeu/jscript/compilation/control/ThrowStatement.java index 3e19c1f..0993321 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/ThrowStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/ThrowStatement.java @@ -1,16 +1,12 @@ 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.ParseRes; import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Token; +import me.topchetoeu.jscript.compilation.parsing.Source; public class ThrowStatement extends Statement { public final Statement value; @@ -26,21 +22,28 @@ public class ThrowStatement extends Statement { 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(); + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); - var valRes = Parsing.parseValue(filename, tokens, i + n, 0); - n += valRes.n; - if (valRes.isError()) return ParseRes.error(loc, "Expected a throw value.", valRes); + if (!Parsing.isIdentifier(src, i + n, "throw")) return ParseRes.failed(); + n += 5; - 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; + var end = Parsing.parseStatementEnd(src, i + n); + if (end.isSuccess()) { + n += end.n; + return ParseRes.res(new ThrowStatement(loc, null), n); } - else return ParseRes.error(Parsing.getLoc(filename, tokens, i), "Expected an end of statement.", valRes); + + var val = Parsing.parseValue(src, i + n, 0); + if (val.isFailed()) return ParseRes.error(src.loc(i + n), "Expected a value"); + n += val.n; + + end = Parsing.parseStatementEnd(src, i + n); + if (end.isSuccess()) { + n += end.n; + return ParseRes.res(new ThrowStatement(loc, val.result), n); + } + else return end.chainError(src.loc(i + n), "Expected end of statement"); } } diff --git a/src/java/me/topchetoeu/jscript/compilation/control/TryStatement.java b/src/java/me/topchetoeu/jscript/compilation/control/TryStatement.java index 5854df2..78120b0 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/TryStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/TryStatement.java @@ -1,17 +1,14 @@ 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.parsing.Operator; +import me.topchetoeu.jscript.compilation.parsing.ParseRes; import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Token; +import me.topchetoeu.jscript.compilation.parsing.Source; public class TryStatement extends Statement { public final Statement tryBody; @@ -61,44 +58,56 @@ public class TryStatement extends Statement { this.name = name; } - public static ParseRes parse(Filename filename, List tokens, int i) { - var loc = Parsing.getLoc(filename, tokens, i); - int n = 0; + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); - if (!Parsing.isIdentifier(tokens, i + n++, "try")) return ParseRes.failed(); + if (!Parsing.isIdentifier(src, i + n, "try")) return ParseRes.failed(); + n += 3; - var res = Parsing.parseStatement(filename, tokens, i + n); - if (!res.isSuccess()) return ParseRes.error(loc, "Expected an if body.", res); - n += res.n; + var tryBody = CompoundStatement.parse(src, i + n); + if (!tryBody.isSuccess()) return tryBody.chainError(src.loc(i + n), "Expected a try body"); + n += tryBody.n; + n += Parsing.skipEmpty(src, i + 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)) { + if (Parsing.isIdentifier(src, i + n, "catch")) { + n += 5; + n += Parsing.skipEmpty(src, i + n); + if (src.is(i + n, "(")) { n++; - var nameRes = Parsing.parseIdentifier(tokens, i + n++); - if (!nameRes.isSuccess()) return ParseRes.error(loc, "Expected a catch variable name."); + var nameRes = Parsing.parseIdentifier(src, i + n); + if (!nameRes.isSuccess()) return nameRes.chainError(src.loc(i + n), "xpected 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."); + n += nameRes.n; + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after catch variable name"); + n++; } - 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; + var bodyRes = CompoundStatement.parse(src, i + n); + if (!bodyRes.isSuccess()) return tryBody.chainError(src.loc(i + n), "Expected a catch body"); + n += bodyRes.n; + n += Parsing.skipEmpty(src, i + n); + + catchBody = bodyRes.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; + if (Parsing.isIdentifier(src, i + n, "finally")) { + n += 7; + + var bodyRes = CompoundStatement.parse(src, i + n); + if (!bodyRes.isSuccess()) return tryBody.chainError(src.loc(i + n), "Expected a finally body"); + n += bodyRes.n; + n += Parsing.skipEmpty(src, i + n); + finallyBody = bodyRes.result; } - return ParseRes.res(new TryStatement(loc, res.result, catchBody, finallyBody, name), n); + if (finallyBody == null && catchBody == null) ParseRes.error(src.loc(i + n), "Expected catch or finally"); + + return ParseRes.res(new TryStatement(loc, tryBody.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 ed9cc06..2aa1cdb 100644 --- a/src/java/me/topchetoeu/jscript/compilation/control/WhileStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/control/WhileStatement.java @@ -1,18 +1,14 @@ 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.ParseRes; import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Token; +import me.topchetoeu.jscript.compilation.parsing.Source; public class WhileStatement extends Statement { public final Statement condition, body; @@ -38,14 +34,20 @@ 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(); - + public static ParseRes parseLabel(Source src, int i) { + int n = Parsing.skipEmpty(src, i); + + var nameRes = Parsing.parseIdentifier(src, i + n); + if (!nameRes.isSuccess()) return nameRes.chainError(); + n += nameRes.n; + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i + n, ":")) return ParseRes.failed(); + n++; + return ParseRes.res(nameRes.result, n); } + public WhileStatement(Location loc, String label, Statement condition, Statement body) { super(loc); this.label = label; @@ -65,24 +67,31 @@ 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; + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); - var labelRes = WhileStatement.parseLabel(tokens, i + n); + var labelRes = WhileStatement.parseLabel(src, i + n); n += labelRes.n; + n += Parsing.skipEmpty(src, i + 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'."); + if (!Parsing.isIdentifier(src, i + n, "while")) return ParseRes.failed(); + n += 5; + n += Parsing.skipEmpty(src, i + n); - var condRes = Parsing.parseValue(filename, tokens, i + n, 0); - if (!condRes.isSuccess()) return ParseRes.error(loc, "Expected a while condition.", condRes); + if (!src.is(i + n, "(")) return ParseRes.error(src.loc(i + n), "Expected a open paren after 'while'."); + n++; + + var condRes = Parsing.parseValue(src, i + n, 0); + if (!condRes.isSuccess()) return condRes.chainError(src.loc(i + n), "Expected a while condition."); n += condRes.n; + n += Parsing.skipEmpty(src, i + n); - if (!Parsing.isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.error(loc, "Expected a closing paren after while condition."); + if (!src.is(i + n, ")")) return ParseRes.error(src.loc(i + n), "Expected a closing paren after while condition."); + n++; - var res = Parsing.parseStatement(filename, tokens, i + n); - if (!res.isSuccess()) return ParseRes.error(loc, "Expected a while body.", res); + var res = Parsing.parseStatement(src, i + n); + if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a while body."); 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/Operator.java b/src/java/me/topchetoeu/jscript/compilation/parsing/Operator.java index 3e06258..acfb9c9 100644 --- a/src/java/me/topchetoeu/jscript/compilation/parsing/Operator.java +++ b/src/java/me/topchetoeu/jscript/compilation/parsing/Operator.java @@ -1,113 +1,48 @@ package me.topchetoeu.jscript.compilation.parsing; -import java.util.HashMap; -import java.util.Map; +import java.util.Arrays; +import java.util.LinkedHashSet; import me.topchetoeu.jscript.common.Operation; public enum Operator { - MULTIPLY("*", Operation.MULTIPLY, 13), - DIVIDE("/", Operation.DIVIDE, 12), - MODULO("%", Operation.MODULO, 12), - SUBTRACT("-", Operation.SUBTRACT, 11), - ADD("+", Operation.ADD, 11), - SHIFT_RIGHT(">>", Operation.SHIFT_RIGHT, 10), - SHIFT_LEFT("<<", Operation.SHIFT_LEFT, 10), - USHIFT_RIGHT(">>>", Operation.USHIFT_RIGHT, 10), - GREATER(">", Operation.GREATER, 9), - LESS("<", Operation.LESS, 9), - GREATER_EQUALS(">=", Operation.GREATER_EQUALS, 9), - LESS_EQUALS("<=", Operation.LESS_EQUALS, 9), - NOT_EQUALS("!=", Operation.LOOSE_NOT_EQUALS, 8), - LOOSE_NOT_EQUALS("!==", Operation.NOT_EQUALS, 8), - EQUALS("==", Operation.LOOSE_EQUALS, 8), - LOOSE_EQUALS("===", Operation.EQUALS, 8), - AND("&", Operation.AND, 7), - XOR("^", Operation.XOR, 6), - OR("|", Operation.OR, 5), - LAZY_AND("&&", 4), - LAZY_OR("||", 3), - ASSIGN_SHIFT_LEFT("<<=", 2, true), - ASSIGN_SHIFT_RIGHT(">>=", 2, true), - ASSIGN_USHIFT_RIGHT(">>>=", 2, true), - ASSIGN_AND("&=", 2, true), - ASSIGN_OR("|=", 2, true), - ASSIGN_XOR("^=", 2, true), - ASSIGN_MODULO("%=", 2, true), - ASSIGN_DIVIDE("/=", 2, true), - ASSIGN_MULTIPLY("*=", 2, true), - ASSIGN_SUBTRACT("-=", 2, true), - ASSIGN_ADD("+=", 2, true), - ASSIGN("=", 2, true), - SEMICOLON(";"), - COLON(":"), - PAREN_OPEN("("), - PAREN_CLOSE(")"), - BRACKET_OPEN("["), - BRACKET_CLOSE("]"), - BRACE_OPEN("{"), - BRACE_CLOSE("}"), - DOT("."), - COMMA(","), - NOT("!"), - QUESTION("?"), - INVERSE("~"), - INCREASE("++"), - DECREASE("--"); + MULTIPLY("*", Operation.MULTIPLY, 13, true), + DIVIDE("/", Operation.DIVIDE, 12, true), + MODULO("%", Operation.MODULO, 12, true), + SUBTRACT("-", Operation.SUBTRACT, 11, true), + ADD("+", Operation.ADD, 11, true), + SHIFT_RIGHT(">>", Operation.SHIFT_RIGHT, 10, true), + SHIFT_LEFT("<<", Operation.SHIFT_LEFT, 10, true), + USHIFT_RIGHT(">>>", Operation.USHIFT_RIGHT, 10, true), + GREATER(">", Operation.GREATER, 9, false), + LESS("<", Operation.LESS, 9, false), + GREATER_EQUALS(">=", Operation.GREATER_EQUALS, 9, false), + LESS_EQUALS("<=", Operation.LESS_EQUALS, 9, false), + NOT_EQUALS("!=", Operation.LOOSE_NOT_EQUALS, 8, false), + LOOSE_NOT_EQUALS("!==", Operation.NOT_EQUALS, 8, false), + EQUALS("==", Operation.LOOSE_EQUALS, 8, false), + LOOSE_EQUALS("===", Operation.EQUALS, 8, false), + AND("&", Operation.AND, 7, true), + XOR("^", Operation.XOR, 6, true), + OR("|", Operation.OR, 5, true); public final String readable; public final Operation operation; public final int precedence; - public final boolean reverse; - private static final Map ops = new HashMap<>(); + public final boolean assignable; + + public static final LinkedHashSet opsByLength = new LinkedHashSet(); static { - for (var el : Operator.values()) { - ops.put(el.readable, el); - } + var vals = Operator.values(); + Arrays.sort(vals, (a, b) -> b.readable.length() - a.readable.length()); + for (var el : vals) opsByLength.add(el); } - public boolean isAssign() { return precedence == 2; } - - public static Operator parse(String val) { - return ops.get(val); - } - - private Operator() { - this.readable = null; - this.operation = null; - this.precedence = -1; - this.reverse = false; - } - private Operator(String value) { - this.readable = value; - this.operation = null; - this.precedence = -1; - this.reverse = false; - } - private Operator(String value, int precedence) { - this.readable = value; - this.operation = null; - this.precedence = precedence; - this.reverse = false; - } - private Operator(String value, int precedence, boolean reverse) { - this.readable = value; - this.operation = null; - this.precedence = precedence; - this.reverse = reverse; - } - - private Operator(String value, Operation funcName, int precedence) { + private Operator(String value, Operation funcName, int precedence, boolean assignable) { this.readable = value; this.operation = funcName; this.precedence = precedence; - this.reverse = false; - } - private Operator(String value, Operation funcName, int precedence, boolean reverse) { - this.readable = value; - this.operation = funcName; - this.precedence = precedence; - this.reverse = reverse; + this.assignable = assignable; } } \ No newline at end of file diff --git a/src/java/me/topchetoeu/jscript/common/ParseRes.java b/src/java/me/topchetoeu/jscript/compilation/parsing/ParseRes.java similarity index 53% rename from src/java/me/topchetoeu/jscript/common/ParseRes.java rename to src/java/me/topchetoeu/jscript/compilation/parsing/ParseRes.java index a373a5d..ae539aa 100644 --- a/src/java/me/topchetoeu/jscript/common/ParseRes.java +++ b/src/java/me/topchetoeu/jscript/compilation/parsing/ParseRes.java @@ -1,13 +1,12 @@ -package me.topchetoeu.jscript.common; +package me.topchetoeu.jscript.compilation.parsing; -import java.util.List; -import java.util.Map; - -import me.topchetoeu.jscript.compilation.parsing.TestRes; -import me.topchetoeu.jscript.compilation.parsing.Token; -import me.topchetoeu.jscript.compilation.parsing.Parsing.Parser; +import me.topchetoeu.jscript.common.Location; public class ParseRes { + public static interface Parser { + public ParseRes parse(Source src, int i); + } + public static enum State { SUCCESS, FAILED, @@ -34,11 +33,11 @@ public class ParseRes { if (!state.isSuccess()) return this; return new ParseRes<>(state, null, result, i); } - public ParseRes addN(int i) { + public ParseRes addN(int n) { if (!state.isSuccess()) return this; - return new ParseRes<>(state, null, result, this.n + i); + return new ParseRes<>(state, null, result, this.n + n); } - public ParseRes transform() { + public ParseRes chainError() { if (isSuccess()) throw new RuntimeException("Can't transform a ParseRes that hasn't failed."); return new ParseRes<>(state, error, null, 0); } @@ -59,41 +58,26 @@ public class ParseRes { if (loc != null) error = loc + ": " + error; return new ParseRes<>(State.ERROR, error, null, 0); } - public static ParseRes error(Location loc, String error, ParseRes other) { + public ParseRes chainError(Location loc, String error) { if (loc != null) error = loc + ": " + error; - if (!other.isError()) return new ParseRes<>(State.ERROR, error, null, 0); - return new ParseRes<>(State.ERROR, other.error, null, 0); + if (!this.isError()) return new ParseRes<>(State.ERROR, error, null, 0); + return new ParseRes<>(State.ERROR, this.error, null, 0); } public static ParseRes res(T val, int i) { return new ParseRes<>(State.SUCCESS, null, val, i); } @SafeVarargs - public static ParseRes any(ParseRes ...parsers) { - return any(List.of(parsers)); - } - public static ParseRes any(List> parsers) { - ParseRes best = null; - ParseRes error = ParseRes.failed(); + @SuppressWarnings("all") + // to hell with all of java's bullshit generics that do jack shit nothing + public static ParseRes first(Source src, int i, Parser ...parsers) { + int n = Parsing.skipEmpty(src, i); + ParseRes error = ParseRes.failed(); for (var parser : parsers) { - if (parser.isSuccess()) { - if (best == null || best.n < parser.n) best = parser; - } - else if (parser.isError() && error.isFailed()) error = parser.transform(); - } - - if (best != null) return best; - else return error; - } - @SafeVarargs - public static ParseRes first(String filename, List tokens, Map> named, Parser ...parsers) { - ParseRes error = ParseRes.failed(); - - for (var parser : parsers) { - var res = parser.parse(null, tokens, 0); - if (res.isSuccess()) return res; - else if (res.isError() && error.isFailed()) error = res.transform(); + var res = parser.parse(src, i + n); + if (res.isSuccess()) return res.addN(n); + if (res.isError() && error.isFailed()) error = res.chainError(); } return error; diff --git a/src/java/me/topchetoeu/jscript/compilation/parsing/Parsing.java b/src/java/me/topchetoeu/jscript/compilation/parsing/Parsing.java index 321ec1a..c5b377a 100644 --- a/src/java/me/topchetoeu/jscript/compilation/parsing/Parsing.java +++ b/src/java/me/topchetoeu/jscript/compilation/parsing/Parsing.java @@ -1,398 +1,49 @@ 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.List; +import java.util.Set; 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.*; import me.topchetoeu.jscript.compilation.control.*; import me.topchetoeu.jscript.compilation.scope.LocalScopeRecord; import me.topchetoeu.jscript.compilation.values.*; +import me.topchetoeu.jscript.compilation.values.constants.BoolStatement; +import me.topchetoeu.jscript.compilation.values.constants.NullStatement; +import me.topchetoeu.jscript.compilation.values.constants.NumberStatement; +import me.topchetoeu.jscript.compilation.values.constants.StringStatement; 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); - } - - public static class ObjProp { - public final String name; - public final String access; - public final FunctionStatement func; - - public ObjProp(String name, String access, FunctionStatement func) { - this.name = name; - this.access = access; - this.func = func; - } - } - public static final HashMap> functions = new HashMap<>(); - private static final HashSet reserved = new HashSet(); - static { - reserved.add("true"); - reserved.add("false"); - reserved.add("void"); - reserved.add("null"); - reserved.add("this"); - reserved.add("if"); - reserved.add("else"); - reserved.add("try"); - reserved.add("catch"); - reserved.add("finally"); - reserved.add("for"); - reserved.add("do"); - reserved.add("while"); - reserved.add("switch"); - reserved.add("case"); - reserved.add("default"); - reserved.add("new"); - reserved.add("function"); - reserved.add("var"); - reserved.add("return"); - reserved.add("throw"); - reserved.add("typeof"); - reserved.add("delete"); - reserved.add("break"); - reserved.add("continue"); - reserved.add("debugger"); - reserved.add("implements"); - reserved.add("interface"); - reserved.add("package"); - reserved.add("private"); - reserved.add("protected"); - reserved.add("public"); - reserved.add("static"); - // Although ES5 allow these, we will comply to ES6 here - reserved.add("const"); - reserved.add("let"); - // These are allowed too, however our parser considers them keywords - reserved.add("undefined"); - reserved.add("arguments"); - reserved.add("globalThis"); - reserved.add("window"); - reserved.add("self"); - // We allow yield and await, because they're part of the custom async and generator functions - } + private static final Set reserved = Set.of( + "true", "false", "void", "null", "this", "if", "else", "try", "catch", + "finally", "for", "do", "while", "switch", "case", "default", "new", + "function", "var", "return", "throw", "typeof", "delete", "break", + "continue", "debugger", "implements", "interface", "package", "private", + "protected", "public", "static", - public static boolean isDigit(char c) { - return c >= '0' && c <= '9'; - } - public static boolean isWhitespace(char c) { - return isAny(c, " \t\r\n"); - } - public static boolean isLetter(char c) { - return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); - } - public static boolean isAlphanumeric(char c) { - return isLetter(c) || isDigit(c); + // Although ES5 allow these, we will comply to ES6 here + "const", "let", + + // These are allowed too, however our parser considers them keywords + // We allow yield and await, because they're part of the custom async and generator functions + "undefined", "arguments", "globalThis", "window", "self" + ); + + public static boolean isDigit(Character c) { + return c != null && c >= '0' && c <= '9'; } public static boolean isAny(char c, String alphabet) { return alphabet.contains(Character.toString(c)); } - private static final int CURR_NONE = 0; - private static final int CURR_NUMBER = 1; - private static final int CURR_FLOAT = 11; - private static final int CURR_SCIENTIFIC_NOT = 12; - private static final int CURR_NEG_SCIENTIFIC_NOT = 13; - private static final int CURR_HEX = 14; - private static final int CURR_STRING = 2; - private static final int CURR_LITERAL = 3; - private static final int CURR_OPERATOR = 4; - private static final int CURR_REGEX = 6; - private static final int CURR_REGEX_FLAGS = 7; - private static final int CURR_MULTI_COMMENT = 8; - private static final int CURR_SINGLE_COMMENT = 9; - - private static void addToken(StringBuilder currToken, int currStage, int line, int lastStart, Filename filename, List tokens) { - var res = currToken.toString(); - - switch (currStage) { - case CURR_STRING: tokens.add(new RawToken(res, TokenType.STRING, line, lastStart)); break; - case CURR_REGEX_FLAGS: tokens.add(new RawToken(res, TokenType.REGEX, line, lastStart)); break; - case CURR_NUMBER: - case CURR_HEX: - case CURR_NEG_SCIENTIFIC_NOT: - case CURR_SCIENTIFIC_NOT: - case CURR_FLOAT: - tokens.add(new RawToken(res, TokenType.NUMBER, line, lastStart)); break; - case CURR_LITERAL: tokens.add(new RawToken(res, TokenType.LITERAL, line, lastStart)); break; - case CURR_OPERATOR: tokens.add(new RawToken(res, TokenType.OPERATOR, line, lastStart)); break; - } - - currToken.delete(0, currToken.length()); - } - - // 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); - - // Those are state variables, and will be reset every time a token has ended parsing - boolean lastEscape = false, inBrackets = false; - - int line = 1, start = 1, lastStart = 1, parenI = 0; - var loc = new Location(line, lastStart, filename); - int currStage = CURR_NONE; - - // when we want to continue parsing a token, we will execute continue;, which will skip - // the token end logic - loop: for (int i = 0; i < raw.length(); i++) { - char c = raw.charAt(i); - - start++; - - switch (currStage) { - case CURR_STRING: - currToken.append(c); - - if (!lastEscape) { - if (c == '\n') throw new SyntaxException(loc, "Can't have a multiline string."); - else if (c == '\\') { - lastEscape = true; - continue; - } - else if (c != currToken.charAt(0)) continue; - } - else { - lastEscape = false; - continue; - } - break; - case CURR_REGEX: - currToken.append(c); - if (!lastEscape) { - if (c == '\\') lastEscape = true; - if (c == '/' & parenI == 0 & !inBrackets) { - currStage = CURR_REGEX_FLAGS; - continue; - } - if (c == '[') inBrackets = true; - if (c == ']') inBrackets = false; - if (c == '(' && !inBrackets) parenI++; - if (c == ')' && !inBrackets) parenI--; - } - else lastEscape = false; - continue; - case CURR_REGEX_FLAGS: - if (isAny(c, "dgimsuy")) { - currToken.append(c); - continue; - } - i--; start--; - break; - case CURR_NUMBER: - if (c == '.') currStage = CURR_FLOAT; - else if (c == 'e' || c == 'E') currStage = CURR_SCIENTIFIC_NOT; - else if ((c == 'x' || c == 'X') && currToken.toString().equals("0")) currStage = CURR_HEX; - else if (!isDigit(c)) { - i--; start--; - break; - } - currToken.append(c); - continue; - case CURR_FLOAT: - if (c == 'e' || c == 'E') currStage = CURR_SCIENTIFIC_NOT; - else if (!isDigit(c)) { - i--; start--; - break; - } - currToken.append(c); - continue; - case CURR_SCIENTIFIC_NOT: - if (c == '-') { - if (currToken.toString().endsWith("e")) currStage = CURR_NEG_SCIENTIFIC_NOT; - } - if (currStage == CURR_SCIENTIFIC_NOT && !isDigit(c)) { - i--; start--; - break; - } - currToken.append(c); - continue; - case CURR_NEG_SCIENTIFIC_NOT: - if (isDigit(c)) currToken.append(c); - else { - i--; start--; - break; - } - continue; - case CURR_HEX: - if (isDigit(c) || isAny(c, "ABCDEFabcdef")) currToken.append(c); - else { - i--; start--; - break; - } - continue; - case CURR_SINGLE_COMMENT: - currToken.delete(0, currToken.length()); - if (c != '\n') continue; - else { - line++; - start = 1; - } - break; - case CURR_MULTI_COMMENT: - if (c == '\n') line++; - if (!(currToken.charAt(0) == '*' && c == '/')) { - currToken.delete(0, currToken.length()); - currToken.append(c); - continue; - } - break; - case CURR_LITERAL: - if (isAlphanumeric(c) || c == '_' || c == '$') { - currToken.append(c); - continue; - } - else { i--; start--; } - break; - case CURR_OPERATOR: { - // here we do several things: - // - detect a comment - // - detect a regular expression - // - detect a float number (.xxxx) - // - read an operator greedily - - // this variable keeps track of whether we're still reading an operator - boolean ok = false; - if (currToken.length() == 1) { - // double operators - if (currToken.charAt(0) == c && isAny(c, "&|=+-<>")) ok = true; - // assignments - else if (c == '=' && isAny(currToken.charAt(0), "&|^+-/*%!<>")) ok = true; - // detect float numbers - else if (isDigit(c) && currToken.charAt(0) == '.') { - currStage = CURR_FLOAT; - currToken.append(c); - continue; - } - else if (currToken.charAt(0) == '/') { - // single line comments - if (c == '/') { - currStage = CURR_SINGLE_COMMENT; - continue; - } - // multiline comments - else if (c == '*') { - currStage = CURR_MULTI_COMMENT; - continue; - } - // regular expressions - else { - // regular expressions must be in the start of a file, or be followed by a - // newline, or an operator - // this is because of expressions like 1 / 2 / 3 (/ 2 /) will get recognized as regex - // still, the closing paren must be ignored, because in an expression, we can't have a value, following a paren - var prevToken = tokens.size() == 0 ? null : tokens.get(tokens.size() - 1); - if (tokens.size() == 0 || ( - prevToken.line < line || - prevToken.type == TokenType.OPERATOR && !prevToken.value.equals(")") || - prevToken.value.equals("return") || - prevToken.value.equals("throe") - )) { - // we look for a second / on the same line - // if we don't find one, we determine the current operator - // to be a division - for (int j = i; j < raw.length(); j++) { - if (raw.charAt(j) == '/') { - i--; start--; - currStage = CURR_REGEX; - continue loop; - } - if (raw.charAt(j) == '\n') break; - } - } - } - } - } - if (currToken.length() == 2) { - var a = currToken.charAt(0); - var b = currToken.charAt(1); - if (( - a == '=' && b == '=' || - a == '!' && b == '=' || - a == '<' && b == '<' || - a == '>' && b == '>' || - a == '>' && b == '>' - ) && c == '=') ok = true; - if (a == '>' && b == '>' && c == '>') ok = true; - } - if ( - currToken.length() == 3 && - currToken.charAt(0) == '>' && - currToken.charAt(1) == '>' && - currToken.charAt(2) == '>' && - c == '=' - ) ok = true; - - if (ok) { - currToken.append(c); - continue; - } - else { i--; start--; } - break; - } - default: - // here we detect what type of token we're reading - if (isAny(c, " \t\n\r")) { - if (c == '\n') { - line++; - start = 1; - } - } - else if (isDigit(c)) { - currToken.append(c); - currStage = CURR_NUMBER; - continue; - } - else if (isAlphanumeric(c) || c == '_' || c == '$') { - currToken.append(c); - currStage = CURR_LITERAL; - continue; - } - else if (isAny(c, "+-/*%=!&|^(){}[];.,<>!:~?")) { - currToken.append(c); - currStage = CURR_OPERATOR; - continue; - } - else if (c == '"' || c == '\'') { - currToken.append(c); - currStage = CURR_STRING; - continue; - } - else throw new SyntaxException(new Location(line, start, filename), String.format("Unrecognized character %s.", c)); - } - - // if we got here, we know that we have encountered the end of a token - addToken(currToken, currStage, line, lastStart, filename, tokens); - lastEscape = inBrackets = false; - currStage = CURR_NONE; - lastStart = start; - } - - // here, we save a leftover token (if any) - switch (currStage) { - case CURR_STRING: throw new SyntaxException(new Location(line, start, filename), "Unterminated string literal."); - case CURR_REGEX: throw new SyntaxException(new Location(line, start, filename), "Incomplete regex."); - } - addToken(currToken, currStage, line, lastStart, filename, tokens); - - return tokens; - } - public static int fromHex(char c) { if (c >= 'A' && c <= 'F') return c - 'A' + 10; if (c >= 'a' && c <= 'f') return c - 'a' + 10; @@ -400,373 +51,256 @@ public class Parsing { return -1; } - public static boolean inBounds(List tokens, int i) { - return i >= 0 && i < tokens.size(); + public static int skipEmpty(Source src, int i) { + int n = 0; + + while (n < src.size() && src.is(i + n, Character::isWhitespace)) n++; + + return n; } - private static String parseString(Location loc, String literal) { + public static ParseRes parseChar(Source src, int i) { + int n = 0; + + if (src.is(i + n, '\\')) { + n++; + char c = src.at(i + n++); + + if (c == 'b') return ParseRes.res('\b', n); + else if (c == 't') return ParseRes.res('\t', n); + else if (c == 'n') return ParseRes.res('\n', n); + else if (c == 'f') return ParseRes.res('\f', n); + else if (c == 'r') return ParseRes.res('\r', n); + else if (c == '0') { + if (src.is(i + n, Parsing::isDigit)) return ParseRes.error(src.loc(i), "Octal escape sequences are not allowed"); + else return ParseRes.res('\0', n); + } + else if (c >= '1' && c <= '9') return ParseRes.error(src.loc(i), "Octal escape sequences are not allowed"); + else if (c == 'x') { + var newC = 0; + + for (var j = 0; j < 2; j++) { + if (i + n >= src.size()) return ParseRes.error(src.loc(i), "Invalid hexadecimal escape sequence."); + + int val = fromHex(src.at(i + n)); + if (val == -1) throw new SyntaxException(src.loc(i + n), "Invalid hexadecimal escape sequence."); + n++; + + newC = (newC << 4) | val; + } + + return ParseRes.res((char)newC, n); + } + else if (c == 'u') { + var newC = 0; + + for (var j = 0; j < 4; j++) { + if (i + n >= src.size()) return ParseRes.error(src.loc(i), "Invalid Unicode escape sequence"); + + int val = fromHex(src.at(i + n)); + if (val == -1) throw new SyntaxException(src.loc(i + n), "Invalid Unicode escape sequence"); + n++; + + newC = (newC << 4) | val; + } + + return ParseRes.res((char)newC, n); + } + else if (c == '\n') return ParseRes.res(null, n); + } + + return ParseRes.res(src.at(i + n), n + 1); + } + + public static ParseRes parseIdentifier(Source src, int i) { + var n = skipEmpty(src, i); var res = new StringBuilder(); + var first = false; - for (var i = 1; i < literal.length() - 1; i++) { - if (literal.charAt(i) == '\\') { - char c = literal.charAt(++i); - if (c == 'b') res.append('\b'); - else if (c == 't') res.append('\t'); - else if (c == 'n') res.append('\n'); - else if (c == 'f') res.append('\f'); - else if (c == 'r') res.append('\r'); - else if (c == '0') { - if (i + 1 >= literal.length()) res.append((char)0); - c = literal.charAt(i + 1); - if (c >= '0' && c <= '9') throw new SyntaxException(loc.add(i), "Octal escape sequences not allowed."); - res.append((char)0); - } - else if (c >= '1' && c <= '9') { - throw new SyntaxException(loc.add(i), "Octal escape sequences not allowed."); - } - else if (c == 'x') { - var newC = 0; - i++; - for (var j = 0; j < 2; j++) { - if (i >= literal.length()) throw new SyntaxException(loc.add(i), "Incomplete unicode escape sequence."); - int val = fromHex(literal.charAt(i++)); - if (val == -1) throw new SyntaxException(loc.add(i + 1), "Invalid character in unicode escape sequence."); - newC = (newC << 4) | val; - } - i--; + while (true) { + if (i + n > src.size()) break; + char c = src.at(i + n, '\0'); - res.append((char)newC); - } - else if (c == 'u') { - var newC = 0; - i++; - for (var j = 0; j < 4; j++) { - if (i >= literal.length()) throw new SyntaxException(loc.add(i), "Incomplete unicode escape sequence."); - int val = fromHex(literal.charAt(i++)); - if (val == -1) throw new SyntaxException(loc.add(i + 1), "Invalid character in unicode escape sequence."); - newC = (newC << 4) | val; - } - i--; - - res.append((char)newC); - } - else res.append(c); - } - else res.append(literal.charAt(i)); + if (first && !Character.isLetterOrDigit(c) && c != '_' && c != '$') break; + if (!first && !Character.isLetter(c) && c != '_' && c != '$') break; + res.append(c); + n++; } - return res.toString(); + if (res.length() <= 0) return ParseRes.failed(); + else return ParseRes.res(res.toString(), n); } - private static String parseRegex(Location loc, String literal) { + public static ParseRes parseIdentifier(Source src, int i, String test) { + var n = skipEmpty(src, i); var res = new StringBuilder(); + var first = true; - int end = literal.lastIndexOf('/'); + while (true) { + if (i + n > src.size()) break; + char c = src.at(i + n, '\0'); - for (var i = 1; i < end; i++) { - if (literal.charAt(i) == '\\') { - char c = literal.charAt(++i); - if (c == 'b') res.append('\b'); - else if (c == 't') res.append('\t'); - else if (c == 'n') res.append('\n'); - else if (c == 'f') res.append('\f'); - else if (c == 'r') res.append('\r'); - else if (c == '0') { - if (i + 1 >= literal.length()) res.append((char)0); - c = literal.charAt(i + 1); - if (c >= '0' && c <= '9') throw new SyntaxException(loc.add(i), "Octal escape sequences not allowed."); - res.append((char)0); - } - else if (c >= '1' && c <= '9') { - res.append((char)(c - '0')); - i++; - } - else if (c == 'x') { - var newC = 0; - i++; - for (var j = 0; j < 2; j++) { - if (i >= literal.length()) throw new SyntaxException(loc.add(i), "Incomplete unicode escape sequence."); - int val = fromHex(literal.charAt(i++)); - if (val == -1) throw new SyntaxException(loc.add(i + 1), "Invalid character in unicode escape sequence."); - newC = (newC << 4) | val; - } - i--; - - res.append((char)newC); - } - else if (c == 'u') { - var newC = 0; - i++; - for (var j = 0; j < 4; j++) { - if (i >= literal.length()) throw new SyntaxException(loc.add(i), "Incomplete unicode escape sequence."); - int val = fromHex(literal.charAt(i++)); - if (val == -1) throw new SyntaxException(loc.add(i + 1), "Invalid character in unicode escape sequence."); - newC = (newC << 4) | val; - } - i--; - - res.append((char)newC); - } - else res.append("\\" + c); - } - else res.append(literal.charAt(i)); + if (first && !Character.isLetterOrDigit(c) && c != '_' && c != '$') break; + if (!first && !Character.isLetter(c) && c != '_' && c != '$') break; + first = false; + res.append(c); + n++; } - return '/' + res.toString() + literal.substring(end); - } - - private static double parseHex(String literal) { - double res = 0; - - for (int i = 2; i < literal.length(); i++) { - res *= 16; - int dig = fromHex(literal.charAt(i)); - res += dig; - } - - return res; - } - - public static Double parseNumber(boolean octals, String value) { - if (value.startsWith("0x") || value.startsWith("0X")) { - if (value.length() == 2) return null; - return parseHex(value); - } - if (value.endsWith("e") || value.endsWith("E") || value.endsWith("-")) return null; - - int i = 0; - double res = 0, dotDivisor = 1; - boolean e = false, dot = false; - int exponent = 0; - - for (; i < value.length(); i++) { - char c = value.charAt(i); - if (c == '.') { dot = true; break; } - if (c == 'e') { e = true; break; } - if (!isDigit(c)) break; - - res = res * 10 + c - '0'; - } - - if (dot) for (i++; i < value.length(); i++) { - char c = value.charAt(i); - if (c == 'e') { e = true; break; } - if (!isDigit(c)) break; - - res += (c - '0') / (dotDivisor *= 10); - } - - if (e) for (i++; i < value.length(); i++) { - char c = value.charAt(i); - if (!isDigit(c)) break; - exponent = 10 * exponent + c - '0'; - } - - if (exponent < 0) for (int j = 0; j < -exponent; j++) res /= 10; - else for (int j = 0; j < exponent; j++) res *= 10; - - return res; - } - private static double parseNumber(Location loc, String value) { - var res = parseNumber(false, value); - if (res == null) - throw new SyntaxException(loc, "Invalid number format."); - else return res; - } - - private static List parseTokens(Filename filename, Collection tokens) { - var res = new ArrayList(); - - for (var el : tokens) { - var loc = new Location(el.line, el.start, filename); - switch (el.type) { - case LITERAL: res.add(Token.identifier(el.line, el.start, el.value)); break; - case NUMBER: res.add(Token.number(el.line, el.start, parseNumber(loc, el.value), el.value)); break; - case STRING: res.add(Token.string(el.line, el.start, parseString(loc, el.value), el.value)); break; - case REGEX: res.add(Token.regex(el.line, el.start, parseRegex(loc, el.value), el.value)); break; - case OPERATOR: - Operator op = Operator.parse(el.value); - if (op == null) throw new SyntaxException(loc, String.format("Unrecognized operator '%s'.", el.value)); - res.add(Token.operator(el.line, el.start, op)); - break; - } - } - - return res; - } - - public static List tokenize(Filename filename, String raw) { - return parseTokens(filename, splitTokens(filename, raw)); - } - - public static Location getLoc(Filename filename, List tokens, int i) { - if (tokens.size() == 0 || tokens.size() == 0) return new Location(1, 1, filename); - if (i >= tokens.size()) i = tokens.size() - 1; - return new Location(tokens.get(i).line, tokens.get(i).start, filename); - } - public static int getLines(List tokens) { - if (tokens.size() == 0) return 1; - return tokens.get(tokens.size() - 1).line; - } - - public static ParseRes parseIdentifier(List tokens, int i) { - if (inBounds(tokens, i)) { - if (tokens.get(i).isIdentifier()) { - return ParseRes.res(tokens.get(i).identifier(), 1); - } - else return ParseRes.failed(); - } + if (res.length() <= 0) return ParseRes.failed(); + else if (test == null || res.toString().equals(test)) return ParseRes.res(res.toString(), n); else return ParseRes.failed(); } - public static ParseRes parseOperator(List tokens, int i) { - if (inBounds(tokens, i)) { - if (tokens.get(i).isOperator()) { - return ParseRes.res(tokens.get(i).operator(), 1); - } - else return ParseRes.failed(); - } + public static boolean isIdentifier(Source src, int i, String test) { + return parseIdentifier(src, i, test).isSuccess(); + } + + public static ParseRes parseOperator(Source src, int i, String op) { + var n = skipEmpty(src, i); + + if (src.is(i + n, op)) return ParseRes.res(op, n + op.length()); else return ParseRes.failed(); } - public static boolean isIdentifier(List tokens, int i, String lit) { - if (inBounds(tokens, i)) { - if (tokens.get(i).isIdentifier(lit)) { - return true; - } - else return false; + public static ParseRes parseStatementEnd(Source src, int i) { + var n = skipEmpty(src, i); + if (i >= src.size()) return ParseRes.res(true, n + 1); + + for (var j = i; j < i + n; j++) { + if (src.is(j, '\n')) return ParseRes.res(true, n); } - else return false; - } - public static boolean isOperator(List tokens, int i, Operator op) { - if (inBounds(tokens, i)) { - if (tokens.get(i).isOperator(op)) { - return true; - } - else return false; - } - else return false; - } - public static boolean isStatementEnd(List tokens, int i) { - if (isOperator(tokens, i, Operator.SEMICOLON)) return true; - if (isOperator(tokens, i, Operator.BRACE_CLOSE)) return true; - if (i < 0) return false; - if (i >= tokens.size()) return true; - return getLoc(null, tokens, i).line() > getLoc(null, tokens, i - 1).line(); + + if (src.is(i + n, ';')) return ParseRes.res(true, n + 1); + if (src.is(i + n, '}')) return ParseRes.res(true, n); + + return ParseRes.failed(); } public static boolean checkVarName(String name) { return !reserved.contains(name); } - public static ParseRes> parseParamList(Filename filename, List tokens, int i) { - var loc = getLoc(filename, tokens, i); - int n = 0; + public static ParseRes> parseParamList(Source src, int i) { + var n = skipEmpty(src, i); - if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.error(loc, "Expected a parameter list."); + var openParen = parseOperator(src, i + n, "("); + if (!openParen.isSuccess()) return openParen.chainError(src.loc(i + n), "Expected a parameter list."); + n += openParen.n; var args = new ArrayList(); - if (isOperator(tokens, i + n, Operator.PAREN_CLOSE)) { - n++; - } - else { + var closeParen = parseOperator(src, i + n, ")"); + n += closeParen.n; + + if (!closeParen.isSuccess()) { while (true) { - var argRes = parseIdentifier(tokens, i + n); + var argRes = parseIdentifier(src, i + n); if (argRes.isSuccess()) { args.add(argRes.result); n++; - if (isOperator(tokens, i + n, Operator.COMMA)) { + + n += skipEmpty(src, i); + + if (src.is(i + n, ",")) { n++; + n += skipEmpty(src, i + n); } - if (isOperator(tokens, i + n, Operator.PAREN_CLOSE)) { + if (src.is(i + n, ")")) { n++; break; } } - else return ParseRes.error(loc, "Expected an argument, comma or a closing brace."); + else return ParseRes.error(src.loc(i + n), "Expected an argument, or a closing brace."); } } return ParseRes.res(args, n); } - public static ParseRes parseParens(Filename filename, List tokens, int i) { + public static ParseRes parseParens(Source src, int i) { int n = 0; - if (!isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.failed(); - var res = parseValue(filename, tokens, i + n, 0); - if (!res.isSuccess()) return res; + var openParen = parseOperator(src, i + n, "("); + if (!openParen.isSuccess()) return openParen.chainError(); + n += openParen.n; + + var res = parseValue(src, i + n, 0); + if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected an expression in parens"); n += res.n; - if (!isOperator(tokens, i + n++, Operator.PAREN_CLOSE)) return ParseRes.failed(); + var closeParen = parseOperator(src, i + n, ")"); + if (!closeParen.isSuccess()) return closeParen.chainError(src.loc(i + n), "Expected a closing paren"); + n += closeParen.n; return ParseRes.res(res.result, n); } - public static ParseRes parseSimple(Filename filename, List tokens, int i, boolean statement) { - var res = new ArrayList>(); - - if (!statement) { - res.add(ObjectStatement.parse(filename, tokens, i)); - res.add(FunctionStatement.parseFunction(filename, tokens, i, false)); - } - - res.addAll(List.of( - VariableStatement.parseVariable(filename, tokens, i), - parseLiteral(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), - 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 parseSimple(Source src, int i, boolean statement) { + return ParseRes.first(src, i, + (a, b) -> statement ? ParseRes.failed() : ObjectStatement.parse(a, b), + (a, b) -> statement ? ParseRes.failed() : FunctionStatement.parseFunction(a, b, false), + VariableStatement::parse, + Parsing::parseLiteral, + StringStatement::parse, + RegexStatement::parse, + NumberStatement::parse, + ChangeStatement::parsePrefixDecrease, + ChangeStatement::parsePrefixIncrease, + OperationStatement::parsePrefix, + ArrayStatement::parse, + Parsing::parseParens, + CallStatement::parseNew, + TypeofStatement::parse, + DiscardStatement::parse, + DeleteStatement::parse + ); } - 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(); + public static ParseRes parseLiteral(Source src, int i) { + var n = skipEmpty(src, i); + var loc = src.loc(i + n); - 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); + var id = parseIdentifier(src, i); + if (!id.isSuccess()) return id.chainError(); + n += id.n; + + if (id.result.equals("true")) return ParseRes.res(new BoolStatement(loc, true), n); + if (id.result.equals("false")) return ParseRes.res(new BoolStatement(loc, false), n); + if (id.result.equals("undefined")) return ParseRes.res(new DiscardStatement(loc, null), n); + if (id.result.equals("null")) return ParseRes.res(new NullStatement(loc), n); + if (id.result.equals("this")) return ParseRes.res(new VariableIndexStatement(loc, 0), n); + if (id.result.equals("arguments")) return ParseRes.res(new VariableIndexStatement(loc, 1), n); + if (id.result.equals("globalThis")) return ParseRes.res(new GlobalThisStatement(loc), n); return ParseRes.failed(); } - public static ParseRes parseValue(Filename filename, List tokens, int i, int precedence, boolean statement) { + public static ParseRes parseValue(Source src, int i, int precedence, boolean statement) { + var n = skipEmpty(src, i); Statement prev = null; - int n = 0; while (true) { if (prev == null) { - var res = parseSimple(filename, tokens, i + n, statement); + var res = parseSimple(src, i + n, statement); if (res.isSuccess()) { n += res.n; prev = res.result; } - else if (res.isError()) return res.transform(); + else if (res.isError()) return res.chainError(); else break; } else { - var res = ParseRes.any( - 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) + var _prev = prev; + ParseRes res = ParseRes.first(src, i + n, + (s, j) -> OperationStatement.parseInstanceof(s, j, _prev, precedence), + (s, j) -> OperationStatement.parseIn(s, j, _prev, precedence), + (s, j) -> LazyOrStatement.parse(s, j, _prev, precedence), + (s, j) -> LazyAndStatement.parse(s, j, _prev, precedence), + (s, j) -> ChangeStatement.parsePostfixIncrease(s, j, _prev, precedence), + (s, j) -> ChangeStatement.parsePostfixDecrease(s, j, _prev, precedence), + (s, j) -> AssignableStatement.parse(s, j, _prev, precedence), + (s, j) -> OperationStatement.parseOperator(s, j, _prev, precedence), + (s, j) -> IfStatement.parseTernary(s, j, _prev, precedence), + (s, j) -> IndexStatement.parseMember(s, j, _prev, precedence), + (s, j) -> IndexStatement.parseIndex(s, j, _prev, precedence), + (s, j) -> CallStatement.parseCall(s, j, _prev, precedence), + (s, j) -> CompoundStatement.parseComma(s, j, _prev, precedence) ); if (res.isSuccess()) { @@ -774,7 +308,7 @@ public class Parsing { prev = res.result; continue; } - else if (res.isError()) return res.transform(); + else if (res.isError()) return res.chainError(); break; } @@ -783,61 +317,59 @@ public class Parsing { if (prev == null) return ParseRes.failed(); else return ParseRes.res(prev, n); } - public static ParseRes parseValue(Filename filename, List tokens, int i, int precedence) { - return parseValue(filename, tokens, i, precedence, false); + public static ParseRes parseValue(Source src, int i, int precedence) { + return parseValue(src, i, precedence, false); } - public static ParseRes parseValueStatement(Filename filename, List tokens, int i) { - var valRes = parseValue(filename, tokens, i, 0, true); - if (!valRes.isSuccess()) return valRes.transform(); + public static ParseRes parseValueStatement(Source src, int i) { + var res = parseValue(src, i, 0, true); + if (!res.isSuccess()) return res.chainError(); - var res = ParseRes.res(valRes.result, valRes.n); + var end = parseStatementEnd(src, i + res.n); + if (!end.isSuccess()) return ParseRes.error(src.loc(i + res.n), "Expected an end of statement"); - if (isStatementEnd(tokens, i + res.n)) { - if (isOperator(tokens, i + res.n, Operator.SEMICOLON)) return res.addN(1); - else return res; - } - else if (isIdentifier(tokens, i, "const") || isIdentifier(tokens, i, "let")) { - return ParseRes.error(getLoc(filename, tokens, i), "Detected the usage of 'const'/'let'. Please, use 'var' instead."); - } - else return ParseRes.error(getLoc(filename, tokens, i), "Expected an end of statement.", res); + return res.addN(end.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( - 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) + public static ParseRes parseStatement(Source src, int i) { + var n = skipEmpty(src, i); + + if (src.is(i + n, ";")) return ParseRes.res(new DiscardStatement(src.loc(i+ n), null), n + 1); + if (isIdentifier(src, i + n, "with")) return ParseRes.error(src.loc(i + n), "'with' statements are not allowed."); + + ParseRes res = ParseRes.first(src, i + n, + VariableDeclareStatement::parse, + ReturnStatement::parse, + ThrowStatement::parse, + ContinueStatement::parse, + BreakStatement::parse, + DebugStatement::parse, + IfStatement::parse, + WhileStatement::parse, + SwitchStatement::parse, + ForStatement::parse, + ForInStatement::parse, + ForOfStatement::parse, + DoWhileStatement::parse, + TryStatement::parse, + CompoundStatement::parse, + (s, j) -> FunctionStatement.parseFunction(s, j, true), + Parsing::parseValueStatement ); + return res.addN(n); } public static Statement[] parse(Filename filename, String raw) { - var tokens = tokenize(filename, raw); + var src = new Source(filename, raw); var list = new ArrayList(); int i = 0; while (true) { - if (i >= tokens.size()) break; + if (i >= src.size()) break; - var res = Parsing.parseStatement(filename, tokens, i); + var res = Parsing.parseStatement(src, i); - if (res.isError()) throw new SyntaxException(getLoc(filename, tokens, i), res.error); - else if (res.isFailed()) throw new SyntaxException(getLoc(filename, tokens, i), "Unexpected syntax."); + if (res.isError()) throw new SyntaxException(src.loc(i), res.error); + else if (res.isFailed()) throw new SyntaxException(src.loc(i), "Unexpected syntax"); i += res.n; @@ -874,4 +406,134 @@ public class Parsing { public static CompileResult compile(Filename filename, String raw) { return compile(parse(filename, raw)); } + + private static ParseRes parseHex(Source src, int i) { + int n = 0; + double res = 0; + + while (true) { + int digit = Parsing.fromHex(src.at(i + n, '\0')); + if (digit < 0) { + if (n <= 0) return ParseRes.failed(); + else return ParseRes.res(res, n); + } + n++; + + res *= 16; + res += digit; + } + } + private static ParseRes parseOct(Source src, int i) { + int n = 0; + double res = 0; + + while (true) { + int digit = src.at(i + n, '\0') - '0'; + if (digit < 0 || digit > 9) break; + if (digit > 7) return ParseRes.error(src.loc(i + n), "Digits in octal literals must be from 0 to 7, encountered " + digit); + + if (digit < 0) { + if (n <= 0) return ParseRes.failed(); + else return ParseRes.res(res, n); + } + n++; + + res *= 8; + res += digit; + } + + return ParseRes.res(res, n); + } + + public static ParseRes parseString(Source src, int i) { + var n = skipEmpty(src, i); + + char quote; + + if (src.is(i + n, '\'')) quote = '\''; + else if (src.is(i + n, '"')) quote = '"'; + else return ParseRes.failed(); + n++; + + var res = new StringBuilder(); + + while (true) { + if (i + n >= src.size()) return ParseRes.error(src.loc(i + n), "Unterminated string literal"); + if (src.is(i + n, quote)) { + n++; + return ParseRes.res(res.toString(), n); + } + + var charRes = parseChar(src, i + n); + if (!charRes.isSuccess()) return charRes.chainError(src.loc(i + n), "Invalid character"); + n += charRes.n; + + if (charRes.result != null) res.append(charRes.result); + } + } + public static ParseRes parseNumber(Source src, int i) { + var n = skipEmpty(src, i); + + double whole = 0; + double fract = 0; + long exponent = 0; + boolean parsedAny = false; + + if (src.is(i + n, "0x") || src.is(i + n, "0X")) { + n += 2; + + var res = parseHex(src, i); + if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Incomplete hexadecimal literal"); + else return res.addN(2); + } + else if (src.is(i + n, "0o") || src.is(i + n, "0O")) { + n += 2; + + var res = parseOct(src, i); + if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Incomplete octal literal"); + else return res.addN(2); + } + else if (src.is(i + n, '0')) { + n++; + parsedAny = true; + if (src.is(i + n, Parsing::isDigit)) return ParseRes.error(src.loc(i + n), "Decimals with leading zeroes are not allowed"); + } + + while (src.is(i + n, Parsing::isDigit)) { + parsedAny = true; + whole *= 10; + whole += src.at(i + n++) - '0'; + } + + if (src.is(i + n, '.')) { + parsedAny = true; + n++; + + while (src.is(i + n, Parsing::isDigit)) { + fract += src.at(i + n++) - '0'; + fract /= 10; + } + } + + if (src.is(i + n, 'e') || src.is(i + n, 'E')) { + n++; + parsedAny = true; + boolean negative = src.is(i + n, '-'); + boolean parsedE = false; + if (negative) n++; + + while (src.is(i + n, Parsing::isDigit)) { + parsedE = true; + exponent *= 10; + + if (negative) exponent -= src.at(i + n++) - '0'; + else exponent += src.at(i + n++) - '0'; + } + + if (!parsedE) return ParseRes.error(src.loc(i + n), "Incomplete number exponent"); + } + + if (!parsedAny) return ParseRes.failed(); + else return ParseRes.res((whole + fract) * NumberStatement.power(10, exponent), n); + } } diff --git a/src/java/me/topchetoeu/jscript/compilation/parsing/Source.java b/src/java/me/topchetoeu/jscript/compilation/parsing/Source.java new file mode 100644 index 0000000..bcfc56e --- /dev/null +++ b/src/java/me/topchetoeu/jscript/compilation/parsing/Source.java @@ -0,0 +1,69 @@ +package me.topchetoeu.jscript.compilation.parsing; + +import java.util.function.Predicate; + +import me.topchetoeu.jscript.common.Filename; +import me.topchetoeu.jscript.common.Location; + +public class Source { + public final Filename filename; + public final String src; + + private int[] lineStarts; + + public Location loc(int offset) { + return new SourceLocation(filename, lineStarts, offset); + } + public boolean is(int i, char c) { + return i >= 0 && i < src.length() && src.charAt(i) == c; + } + public boolean is(int i, String src) { + if (i < 0 || i + src.length() > size()) return false; + + for (int j = 0; j < src.length(); j++) { + if (at(i + j) != src.charAt(j)) return false; + } + + return true; + } + public boolean is(int i, Predicate predicate) { + if (i < 0 || i >= src.length()) return false; + return predicate.test(at(i)); + } + public char at(int i) { + return src.charAt(i); + } + public char at(int i, char defaultVal) { + if (i < 0 || i >= src.length()) return defaultVal; + else return src.charAt(i); + } + public int size() { + return src.length(); + } + public String slice(int start, int end) { + return src.substring(start, end); + } + + public Source(Filename filename, String src) { + this.filename = filename; + this.src = src; + + int n = 1; + lineStarts = new int[16]; + lineStarts[0] = 0; + + for (int i = src.indexOf("\n"); i > 0; i = src.indexOf("\n", i + 1)) { + if (n >= lineStarts.length) { + var newArr = new int[lineStarts.length * 2]; + System.arraycopy(lineStarts, 0, newArr, 0, n); + lineStarts = newArr; + } + + lineStarts[n++] = i + 1; + } + + var newArr = new int[n]; + System.arraycopy(lineStarts, 0, newArr, 0, n); + lineStarts = newArr; + } +} diff --git a/src/java/me/topchetoeu/jscript/compilation/parsing/SourceLocation.java b/src/java/me/topchetoeu/jscript/compilation/parsing/SourceLocation.java new file mode 100644 index 0000000..8eafffa --- /dev/null +++ b/src/java/me/topchetoeu/jscript/compilation/parsing/SourceLocation.java @@ -0,0 +1,55 @@ +package me.topchetoeu.jscript.compilation.parsing; + +import me.topchetoeu.jscript.common.Filename; +import me.topchetoeu.jscript.common.Location; + +public class SourceLocation extends Location { + private int[] lineStarts; + private int line; + private int start; + private final Filename filename; + private final int offset; + + private void update() { + if (lineStarts == null) return; + + int start = 0; + int end = lineStarts.length - 1; + + while (true) { + if (start + 1 >= end) break; + var mid = -((-start - end) >> 1); + var el = lineStarts[mid]; + + if (el < offset) start = mid; + else if (el > offset) end = mid; + else { + this.line = mid; + this.start = 0; + this.lineStarts = null; + return; + } + } + + this.line = start; + this.start = offset - lineStarts[start]; + this.lineStarts = null; + return; + } + + @Override public Filename filename() { return filename; } + @Override public int line() { + update(); + return line; + } + @Override public int start() { + update(); + return start; + } + + public SourceLocation(Filename filename, int[] lineStarts, int offset) { + this.filename = filename; + this.lineStarts = lineStarts; + this.offset = offset; + } +} diff --git a/src/java/me/topchetoeu/jscript/compilation/parsing/TestRes.java b/src/java/me/topchetoeu/jscript/compilation/parsing/TestRes.java index ec234df..c399a9e 100644 --- a/src/java/me/topchetoeu/jscript/compilation/parsing/TestRes.java +++ b/src/java/me/topchetoeu/jscript/compilation/parsing/TestRes.java @@ -1,8 +1,7 @@ package me.topchetoeu.jscript.compilation.parsing; import me.topchetoeu.jscript.common.Location; -import me.topchetoeu.jscript.common.ParseRes; -import me.topchetoeu.jscript.common.ParseRes.State; +import me.topchetoeu.jscript.compilation.parsing.ParseRes.State; public class TestRes { public final State state; diff --git a/src/java/me/topchetoeu/jscript/compilation/values/ArrayStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/ArrayStatement.java index 8ffbf4d..9d60587 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/ArrayStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/ArrayStatement.java @@ -1,17 +1,14 @@ 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.ParseRes; import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Token; +import me.topchetoeu.jscript.compilation.parsing.Source; public class ArrayStatement extends Statement { public final Statement[] statements; @@ -46,36 +43,42 @@ public class ArrayStatement extends Statement { 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(); + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!src.is(i + n, "[")) return ParseRes.failed(); + n++; var values = new ArrayList(); loop: while (true) { - if (Parsing.isOperator(tokens, i + n, Operator.BRACKET_CLOSE)) { + n += Parsing.skipEmpty(src, i + n); + if (src.is(i + n, "]")) { n++; break; } - while (Parsing.isOperator(tokens, i + n, Operator.COMMA)) { + while (src.is(i + n, ",")) { n++; + n += Parsing.skipEmpty(src, i + n); values.add(null); - if (Parsing.isOperator(tokens, i + n, Operator.BRACKET_CLOSE)) { + + if (src.is(i + n, "]")) { 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; + var res = Parsing.parseValue(src, i + n, 2); + if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected an array element."); + n += res.n; + n += Parsing.skipEmpty(src, i + n); values.add(res.result); - if (Parsing.isOperator(tokens, i + n, Operator.COMMA)) n++; - else if (Parsing.isOperator(tokens, i + n, Operator.BRACKET_CLOSE)) { + if (src.is(i + n, ",")) n++; + else if (src.is(i + n, "]")) { n++; break; } diff --git a/src/java/me/topchetoeu/jscript/compilation/values/CallStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/CallStatement.java index e8a82d6..4cb52ed 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/CallStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/CallStatement.java @@ -1,18 +1,15 @@ 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.ParseRes; import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Token; +import me.topchetoeu.jscript.compilation.parsing.Source; public class CallStatement extends Statement { public final Statement func; @@ -47,51 +44,58 @@ public class CallStatement extends Statement { 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; - + public static ParseRes parseCall(Source src, int i, Statement prev, int precedence) { if (precedence > 17) return ParseRes.failed(); - if (!Parsing.isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.failed(); + + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!src.is(i + n, "(")) return ParseRes.failed(); + n++; var args = new ArrayList(); boolean prevArg = false; while (true) { - var argRes = Parsing.parseValue(filename, tokens, i + n, 2); + var argRes = Parsing.parseValue(src, i + n, 2); + n += argRes.n; + n += Parsing.skipEmpty(src, i + n); + 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)) { + else if (argRes.isError()) return argRes.chainError(); + else if (prevArg && src.is(i + n, ",")) { prevArg = false; n++; } - else if (Parsing.isOperator(tokens, i + n, Operator.PAREN_CLOSE)) { + else if (src.is(i + n, ")")) { 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."); + else if (prevArg) return ParseRes.error(src.loc(i + n), "Expected a comma or a closing paren"); + else return ParseRes.error(src.loc(i + n), "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(); + public static ParseRes parseNew(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); - var valRes = Parsing.parseValue(filename, tokens, i + n, 18); + if (!Parsing.isIdentifier(src, i + n, "new")) return ParseRes.failed(); + n += 3; + + var valRes = Parsing.parseValue(src, i + n, 18); + if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'new' keyword."); 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); + var callRes = CallStatement.parseCall(src, i + n, valRes.result, 0); + if (callRes.isFailed()) return ParseRes.res(new CallStatement(loc, true, valRes.result), n); + if (callRes.isError()) return callRes.chainError(); + n += callRes.n; + + return ParseRes.res(new CallStatement(loc, true, callRes.result.func, callRes.result.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 0531b4d..e8cd584 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/ChangeStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/ChangeStatement.java @@ -1,18 +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.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.ParseRes; import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Token; +import me.topchetoeu.jscript.compilation.parsing.Source; +import me.topchetoeu.jscript.compilation.values.constants.NumberStatement; public class ChangeStatement extends Statement { public final AssignableStatement value; @@ -20,7 +17,7 @@ public class ChangeStatement extends Statement { public final boolean postfix; @Override public void compile(CompileResult target, boolean pollute) { - value.toAssign(new ConstantStatement(loc(), -addAmount), Operation.SUBTRACT).compile(target, true); + value.toAssign(new NumberStatement(loc(), -addAmount), Operation.SUBTRACT).compile(target, true); if (!pollute) target.add(Instruction.discard()); else if (postfix) { target.add(Instruction.pushValue(addAmount)); @@ -35,39 +32,55 @@ public class ChangeStatement extends Statement { 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 parsePrefixIncrease(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!src.is(i + n, "++")) return ParseRes.failed(); + n += 2; + + var res = Parsing.parseValue(src, i + n, 15); + if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected assignable value after prefix operator."); + else if (!(res.result instanceof AssignableStatement)) return ParseRes.error(src.loc(i + n), "Expected assignable value after prefix operator."); + + return ParseRes.res(new ChangeStatement(loc, (AssignableStatement)res.result, 1, 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; - + public static ParseRes parsePrefixDecrease(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!src.is(i + n, "--")) return ParseRes.failed(); + n += 2; + + var res = Parsing.parseValue(src, i + n, 15); + if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected assignable value after prefix operator."); + else if (!(res.result instanceof AssignableStatement)) return ParseRes.error(src.loc(i + n), "Expected assignable value after prefix operator."); + + return ParseRes.res(new ChangeStatement(loc, (AssignableStatement)res.result, -1, false), n + res.n); + } + + public static ParseRes parsePostfixIncrease(Source src, int i, Statement prev, int precedence) { 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); + + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!src.is(i + n, "++")) return ParseRes.failed(); + if (!(prev instanceof AssignableStatement)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator."); + n += 2; + + return ParseRes.res(new ChangeStatement(loc, (AssignableStatement)prev, 1, true), n); + } + public static ParseRes parsePostfixDecrease(Source src, int i, Statement prev, int precedence) { + if (precedence > 15) return ParseRes.failed(); + + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!src.is(i + n, "--")) return ParseRes.failed(); + if (!(prev instanceof AssignableStatement)) return ParseRes.error(src.loc(i + n), "Expected assignable value before suffix operator."); + n += 2; + + return ParseRes.res(new ChangeStatement(loc, (AssignableStatement)prev, -1, true), n); } } diff --git a/src/java/me/topchetoeu/jscript/compilation/values/ConstantStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/ConstantStatement.java deleted file mode 100644 index 60a8503..0000000 --- a/src/java/me/topchetoeu/jscript/compilation/values/ConstantStatement.java +++ /dev/null @@ -1,74 +0,0 @@ -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; - public final boolean isNull; - - @Override public boolean pure() { return true; } - - @Override - public void compile(CompileResult target, boolean pollute) { - if (pollute) { - if (isNull) target.add(Instruction.pushNull()); - else if (value instanceof Double) target.add(Instruction.pushValue((Double)value)); - else if (value instanceof String) target.add(Instruction.pushValue((String)value)); - else if (value instanceof Boolean) target.add(Instruction.pushValue((Boolean)value)); - else target.add(Instruction.pushUndefined()); - } - } - - private ConstantStatement(Location loc, Object val, boolean isNull) { - super(loc); - this.value = val; - this.isNull = isNull; - } - - public ConstantStatement(Location loc, boolean val) { - this(loc, val, false); - } - public ConstantStatement(Location loc, String val) { - this(loc, val, false); - } - public ConstantStatement(Location loc, double val) { - this(loc, val, false); - } - - public static ConstantStatement ofUndefined(Location loc) { - return new ConstantStatement(loc, null, false); - } - 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 cd13dd8..14041f6 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/DiscardStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/DiscardStatement.java @@ -1,24 +1,20 @@ 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.ParseRes; import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Token; +import me.topchetoeu.jscript.compilation.parsing.Source; public class DiscardStatement extends Statement { public final Statement value; @Override public boolean pure() { return value.pure(); } - @Override - public void compile(CompileResult target, boolean pollute) { - value.compile(target, false); + @Override public void compile(CompileResult target, boolean pollute) { + if (value != null) value.compile(target, false); if (pollute) target.add(Instruction.pushUndefined()); } @@ -27,13 +23,15 @@ public class DiscardStatement extends Statement { 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(); + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); - var valRes = Parsing.parseValue(filename, tokens, i + n, 14); - if (!valRes.isSuccess()) return ParseRes.error(loc, "Expected a value after 'void' keyword.", valRes); + if (!Parsing.isIdentifier(src, i + n, "void")) return ParseRes.failed(); + n += 4; + + var valRes = Parsing.parseValue(src, i + n, 14); + if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'void' keyword."); 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 a54d082..f037bd8 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/FunctionStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/FunctionStatement.java @@ -1,20 +1,15 @@ 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.ParseRes; import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Token; +import me.topchetoeu.jscript.compilation.parsing.Source; import me.topchetoeu.jscript.runtime.exceptions.SyntaxException; public class FunctionStatement extends Statement { @@ -133,47 +128,30 @@ public class FunctionStatement extends Statement { 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; + public static ParseRes parseFunction(Source src, int i, boolean statement) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); - if (!Parsing.isIdentifier(tokens, i + n++, "function")) return ParseRes.failed(); + if (!Parsing.isIdentifier(src, i + n, "function")) return ParseRes.failed(); + n += 8; - 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; + var nameRes = Parsing.parseIdentifier(src, i + n); + if (!nameRes.isSuccess() && statement) return ParseRes.error(src.loc(i + n), "A statement function requires a name"); n += nameRes.n; + n += Parsing.skipEmpty(src, i + n); - if (!Parsing.isOperator(tokens, i + n++, Operator.PAREN_OPEN)) return ParseRes.error(loc, "Expected a parameter list."); + var args = Parsing.parseParamList(src, i + n); + if (!args.isSuccess()) return args.chainError(src.loc(i + n), "Expected a parameter list"); + n += args.n; - 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); + var res = CompoundStatement.parse(src, i + n); + if (!res.isSuccess()) res.chainError(src.loc(i + n), "Expected a compound statement for function."); 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); + return ParseRes.res(new FunctionStatement( + loc, src.loc(i + n - 1), + nameRes.result, args.result.toArray(String[]::new), + statement, res.result + ), n); } } diff --git a/src/java/me/topchetoeu/jscript/compilation/values/IndexStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/IndexStatement.java index 29dcbd3..0b4bf48 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/IndexStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/IndexStatement.java @@ -1,19 +1,16 @@ 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.ParseRes; import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Token; +import me.topchetoeu.jscript.compilation.parsing.Source; +import me.topchetoeu.jscript.compilation.values.constants.StringStatement; public class IndexStatement extends AssignableStatement { public final Statement object; @@ -42,33 +39,38 @@ public class IndexStatement extends AssignableStatement { 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; - + public static ParseRes parseIndex(Source src, int i, Statement prev, int precedence) { 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); + + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!src.is(i + n, "[")) return ParseRes.failed(); + n++; + + var valRes = Parsing.parseValue(src, i + n, 0); + if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value in index expression"); n += valRes.n; - - if (!Parsing.isOperator(tokens, i + n++, Operator.BRACKET_CLOSE)) return ParseRes.error(loc, "Expected a closing bracket."); - + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i + n, "]")) return ParseRes.error(src.loc(i + n), "Expected a closing bracket"); + n++; + 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; - + public static ParseRes parseMember(Source src, int i, Statement prev, int precedence) { 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); + + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!src.is(i + n, ".")) return ParseRes.failed(); + n++; + + var literal = Parsing.parseIdentifier(src, i + n); + if (!literal.isSuccess()) return literal.chainError(src.loc(i + n), "Expected an identifier after member access."); + n += literal.n; + + return ParseRes.res(new IndexStatement(loc, prev, new StringStatement(loc, literal.result)), n); } } diff --git a/src/java/me/topchetoeu/jscript/compilation/values/LazyAndStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/LazyAndStatement.java index 306ec25..b556e07 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/LazyAndStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/LazyAndStatement.java @@ -4,6 +4,9 @@ import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Location; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.parsing.ParseRes; +import me.topchetoeu.jscript.compilation.parsing.Parsing; +import me.topchetoeu.jscript.compilation.parsing.Source; public class LazyAndStatement extends Statement { public final Statement first, second; @@ -25,4 +28,20 @@ public class LazyAndStatement extends Statement { this.first = first; this.second = second; } + + + public static ParseRes parse(Source src, int i, Statement prev, int precedence) { + if (precedence < 4) return ParseRes.failed(); + var n = Parsing.skipEmpty(src, i); + + if (!src.is(i + n, "&&")) return ParseRes.failed(); + var loc = src.loc(i + n); + n += 2; + + var res = Parsing.parseValue(src, i + n, 4); + if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a value after the '&&' operator."); + n += res.n; + + return ParseRes.res(new LazyAndStatement(loc, prev, res.result), n); + } } diff --git a/src/java/me/topchetoeu/jscript/compilation/values/LazyOrStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/LazyOrStatement.java index b1461ed..80ec4be 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/LazyOrStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/LazyOrStatement.java @@ -4,6 +4,9 @@ import me.topchetoeu.jscript.common.Instruction; import me.topchetoeu.jscript.common.Location; import me.topchetoeu.jscript.compilation.CompileResult; import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.parsing.ParseRes; +import me.topchetoeu.jscript.compilation.parsing.Parsing; +import me.topchetoeu.jscript.compilation.parsing.Source; public class LazyOrStatement extends Statement { public final Statement first, second; @@ -25,4 +28,20 @@ public class LazyOrStatement extends Statement { this.first = first; this.second = second; } + + + public static ParseRes parse(Source src, int i, Statement prev, int precedence) { + if (precedence < 3) return ParseRes.failed(); + var n = Parsing.skipEmpty(src, i); + + if (!src.is(i + n, "||")) return ParseRes.failed(); + var loc = src.loc(i + n); + n += 2; + + var res = Parsing.parseValue(src, i + n, 4); + if (!res.isSuccess()) return res.chainError(src.loc(i + n), "Expected a value after the '||' operator."); + n += res.n; + + return ParseRes.res(new LazyOrStatement(loc, prev, res.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 b98e84e..91dbb6f 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/ObjectStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/ObjectStatement.java @@ -2,22 +2,30 @@ 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.ParseRes; import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Parsing.ObjProp; -import me.topchetoeu.jscript.compilation.parsing.Token; +import me.topchetoeu.jscript.compilation.parsing.Source; public class ObjectStatement extends Statement { + public static class ObjProp { + public final String name; + public final String access; + public final FunctionStatement func; + + public ObjProp(String name, String access, FunctionStatement func) { + this.name = name; + this.access = access; + this.func = func; + } + } + public final Map map; public final Map getters; public final Map setters; @@ -68,100 +76,107 @@ public class ObjectStatement extends Statement { this.setters = setters; } - private static ParseRes parsePropName(Filename filename, List tokens, int i) { - var loc = Parsing.getLoc(filename, tokens, i); + private static ParseRes parsePropName(Source src, int i) { + var n = Parsing.skipEmpty(src, 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); + var res = ParseRes.first(src, i + n, + Parsing::parseIdentifier, + Parsing::parseString, + Parsing::parseNumber + ); n += res.n; - var end = Parsing.getLoc(filename, tokens, i + n - 1); + if (!res.isSuccess()) return res.chainError(); + return ParseRes.res(res.result.toString(), n); + } + private static ParseRes parseObjectProp(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + var access = Parsing.parseIdentifier(src, i + n); + if (!access.isSuccess()) return ParseRes.failed(); + if (!access.result.equals("get") && !access.result.equals("set")) return ParseRes.failed(); + n += access.n; + + var name = parsePropName(src, i + n); + if (!name.isSuccess()) return name.chainError(src.loc(i + n), "Expected a property name after '" + access + "'"); + n += name.n; + + var params = Parsing.parseParamList(src, i + n); + if (!params.isSuccess()) return params.chainError(src.loc(i + n), "Expected an argument list"); + n += params.n; + + var body = CompoundStatement.parse(src, i + n); + if (!body.isSuccess()) return body.chainError(src.loc(i + n), "Expected a compound statement for property accessor."); + n += body.n; + + var end = src.loc(i + n - 1); return ParseRes.res(new ObjProp( - name, access, - new FunctionStatement(loc, end, access + " " + name.toString(), argsRes.result.toArray(String[]::new), false, res.result) + name.result, access.result, + new FunctionStatement(loc, end, access + " " + name.result.toString(), params.result.toArray(String[]::new), false, body.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(); - + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!src.is(i + n, "{")) return ParseRes.failed(); + n++; + n += Parsing.skipEmpty(src, i + n); + var values = new LinkedHashMap(); var getters = new LinkedHashMap(); var setters = new LinkedHashMap(); - - if (Parsing.isOperator(tokens, i + n, Operator.BRACE_CLOSE)) { + + if (src.is(i + n, "}")) { 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); - } + var prop = parseObjectProp(src, i + n); + + if (prop.isSuccess()) { + n += prop.n; + + if (prop.result.access.equals("set")) setters.put(prop.result.name, prop.result.func); + else getters.put(prop.result.name, prop.result.func); } else { - var 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)) { + var name = parsePropName(src, i + n); + if (!name.isSuccess()) return prop.chainError(src.loc(i + n), "Expected a field name"); + n += name.n; + n += Parsing.skipEmpty(src, i + n); + + if (!src.is(i + n, ":")) return ParseRes.error(src.loc(i + n), "Expected a colon"); n++; - if (Parsing.isOperator(tokens, i + n, Operator.BRACE_CLOSE)) { + + var valRes = Parsing.parseValue(src, i + n, 2); + if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value in array list"); + n += valRes.n; + + values.put(name.result, valRes.result); + } + + n += Parsing.skipEmpty(src, i + n); + if (src.is(i + n, ",")) { + n++; + n += Parsing.skipEmpty(src, i + n); + + if (src.is(i + n, "}")) { n++; break; } + continue; } - else if (Parsing.isOperator(tokens, i + n, Operator.BRACE_CLOSE)) { + else if (src.is(i + n, "}")) { n++; break; } - else ParseRes.error(loc, "Expected a comma or a closing brace."); + else ParseRes.error(src.loc(i + n), "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 55d373d..2c98e42 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/OperationStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/OperationStatement.java @@ -1,18 +1,14 @@ 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.ParseRes; import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Token; +import me.topchetoeu.jscript.compilation.parsing.Source; public class OperationStatement extends Statement { public final Statement[] args; @@ -41,75 +37,74 @@ public class OperationStatement extends Statement { 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; + public static ParseRes parsePrefix(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); Operation operation = null; + String op; - 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; + if (src.is(i + n, op = "+")) operation = Operation.POS; + else if (src.is(i + n, op = "-")) operation = Operation.NEG; + else if (src.is(i + n, op = "~")) operation = Operation.INVERSE; + else if (src.is(i + n, op = "!")) operation = Operation.NOT; else return ParseRes.failed(); - var res = Parsing.parseValue(filename, tokens, n + i, 14); + n++; + + var res = Parsing.parseValue(src, i + n, 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); + else return res.chainError(src.loc(i + n), String.format("Expected a value after the unary operator '%s'.", op)); } - public static ParseRes parseInstanceof(Filename filename, List tokens, int i, Statement prev, int precedence) { - var loc = Parsing.getLoc(filename, tokens, i); - int n = 0; - + public static ParseRes parseInstanceof(Source src, int i, Statement prev, int precedence) { 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); + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + var kw = Parsing.parseIdentifier(src, i + n, "instanceof"); + if (!kw.isSuccess()) return kw.chainError(); + n += kw.n; + + var valRes = Parsing.parseValue(src, i + n, 10); + if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'instanceof'."); 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; - + public static ParseRes parseIn(Source src, int i, Statement prev, int precedence) { 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); + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + var kw = Parsing.parseIdentifier(src, i + n, "in"); + if (!kw.isSuccess()) return kw.chainError(); + n += kw.n; + + var valRes = Parsing.parseValue(src, i + n, 10); + if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'in'."); n += valRes.n; - return ParseRes.res(new OperationStatement(loc, Operation.IN, prev, valRes.result), n); + return ParseRes.res(new OperationStatement(loc, Operation.IN, valRes.result, prev), 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; + public static ParseRes parseOperator(Source src, int i, Statement prev, int precedence) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); - var opRes = Parsing.parseOperator(tokens, i + n++); - if (!opRes.isSuccess()) return ParseRes.failed(); - var op = opRes.result; + for (var op : Operator.opsByLength) { + if (!src.is(i + n, op.readable)) continue; + if (op.precedence < precedence) return ParseRes.failed(); + n += op.readable.length(); - if (op.precedence < precedence) return ParseRes.failed(); - if (op.isAssign()) return AssignableStatement.parse(filename, tokens, i + n - 1, prev, precedence); + var res = Parsing.parseValue(src, i + n, op.precedence + 1); + if (!res.isSuccess()) return res.chainError(src.loc(i + n), String.format("Expected a value after the '%s' operator.", op.readable)); + n += res.n; - 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); } - return ParseRes.res(new OperationStatement(loc, op.operation, prev, res.result), n); + return ParseRes.failed(); } } diff --git a/src/java/me/topchetoeu/jscript/compilation/values/RegexStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/RegexStatement.java index b7a753a..8b240d0 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/RegexStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/RegexStatement.java @@ -1,15 +1,12 @@ 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.ParseRes; import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Token; +import me.topchetoeu.jscript.compilation.parsing.Source; public class RegexStatement extends Statement { public final String pattern, flags; @@ -23,19 +20,56 @@ 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); + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + + if (!src.is(i + n, '/')) return ParseRes.failed(); + var loc = src.loc(i + n); + n++; + + var source = new StringBuilder(); + var flags = new StringBuilder(); + + var inBrackets = false; + + while (true) { + if (src.is(i + n, '[')) { + n++; + inBrackets = true; + source.append(src.at(i + n)); + continue; } - else return ParseRes.failed(); + else if (src.is(i + n, ']')) { + n++; + inBrackets = false; + source.append(src.at(i + n)); + continue; + } + else if (src.is(i + n, '/') && !inBrackets) { + n++; + break; + } + + var charRes = Parsing.parseChar(src, i + n); + if (charRes.result == null) return ParseRes.error(src.loc(i + n), "Multiline regular expressions are not allowed"); + source.append(charRes.result); + n++; } - return ParseRes.failed(); + + while (true) { + char c = src.at(i + n, '\0'); + + if (src.is(i + n, v -> Parsing.isAny(c, "dgimsuy"))) { + if (flags.indexOf(c + "") >= 0) return ParseRes.error(src.loc(i + n), "The flags of a regular expression may not be repeated"); + flags.append(c); + } + else break; + + n++; + } + + return ParseRes.res(new RegexStatement(loc, source.toString(), flags.toString()), n); } public RegexStatement(Location loc, String pattern, String flags) { diff --git a/src/java/me/topchetoeu/jscript/compilation/values/TypeofStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/TypeofStatement.java index 267c748..89ddc98 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/TypeofStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/TypeofStatement.java @@ -1,15 +1,12 @@ 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.ParseRes; import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Token; +import me.topchetoeu.jscript.compilation.parsing.Source; public class TypeofStatement extends Statement { public final Statement value; @@ -36,13 +33,15 @@ public class TypeofStatement extends Statement { 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); + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + if (!Parsing.isIdentifier(src, i + n, "typeof")) return ParseRes.failed(); + n += 6; + + var valRes = Parsing.parseValue(src, i + n, 15); + if (!valRes.isSuccess()) return valRes.chainError(src.loc(i + n), "Expected a value after 'typeof' keyword."); 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 6349c40..53933bb 100644 --- a/src/java/me/topchetoeu/jscript/compilation/values/VariableStatement.java +++ b/src/java/me/topchetoeu/jscript/compilation/values/VariableStatement.java @@ -1,17 +1,14 @@ 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.ParseRes; import me.topchetoeu.jscript.compilation.parsing.Parsing; -import me.topchetoeu.jscript.compilation.parsing.Token; +import me.topchetoeu.jscript.compilation.parsing.Source; public class VariableStatement extends AssignableStatement { public final String name; @@ -35,19 +32,21 @@ public class VariableStatement extends AssignableStatement { 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(); - + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + var literal = Parsing.parseIdentifier(src, i); + if (!literal.isSuccess()) return literal.chainError(); + n += literal.n; + 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)); + if (literal.result.equals("await")) return ParseRes.error(src.loc(i + n), "'await' expressions are not supported."); + if (literal.result.equals("const")) return ParseRes.error(src.loc(i + n), "'const' declarations are not supported."); + if (literal.result.equals("let")) return ParseRes.error(src.loc(i + n), "'let' declarations are not supported."); + return ParseRes.error(src.loc(i + n), String.format("Unexpected keyword '%s'.", literal.result)); } - - return ParseRes.res(new VariableStatement(loc, literal.result), 1); + + return ParseRes.res(new VariableStatement(loc, literal.result), n); } } diff --git a/src/java/me/topchetoeu/jscript/compilation/values/constants/BoolStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/constants/BoolStatement.java new file mode 100644 index 0000000..51c6db7 --- /dev/null +++ b/src/java/me/topchetoeu/jscript/compilation/values/constants/BoolStatement.java @@ -0,0 +1,21 @@ +package me.topchetoeu.jscript.compilation.values.constants; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Statement; + +public class BoolStatement extends Statement { + public final boolean value; + + @Override public boolean pure() { return true; } + + @Override public void compile(CompileResult target, boolean pollute) { + if (pollute) target.add(Instruction.pushValue(value)); + } + + public BoolStatement(Location loc, boolean value) { + super(loc); + this.value = value; + } +} diff --git a/src/java/me/topchetoeu/jscript/compilation/values/constants/ConstantStatements.java b/src/java/me/topchetoeu/jscript/compilation/values/constants/ConstantStatements.java new file mode 100644 index 0000000..b21545f --- /dev/null +++ b/src/java/me/topchetoeu/jscript/compilation/values/constants/ConstantStatements.java @@ -0,0 +1,4 @@ +package me.topchetoeu.jscript.compilation.values.constants; + +public class ConstantStatements { +} diff --git a/src/java/me/topchetoeu/jscript/compilation/values/constants/NullStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/constants/NullStatement.java new file mode 100644 index 0000000..7238ce6 --- /dev/null +++ b/src/java/me/topchetoeu/jscript/compilation/values/constants/NullStatement.java @@ -0,0 +1,16 @@ +package me.topchetoeu.jscript.compilation.values.constants; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Statement; + +public class NullStatement extends Statement { + @Override public boolean pure() { return true; } + + @Override public void compile(CompileResult target, boolean pollute) { + target.add(Instruction.pushNull()); + } + + public NullStatement(Location loc) { super(loc); } +} diff --git a/src/java/me/topchetoeu/jscript/compilation/values/constants/NumberStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/constants/NumberStatement.java new file mode 100644 index 0000000..2640cd3 --- /dev/null +++ b/src/java/me/topchetoeu/jscript/compilation/values/constants/NumberStatement.java @@ -0,0 +1,42 @@ +package me.topchetoeu.jscript.compilation.values.constants; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.parsing.ParseRes; +import me.topchetoeu.jscript.compilation.parsing.Parsing; +import me.topchetoeu.jscript.compilation.parsing.Source; + +public class NumberStatement extends Statement { + public final double value; + + @Override public boolean pure() { return true; } + + @Override public void compile(CompileResult target, boolean pollute) { + if (pollute) target.add(Instruction.pushValue(value)); + } + + public NumberStatement(Location loc, double value) { + super(loc); + this.value = value; + } + + public static double power(double a, long b) { + if (b == 0) return 1; + if (b == 1) return a; + if (b < 0) return 1 / power(a, -b); + + if ((b & 1) == 0) return power(a * a, b / 2); + else return a * power(a * a, b / 2); + } + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + var res = Parsing.parseNumber(src, i + n); + if (res.isSuccess()) return ParseRes.res(new NumberStatement(loc, res.result), n + res.n); + else return res.chainError(); + } +} diff --git a/src/java/me/topchetoeu/jscript/compilation/values/constants/StringStatement.java b/src/java/me/topchetoeu/jscript/compilation/values/constants/StringStatement.java new file mode 100644 index 0000000..02fad21 --- /dev/null +++ b/src/java/me/topchetoeu/jscript/compilation/values/constants/StringStatement.java @@ -0,0 +1,33 @@ +package me.topchetoeu.jscript.compilation.values.constants; + +import me.topchetoeu.jscript.common.Instruction; +import me.topchetoeu.jscript.common.Location; +import me.topchetoeu.jscript.compilation.CompileResult; +import me.topchetoeu.jscript.compilation.Statement; +import me.topchetoeu.jscript.compilation.parsing.ParseRes; +import me.topchetoeu.jscript.compilation.parsing.Parsing; +import me.topchetoeu.jscript.compilation.parsing.Source; + +public class StringStatement extends Statement { + public final String value; + + @Override public boolean pure() { return true; } + + @Override public void compile(CompileResult target, boolean pollute) { + if (pollute) target.add(Instruction.pushValue(value)); + } + + public StringStatement(Location loc, String value) { + super(loc); + this.value = value; + } + + public static ParseRes parse(Source src, int i) { + var n = Parsing.skipEmpty(src, i); + var loc = src.loc(i + n); + + var res = Parsing.parseString(src, i + n); + if (res.isSuccess()) return ParseRes.res(new StringStatement(loc, res.result), n + res.n); + else return res.chainError(); + } +} diff --git a/src/java/me/topchetoeu/jscript/runtime/SimpleRepl.java b/src/java/me/topchetoeu/jscript/runtime/SimpleRepl.java index 5253aa3..4381f4f 100644 --- a/src/java/me/topchetoeu/jscript/runtime/SimpleRepl.java +++ b/src/java/me/topchetoeu/jscript/runtime/SimpleRepl.java @@ -19,7 +19,7 @@ import me.topchetoeu.jscript.runtime.values.functions.NativeFunction; import me.topchetoeu.jscript.runtime.values.primitives.VoidValue; public class SimpleRepl { - static Thread engineTask, debugTask; + static Thread engineTask; static Engine engine = new Engine(); static Environment environment = Environment.empty(); @@ -69,11 +69,6 @@ public class SimpleRepl { } private static void initEnv() { - var glob = GlobalScope.get(environment); - - glob.define(null, false, new NativeFunction("exit", args -> { - throw new InterruptException(); - })); // glob.define(null, false, new NativeFunction("go", args -> { // try { // var f = Path.of("do.js"); @@ -84,13 +79,6 @@ public class SimpleRepl { // throw new EngineException("Couldn't open do.js"); // } // })); - glob.define(null, false, new NativeFunction("log", args -> { - for (var el : args.args) { - System.out.print(el.toReadable(args.env)); - } - - return null; - })); // var fs = new RootFilesystem(PermissionsProvider.get(environment)); // fs.protocols.put("temp", new MemoryFilesystem(Mode.READ_WRITE)); @@ -107,6 +95,18 @@ public class SimpleRepl { environment.add(Compiler.KEY, (filename, source) -> { return Parsing.compile(filename, source).body(); }); + + var glob = GlobalScope.get(environment); + glob.define(null, false, new NativeFunction("exit", args -> { + throw new InterruptException(); + })); + glob.define(null, false, new NativeFunction("log", args -> { + for (var el : args.args) { + System.out.print(el.toReadable(args.env)); + } + + return null; + })); } private static void initEngine() { // var ctx = new DebugContext(); @@ -131,7 +131,7 @@ public class SimpleRepl { reader.start(); engine.thread().join(); - debugTask.interrupt(); + // debugTask.interrupt(); engineTask.interrupt(); } } diff --git a/src/java/me/topchetoeu/jscript/runtime/values/functions/FunctionValue.java b/src/java/me/topchetoeu/jscript/runtime/values/functions/FunctionValue.java index 63a778b..83f0770 100644 --- a/src/java/me/topchetoeu/jscript/runtime/values/functions/FunctionValue.java +++ b/src/java/me/topchetoeu/jscript/runtime/values/functions/FunctionValue.java @@ -53,7 +53,7 @@ public abstract class FunctionValue extends ObjectValue { return super.getOwnMember(env, key); } @Override public boolean deleteOwnMember(Environment env, Value key) { - if (!super.deleteMember(env, key)) return false; + if (!super.deleteOwnMember(env, key)) return false; var el = key.toString(env).value;