Improve type assumption error messages

includes codestyle fixes
This commit is contained in:
Arne Keller 2021-07-10 13:01:06 +02:00
parent adb10a01aa
commit 301af06c69
7 changed files with 116 additions and 88 deletions

View File

@ -7,5 +7,10 @@ public enum ExpectedInput {
/**
* Any kind of lambda term.
*/
TERM
TERM,
/**
* Any kind of type.
*/
TYPE
}

View File

@ -31,12 +31,7 @@ public enum ParseError {
/**
* This error was created when parsing the type assumptions
*/
TYPE_ASSUMPTION_ERROR,
/**
* initial error type
*/
INITIAL_ERROR
TYPE_ASSUMPTION_ERROR
}
private Optional<Token> cause = Optional.empty();
@ -46,7 +41,7 @@ public enum ParseError {
private char wrongChar = '\0';
private char correctChar = '\0';
private int position = -1;
private ErrorType errorType = ErrorType.INITIAL_ERROR;
private Optional<ErrorType> errorType = Optional.empty();
/**
* Attach a token to this error.
@ -58,7 +53,7 @@ public enum ParseError {
this.cause = Optional.of(cause);
this.term = cause.getSourceText();
this.position = cause.getPos();
this.errorType = errorType;
this.errorType = Optional.of(errorType);
return this;
}
@ -123,7 +118,7 @@ public enum ParseError {
this.wrongChar = cause;
this.position = position;
this.term = term;
this.errorType = errorType;
this.errorType = Optional.of(errorType);
return this;
}
@ -172,12 +167,12 @@ public enum ParseError {
/**
* @return the error type
*/
public ErrorType getErrorType() {
public Optional<ErrorType> getErrorType() {
return errorType;
}
protected void setErrorType(ErrorType errorType) {
this.errorType = errorType;
this.errorType = Optional.of(errorType);
}
ParseError() {

View File

@ -6,6 +6,7 @@ import edu.kit.typicalc.model.type.*;
import edu.kit.typicalc.util.Result;
import java.util.*;
import java.util.function.UnaryOperator;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -119,6 +120,13 @@ public class TypeAssumptionParser {
return this;
}
ParserResult<T> modifyError(UnaryOperator<ParseError> fun) {
if (error.isPresent()) {
error = Optional.of(fun.apply(error.get()));
}
return this;
}
Optional<Token> extraToken() {
return this.extraToken;
}
@ -129,7 +137,7 @@ public class TypeAssumptionParser {
}
private static class InitialState implements ParserState<Map<VarTerm, TypeAbstraction>> {
private Map<VarTerm, TypeAbstraction> alreadyParsed = new LinkedHashMap<>();
private final Map<VarTerm, TypeAbstraction> alreadyParsed;
InitialState(Map<VarTerm, TypeAbstraction> alreadyParsed) {
this.alreadyParsed = alreadyParsed;
@ -150,8 +158,9 @@ public class TypeAssumptionParser {
}
private static class ExpectingColon implements ParserState<Map<VarTerm, TypeAbstraction>> {
private Map<VarTerm, TypeAbstraction> alreadyParsed;
private final Map<VarTerm, TypeAbstraction> alreadyParsed;
private final VarTerm var;
ExpectingColon(Map<VarTerm, TypeAbstraction> alreadyParsed, VarTerm var) {
this.alreadyParsed = alreadyParsed;
this.var = var;
@ -159,10 +168,9 @@ public class TypeAssumptionParser {
@Override
public ParserResult<Map<VarTerm, TypeAbstraction>> handle(Token t) {
switch (t.getType()) {
case COLON:
if (t.getType() == TokenType.COLON) {
return new ParserResult<>(new ExpectingTypeDef(alreadyParsed, var));
default:
} else {
return new ParserResult<>(ParseError.UNEXPECTED_TOKEN
.withToken(t, ParseError.ErrorType.TYPE_ASSUMPTION_ERROR));
}
@ -174,6 +182,7 @@ public class TypeAssumptionParser {
private final Set<TypeVariable> typeVariables;
private final VarTerm var;
private Optional<ParserState<Type>> state = Optional.empty();
ExpectingTypeDef(Map<VarTerm, TypeAbstraction> alreadyParsed, VarTerm var) {
this.alreadyParsed = alreadyParsed;
this.typeVariables = new TreeSet<>();
@ -188,15 +197,14 @@ public class TypeAssumptionParser {
@Override
public ParserResult<Map<VarTerm, TypeAbstraction>> handle(Token t) {
switch (t.getType()) {
case UNIVERSAL_QUANTIFIER:
if (t.getType() == TokenType.UNIVERSAL_QUANTIFIER) {
if (typeVariables.isEmpty()) {
return new ParserResult<>(new ExpectingTypeVariables(alreadyParsed, var));
} else {
return new ParserResult<>(ParseError.UNEXPECTED_TOKEN
.withToken(t, ParseError.ErrorType.TYPE_ASSUMPTION_ERROR));
}
default:
}
if (state.isPresent()) {
ParserState<Type> status = state.get();
// already parsing type
@ -215,13 +223,12 @@ public class TypeAssumptionParser {
// attempt to parse as type
ParserResult<Type> result = new ParseTypeState1(alreadyParsed.size()).handle(t);
if (result.isError()) {
return result.castError();
return result.modifyError(error -> error.expectedType(TokenType.UNIVERSAL_QUANTIFIER)).castError();
}
state = Optional.of(result.getState());
return new ParserResult<>(this);
}
}
}
private static class ExpectingCommaBeforeNextType implements ParserState<Map<VarTerm, TypeAbstraction>> {
private final Map<VarTerm, TypeAbstraction> alreadyParsed;
@ -343,7 +350,8 @@ public class TypeAssumptionParser {
return new ParserResult<>(parsedType.get()).attachToken(t);
}
return new ParserResult<>(ParseError.UNEXPECTED_TOKEN
.withToken(t, ParseError.ErrorType.TYPE_ASSUMPTION_ERROR));
.withToken(t, ParseError.ErrorType.TYPE_ASSUMPTION_ERROR)
.expectedInput(ExpectedInput.TYPE));
default:
if (state.isPresent()) {
return handleInner(t);
@ -408,14 +416,11 @@ public class TypeAssumptionParser {
@Override
public ParserResult<Type> handle(Token t) {
switch (t.getType()) {
case ARROW:
if (state.isEmpty()) {
if (t.getType() == TokenType.ARROW && state.isEmpty()) {
// try parsing remainder as type
state = Optional.of(new ParseTypeState1(typeVariableUniqueIndex));
return new ParserResult<>(this);
}
default:
if (state.isPresent()) {
ParserState<Type> status = state.get();
// already parsing type
@ -434,13 +439,13 @@ public class TypeAssumptionParser {
}
}
}
}
private static class ExpectingTypeVariables implements ParserState<Map<VarTerm, TypeAbstraction>> {
private final Map<VarTerm, TypeAbstraction> alreadyParsed;
private final VarTerm var;
private final Set<TypeVariable> variables = new TreeSet<>();
private boolean expectCommaOrDot = false;
ExpectingTypeVariables(Map<VarTerm, TypeAbstraction> alreadyParsed, VarTerm var) {
this.alreadyParsed = alreadyParsed;
this.var = var;

View File

@ -47,6 +47,14 @@ public class ErrorView extends VerticalLayout implements LocaleChangeObserver {
add(container, infoContent);
}
private String errorMessageForToken(Token token) {
if (token.getType() != Token.TokenType.EOF) {
return getTranslation("error.wrongCharacter") + token.getText();
} else {
return getTranslation("error.tooFewTokensHelp");
}
}
private Component buildErrorMessage(ParseError error) {
VerticalLayout additionalInformation = new VerticalLayout();
additionalInformation.setId(ADDITIONAL_INFO_ID);
@ -54,26 +62,21 @@ public class ErrorView extends VerticalLayout implements LocaleChangeObserver {
Paragraph summary = new Paragraph(getTranslation("root." + error.toString()));
summary.setId(ERROR_SUMMARY_ID);
String term = error.getTerm();
String descriptionForError;
ParseError.ErrorType errorType = error.getErrorType();
if (errorType == ParseError.ErrorType.TERM_ERROR) {
String descriptionForError = null;
Optional<ParseError.ErrorType> errorType = error.getErrorType();
if (errorType.isEmpty()) {
throw new IllegalStateException();
}
if (errorType.get() == ParseError.ErrorType.TERM_ERROR) {
descriptionForError = "error.termForError";
} else if (errorType == ParseError.ErrorType.TYPE_ASSUMPTION_ERROR) {
} else if (errorType.get() == ParseError.ErrorType.TYPE_ASSUMPTION_ERROR) {
descriptionForError = "error.typeAssumptionForError";
} else {
//should never happen
descriptionForError = "error";
}
switch (error) {
case UNEXPECTED_TOKEN:
Optional<Token> cause = error.getCause();
if (cause.isPresent()) {
String errorText;
if (cause.get().getType() != Token.TokenType.EOF) {
errorText = getTranslation("error.wrongCharacter") + cause.get().getText();
} else {
errorText = getTranslation("error.tooFewTokensHelp");
}
String errorText = errorMessageForToken(cause.get());
additionalInformation.add(new Span(new Pre(getTranslation(descriptionForError) + term
+ "\n" + " ".repeat(Math.max(getTranslation(descriptionForError).length(),
cause.get().getPos() + getTranslation(descriptionForError).length()))

View File

@ -128,6 +128,7 @@ tokentype.EQUALS==
tokentype.ARROW=->
tokentype.EOF=Ende der Eingabe
expectedinput.TERM=beliebiger Term
expectedinput.TYPE=beliebiger Typ
root.or=oder
root.slideExp=Typicalc ist eine Anwendung zur Visualisierung von Typinferenz. In der folgenden Slideshow wird ein \
Beispielszenario mit den wichtigsten Funktionen der Website vorgeführt. Das relevante Bedienelement ist jeweils mit \

View File

@ -120,6 +120,7 @@ tokentype.EQUALS==
tokentype.ARROW=->
tokentype.EOF=End of input
expectedinput.TERM=any lambda term
expectedinput.TYPE=any type
root.or=or
root.slideExp=Typicalc is an application for the visualisation of type inference. The slideshow below the text \
demonstrates the most important features of the website. In each slide the relevant operating element is surrounded \

View File

@ -6,7 +6,6 @@ import edu.kit.typicalc.util.Result;
import org.junit.jupiter.api.Test;
import java.util.*;
import java.util.stream.Collectors;
import static edu.kit.typicalc.model.type.NamedType.BOOLEAN;
import static edu.kit.typicalc.model.type.NamedType.INT;
@ -328,4 +327,23 @@ class TypeAssumptionParserTest {
assertEquals(entry.getValue(), type.unwrap().get(new VarTerm("type1")));
}
}
@Test
void errorCase1() {
ParseError e = parse("id: a ->");
assertEquals(ExpectedInput.TYPE, e.getExpectedInput().get());
assertEquals(Token.TokenType.EOF, e.getCause().get().getType());
}
@Test
void errorCase2() {
ParseError e = parse("id: ");
assertEquals(ExpectedInput.TYPE, e.getExpectedInput().get());
assertTrue(e.getExpected().get().contains(Token.TokenType.UNIVERSAL_QUANTIFIER));
assertEquals(Token.TokenType.EOF, e.getCause().get().getType());
}
static ParseError parse(String input) {
return new TypeAssumptionParser().parse(input).unwrapError();
}
}