Implement multiple error reporting and recovery in Parser

This commit is contained in:
Riccardo Azzolini 2019-01-29 21:36:11 +01:00
parent f0d2cdc1ab
commit bdb1fc738e
4 changed files with 331 additions and 99 deletions

View File

@ -17,7 +17,7 @@ public class RulesDsl {
final List<DslException> errors = new ArrayList<>();
final Lexer lexer = new Lexer(source, errors::add);
final Parser parser = new Parser(lexer.lex());
final Parser parser = new Parser(lexer.lex(), errors::add);
final List<PatternRule> rules = parser.parse();
for (final PatternRule rule : rules) {

View File

@ -2,18 +2,16 @@ package it.cavallium.warppi.math.rules.dsl.frontend;
import it.cavallium.warppi.math.MathematicalSymbols;
import it.cavallium.warppi.math.rules.RuleType;
import it.cavallium.warppi.math.rules.dsl.DslException;
import it.cavallium.warppi.math.rules.dsl.Pattern;
import it.cavallium.warppi.math.rules.dsl.PatternRule;
import it.cavallium.warppi.math.rules.dsl.patterns.*;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import static it.cavallium.warppi.math.rules.dsl.frontend.TokenType.*;
@ -21,11 +19,20 @@ import static it.cavallium.warppi.math.rules.dsl.frontend.TokenType.*;
* Converts a list of tokens to a list of <code>PatternRule</code>s.
*/
public class Parser {
private static final Map<TokenType, RuleType> ruleTypes = Map.ofEntries(
Map.entry(REDUCTION, RuleType.REDUCTION),
Map.entry(EXPANSION, RuleType.EXPANSION),
Map.entry(CALCULATION, RuleType.CALCULATION),
Map.entry(EXISTENCE, RuleType.EXISTENCE)
);
private final List<Token> tokens;
private final Consumer<? super DslException> errorReporter;
private int current = 0;
public Parser(List<Token> tokens) {
public Parser(final List<Token> tokens, final Consumer<? super DslException> errorReporter) {
this.tokens = tokens;
this.errorReporter = errorReporter;
}
public List<PatternRule> parse() {
@ -34,9 +41,14 @@ public class Parser {
// rules = { rule } , EOF ;
private List<PatternRule> rules() {
List<PatternRule> rules = new ArrayList<>();
final List<PatternRule> rules = new ArrayList<>();
while (!atEnd()) {
rules.add(rule());
try {
rules.add(rule());
} catch (UnexpectedTokenException e) {
errorReporter.accept(e);
synchronizeTo(ruleTypes.keySet()); // Skip to the next rule to minimize "false" errors
}
}
return rules;
}
@ -44,40 +56,34 @@ public class Parser {
// rule = rule header , rule body ;
// rule header = rule type , IDENTIFIER , COLON ;
// rule body = pattern , ARROW , replacements ;
private PatternRule rule() {
RuleType type = ruleType();
String name = matchOrFail(IDENTIFIER).lexeme;
private PatternRule rule() throws UnexpectedTokenException {
final RuleType type = ruleType();
final String name = matchOrFail(IDENTIFIER).lexeme;
matchOrFail(COLON);
Pattern target = pattern();
final Pattern target = pattern();
matchOrFail(ARROW);
List<Pattern> replacements = replacements();
final List<Pattern> replacements = replacements();
return new PatternRule(name, type, target, replacements);
}
// rule type = REDUCTION | EXPANSION | CALCULATION | EXISTENCE ;
private RuleType ruleType() {
switch (pop().type) {
case REDUCTION:
return RuleType.REDUCTION;
case EXPANSION:
return RuleType.EXPANSION;
case CALCULATION:
return RuleType.CALCULATION;
case EXISTENCE:
return RuleType.EXISTENCE;
private RuleType ruleType() throws UnexpectedTokenException {
final Token curToken = pop();
if (!ruleTypes.containsKey(curToken.type)) {
throw new UnexpectedTokenException(curToken, ruleTypes.keySet());
}
throw new RuntimeException("Expected rule type");
return ruleTypes.get(curToken.type);
}
// pattern = equation ;
private Pattern pattern() {
private Pattern pattern() throws UnexpectedTokenException {
return equation();
}
// replacements = pattern
// | LEFT_BRACKET , patterns , RIGHT_BRACKET ;
// patterns = [ pattern , { COMMA , pattern } , [ COMMA ] ] ;
private List<Pattern> replacements() {
private List<Pattern> replacements() throws UnexpectedTokenException {
if (match(LEFT_BRACKET) == null) {
return Collections.singletonList(pattern());
}
@ -85,7 +91,7 @@ public class Parser {
if (match(RIGHT_BRACKET) != null) {
return Collections.emptyList();
} else {
List<Pattern> pats = new ArrayList<>();
final List<Pattern> pats = new ArrayList<>();
do {
pats.add(pattern());
} while (match(COMMA) != null && peek().type != RIGHT_BRACKET);
@ -95,7 +101,7 @@ public class Parser {
}
// equation = sum , [ EQUALS , sum ] ;
private Pattern equation() {
private Pattern equation() throws UnexpectedTokenException {
Pattern pat = sum();
if (match(EQUALS) != null) {
pat = new EquationPattern(pat, sum());
@ -104,7 +110,7 @@ public class Parser {
}
// sum = product , { ( PLUS | MINUS | PLUS_MINUS ) product } ;
private Pattern sum() {
private Pattern sum() throws UnexpectedTokenException {
return matchLeftAssoc(this::product, Map.ofEntries(
Map.entry(PLUS, SumPattern::new),
Map.entry(MINUS, SubtractionPattern::new),
@ -113,7 +119,7 @@ public class Parser {
}
// product = unary , { ( TIMES | DIVIDE ) unary } ;
private Pattern product() {
private Pattern product() throws UnexpectedTokenException {
return matchLeftAssoc(this::unary, Map.ofEntries(
Map.entry(TIMES, MultiplicationPattern::new),
Map.entry(DIVIDE, DivisionPattern::new)
@ -122,7 +128,7 @@ public class Parser {
// unary = ( PLUS | MINUS ) unary
// | power ;
private Pattern unary() {
private Pattern unary() throws UnexpectedTokenException {
if (match(PLUS) != null) {
return unary();
} else if (match(MINUS) != null) {
@ -133,7 +139,7 @@ public class Parser {
}
// power = ( function | primary ) , [ POWER , power ] ;
private Pattern power() {
private Pattern power() throws UnexpectedTokenException {
Pattern pat = functionOrPrimary();
if (match(POWER) != null) {
pat = new PowerPattern(pat, power());
@ -141,14 +147,14 @@ public class Parser {
return pat;
}
private Pattern functionOrPrimary() {
Pattern function = tryFunction();
private Pattern functionOrPrimary() throws UnexpectedTokenException {
final Pattern function = tryFunction();
return function != null ? function : primary();
}
// function = ( ARCCOS | ARCSIN | ARCTAN | COS | SIN | SQRT | TAN ) , LEFT_PAREN , sum , RIGHT_PAREN
// | ( LOG | ROOT ) LEFT_PAREN , sum , COMMA , sum , RIGHT_PAREN ;
private Pattern tryFunction() {
private Pattern tryFunction() throws UnexpectedTokenException {
final Map<TokenType, Function<Pattern, Pattern>> oneArg = Map.ofEntries(
Map.entry(ARCCOS, ArcCosinePattern::new),
Map.entry(ARCSIN, ArcSinePattern::new),
@ -175,14 +181,14 @@ public class Parser {
return null;
}
private Pattern oneArgFunction(final Function<Pattern, Pattern> constructor) {
private Pattern oneArgFunction(final Function<Pattern, Pattern> constructor) throws UnexpectedTokenException {
matchOrFail(LEFT_PAREN);
final Pattern arg = pattern();
matchOrFail(RIGHT_PAREN);
return constructor.apply(arg);
}
private Pattern twoArgFunction(final BiFunction<Pattern, Pattern, Pattern> constructor) {
private Pattern twoArgFunction(final BiFunction<Pattern, Pattern, Pattern> constructor) throws UnexpectedTokenException {
matchOrFail(LEFT_PAREN);
final Pattern firstArg = pattern();
matchOrFail(COMMA);
@ -194,8 +200,8 @@ public class Parser {
// primary = NUMBER | constant | IDENTIFIER | UNDEFINED
// | LEFT_PAREN sum RIGHT_PAREN ;
// constant = PI | E ;
private Pattern primary() {
Token curToken = pop();
private Pattern primary() throws UnexpectedTokenException {
final Token curToken = pop();
switch (curToken.type) {
case PI:
return new ConstantPattern(MathematicalSymbols.PI);
@ -212,42 +218,50 @@ public class Parser {
matchOrFail(RIGHT_PAREN);
return grouped;
}
throw new RuntimeException("Unexpected " + curToken);
throw new UnexpectedTokenException(curToken);
}
private Pattern matchLeftAssoc(
final Supplier<Pattern> operandParser,
final PatternParser operandParser,
final Map<TokenType, BiFunction<Pattern, Pattern, Pattern>> operators
) {
Pattern pat = operandParser.get();
) throws UnexpectedTokenException {
Pattern pat = operandParser.parse();
while (operators.containsKey(peek().type)) {
final Token operatorToken = pop();
final BiFunction<Pattern, Pattern, Pattern> constructor = operators.get(operatorToken.type);
pat = constructor.apply(pat, operandParser.get());
pat = constructor.apply(pat, operandParser.parse());
}
return pat;
}
private Token matchOrFail(final TokenType expectedType) {
private Token matchOrFail(final TokenType expectedType) throws UnexpectedTokenException {
final Token matched = match(expectedType);
if (matched == null) {
throw new RuntimeException("Expected " + expectedType);
throw new UnexpectedTokenException(tokens.get(current), expectedType);
}
return matched;
}
private Token match(final TokenType expectedType) {
final Token curToken = tokens.get(current);
if (curToken.type == expectedType) {
current++;
return curToken;
} else {
if (curToken.type != expectedType) {
return null;
}
current++;
return curToken;
}
private void synchronizeTo(final Set<TokenType> types) {
while (!atEnd() && !types.contains(tokens.get(current).type)) {
current++;
}
}
private Token pop() {
private Token pop() throws UnexpectedTokenException {
final Token curToken = tokens.get(current);
if (atEnd()) {
throw new UnexpectedTokenException(curToken); // Avoid popping EOF
}
current++;
return curToken;
}
@ -259,4 +273,9 @@ public class Parser {
private boolean atEnd() {
return tokens.get(current).type == EOF;
}
@FunctionalInterface
private interface PatternParser {
Pattern parse() throws UnexpectedTokenException;
}
}

View File

@ -0,0 +1,64 @@
package it.cavallium.warppi.math.rules.dsl.frontend;
import it.cavallium.warppi.math.rules.dsl.DslException;
import java.util.Objects;
import java.util.Set;
/**
* Thrown when DSL source code contains a token which doesn't match the grammar.
*/
public class UnexpectedTokenException extends DslException {
private final Token unexpected;
private final Set<TokenType> suggested;
public UnexpectedTokenException(final Token unexpected, final Set<TokenType> suggested) {
this.unexpected = unexpected;
this.suggested = suggested;
}
public UnexpectedTokenException(final Token unexpected, final TokenType... suggested) {
this.unexpected = unexpected;
this.suggested = Set.of(suggested);
}
@Override
public int getPosition() {
return unexpected.position;
}
@Override
public int getLength() {
return unexpected.lexeme.length();
}
/**
* @return The unexpected token.
*/
public Token getUnexpected() {
return unexpected;
}
/**
* @return A (possibly empty) set of <code>TokenType</code>s which would match the grammar.
* As the name implies, this is only a suggestion: the parser can't list all allowed token types
* when it detects an error.
*/
public Set<TokenType> getSuggested() {
return suggested;
}
@Override
public boolean equals(final Object o) {
if (!(o instanceof UnexpectedTokenException)) {
return false;
}
final UnexpectedTokenException other = (UnexpectedTokenException) o;
return this.unexpected.equals(other.unexpected) && this.suggested.equals(other.suggested);
}
@Override
public int hashCode() {
return Objects.hash(unexpected, suggested);
}
}

View File

@ -2,12 +2,15 @@ package it.cavallium.warppi.math.rules.dsl.frontend;
import it.cavallium.warppi.math.MathematicalSymbols;
import it.cavallium.warppi.math.rules.RuleType;
import it.cavallium.warppi.math.rules.dsl.DslException;
import it.cavallium.warppi.math.rules.dsl.Pattern;
import it.cavallium.warppi.math.rules.dsl.PatternRule;
import it.cavallium.warppi.math.rules.dsl.patterns.*;
import org.junit.Before;
import org.junit.Test;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@ -16,12 +19,19 @@ import static it.cavallium.warppi.math.rules.dsl.frontend.TokenType.*;
import static org.junit.Assert.*;
public class ParserTest {
private final List<DslException> errors = new ArrayList<>();
@Before
public void setUp() {
errors.clear();
}
@Test
public void noRules() {
final List<Token> tokens = Collections.singletonList(
new Token(EOF, "", 0)
);
final Parser parser = new Parser(tokens);
final Parser parser = new Parser(tokens, errors::add);
assertEquals(Collections.emptyList(), parser.parse());
}
@ -68,7 +78,7 @@ public class ParserTest {
new Token(RIGHT_BRACKET, "]", 138),
new Token(EOF, "", 140)
);
final Parser parser = new Parser(tokens);
final Parser parser = new Parser(tokens, errors::add);
// x + y * z = -(a_123 +- 3 / 2.2)
final Pattern target = new EquationPattern(
@ -127,7 +137,7 @@ public class ParserTest {
new Token(RIGHT_BRACKET, "]", 0),
new Token(EOF, "", 0)
);
final Parser parser = new Parser(tokens);
final Parser parser = new Parser(tokens, errors::add);
final List<PatternRule> expected = Collections.singletonList(new PatternRule(
"test",
@ -157,7 +167,7 @@ public class ParserTest {
new Token(NUMBER, "2", 0),
new Token(EOF, "", 0)
);
final Parser parser = new Parser(tokens);
final Parser parser = new Parser(tokens, errors::add);
final List<PatternRule> expected = Collections.singletonList(new PatternRule(
"test",
@ -191,7 +201,7 @@ public class ParserTest {
new Token(RIGHT_BRACKET, "]", 0),
new Token(EOF, "", 0)
);
final Parser parser = new Parser(tokens);
final Parser parser = new Parser(tokens, errors::add);
final List<PatternRule> expected = Collections.singletonList(new PatternRule(
"test",
@ -239,7 +249,7 @@ public class ParserTest {
new Token(EOF, "", 0)
);
final Parser parser = new Parser(tokens);
final Parser parser = new Parser(tokens, errors::add);
final List<PatternRule> expected = Arrays.asList(
new PatternRule(
@ -282,11 +292,11 @@ public class ParserTest {
new Token(LEFT_BRACKET, "[", 0),
new Token(RIGHT_BRACKET, "]", 0)
);
final Parser parser = new Parser(tokens);
final Parser parser = new Parser(tokens, errors::add);
parser.parse();
}
@Test(expected = RuntimeException.class)
@Test
public void incompleteRule() {
final List<Token> tokens = Arrays.asList(
new Token(EXISTENCE, "existence", 0),
@ -298,21 +308,34 @@ public class ParserTest {
new Token(ARROW, "->", 0),
new Token(EOF, "", 0)
);
final Parser parser = new Parser(tokens);
parser.parse();
final Parser parser = new Parser(tokens, errors::add);
assertTrue(parser.parse().isEmpty());
final List<DslException> expectedErrors = Collections.singletonList(new UnexpectedTokenException(
new Token(EOF, "", 0)
));
assertEquals(expectedErrors, errors);
}
@Test(expected = RuntimeException.class)
@Test
public void missingRuleType() {
final List<Token> tokens = Arrays.asList(
new Token(IDENTIFIER, "test", 0),
new Token(EOF, "", 0)
);
final Parser parser = new Parser(tokens);
parser.parse();
final Parser parser = new Parser(tokens, errors::add);
assertTrue(parser.parse().isEmpty());
final List<DslException> expectedErrors = Collections.singletonList(new UnexpectedTokenException(
new Token(IDENTIFIER, "test", 0),
REDUCTION, EXPANSION, CALCULATION, EXISTENCE
));
assertEquals(expectedErrors, errors);
}
@Test(expected = RuntimeException.class)
@Test
public void unexpectedTokenPrimary() {
final List<Token> tokens = Arrays.asList(
new Token(EXISTENCE, "existence", 0),
@ -326,22 +349,35 @@ public class ParserTest {
new Token(RIGHT_BRACKET, "]", 0),
new Token(EOF, "", 0)
);
final Parser parser = new Parser(tokens);
parser.parse();
final Parser parser = new Parser(tokens, errors::add);
assertTrue(parser.parse().isEmpty());
final List<DslException> expectedErrors = Collections.singletonList(new UnexpectedTokenException(
new Token(CALCULATION, "calculation", 0)
));
assertEquals(expectedErrors, errors);
}
@Test(expected = RuntimeException.class)
@Test
public void missingRuleName() {
final List<Token> tokens = Arrays.asList(
new Token(REDUCTION, "reduction", 0),
new Token(COLON, ":", 0),
new Token(EOF, "", 0)
);
final Parser parser = new Parser(tokens);
parser.parse();
final Parser parser = new Parser(tokens, errors::add);
assertTrue(parser.parse().isEmpty());
final List<DslException> expectedErrors = Collections.singletonList(new UnexpectedTokenException(
new Token(COLON, ":", 0),
IDENTIFIER
));
assertEquals(expectedErrors, errors);
}
@Test(expected = RuntimeException.class)
@Test
public void missingColon() {
final List<Token> tokens = Arrays.asList(
new Token(REDUCTION, "reduction", 0),
@ -351,25 +387,39 @@ public class ParserTest {
new Token(IDENTIFIER, "x", 0),
new Token(EOF, "", 0)
);
final Parser parser = new Parser(tokens);
parser.parse();
final Parser parser = new Parser(tokens, errors::add);
assertTrue(parser.parse().isEmpty());
final List<DslException> expectedErrors = Collections.singletonList(new UnexpectedTokenException(
new Token(IDENTIFIER, "x", 0),
COLON
));
assertEquals(expectedErrors, errors);
}
@Test(expected = RuntimeException.class)
@Test
public void missingArrow() {
final List<Token> tokens = Arrays.asList(
new Token(REDUCTION, "reduction", 0),
new Token(IDENTIFIER, "test", 0),
new Token(COLON, ":", 0),
new Token(IDENTIFIER, "x", 0),
new Token(IDENTIFIER, "x", 0),
new Token(IDENTIFIER, "y", 0),
new Token(EOF, "", 0)
);
final Parser parser = new Parser(tokens);
parser.parse();
final Parser parser = new Parser(tokens, errors::add);
assertTrue(parser.parse().isEmpty());
final List<DslException> expectedErrors = Collections.singletonList(new UnexpectedTokenException(
new Token(IDENTIFIER, "y", 0),
ARROW
));
assertEquals(expectedErrors, errors);
}
@Test(expected = RuntimeException.class)
@Test
public void missingRightBracket() {
final List<Token> tokens = Arrays.asList(
new Token(REDUCTION, "reduction", 0),
@ -381,11 +431,18 @@ public class ParserTest {
new Token(IDENTIFIER, "x", 0),
new Token(EOF, "", 0)
);
final Parser parser = new Parser(tokens);
parser.parse();
final Parser parser = new Parser(tokens, errors::add);
assertTrue(parser.parse().isEmpty());
final List<DslException> expectedErrors = Collections.singletonList(new UnexpectedTokenException(
new Token(EOF, "", 0),
RIGHT_BRACKET
));
assertEquals(expectedErrors, errors);
}
@Test(expected = RuntimeException.class)
@Test
public void missingOneArgFunctionLeftParen() {
final List<Token> tokens = Arrays.asList(
new Token(EXISTENCE, "existence", 0),
@ -399,11 +456,18 @@ public class ParserTest {
new Token(RIGHT_BRACKET, "]", 0),
new Token(EOF, "", 0)
);
final Parser parser = new Parser(tokens);
parser.parse();
final Parser parser = new Parser(tokens, errors::add);
assertTrue(parser.parse().isEmpty());
final List<DslException> expectedErrors = Collections.singletonList(new UnexpectedTokenException(
new Token(IDENTIFIER, "x", 0),
LEFT_PAREN
));
assertEquals(expectedErrors, errors);
}
@Test(expected = RuntimeException.class)
@Test
public void missingOneArgFunctionRightParen() {
final List<Token> tokens = Arrays.asList(
new Token(EXISTENCE, "existence", 0),
@ -417,11 +481,18 @@ public class ParserTest {
new Token(RIGHT_BRACKET, "]", 0),
new Token(EOF, "", 0)
);
final Parser parser = new Parser(tokens);
parser.parse();
final Parser parser = new Parser(tokens, errors::add);
assertTrue(parser.parse().isEmpty());
final List<DslException> expectedErrors = Collections.singletonList(new UnexpectedTokenException(
new Token(ARROW, "->", 0),
RIGHT_PAREN
));
assertEquals(expectedErrors, errors);
}
@Test(expected = RuntimeException.class)
@Test
public void missingTwoArgFunctionLeftParen() {
final List<Token> tokens = Arrays.asList(
new Token(EXISTENCE, "existence", 0),
@ -437,11 +508,18 @@ public class ParserTest {
new Token(RIGHT_BRACKET, "]", 0),
new Token(EOF, "", 0)
);
final Parser parser = new Parser(tokens);
parser.parse();
final Parser parser = new Parser(tokens, errors::add);
assertTrue(parser.parse().isEmpty());
final List<DslException> expectedErrors = Collections.singletonList(new UnexpectedTokenException(
new Token(IDENTIFIER, "x", 0),
LEFT_PAREN
));
assertEquals(expectedErrors, errors);
}
@Test(expected = RuntimeException.class)
@Test
public void missingTwoArgFunctionComma() {
final List<Token> tokens = Arrays.asList(
new Token(EXISTENCE, "existence", 0),
@ -457,11 +535,18 @@ public class ParserTest {
new Token(RIGHT_BRACKET, "]", 0),
new Token(EOF, "", 0)
);
final Parser parser = new Parser(tokens);
parser.parse();
final Parser parser = new Parser(tokens, errors::add);
assertTrue(parser.parse().isEmpty());
final List<DslException> expectedErrors = Collections.singletonList(new UnexpectedTokenException(
new Token(IDENTIFIER, "y", 0),
COMMA
));
assertEquals(expectedErrors, errors);
}
@Test(expected = RuntimeException.class)
@Test
public void missingTwoArgFunctionRightParen() {
final List<Token> tokens = Arrays.asList(
new Token(EXISTENCE, "existence", 0),
@ -477,11 +562,18 @@ public class ParserTest {
new Token(RIGHT_BRACKET, "]", 0),
new Token(EOF, "", 0)
);
final Parser parser = new Parser(tokens);
parser.parse();
final Parser parser = new Parser(tokens, errors::add);
assertTrue(parser.parse().isEmpty());
final List<DslException> expectedErrors = Collections.singletonList(new UnexpectedTokenException(
new Token(ARROW, "->", 0),
RIGHT_PAREN
));
assertEquals(expectedErrors, errors);
}
@Test(expected = RuntimeException.class)
@Test
public void missingExpressionRightParen() {
final List<Token> tokens = Arrays.asList(
new Token(EXISTENCE, "existence", 0),
@ -494,7 +586,64 @@ public class ParserTest {
new Token(RIGHT_BRACKET, "]", 0),
new Token(EOF, "", 0)
);
final Parser parser = new Parser(tokens);
parser.parse();
final Parser parser = new Parser(tokens, errors::add);
assertTrue(parser.parse().isEmpty());
final List<DslException> expectedErrors = Collections.singletonList(new UnexpectedTokenException(
new Token(ARROW, "->", 0),
RIGHT_PAREN
));
assertEquals(expectedErrors, errors);
}
@Test
public void recoveryToNextRule() {
final List<Token> tokens = Arrays.asList(
new Token(REDUCTION, "reduction", 0),
new Token(IDENTIFIER, "test1", 0),
new Token(COLON, ":", 0),
new Token(IDENTIFIER, "x", 0),
new Token(TIMES, "+", 0),
new Token(ARROW, "->", 0),
new Token(IDENTIFIER, "x", 0),
new Token(EXPANSION, "expansion", 0),
new Token(IDENTIFIER, "test2", 0),
new Token(IDENTIFIER, "x", 0),
new Token(ARROW, "->", 0),
new Token(MINUS, "-", 0),
new Token(MINUS, "-", 0),
new Token(IDENTIFIER, "x", 0),
new Token(CALCULATION, "calculation", 0),
new Token(IDENTIFIER, "test3", 0),
new Token(COLON, ":", 0),
new Token(NUMBER, "1", 0),
new Token(PLUS, "+", 0),
new Token(NUMBER, "1", 0),
new Token(ARROW, "->", 0),
new Token(NUMBER, "2", 0),
new Token(EOF, "", 0)
);
final Parser parser = new Parser(tokens, errors::add);
final List<PatternRule> expectedRules = Collections.singletonList(new PatternRule(
"test3",
RuleType.CALCULATION,
new SumPattern(
new NumberPattern(new BigDecimal(1)),
new NumberPattern(new BigDecimal(1))
),
new NumberPattern(new BigDecimal(2))
));
assertEquals(expectedRules, parser.parse());
final List<DslException> expectedErrors = Arrays.asList(
new UnexpectedTokenException(new Token(ARROW, "->", 0)),
new UnexpectedTokenException(new Token(IDENTIFIER, "x", 0), COLON)
);
assertEquals(expectedErrors, errors);
}
}