From 301af06c690fdba089109ca81dc3181ee943cac6 Mon Sep 17 00:00:00 2001 From: Arne Keller Date: Sat, 10 Jul 2021 13:01:06 +0200 Subject: [PATCH] Improve type assumption error messages includes codestyle fixes --- .../typicalc/model/parser/ExpectedInput.java | 7 +- .../kit/typicalc/model/parser/ParseError.java | 17 +-- .../model/parser/TypeAssumptionParser.java | 129 +++++++++--------- .../view/content/errorcontent/ErrorView.java | 29 ++-- .../language/translation_de.properties | 1 + .../language/translation_en.properties | 1 + .../parser/TypeAssumptionParserTest.java | 20 ++- 7 files changed, 116 insertions(+), 88 deletions(-) diff --git a/src/main/java/edu/kit/typicalc/model/parser/ExpectedInput.java b/src/main/java/edu/kit/typicalc/model/parser/ExpectedInput.java index db0ae5c..8fb4556 100644 --- a/src/main/java/edu/kit/typicalc/model/parser/ExpectedInput.java +++ b/src/main/java/edu/kit/typicalc/model/parser/ExpectedInput.java @@ -7,5 +7,10 @@ public enum ExpectedInput { /** * Any kind of lambda term. */ - TERM + TERM, + + /** + * Any kind of type. + */ + TYPE } diff --git a/src/main/java/edu/kit/typicalc/model/parser/ParseError.java b/src/main/java/edu/kit/typicalc/model/parser/ParseError.java index 3a4c3af..a180efa 100644 --- a/src/main/java/edu/kit/typicalc/model/parser/ParseError.java +++ b/src/main/java/edu/kit/typicalc/model/parser/ParseError.java @@ -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 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 = 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 getErrorType() { return errorType; } protected void setErrorType(ErrorType errorType) { - this.errorType = errorType; + this.errorType = Optional.of(errorType); } ParseError() { diff --git a/src/main/java/edu/kit/typicalc/model/parser/TypeAssumptionParser.java b/src/main/java/edu/kit/typicalc/model/parser/TypeAssumptionParser.java index 0c853a2..387124e 100644 --- a/src/main/java/edu/kit/typicalc/model/parser/TypeAssumptionParser.java +++ b/src/main/java/edu/kit/typicalc/model/parser/TypeAssumptionParser.java @@ -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 modifyError(UnaryOperator fun) { + if (error.isPresent()) { + error = Optional.of(fun.apply(error.get())); + } + return this; + } + Optional extraToken() { return this.extraToken; } @@ -129,7 +137,7 @@ public class TypeAssumptionParser { } private static class InitialState implements ParserState> { - private Map alreadyParsed = new LinkedHashMap<>(); + private final Map alreadyParsed; InitialState(Map alreadyParsed) { this.alreadyParsed = alreadyParsed; @@ -150,8 +158,9 @@ public class TypeAssumptionParser { } private static class ExpectingColon implements ParserState> { - private Map alreadyParsed; + private final Map alreadyParsed; private final VarTerm var; + ExpectingColon(Map alreadyParsed, VarTerm var) { this.alreadyParsed = alreadyParsed; this.var = var; @@ -159,12 +168,11 @@ public class TypeAssumptionParser { @Override public ParserResult> handle(Token t) { - switch (t.getType()) { - case COLON: - return new ParserResult<>(new ExpectingTypeDef(alreadyParsed, var)); - default: - return new ParserResult<>(ParseError.UNEXPECTED_TOKEN - .withToken(t, ParseError.ErrorType.TYPE_ASSUMPTION_ERROR)); + if (t.getType() == TokenType.COLON) { + return new ParserResult<>(new ExpectingTypeDef(alreadyParsed, var)); + } else { + return new ParserResult<>(ParseError.UNEXPECTED_TOKEN + .withToken(t, ParseError.ErrorType.TYPE_ASSUMPTION_ERROR)); } } } @@ -174,6 +182,7 @@ public class TypeAssumptionParser { private final Set typeVariables; private final VarTerm var; private Optional> state = Optional.empty(); + ExpectingTypeDef(Map alreadyParsed, VarTerm var) { this.alreadyParsed = alreadyParsed; this.typeVariables = new TreeSet<>(); @@ -188,38 +197,36 @@ public class TypeAssumptionParser { @Override public ParserResult> handle(Token t) { - switch (t.getType()) { - case 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 status = state.get(); - // already parsing type - ParserResult result = status.handle(t); - if (result.isResult()) { - alreadyParsed.put(var, new TypeAbstraction(result.getResult(), typeVariables)); - return new ParserResult<>(new ExpectingCommaBeforeNextType(alreadyParsed)) - .copyToken(result); - } else if (result.isError()) { - return result.castError(); - } else { - state = Optional.of(result.getState()); - return new ParserResult<>(this); - } - } - // attempt to parse as type - ParserResult result = new ParseTypeState1(alreadyParsed.size()).handle(t); - if (result.isError()) { - return result.castError(); - } + 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)); + } + } + if (state.isPresent()) { + ParserState status = state.get(); + // already parsing type + ParserResult result = status.handle(t); + if (result.isResult()) { + alreadyParsed.put(var, new TypeAbstraction(result.getResult(), typeVariables)); + return new ParserResult<>(new ExpectingCommaBeforeNextType(alreadyParsed)) + .copyToken(result); + } else if (result.isError()) { + return result.castError(); + } else { state = Optional.of(result.getState()); return new ParserResult<>(this); + } } + // attempt to parse as type + ParserResult result = new ParseTypeState1(alreadyParsed.size()).handle(t); + if (result.isError()) { + return result.modifyError(error -> error.expectedType(TokenType.UNIVERSAL_QUANTIFIER)).castError(); + } + state = Optional.of(result.getState()); + return new ParserResult<>(this); } } @@ -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,30 +416,26 @@ public class TypeAssumptionParser { @Override public ParserResult handle(Token t) { - switch (t.getType()) { - case ARROW: - if (state.isEmpty()) { - // try parsing remainder as type - state = Optional.of(new ParseTypeState1(typeVariableUniqueIndex)); - return new ParserResult<>(this); - } - default: - if (state.isPresent()) { - ParserState status = state.get(); - // already parsing type - ParserResult result = status.handle(t); - if (result.isResult()) { - return result; - } else if (result.isError()) { - return result.castError(); - } else { - state = Optional.of(result.getState()); - return new ParserResult<>(this); - } - } else { - return new ParserResult<>(ParseError.UNEXPECTED_TOKEN - .withToken(t, ParseError.ErrorType.TYPE_ASSUMPTION_ERROR)); - } + if (t.getType() == TokenType.ARROW && state.isEmpty()) { + // try parsing remainder as type + state = Optional.of(new ParseTypeState1(typeVariableUniqueIndex)); + return new ParserResult<>(this); + } + if (state.isPresent()) { + ParserState status = state.get(); + // already parsing type + ParserResult result = status.handle(t); + if (result.isResult()) { + return result; + } else if (result.isError()) { + return result.castError(); + } else { + state = Optional.of(result.getState()); + return new ParserResult<>(this); + } + } else { + return new ParserResult<>(ParseError.UNEXPECTED_TOKEN + .withToken(t, ParseError.ErrorType.TYPE_ASSUMPTION_ERROR)); } } } @@ -441,6 +445,7 @@ public class TypeAssumptionParser { private final VarTerm var; private final Set variables = new TreeSet<>(); private boolean expectCommaOrDot = false; + ExpectingTypeVariables(Map alreadyParsed, VarTerm var) { this.alreadyParsed = alreadyParsed; this.var = var; diff --git a/src/main/java/edu/kit/typicalc/view/content/errorcontent/ErrorView.java b/src/main/java/edu/kit/typicalc/view/content/errorcontent/ErrorView.java index b67c8fa..3dbe2a7 100644 --- a/src/main/java/edu/kit/typicalc/view/content/errorcontent/ErrorView.java +++ b/src/main/java/edu/kit/typicalc/view/content/errorcontent/ErrorView.java @@ -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 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 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())) diff --git a/src/main/resources/language/translation_de.properties b/src/main/resources/language/translation_de.properties index e79f97b..cbc45e6 100644 --- a/src/main/resources/language/translation_de.properties +++ b/src/main/resources/language/translation_de.properties @@ -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 \ diff --git a/src/main/resources/language/translation_en.properties b/src/main/resources/language/translation_en.properties index aafcb52..f2609e1 100644 --- a/src/main/resources/language/translation_en.properties +++ b/src/main/resources/language/translation_en.properties @@ -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 \ diff --git a/src/test/java/edu/kit/typicalc/model/parser/TypeAssumptionParserTest.java b/src/test/java/edu/kit/typicalc/model/parser/TypeAssumptionParserTest.java index cb7bd50..17bd733 100644 --- a/src/test/java/edu/kit/typicalc/model/parser/TypeAssumptionParserTest.java +++ b/src/test/java/edu/kit/typicalc/model/parser/TypeAssumptionParserTest.java @@ -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(); + } }