More informative parser errors with ranges, removed LexerException

This commit is contained in:
aNNiMON 2023-09-27 17:34:20 +03:00 committed by Victor Melnik
parent 923f0edbcb
commit ea38227b44
11 changed files with 83 additions and 74 deletions

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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)));
}
}

View File

@ -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;
}

View File

@ -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<Token> 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

View File

@ -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;

View File

@ -1,4 +1,4 @@
package com.annimon.ownlang.parser;
package com.annimon.ownlang.parser.error;
import com.annimon.ownlang.Console;
import java.util.ArrayList;

View File

@ -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<Token> result, TokenType... tokenTypes) {

View File

@ -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