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; package com.annimon.ownlang.exceptions;
/** import com.annimon.ownlang.parser.error.ParseError;
* Base type for all lexer and parser exceptions import com.annimon.ownlang.parser.error.ParseErrors;
*/
public abstract class OwnLangParserException extends RuntimeException {
public OwnLangParserException() { /**
super(); * Single Exception for Lexer and Parser errors
*/
public class OwnLangParserException extends RuntimeException {
private final ParseErrors parseErrors;
public OwnLangParserException(ParseError parseError) {
super(parseError.toString());
this.parseErrors = new ParseErrors();
parseErrors.add(parseError);;
} }
public OwnLangParserException(String message) { public OwnLangParserException(ParseErrors parseErrors) {
super(message); super(parseErrors.toString());
this.parseErrors = parseErrors;
}
public ParseErrors getParseErrors() {
return parseErrors;
} }
} }

View File

@ -1,34 +1,18 @@
package com.annimon.ownlang.exceptions; package com.annimon.ownlang.exceptions;
import com.annimon.ownlang.parser.Pos;
import com.annimon.ownlang.parser.Range; import com.annimon.ownlang.parser.Range;
/** /**
* *
* @author aNNiMON * @author aNNiMON
*/ */
public final class ParseException extends OwnLangParserException { public final class ParseException extends BaseParserException {
private final Range range;
public ParseException(String message) { public ParseException(String message) {
this(message, Range.ZERO); super(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));
} }
public ParseException(String message, Range range) { public ParseException(String message, Range range) {
super(message); super(message, range);
this.range = range;
}
public Range getRange() {
return range;
} }
} }

View File

