mirror of
https://github.com/aNNiMON/Own-Programming-Language-Tutorial.git
synced 2024-09-20 00:34:20 +03:00
Better explaining parse errors
This commit is contained in:
parent
95d32a6c91
commit
da950f96c2
@ -1,16 +1,35 @@
|
|||||||
package com.annimon.ownlang.exceptions;
|
package com.annimon.ownlang.exceptions;
|
||||||
|
|
||||||
|
import com.annimon.ownlang.parser.Pos;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author aNNiMON
|
* @author aNNiMON
|
||||||
*/
|
*/
|
||||||
public final class ParseException extends RuntimeException {
|
public final class ParseException extends RuntimeException {
|
||||||
|
|
||||||
public ParseException() {
|
private final Pos start;
|
||||||
super();
|
private final Pos end;
|
||||||
|
|
||||||
|
public ParseException(String message) {
|
||||||
|
this(message, Pos.ZERO, Pos.ZERO);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ParseException(String string) {
|
public ParseException(String message, Pos pos) {
|
||||||
super(string);
|
this(message, pos, pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ParseException(String message, Pos start, Pos end) {
|
||||||
|
super(message);
|
||||||
|
this.start = start;
|
||||||
|
this.end = end;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Pos getStart() {
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Pos getEnd() {
|
||||||
|
return end;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,6 +4,6 @@ public record ParseError(Exception exception, Pos pos) {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "ParseError on line " + pos.row() + ": " + exception;
|
return "Error on line " + pos.row() + ": " + exception;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@ 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 java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.function.Function;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@ -70,8 +72,11 @@ public final class Parser {
|
|||||||
while (!match(TokenType.EOF)) {
|
while (!match(TokenType.EOF)) {
|
||||||
try {
|
try {
|
||||||
result.add(statement());
|
result.add(statement());
|
||||||
|
} catch (ParseException parseException) {
|
||||||
|
parseErrors.add(parseException, parseException.getStart());
|
||||||
|
recover();
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
parseErrors.add(ex, getErrorPos());
|
parseErrors.add(ex, getPos());
|
||||||
recover();
|
recover();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -79,7 +84,7 @@ public final class Parser {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Pos getErrorPos() {
|
private Pos getPos() {
|
||||||
if (size == 0) return new Pos(0, 0);
|
if (size == 0) return new Pos(0, 0);
|
||||||
if (pos >= size) return tokens.get(size - 1).pos();
|
if (pos >= size) return tokens.get(size - 1).pos();
|
||||||
return tokens.get(pos).pos();
|
return tokens.get(pos).pos();
|
||||||
@ -166,7 +171,8 @@ public final class Parser {
|
|||||||
private UseStatement useStatement() {
|
private UseStatement useStatement() {
|
||||||
final var modules = new HashSet<String>();
|
final var modules = new HashSet<String>();
|
||||||
do {
|
do {
|
||||||
modules.add(consume(TokenType.WORD).text());
|
modules.add(consumeOrExplainError(TokenType.WORD,
|
||||||
|
Parser::explainUseStatementError).text());
|
||||||
} while (match(TokenType.COMMA));
|
} while (match(TokenType.COMMA));
|
||||||
return new UseStatement(modules);
|
return new UseStatement(modules);
|
||||||
}
|
}
|
||||||
@ -179,21 +185,26 @@ public final class Parser {
|
|||||||
if (expression instanceof Statement statement) {
|
if (expression instanceof Statement statement) {
|
||||||
return statement;
|
return statement;
|
||||||
}
|
}
|
||||||
throw new ParseException("Unknown statement: " + get(0));
|
throw error("Unknown statement: " + get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
private DestructuringAssignmentStatement destructuringAssignment() {
|
private DestructuringAssignmentStatement destructuringAssignment() {
|
||||||
// extract(var1, var2, ...) = ...
|
// extract(var1, var2, ...) = ...
|
||||||
|
final var startPos = getPos();
|
||||||
consume(TokenType.LPAREN);
|
consume(TokenType.LPAREN);
|
||||||
final List<String> variables = new ArrayList<>();
|
final List<String> variables = new ArrayList<>();
|
||||||
while (!match(TokenType.RPAREN)) {
|
while (!match(TokenType.RPAREN)) {
|
||||||
if (lookMatch(0, TokenType.WORD)) {
|
final Token current = get(0);
|
||||||
variables.add(consume(TokenType.WORD).text());
|
variables.add(switch (current.type()) {
|
||||||
} else {
|
case WORD -> consume(TokenType.WORD).text();
|
||||||
variables.add(null);
|
case COMMA -> null;
|
||||||
}
|
default -> throw error(errorUnexpectedTokens(current, TokenType.WORD, TokenType.COMMA));
|
||||||
|
});
|
||||||
match(TokenType.COMMA);
|
match(TokenType.COMMA);
|
||||||
}
|
}
|
||||||
|
if (variables.isEmpty() || variables.stream().allMatch(Objects::isNull)) {
|
||||||
|
throw error(errorDestructuringAssignmentEmpty(), startPos, getPos());
|
||||||
|
}
|
||||||
consume(TokenType.EQ);
|
consume(TokenType.EQ);
|
||||||
return new DestructuringAssignmentStatement(variables, expression());
|
return new DestructuringAssignmentStatement(variables, expression());
|
||||||
}
|
}
|
||||||
@ -299,7 +310,7 @@ public final class Parser {
|
|||||||
} else if (!startsOptionalArgs) {
|
} else if (!startsOptionalArgs) {
|
||||||
arguments.addRequired(name);
|
arguments.addRequired(name);
|
||||||
} else {
|
} else {
|
||||||
throw new ParseException("Required argument cannot be after optional");
|
throw error(errorRequiredArgumentAfterOptional());
|
||||||
}
|
}
|
||||||
match(TokenType.COMMA);
|
match(TokenType.COMMA);
|
||||||
}
|
}
|
||||||
@ -374,7 +385,7 @@ public final class Parser {
|
|||||||
private MatchExpression match() {
|
private MatchExpression match() {
|
||||||
// match expression {
|
// match expression {
|
||||||
// case pattern1: result1
|
// case pattern1: result1
|
||||||
// case pattern2 if extr: result2
|
// case pattern2 if expr: result2
|
||||||
// }
|
// }
|
||||||
final Expression expression = expression();
|
final Expression expression = expression();
|
||||||
consume(TokenType.LBRACE);
|
consume(TokenType.LBRACE);
|
||||||
@ -425,7 +436,7 @@ public final class Parser {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (pattern == null) {
|
if (pattern == null) {
|
||||||
throw new ParseException("Wrong pattern in match expression: " + current);
|
throw error("Wrong pattern in match expression: " + current);
|
||||||
}
|
}
|
||||||
if (match(TokenType.IF)) {
|
if (match(TokenType.IF)) {
|
||||||
// case e if e > 0:
|
// case e if e > 0:
|
||||||
@ -461,7 +472,7 @@ public final class Parser {
|
|||||||
if (fieldDeclaration != null) {
|
if (fieldDeclaration != null) {
|
||||||
classDeclaration.addField(fieldDeclaration);
|
classDeclaration.addField(fieldDeclaration);
|
||||||
} else {
|
} else {
|
||||||
throw new ParseException("Class can contain only assignments and function declarations");
|
throw error("Class can contain only assignments and function declarations");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} while (!match(TokenType.RBRACE));
|
} while (!match(TokenType.RBRACE));
|
||||||
@ -873,12 +884,12 @@ public final class Parser {
|
|||||||
}
|
}
|
||||||
return strExpr;
|
return strExpr;
|
||||||
}
|
}
|
||||||
throw new ParseException("Unknown expression: " + current);
|
throw error("Unknown expression: " + current);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Number createNumber(String text, int radix) {
|
private Number createNumber(String text, int radix) {
|
||||||
// Double
|
// Double
|
||||||
if (text.contains(".")) {
|
if (text.contains(".") || text.contains("e") || text.contains("E")) {
|
||||||
return Double.parseDouble(text);
|
return Double.parseDouble(text);
|
||||||
}
|
}
|
||||||
// Integer
|
// Integer
|
||||||
@ -889,13 +900,23 @@ public final class Parser {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Token consume(TokenType type) {
|
private Token consume(TokenType expectedType) {
|
||||||
final Token current = get(0);
|
final Token actual = get(0);
|
||||||
if (type != current.type()) {
|
if (expectedType != actual.type()) {
|
||||||
throw new ParseException("Token " + current + " doesn't match " + type);
|
throw error(errorUnexpectedToken(actual, expectedType));
|
||||||
}
|
}
|
||||||
pos++;
|
pos++;
|
||||||
return current;
|
return actual;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Token consumeOrExplainError(TokenType expectedType, Function<Token, String> errorMessageFunction) {
|
||||||
|
final Token actual = get(0);
|
||||||
|
if (expectedType != actual.type()) {
|
||||||
|
throw error(errorUnexpectedToken(actual, expectedType)
|
||||||
|
+ errorMessageFunction.apply(actual));
|
||||||
|
}
|
||||||
|
pos++;
|
||||||
|
return actual;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean match(TokenType type) {
|
private boolean match(TokenType type) {
|
||||||
@ -916,4 +937,38 @@ public final class Parser {
|
|||||||
if (position >= size) return EOF;
|
if (position >= size) return EOF;
|
||||||
return tokens.get(position);
|
return tokens.get(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private ParseException error(String message) {
|
||||||
|
return new ParseException(message, getPos());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ParseException error(String message, Pos start, Pos end) {
|
||||||
|
return new ParseException(message, start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String errorUnexpectedToken(Token actual, TokenType expectedType) {
|
||||||
|
return "Expected token with type " + expectedType + ", but found " + actual.shortDescription();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String errorUnexpectedTokens(Token actual, TokenType... expectedTypes) {
|
||||||
|
String tokenTypes = Arrays.stream(expectedTypes).map(Enum::toString).collect(Collectors.joining(", "));
|
||||||
|
return "Expected tokens with types one of " + tokenTypes + ", but found " + actual.shortDescription();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String errorDestructuringAssignmentEmpty() {
|
||||||
|
return "Destructuring assignment should contain at least one variable name to assign." +
|
||||||
|
"\nCorrect syntax: extract(v1, , , v4) = ";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String errorRequiredArgumentAfterOptional() {
|
||||||
|
return "Required argument cannot be placed after optional.";
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String explainUseStatementError(Token current) {
|
||||||
|
String example = current.type().equals(TokenType.TEXT)
|
||||||
|
? "use " + current.text()
|
||||||
|
: "use std, math";
|
||||||
|
return "\nNote: as of OwnLang 2.0.0 use statement simplifies modules list syntax. " +
|
||||||
|
"Correct syntax: " + example;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package com.annimon.ownlang.parser;
|
package com.annimon.ownlang.parser;
|
||||||
|
|
||||||
public record Pos(int row, int col) {
|
public record Pos(int row, int col) {
|
||||||
|
public static final Pos UNKNOWN = new Pos(-1, -1);
|
||||||
|
public static final Pos ZERO = new Pos(0, 0);
|
||||||
|
|
||||||
public Pos normalize() {
|
public Pos normalize() {
|
||||||
return new Pos(Math.max(0, row - 1), Math.max(0, col - 1));
|
return new Pos(Math.max(0, row - 1), Math.max(0, col - 1));
|
||||||
|
@ -5,6 +5,10 @@ package com.annimon.ownlang.parser;
|
|||||||
*/
|
*/
|
||||||
public record Token(TokenType type, String text, Pos pos) {
|
public record Token(TokenType type, String text, Pos pos) {
|
||||||
|
|
||||||
|
public String shortDescription() {
|
||||||
|
return type().name() + " " + text;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return type.name() + " " + pos().format() + " " + text;
|
return type.name() + " " + pos().format() + " " + text;
|
||||||
|
Loading…
Reference in New Issue
Block a user