mirror of
https://github.com/aNNiMON/Own-Programming-Language-Tutorial.git
synced 2024-09-20 08:44:20 +03:00
More informative parser errors with ranges, removed LexerException
This commit is contained in:
parent
923f0edbcb
commit
ea38227b44
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
|
@ -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;
|
@ -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) {
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user