@ -1,6 +1,7 @@
package com.annimon.ownlang.parser; 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.*; import java.util.*;
/** /**
@ -146,7 +147,7 @@ public final class Lexer {
else if (current == '#') tokenizeHexNumber(1); else if (current == '#') tokenizeHexNumber(1);
else if (current == ';') skip(); // ignore semicolon else if (current == ';') skip(); // ignore semicolon
else if (current == '\0') break; else if (current == '\0') break;
else throw error("Unknown token " + current); else throw error("Unknown token " + current, markPos());
} }
return tokens; return tokens;
} }
@ -164,11 +165,11 @@ public final class Lexer {
while (true) { while (true) {
if (current == '.') { if (current == '.') {
decimal = true; decimal = true;
if (hasDot) throw error("Invalid float number " + buffer); if (hasDot) throw error("Invalid float number " + buffer, startPos);
hasDot = true; hasDot = true;
} else if (current == 'e' || current == 'E') { } else if (current == 'e' || current == 'E') {
decimal = true; decimal = true;
int exp = subTokenizeScientificNumber(); int exp = subTokenizeScientificNumber(startPos);
buffer.append(current).append(exp); buffer.append(current).append(exp);
break; break;
} else if (!Character.isDigit(current)) { } else if (!Character.isDigit(current)) {
@ -184,7 +185,7 @@ public final class Lexer {
} }
} }
private int subTokenizeScientificNumber() { private int subTokenizeScientificNumber(Pos startPos) {
int sign = 1; int sign = 1;
switch (next()) { switch (next()) {
case '-': sign = -1; case '-': sign = -1;
@ -204,10 +205,10 @@ public final class Lexer {
current = next(); current = next();
position++; 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 (position >= 4) {
if (sign > 0) throw error("Float number too large"); if (sign > 0) throw error("Float number too large", startPos, markEndPos());
else throw error("Float number too small"); else throw error("Float number too small", startPos, markEndPos());
} }
return sign * result; return sign * result;
} }
@ -227,8 +228,8 @@ public final class Lexer {
current = next(); current = next();
} }
if (buffer.isEmpty()) throw error("Empty HEX value"); if (buffer.isEmpty()) throw error("Empty HEX value", startPos);
if (peek(-1) == '_') throw error("HEX value cannot end with _"); if (peek(-1) == '_') throw error("HEX value cannot end with _", startPos, markEndPos());
addToken(TokenType.HEX_NUMBER, buffer.toString(), startPos); addToken(TokenType.HEX_NUMBER, buffer.toString(), startPos);
} }
@ -290,8 +291,9 @@ public final class Lexer {
final var buffer = createBuffer(); final var buffer = createBuffer();
char current = peek(0); char current = peek(0);
while (current != '`') { while (current != '`') {
if (current == '\0') throw error("Reached end of file while parsing extended word."); if ("\r\n\0".indexOf(current) != -1) {
if (current == '\n' || current == '\r') throw error("Reached end of line while parsing extended word."); throw error("Reached end of line while parsing extended word.", startPos, markEndPos());
}
buffer.append(current); buffer.append(current);
current = next(); current = next();
} }
@ -341,7 +343,7 @@ public final class Lexer {
continue; continue;
} }
if (current == '"') break; 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); buffer.append(current);
current = next(); current = next();
} }
@ -360,11 +362,14 @@ public final class Lexer {
} }
private void tokenizeMultilineComment() { private void tokenizeMultilineComment() {
final Pos startPos = markPos();
skip(); // / skip(); // /
skip(); // * skip(); // *
char current = peek(0); char current = peek(0);
while (current != '*' || peek(1) != '/') { 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(); current = next();
} }
skip(); // * skip(); // *
@ -388,6 +393,10 @@ public final class Lexer {
return new Pos(row, col); return new Pos(row, col);
} }
private Pos markEndPos() {
return new Pos(row, Math.max(0, col - 1));
}
private void skip() { private void skip() {
if (pos >= length) return; if (pos >= length) return;
final char result = input.charAt(pos); final char result = input.charAt(pos);
@ -417,7 +426,11 @@ public final class Lexer {
tokens.add(new Token(type, text, startRow)); tokens.add(new Token(type, text, startRow));
} }
private LexerException error(String text) { private OwnLangParserException error(String text, Pos position) {
return new LexerException(text, markPos()); 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; package com.annimon.ownlang.parser;
import com.annimon.ownlang.exceptions.OwnLangParserException;
import com.annimon.ownlang.exceptions.ParseException; import com.annimon.ownlang.exceptions.ParseException;
import com.annimon.ownlang.lib.NumberValue; import com.annimon.ownlang.lib.NumberValue;
import com.annimon.ownlang.lib.StringValue; import com.annimon.ownlang.lib.StringValue;
import com.annimon.ownlang.lib.UserDefinedFunction; import com.annimon.ownlang.lib.UserDefinedFunction;
import com.annimon.ownlang.parser.ast.*; 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.*;
import java.util.function.Function; import java.util.function.Function;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -19,7 +22,7 @@ public final class Parser {
final Parser parser = new Parser(tokens); final Parser parser = new Parser(tokens);
final Statement program = parser.parse(); final Statement program = parser.parse();
if (parser.getParseErrors().hasErrors()) { if (parser.getParseErrors().hasErrors()) {
throw new ParseException(parser.getParseErrors().toString()); throw new OwnLangParserException(parser.getParseErrors());
} }
return program; return program;
} }

View File

@ -1,6 +1,5 @@
package com.annimon.ownlang.parser.ast; package com.annimon.ownlang.parser.ast;
import com.annimon.ownlang.exceptions.ParseException;
import com.annimon.ownlang.parser.Lexer; import com.annimon.ownlang.parser.Lexer;
import com.annimon.ownlang.parser.Parser; import com.annimon.ownlang.parser.Parser;
import com.annimon.ownlang.parser.SourceLoader; 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 { public Statement loadProgram(String path) throws IOException {
final String input = SourceLoader.readSource(path); final String input = SourceLoader.readSource(path);
final List<Token> tokens = Lexer.tokenize(input); final List<Token> tokens = Lexer.tokenize(input);
final Parser parser = new Parser(tokens); return Parser.parse(tokens);
final Statement program = parser.parse();
if (parser.getParseErrors().hasErrors()) {
throw new ParseException(parser.getParseErrors().toString());
}
return program;
} }
@Override @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.Collections;
import java.util.List; 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 com.annimon.ownlang.Console;
import java.util.ArrayList; import java.util.ArrayList;

View File

@ -1,6 +1,6 @@
package com.annimon.ownlang.parser; 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.api.Test;
import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.Arguments;
@ -108,7 +108,7 @@ public class LexerTest {
@MethodSource("invalidData") @MethodSource("invalidData")
public void testInvalidInput(String name, String input) throws IOException { public void testInvalidInput(String name, String input) throws IOException {
assertThatThrownBy(() -> Lexer.tokenize(input)) assertThatThrownBy(() -> Lexer.tokenize(input))
.isInstanceOf(LexerException.class); .isInstanceOf(OwnLangParserException.class);
} }
private static void assertTokens(List<Token> result, TokenType... tokenTypes) { 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.Console;
import com.annimon.ownlang.Version; 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.exceptions.StoppedException;
import com.annimon.ownlang.lib.Functions; import com.annimon.ownlang.lib.Functions;
import com.annimon.ownlang.lib.UserDefinedFunction; import com.annimon.ownlang.lib.UserDefinedFunction;
@ -84,7 +84,7 @@ public final class Repl {
} }
} }
program.execute(); program.execute();
} catch (LexerException lex) { } catch (OwnLangParserException lex) {
continue; continue;
} catch (StoppedException ex) { } catch (StoppedException ex) {
// skip // skip