Implement multiple error reporting and recovery in Parser
This commit is contained in:
parent
f0d2cdc1ab
commit
bdb1fc738e
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue