From ea38227b446b39515e03b22147aa7ecd3de568fb Mon Sep 17 00:00:00 2001 From: aNNiMON Date: Wed, 27 Sep 2023 17:34:20 +0300 Subject: [PATCH] More informative parser errors with ranges, removed LexerException --- .../exceptions/BaseParserException.java | 20 +++++++++ .../ownlang/exceptions/LexerException.java | 18 -------- .../exceptions/OwnLangParserException.java | 26 ++++++++--- .../ownlang/exceptions/ParseException.java | 22 ++------- .../com/annimon/ownlang/parser/Lexer.java | 45 ++++++++++++------- .../com/annimon/ownlang/parser/Parser.java | 5 ++- .../ownlang/parser/ast/IncludeStatement.java | 8 +--- .../parser/{ => error}/ParseError.java | 3 +- .../parser/{ => error}/ParseErrors.java | 2 +- .../com/annimon/ownlang/parser/LexerTest.java | 4 +- .../java/com/annimon/ownlang/utils/Repl.java | 4 +- 11 files changed, 83 insertions(+), 74 deletions(-) create mode 100644 ownlang-parser/src/main/java/com/annimon/ownlang/exceptions/BaseParserException.java delete mode 100644 ownlang-parser/src/main/java/com/annimon/ownlang/exceptions/LexerException.java rename ownlang-parser/src/main/java/com/annimon/ownlang/parser/{ => error}/ParseError.java (85%) rename ownlang-parser/src/main/java/com/annimon/ownlang/parser/{ => error}/ParseErrors.java (95%) diff --git a/ownlang-parser/src/main/java/com/annimon/ownlang/exceptions/BaseParserException.java b/ownlang-parser/src/main/java/com/annimon/ownlang/exceptions/BaseParserException.java new file mode 100644 index 0000000..1d59a0c --- /dev/null +++ b/ownlang-parser/src/main/java/com/annimon/ownlang/exceptions/BaseParserException.java @@ -0,0 +1,20 @@ +package com.annimon.ownlang.exceptions; + +import com.annimon.ownlang.parser.Range; + +/** + * Base type for all lexer and parser exceptions + */ +public abstract class BaseParserException extends RuntimeException { + + private final Range range; + + public BaseParserException(String message, Range range) { + super(message); + this.range = range; + } + + public Range getRange() { + return range; + } +} 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 deleted file mode 100644 index d1bd4cb..0000000 --- a/ownlang-parser/src/main/java/com/annimon/ownlang/exceptions/LexerException.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.annimon.ownlang.exceptions; - -import com.annimon.ownlang.parser.Pos; - -/** - * - * @author aNNiMON - */ -public final class LexerException extends OwnLangParserException { - - public LexerException(String message) { - super(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/OwnLangParserException.java b/ownlang-parser/src/main/java/com/annimon/ownlang/exceptions/OwnLangParserException.java index 8aaa4a7..6e3d532 100644 --- a/ownlang-parser/src/main/java/com/annimon/ownlang/exceptions/OwnLangParserException.java +++ b/ownlang-parser/src/main/java/com/annimon/ownlang/exceptions/OwnLangParserException.java @@ -1,15 +1,27 @@ package com.annimon.ownlang.exceptions; +import com.annimon.ownlang.parser.error.ParseError; +import com.annimon.ownlang.parser.error.ParseErrors; + /** - * Base type for all lexer and parser exceptions + * Single Exception for Lexer and Parser errors */ -public abstract class OwnLangParserException extends RuntimeException { +public class OwnLangParserException extends RuntimeException { - public OwnLangParserException() { - super(); + private final ParseErrors parseErrors; + + public OwnLangParserException(ParseError parseError) { + super(parseError.toString()); + this.parseErrors = new ParseErrors(); + parseErrors.add(parseError);; } - public OwnLangParserException(String message) { - super(message); + public OwnLangParserException(ParseErrors parseErrors) { + super(parseErrors.toString()); + this.parseErrors = parseErrors; } -} + + public ParseErrors getParseErrors() { + return parseErrors; + } +} \ 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 6b67ca1..2113cbc 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,34 +1,18 @@ package com.annimon.ownlang.exceptions; -import com.annimon.ownlang.parser.Pos; import com.annimon.ownlang.parser.Range; /** * * @author aNNiMON */ -public final class ParseException extends OwnLangParserException { - - private final Range range; +public final class ParseException extends BaseParserException { public ParseException(String message) { - this(message, Range.ZERO); - } - - public ParseException(String message, Pos pos) { - this(message, pos, pos); - } - - public ParseException(String message, Pos start, Pos end) { - this(message, new Range(start, end)); + super(message, Range.ZERO); } public ParseException(String message, Range range) { - super(message); - this.range = range; - } - - public Range getRange() { - return range; + super(message, 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 9a11d54..7ba7fc5 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 @@ -1,6 +1,7 @@ package com.annimon.ownlang.parser; -import com.annimon.ownlang.exceptions.LexerException; +import com.annimon.ownlang.exceptions.OwnLangParserException; +import com.annimon.ownlang.parser.error.ParseError; import java.util.*; /** @@ -146,7 +147,7 @@ public final class Lexer { else if (current == '#') tokenizeHexNumber(1); else if (current == ';') skip(); // ignore semicolon else if (current == '\0') break; - else throw error("Unknown token " + current); + else throw error("Unknown token " + current, markPos()); } return tokens; } @@ -164,11 +165,11 @@ public final class Lexer { while (true) { if (current == '.') { decimal = true; - if (hasDot) throw error("Invalid float number " + buffer); + if (hasDot) throw error("Invalid float number " + buffer, startPos); hasDot = true; } else if (current == 'e' || current == 'E') { decimal = true; - int exp = subTokenizeScientificNumber(); + int exp = subTokenizeScientificNumber(startPos); buffer.append(current).append(exp); break; } else if (!Character.isDigit(current)) { @@ -184,7 +185,7 @@ public final class Lexer { } } - private int subTokenizeScientificNumber() { + private int subTokenizeScientificNumber(Pos startPos) { int sign = 1; switch (next()) { case '-': sign = -1; @@ -204,10 +205,10 @@ public final class Lexer { current = next(); position++; } - if (position == 0 && !hasValue) throw error("Empty floating point exponent"); + if (position == 0 && !hasValue) throw error("Empty floating point exponent", startPos, markEndPos()); if (position >= 4) { - if (sign > 0) throw error("Float number too large"); - else throw error("Float number too small"); + if (sign > 0) throw error("Float number too large", startPos, markEndPos()); + else throw error("Float number too small", startPos, markEndPos()); } return sign * result; } @@ -227,8 +228,8 @@ public final class Lexer { current = next(); } - if (buffer.isEmpty()) throw error("Empty HEX value"); - if (peek(-1) == '_') throw error("HEX value cannot end with _"); + if (buffer.isEmpty()) throw error("Empty HEX value", startPos); + if (peek(-1) == '_') throw error("HEX value cannot end with _", startPos, markEndPos()); addToken(TokenType.HEX_NUMBER, buffer.toString(), startPos); } @@ -290,8 +291,9 @@ public final class Lexer { final var buffer = createBuffer(); char current = peek(0); while (current != '`') { - if (current == '\0') throw error("Reached end of file while parsing extended word."); - if (current == '\n' || current == '\r') throw error("Reached end of line while parsing extended word."); + if ("\r\n\0".indexOf(current) != -1) { + throw error("Reached end of line while parsing extended word.", startPos, markEndPos()); + } buffer.append(current); current = next(); } @@ -341,7 +343,7 @@ public final class Lexer { continue; } if (current == '"') break; - if (current == '\0') throw error("Reached end of file while parsing text string."); + if (current == '\0') throw error("Reached end of file while parsing text string.", startPos, markEndPos()); buffer.append(current); current = next(); } @@ -360,11 +362,14 @@ public final class Lexer { } private void tokenizeMultilineComment() { + final Pos startPos = markPos(); skip(); // / skip(); // * char current = peek(0); while (current != '*' || peek(1) != '/') { - if (current == '\0') throw error("Reached end of file while parsing multiline comment"); + if (current == '\0') { + throw error("Reached end of file while parsing multiline comment", startPos, markEndPos()); + } current = next(); } skip(); // * @@ -388,6 +393,10 @@ public final class Lexer { return new Pos(row, col); } + private Pos markEndPos() { + return new Pos(row, Math.max(0, col - 1)); + } + private void skip() { if (pos >= length) return; final char result = input.charAt(pos); @@ -417,7 +426,11 @@ public final class Lexer { tokens.add(new Token(type, text, startRow)); } - private LexerException error(String text) { - return new LexerException(text, markPos()); + private OwnLangParserException error(String text, Pos position) { + return error(text, position, position); + } + + private OwnLangParserException error(String text, Pos startPos, Pos endPos) { + return new OwnLangParserException(new ParseError(text, new Range(startPos, endPos))); } } 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 668894c..0cc90a2 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 @@ -1,10 +1,13 @@ package com.annimon.ownlang.parser; +import com.annimon.ownlang.exceptions.OwnLangParserException; import com.annimon.ownlang.exceptions.ParseException; import com.annimon.ownlang.lib.NumberValue; import com.annimon.ownlang.lib.StringValue; import com.annimon.ownlang.lib.UserDefinedFunction; import com.annimon.ownlang.parser.ast.*; +import com.annimon.ownlang.parser.error.ParseError; +import com.annimon.ownlang.parser.error.ParseErrors; import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; @@ -19,7 +22,7 @@ public final class Parser { final Parser parser = new Parser(tokens); final Statement program = parser.parse(); if (parser.getParseErrors().hasErrors()) { - throw new ParseException(parser.getParseErrors().toString()); + throw new OwnLangParserException(parser.getParseErrors()); } return program; } diff --git a/ownlang-parser/src/main/java/com/annimon/ownlang/parser/ast/IncludeStatement.java b/ownlang-parser/src/main/java/com/annimon/ownlang/parser/ast/IncludeStatement.java index 0c13129..3b2c5de 100644 --- a/ownlang-parser/src/main/java/com/annimon/ownlang/parser/ast/IncludeStatement.java +++ b/ownlang-parser/src/main/java/com/annimon/ownlang/parser/ast/IncludeStatement.java @@ -1,6 +1,5 @@ package com.annimon.ownlang.parser.ast; -import com.annimon.ownlang.exceptions.ParseException; import com.annimon.ownlang.parser.Lexer; import com.annimon.ownlang.parser.Parser; import com.annimon.ownlang.parser.SourceLoader; @@ -38,12 +37,7 @@ public final class IncludeStatement extends InterruptableNode implements Stateme public Statement loadProgram(String path) throws IOException { final String input = SourceLoader.readSource(path); final List tokens = Lexer.tokenize(input); - final Parser parser = new Parser(tokens); - final Statement program = parser.parse(); - if (parser.getParseErrors().hasErrors()) { - throw new ParseException(parser.getParseErrors().toString()); - } - return program; + return Parser.parse(tokens); } @Override diff --git a/ownlang-parser/src/main/java/com/annimon/ownlang/parser/ParseError.java b/ownlang-parser/src/main/java/com/annimon/ownlang/parser/error/ParseError.java similarity index 85% rename from ownlang-parser/src/main/java/com/annimon/ownlang/parser/ParseError.java rename to ownlang-parser/src/main/java/com/annimon/ownlang/parser/error/ParseError.java index d505921..8fba8ef 100644 --- a/ownlang-parser/src/main/java/com/annimon/ownlang/parser/ParseError.java +++ b/ownlang-parser/src/main/java/com/annimon/ownlang/parser/error/ParseError.java @@ -1,5 +1,6 @@ -package com.annimon.ownlang.parser; +package com.annimon.ownlang.parser.error; +import com.annimon.ownlang.parser.Range; import java.util.Collections; import java.util.List; diff --git a/ownlang-parser/src/main/java/com/annimon/ownlang/parser/ParseErrors.java b/ownlang-parser/src/main/java/com/annimon/ownlang/parser/error/ParseErrors.java similarity index 95% rename from ownlang-parser/src/main/java/com/annimon/ownlang/parser/ParseErrors.java rename to ownlang-parser/src/main/java/com/annimon/ownlang/parser/error/ParseErrors.java index d0de90e..7662742 100644 --- a/ownlang-parser/src/main/java/com/annimon/ownlang/parser/ParseErrors.java +++ b/ownlang-parser/src/main/java/com/annimon/ownlang/parser/error/ParseErrors.java @@ -1,4 +1,4 @@ -package com.annimon.ownlang.parser; +package com.annimon.ownlang.parser.error; import com.annimon.ownlang.Console; import java.util.ArrayList; diff --git a/ownlang-parser/src/test/java/com/annimon/ownlang/parser/LexerTest.java b/ownlang-parser/src/test/java/com/annimon/ownlang/parser/LexerTest.java index d3bd0ab..062cfc8 100644 --- a/ownlang-parser/src/test/java/com/annimon/ownlang/parser/LexerTest.java +++ b/ownlang-parser/src/test/java/com/annimon/ownlang/parser/LexerTest.java @@ -1,6 +1,6 @@ package com.annimon.ownlang.parser; -import com.annimon.ownlang.exceptions.LexerException; +import com.annimon.ownlang.exceptions.OwnLangParserException; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; @@ -108,7 +108,7 @@ public class LexerTest { @MethodSource("invalidData") public void testInvalidInput(String name, String input) throws IOException { assertThatThrownBy(() -> Lexer.tokenize(input)) - .isInstanceOf(LexerException.class); + .isInstanceOf(OwnLangParserException.class); } private static void assertTokens(List result, TokenType... tokenTypes) { 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 17a055e..248008c 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 @@ -2,7 +2,7 @@ package com.annimon.ownlang.utils; import com.annimon.ownlang.Console; import com.annimon.ownlang.Version; -import com.annimon.ownlang.exceptions.LexerException; +import com.annimon.ownlang.exceptions.OwnLangParserException; import com.annimon.ownlang.exceptions.StoppedException; import com.annimon.ownlang.lib.Functions; import com.annimon.ownlang.lib.UserDefinedFunction; @@ -84,7 +84,7 @@ public final class Repl { } } program.execute(); - } catch (LexerException lex) { + } catch (OwnLangParserException lex) { continue; } catch (StoppedException ex) { // skip