diff --git a/ownlang-parser/src/main/java/com/annimon/ownlang/exceptions/LexerException.java b/ownlang-parser/src/main/java/com/annimon/ownlang/exceptions/LexerException.java index 383e01f..66f8a95 100644 --- a/ownlang-parser/src/main/java/com/annimon/ownlang/exceptions/LexerException.java +++ b/ownlang-parser/src/main/java/com/annimon/ownlang/exceptions/LexerException.java @@ -12,7 +12,7 @@ public final class LexerException extends RuntimeException { super(message); } - public LexerException(Pos pos, String message) { + public LexerException(String message, Pos pos) { super(pos.format() + " " + message); } } \ No newline at end of file diff --git a/ownlang-parser/src/main/java/com/annimon/ownlang/exceptions/ParseException.java b/ownlang-parser/src/main/java/com/annimon/ownlang/exceptions/ParseException.java index 0049c6d..1046f04 100644 --- a/ownlang-parser/src/main/java/com/annimon/ownlang/exceptions/ParseException.java +++ b/ownlang-parser/src/main/java/com/annimon/ownlang/exceptions/ParseException.java @@ -1,6 +1,7 @@ package com.annimon.ownlang.exceptions; import com.annimon.ownlang.parser.Pos; +import com.annimon.ownlang.parser.Range; /** * @@ -8,11 +9,10 @@ import com.annimon.ownlang.parser.Pos; */ public final class ParseException extends RuntimeException { - private final Pos start; - private final Pos end; + private final Range range; public ParseException(String message) { - this(message, Pos.ZERO, Pos.ZERO); + this(message, Range.ZERO); } public ParseException(String message, Pos pos) { @@ -20,16 +20,15 @@ public final class ParseException extends RuntimeException { } public ParseException(String message, Pos start, Pos end) { + this(message, new Range(start, end)); + } + + public ParseException(String message, Range range) { super(message); - this.start = start; - this.end = end; + this.range = range; } - public Pos getStart() { - return start; - } - - public Pos getEnd() { - return end; + public Range getRange() { + return range; } } \ No newline at end of file diff --git a/ownlang-parser/src/main/java/com/annimon/ownlang/parser/Lexer.java b/ownlang-parser/src/main/java/com/annimon/ownlang/parser/Lexer.java index fa000d1..28ee57e 100644 --- a/ownlang-parser/src/main/java/com/annimon/ownlang/parser/Lexer.java +++ b/ownlang-parser/src/main/java/com/annimon/ownlang/parser/Lexer.java @@ -411,6 +411,6 @@ public final class Lexer { } private LexerException error(String text) { - return new LexerException(markPos(), text); + return new LexerException(text, markPos()); } } diff --git a/ownlang-parser/src/main/java/com/annimon/ownlang/parser/ParseError.java b/ownlang-parser/src/main/java/com/annimon/ownlang/parser/ParseError.java index 264f043..d505921 100644 --- a/ownlang-parser/src/main/java/com/annimon/ownlang/parser/ParseError.java +++ b/ownlang-parser/src/main/java/com/annimon/ownlang/parser/ParseError.java @@ -1,9 +1,19 @@ package com.annimon.ownlang.parser; -public record ParseError(Exception exception, Pos pos) { +import java.util.Collections; +import java.util.List; + +public record ParseError(String message, Range range, List stackTraceElements) { + public ParseError(String message, Range range) { + this(message, range, Collections.emptyList()); + } + + public boolean hasStackTrace() { + return !stackTraceElements.isEmpty(); + } @Override public String toString() { - return "Error on line " + pos.row() + ": " + exception; + return "Error on line " + range().start().row() + ": " + message; } } diff --git a/ownlang-parser/src/main/java/com/annimon/ownlang/parser/ParseErrors.java b/ownlang-parser/src/main/java/com/annimon/ownlang/parser/ParseErrors.java index 4d1c44f..d0de90e 100644 --- a/ownlang-parser/src/main/java/com/annimon/ownlang/parser/ParseErrors.java +++ b/ownlang-parser/src/main/java/com/annimon/ownlang/parser/ParseErrors.java @@ -17,8 +17,8 @@ public final class ParseErrors implements Iterable { errors.clear(); } - public void add(Exception ex, Pos pos) { - errors.add(new ParseError(ex, pos)); + public void add(ParseError parseError) { + errors.add(parseError); } public boolean hasErrors() { diff --git a/ownlang-parser/src/main/java/com/annimon/ownlang/parser/Parser.java b/ownlang-parser/src/main/java/com/annimon/ownlang/parser/Parser.java index 980300f..f4df30d 100644 --- a/ownlang-parser/src/main/java/com/annimon/ownlang/parser/Parser.java +++ b/ownlang-parser/src/main/java/com/annimon/ownlang/parser/Parser.java @@ -24,7 +24,7 @@ public final class Parser { return program; } - private static final Token EOF = new Token(TokenType.EOF, "", new Pos(-1, -1)); + private static final Token EOF = new Token(TokenType.EOF, "", Pos.UNKNOWN); private static final EnumMap ASSIGN_OPERATORS; static { @@ -50,7 +50,7 @@ public final class Parser { private final ParseErrors parseErrors; private Statement parsedStatement; - private int pos; + private int index; public Parser(List tokens) { this.tokens = tokens; @@ -72,11 +72,11 @@ public final class Parser { while (!match(TokenType.EOF)) { try { result.add(statement()); - } catch (ParseException parseException) { - parseErrors.add(parseException, parseException.getStart()); + } catch (ParseException ex) { + parseErrors.add(new ParseError(ex.getMessage(), ex.getRange())); recover(); } catch (Exception ex) { - parseErrors.add(ex, getPos()); + parseErrors.add(new ParseError(ex.getMessage(), getRange(), List.of(ex.getStackTrace()))); recover(); } } @@ -84,20 +84,14 @@ public final class Parser { return result; } - private Pos getPos() { - if (size == 0) return new Pos(0, 0); - if (pos >= size) return tokens.get(size - 1).pos(); - return tokens.get(pos).pos(); - } - private void recover() { - int preRecoverPosition = pos; - for (int i = preRecoverPosition; i <= size; i++) { - pos = i; + int preRecoverIndex = index; + for (int i = preRecoverIndex; i <= size; i++) { + index = i; try { statement(); // successfully parsed, - pos = i; // restore position + index = i; // restore position return; } catch (Exception ex) { // fail @@ -190,7 +184,7 @@ public final class Parser { private DestructuringAssignmentStatement destructuringAssignment() { // extract(var1, var2, ...) = ... - final var startPos = getPos(); + final var startTokenIndex = index; consume(TokenType.LPAREN); final List variables = new ArrayList<>(); while (!match(TokenType.RPAREN)) { @@ -203,7 +197,7 @@ public final class Parser { match(TokenType.COMMA); } if (variables.isEmpty() || variables.stream().allMatch(Objects::isNull)) { - throw error(errorDestructuringAssignmentEmpty(), startPos, getPos()); + throw error(errorDestructuringAssignmentEmpty(), startTokenIndex, index); } consume(TokenType.EQ); return new DestructuringAssignmentStatement(variables, expression()); @@ -493,16 +487,16 @@ public final class Parser { private AssignmentExpression assignmentStrict() { // x[0].prop += ... - final int position = pos; + final int position = index; final Expression targetExpr = qualifiedName(); if (!(targetExpr instanceof Accessible)) { - pos = position; + index = position; return null; } final TokenType currentType = get(0).type(); if (!ASSIGN_OPERATORS.containsKey(currentType)) { - pos = position; + index = position; return null; } match(currentType); @@ -905,7 +899,7 @@ public final class Parser { if (expectedType != actual.type()) { throw error(errorUnexpectedToken(actual, expectedType)); } - pos++; + index++; return actual; } @@ -915,7 +909,7 @@ public final class Parser { throw error(errorUnexpectedToken(actual, expectedType) + errorMessageFunction.apply(actual)); } - pos++; + index++; return actual; } @@ -924,7 +918,7 @@ public final class Parser { if (type != current.type()) { return false; } - pos++; + index++; return true; } @@ -933,17 +927,33 @@ public final class Parser { } private Token get(int relativePosition) { - final int position = pos + relativePosition; + final int position = index + relativePosition; if (position >= size) return EOF; return tokens.get(position); } - private ParseException error(String message) { - return new ParseException(message, getPos()); + private Range getRange() { + return getRange(index, index); } - private static ParseException error(String message, Pos start, Pos end) { - return new ParseException(message, start, end); + private Range getRange(int startIndex, int endIndex) { + if (size == 0) return Range.ZERO; + final int last = size - 1; + Pos start = tokens.get(Math.min(startIndex, last)).pos(); + if (startIndex == endIndex) { + return new Range(start, start); + } else { + Pos end = tokens.get(Math.min(endIndex, last)).pos(); + return new Range(start, end); + } + } + + private ParseException error(String message) { + return new ParseException(message, getRange()); + } + + private ParseException error(String message, int startIndex, int endIndex) { + return new ParseException(message, getRange(startIndex, endIndex)); } private static String errorUnexpectedToken(Token actual, TokenType expectedType) { diff --git a/ownlang-parser/src/main/java/com/annimon/ownlang/parser/Range.java b/ownlang-parser/src/main/java/com/annimon/ownlang/parser/Range.java new file mode 100644 index 0000000..6902d48 --- /dev/null +++ b/ownlang-parser/src/main/java/com/annimon/ownlang/parser/Range.java @@ -0,0 +1,27 @@ +package com.annimon.ownlang.parser; + +import java.util.Objects; + +public record Range(Pos start, Pos end) { + public static final Range ZERO = new Range(Pos.ZERO, Pos.ZERO); + + public Range normalize() { + return new Range(start.normalize(), end.normalize()); + } + + public boolean isEqualPosition() { + return Objects.equals(start, end); + } + + public boolean isOnSameLine() { + return start.row() == end.row(); + } + + public String format() { + if (isOnSameLine()) { + return start.format(); + } else { + return start.format() + "..." + end.format(); + } + } +} diff --git a/ownlang-utils/src/main/java/com/annimon/ownlang/utils/Repl.java b/ownlang-utils/src/main/java/com/annimon/ownlang/utils/Repl.java index 1e38482..17a055e 100644 --- a/ownlang-utils/src/main/java/com/annimon/ownlang/utils/Repl.java +++ b/ownlang-utils/src/main/java/com/annimon/ownlang/utils/Repl.java @@ -33,7 +33,7 @@ public final class Repl { RESET = ":reset", EXIT = ":exit"; - private static final Token PRINTLN_TOKEN = new Token(TokenType.PRINTLN, "", new Pos(0, 0)); + private static final Token PRINTLN_TOKEN = new Token(TokenType.PRINTLN, "", Pos.ZERO); public static void main(String[] args) { System.out.println("Welcome to OwnLang " + Version.VERSION + " REPL");