mirror of
https://gitlab.kit.edu/uskyk/typicalc.git
synced 2024-11-08 18:30:42 +00:00
Display expected tokens on ParseError
This commit is contained in:
parent
9767002ad1
commit
d8b547177a
@ -69,7 +69,7 @@ public class LambdaParser {
|
|||||||
TokenType current = token.getType();
|
TokenType current = token.getType();
|
||||||
Optional<ParseError> error = nextToken();
|
Optional<ParseError> error = nextToken();
|
||||||
if (current != type) {
|
if (current != type) {
|
||||||
return Optional.of(ParseError.UNEXPECTED_TOKEN.withToken(lastToken));
|
return Optional.of(ParseError.UNEXPECTED_TOKEN.withToken(lastToken).expectedType(type));
|
||||||
}
|
}
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
@ -84,11 +84,15 @@ public class LambdaParser {
|
|||||||
if (t.isError()) {
|
if (t.isError()) {
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
|
Token last = token;
|
||||||
Optional<ParseError> next = expect(TokenType.EOF);
|
Optional<ParseError> next = expect(TokenType.EOF);
|
||||||
if (next.isEmpty()) {
|
if (next.isEmpty()) {
|
||||||
return t;
|
return t;
|
||||||
}
|
}
|
||||||
return new Result<>(null, next.get());
|
return new Result<>(null,
|
||||||
|
(last.getType() == TokenType.EOF ? ParseError.TOO_FEW_TOKENS : ParseError.UNEXPECTED_TOKEN)
|
||||||
|
.withToken(last)
|
||||||
|
.expectedTypes(ATOM_START_TOKENS));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
package edu.kit.typicalc.model.parser;
|
package edu.kit.typicalc.model.parser;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Errors that can occur when parsing a lambda term.
|
* Errors that can occur when parsing a lambda term or type assumption.
|
||||||
*
|
*
|
||||||
* @see LambdaLexer
|
* @see LambdaLexer
|
||||||
* @see LambdaParser
|
* @see LambdaParser
|
||||||
@ -23,7 +27,8 @@ public enum ParseError {
|
|||||||
*/
|
*/
|
||||||
UNEXPECTED_CHARACTER;
|
UNEXPECTED_CHARACTER;
|
||||||
|
|
||||||
private Token cause = new Token(Token.TokenType.EOF, "", -1);
|
private Optional<Token> cause = Optional.empty();
|
||||||
|
private Optional<Collection<Token.TokenType>> needed = Optional.empty();
|
||||||
private char wrongChar = '\0';
|
private char wrongChar = '\0';
|
||||||
private int position = -1;
|
private int position = -1;
|
||||||
|
|
||||||
@ -34,7 +39,29 @@ public enum ParseError {
|
|||||||
* @return this object
|
* @return this object
|
||||||
*/
|
*/
|
||||||
public ParseError withToken(Token cause) {
|
public ParseError withToken(Token cause) {
|
||||||
this.cause = cause;
|
this.cause = Optional.of(cause);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attach an expected token type to this error.
|
||||||
|
*
|
||||||
|
* @param needed the required token type
|
||||||
|
* @return this object
|
||||||
|
*/
|
||||||
|
public ParseError expectedType(Token.TokenType needed) {
|
||||||
|
this.needed = Optional.of(List.of(needed));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attach expected token types to this error.
|
||||||
|
*
|
||||||
|
* @param needed the possible token types
|
||||||
|
* @return this object
|
||||||
|
*/
|
||||||
|
public ParseError expectedTypes(Collection<Token.TokenType> needed) {
|
||||||
|
this.needed = Optional.of(needed);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,16 +79,29 @@ public enum ParseError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return the token associated with this error, or null if none
|
* @return the token associated with this error
|
||||||
*/
|
*/
|
||||||
public Token getCause() {
|
public Optional<Token> getCause() {
|
||||||
return cause;
|
return cause;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the expected/possible token(s) if this error is UNEXPECTED_TOKEN
|
||||||
|
*/
|
||||||
|
public Optional<Collection<Token.TokenType>> getExpected() {
|
||||||
|
return needed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the wrong character if this error is UNEXPECTED_CHARACTER ('\0' otherwise)
|
||||||
|
*/
|
||||||
public char getWrongCharacter() {
|
public char getWrongCharacter() {
|
||||||
return wrongChar;
|
return wrongChar;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the character position if this error is UNEXPECTED_CHARACTER
|
||||||
|
*/
|
||||||
public int getPosition() {
|
public int getPosition() {
|
||||||
return position;
|
return position;
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ public class Token {
|
|||||||
* VARIABLE and NUMBER have a regular expression associated with them.
|
* VARIABLE and NUMBER have a regular expression associated with them.
|
||||||
* EOF is a special token to indicate that the end of file is reached.
|
* EOF is a special token to indicate that the end of file is reached.
|
||||||
*/
|
*/
|
||||||
enum TokenType {
|
public enum TokenType {
|
||||||
LAMBDA, // λ or a backslash
|
LAMBDA, // λ or a backslash
|
||||||
VARIABLE, // [a-z][a-zA-Z0-9]* except "let" or "in" or constants
|
VARIABLE, // [a-z][a-zA-Z0-9]* except "let" or "in" or constants
|
||||||
LET, // let
|
LET, // let
|
||||||
@ -25,7 +25,7 @@ public class Token {
|
|||||||
DOT, // .
|
DOT, // .
|
||||||
EQUALS, // =
|
EQUALS, // =
|
||||||
ARROW, // ->
|
ARROW, // ->
|
||||||
EOF // pseudo token if end of file is reached
|
EOF // pseudo token if end of input is reached
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -38,16 +38,22 @@ public class TypicalcI18NProvider implements I18NProvider {
|
|||||||
public String getTranslation(String key, Locale locale, Object... params) {
|
public String getTranslation(String key, Locale locale, Object... params) {
|
||||||
ResourceBundle bundle = ResourceBundle.getBundle(LANGUAGE_BUNDLE_PREFIX, locale);
|
ResourceBundle bundle = ResourceBundle.getBundle(LANGUAGE_BUNDLE_PREFIX, locale);
|
||||||
|
|
||||||
|
String result;
|
||||||
if (bundle.containsKey(key)) {
|
if (bundle.containsKey(key)) {
|
||||||
return bundle.getString(key);
|
result = bundle.getString(key);
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
return this.generalBundle.getString(key);
|
result = this.generalBundle.getString(key);
|
||||||
} catch (MissingResourceException exception) {
|
} catch (MissingResourceException exception) {
|
||||||
// this is only the case for untranslated texts
|
// this is only the case for untranslated texts
|
||||||
return "?[" + key + "]?";
|
return "?[" + key + "]?";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// replace placeholders {0} ...
|
||||||
|
for (int i = 0; i < params.length; i++) {
|
||||||
|
result = result.replace(String.format("{%d}", i), params[i].toString());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ package edu.kit.typicalc.view.content.errorcontent;
|
|||||||
|
|
||||||
import com.vaadin.flow.component.Component;
|
import com.vaadin.flow.component.Component;
|
||||||
import com.vaadin.flow.component.dependency.CssImport;
|
import com.vaadin.flow.component.dependency.CssImport;
|
||||||
import com.vaadin.flow.component.details.Details;
|
|
||||||
import com.vaadin.flow.component.html.Div;
|
import com.vaadin.flow.component.html.Div;
|
||||||
import com.vaadin.flow.component.html.H3;
|
import com.vaadin.flow.component.html.H3;
|
||||||
import com.vaadin.flow.component.html.Paragraph;
|
import com.vaadin.flow.component.html.Paragraph;
|
||||||
@ -11,8 +10,12 @@ import com.vaadin.flow.component.orderedlayout.VerticalLayout;
|
|||||||
import com.vaadin.flow.i18n.LocaleChangeEvent;
|
import com.vaadin.flow.i18n.LocaleChangeEvent;
|
||||||
import com.vaadin.flow.i18n.LocaleChangeObserver;
|
import com.vaadin.flow.i18n.LocaleChangeObserver;
|
||||||
import edu.kit.typicalc.model.parser.ParseError;
|
import edu.kit.typicalc.model.parser.ParseError;
|
||||||
|
import edu.kit.typicalc.model.parser.Token;
|
||||||
import edu.kit.typicalc.view.main.InfoContent;
|
import edu.kit.typicalc.view.main.InfoContent;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
@CssImport("./styles/view/error-view.css")
|
@CssImport("./styles/view/error-view.css")
|
||||||
public class ErrorView extends VerticalLayout implements LocaleChangeObserver {
|
public class ErrorView extends VerticalLayout implements LocaleChangeObserver {
|
||||||
private static final long serialVersionUID = 239587L;
|
private static final long serialVersionUID = 239587L;
|
||||||
@ -21,8 +24,6 @@ public class ErrorView extends VerticalLayout implements LocaleChangeObserver {
|
|||||||
private static final String ADDITIONAL_INFO_ID = "errorAdditionalInfo";
|
private static final String ADDITIONAL_INFO_ID = "errorAdditionalInfo";
|
||||||
private static final String ERROR_SUMMARY_ID = "errorSummary";
|
private static final String ERROR_SUMMARY_ID = "errorSummary";
|
||||||
|
|
||||||
private static final int NO_ADDITIONAL_INFO = -1;
|
|
||||||
|
|
||||||
private final H3 heading;
|
private final H3 heading;
|
||||||
private final Div errorMessage;
|
private final Div errorMessage;
|
||||||
private final ParseError error;
|
private final ParseError error;
|
||||||
@ -44,9 +45,18 @@ public class ErrorView extends VerticalLayout implements LocaleChangeObserver {
|
|||||||
Paragraph summary = new Paragraph(getTranslation("root." + error.toString()));
|
Paragraph summary = new Paragraph(getTranslation("root." + error.toString()));
|
||||||
summary.setId(ERROR_SUMMARY_ID);
|
summary.setId(ERROR_SUMMARY_ID);
|
||||||
|
|
||||||
if (error == ParseError.TOO_FEW_TOKENS) {
|
switch (error) {
|
||||||
|
case TOO_FEW_TOKENS:
|
||||||
additionalInformation.add(new Span(getTranslation("root.tooFewTokensHelp")));
|
additionalInformation.add(new Span(getTranslation("root.tooFewTokensHelp")));
|
||||||
} else if (error == ParseError.UNEXPECTED_CHARACTER) {
|
break;
|
||||||
|
case UNEXPECTED_TOKEN:
|
||||||
|
Optional<Token> cause = error.getCause();
|
||||||
|
if (cause.isPresent()) {
|
||||||
|
additionalInformation.add(new Span(getTranslation("root.wrongCharacter") + cause.get().getText()));
|
||||||
|
additionalInformation.add(new Span(getTranslation("root.position") + cause.get().getPos()));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case UNEXPECTED_CHARACTER:
|
||||||
char c = error.getWrongCharacter();
|
char c = error.getWrongCharacter();
|
||||||
if (c != '\0') {
|
if (c != '\0') {
|
||||||
additionalInformation.add(new Span(getTranslation("root.wrongCharacter") + c));
|
additionalInformation.add(new Span(getTranslation("root.wrongCharacter") + c));
|
||||||
@ -54,16 +64,29 @@ public class ErrorView extends VerticalLayout implements LocaleChangeObserver {
|
|||||||
} else {
|
} else {
|
||||||
return summary;
|
return summary;
|
||||||
}
|
}
|
||||||
} else {
|
break;
|
||||||
if (error.getCause().getPos() == NO_ADDITIONAL_INFO) {
|
default:
|
||||||
return summary;
|
throw new IllegalStateException(); // delete when updating to Java 12+
|
||||||
} else {
|
|
||||||
additionalInformation.add(new Span(getTranslation("root.wrongCharacter") + error.getCause().getText()));
|
|
||||||
additionalInformation.add(new Span(getTranslation("root.position") + error.getCause().getPos()));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Details(summary, additionalInformation);
|
// add expected tokens, if available
|
||||||
|
Optional<Collection<Token.TokenType>> expected = error.getExpected();
|
||||||
|
if (expected.isPresent()) {
|
||||||
|
Collection<Token.TokenType> possible = expected.get();
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (Token.TokenType t : possible) {
|
||||||
|
if (sb.length() > 0) {
|
||||||
|
sb.append(' ');
|
||||||
|
sb.append(getTranslation("root.or"));
|
||||||
|
sb.append(' ');
|
||||||
|
}
|
||||||
|
sb.append(getTranslation("tokentype." + t));
|
||||||
|
}
|
||||||
|
additionalInformation.add(new Span(
|
||||||
|
getTranslation("error.expectedToken", sb.toString())));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Div(summary, additionalInformation);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -100,6 +100,21 @@ root.UNEXPECTED_CHARACTER=Die Eingabe enthält ein Zeichen, welches an dieser St
|
|||||||
error.heading=Syntaktisch falsche Eingabe!
|
error.heading=Syntaktisch falsche Eingabe!
|
||||||
root.wrongCharacter=Falsches Zeichen: \u0020
|
root.wrongCharacter=Falsches Zeichen: \u0020
|
||||||
root.position=An Position: \u0020
|
root.position=An Position: \u0020
|
||||||
|
error.expectedToken=Erwartet: {0}
|
||||||
|
tokentype.LAMBDA=λ
|
||||||
|
tokentype.VARIABLE=Variable
|
||||||
|
tokentype.LET=let
|
||||||
|
tokentype.IN=in
|
||||||
|
tokentype.TRUE=true
|
||||||
|
tokentype.FALSE=false
|
||||||
|
tokentype.NUMBER=Zahl
|
||||||
|
tokentype.LEFT_PARENTHESIS=(
|
||||||
|
tokentype.RIGHT_PARENTHESIS=)
|
||||||
|
tokentype.DOT=.
|
||||||
|
tokentype.EQUALS==
|
||||||
|
tokentype.ARROW=->
|
||||||
|
tokentype.EOF=Ende der Eingabe
|
||||||
|
root.or=oder
|
||||||
root.slideExp=Typicalc ist eine Anwendung zur Visualisierung von Typinferenz. In der folgenden Slideshow wird ein \
|
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 \
|
Beispielszenario mit den wichtigsten Funktionen der Website vorgeführt. Das relevante Bedienelement ist jeweils mit \
|
||||||
einem roten Rahmen hervorgehoben. Zwischen einzelnen Slides kann mithilfe der Knöpfe am unteren Ende der Seite \
|
einem roten Rahmen hervorgehoben. Zwischen einzelnen Slides kann mithilfe der Knöpfe am unteren Ende der Seite \
|
||||||
|
@ -98,7 +98,7 @@ class LambdaParserTest {
|
|||||||
parser = new LambdaParser("x)");
|
parser = new LambdaParser("x)");
|
||||||
ParseError error = parser.parse().unwrapError();
|
ParseError error = parser.parse().unwrapError();
|
||||||
assertEquals(ParseError.UNEXPECTED_TOKEN, error);
|
assertEquals(ParseError.UNEXPECTED_TOKEN, error);
|
||||||
assertEquals(new Token(TokenType.RIGHT_PARENTHESIS, ")", 1), error.getCause());
|
assertEquals(new Token(TokenType.RIGHT_PARENTHESIS, ")", 1), error.getCause().get());
|
||||||
parser = new LambdaParser("??");
|
parser = new LambdaParser("??");
|
||||||
assertEquals(ParseError.UNEXPECTED_CHARACTER, parser.parse().unwrapError());
|
assertEquals(ParseError.UNEXPECTED_CHARACTER, parser.parse().unwrapError());
|
||||||
parser = new LambdaParser("aλ");
|
parser = new LambdaParser("aλ");
|
||||||
@ -110,39 +110,39 @@ class LambdaParserTest {
|
|||||||
parser = new LambdaParser("x 123333333333333");
|
parser = new LambdaParser("x 123333333333333");
|
||||||
error = parser.parse().unwrapError();
|
error = parser.parse().unwrapError();
|
||||||
assertEquals(ParseError.UNEXPECTED_CHARACTER, error);
|
assertEquals(ParseError.UNEXPECTED_CHARACTER, error);
|
||||||
assertEquals(new Token(TokenType.NUMBER, "123333333333333", 2), error.getCause());
|
assertEquals(new Token(TokenType.NUMBER, "123333333333333", 2), error.getCause().get());
|
||||||
parser = new LambdaParser("λ)");
|
parser = new LambdaParser("λ)");
|
||||||
error = parser.parse().unwrapError();
|
error = parser.parse().unwrapError();
|
||||||
assertEquals(ParseError.UNEXPECTED_TOKEN, error);
|
assertEquals(ParseError.UNEXPECTED_TOKEN, error);
|
||||||
assertEquals(new Token(TokenType.RIGHT_PARENTHESIS, ")", 1), error.getCause());
|
assertEquals(new Token(TokenType.RIGHT_PARENTHESIS, ")", 1), error.getCause().get());
|
||||||
parser = new LambdaParser("λx=");
|
parser = new LambdaParser("λx=");
|
||||||
error = parser.parse().unwrapError();
|
error = parser.parse().unwrapError();
|
||||||
assertEquals(ParseError.UNEXPECTED_TOKEN, error);
|
assertEquals(ParseError.UNEXPECTED_TOKEN, error);
|
||||||
assertEquals(new Token(TokenType.EQUALS, "=", 2), error.getCause());
|
assertEquals(new Token(TokenType.EQUALS, "=", 2), error.getCause().get());
|
||||||
parser = new LambdaParser("λx..");
|
parser = new LambdaParser("λx..");
|
||||||
error = parser.parse().unwrapError();
|
error = parser.parse().unwrapError();
|
||||||
assertEquals(ParseError.UNEXPECTED_TOKEN, error);
|
assertEquals(ParseError.UNEXPECTED_TOKEN, error);
|
||||||
assertEquals(new Token(TokenType.DOT, ".", 3), error.getCause());
|
assertEquals(new Token(TokenType.DOT, ".", 3), error.getCause().get());
|
||||||
parser = new LambdaParser("let ) =");
|
parser = new LambdaParser("let ) =");
|
||||||
error = parser.parse().unwrapError();
|
error = parser.parse().unwrapError();
|
||||||
assertEquals(ParseError.UNEXPECTED_TOKEN, error);
|
assertEquals(ParseError.UNEXPECTED_TOKEN, error);
|
||||||
assertEquals(new Token(TokenType.RIGHT_PARENTHESIS, ")", 4), error.getCause());
|
assertEquals(new Token(TokenType.RIGHT_PARENTHESIS, ")", 4), error.getCause().get());
|
||||||
parser = new LambdaParser("let x .");
|
parser = new LambdaParser("let x .");
|
||||||
error = parser.parse().unwrapError();
|
error = parser.parse().unwrapError();
|
||||||
assertEquals(ParseError.UNEXPECTED_TOKEN, error);
|
assertEquals(ParseError.UNEXPECTED_TOKEN, error);
|
||||||
assertEquals(new Token(TokenType.DOT, ".", 6), error.getCause());
|
assertEquals(new Token(TokenType.DOT, ".", 6), error.getCause().get());
|
||||||
parser = new LambdaParser("let x = )");
|
parser = new LambdaParser("let x = )");
|
||||||
error = parser.parse().unwrapError();
|
error = parser.parse().unwrapError();
|
||||||
assertEquals(ParseError.UNEXPECTED_TOKEN, error);
|
assertEquals(ParseError.UNEXPECTED_TOKEN, error);
|
||||||
assertEquals(new Token(TokenType.RIGHT_PARENTHESIS, ")", 8), error.getCause());
|
assertEquals(new Token(TokenType.RIGHT_PARENTHESIS, ")", 8), error.getCause().get());
|
||||||
parser = new LambdaParser("let x = y )");
|
parser = new LambdaParser("let x = y )");
|
||||||
error = parser.parse().unwrapError();
|
error = parser.parse().unwrapError();
|
||||||
assertEquals(ParseError.UNEXPECTED_TOKEN, error);
|
assertEquals(ParseError.UNEXPECTED_TOKEN, error);
|
||||||
assertEquals(new Token(TokenType.RIGHT_PARENTHESIS, ")", 10), error.getCause());
|
assertEquals(new Token(TokenType.RIGHT_PARENTHESIS, ")", 10), error.getCause().get());
|
||||||
parser = new LambdaParser("let x = y in )");
|
parser = new LambdaParser("let x = y in )");
|
||||||
error = parser.parse().unwrapError();
|
error = parser.parse().unwrapError();
|
||||||
assertEquals(ParseError.UNEXPECTED_TOKEN, error);
|
assertEquals(ParseError.UNEXPECTED_TOKEN, error);
|
||||||
assertEquals(new Token(TokenType.RIGHT_PARENTHESIS, ")", 13), error.getCause());
|
assertEquals(new Token(TokenType.RIGHT_PARENTHESIS, ")", 13), error.getCause().get());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -210,7 +210,7 @@ class TypeAssumptionParserTest {
|
|||||||
Result<Map<VarTerm, TypeAbstraction>, ParseError> type = parser.parse(Map.of("type1", entry.getKey()));
|
Result<Map<VarTerm, TypeAbstraction>, ParseError> type = parser.parse(Map.of("type1", entry.getKey()));
|
||||||
assertTrue(type.isError());
|
assertTrue(type.isError());
|
||||||
assertEquals(entry.getValue(), type.unwrapError());
|
assertEquals(entry.getValue(), type.unwrapError());
|
||||||
if (entry.getValue().getCause().getPos() != -1) {
|
if (entry.getValue().getCause().isPresent()) {
|
||||||
assertEquals(entry.getValue().getCause(), type.unwrapError().getCause());
|
assertEquals(entry.getValue().getCause(), type.unwrapError().getCause());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user