Refactor ExpressionParser to improve error handling
This commit is contained in:
parent
399df4d267
commit
f3b43eee9c
@ -25,6 +25,8 @@ package com.github.zafarkhaja.semver;
|
|||||||
|
|
||||||
import com.github.zafarkhaja.semver.expr.Expression;
|
import com.github.zafarkhaja.semver.expr.Expression;
|
||||||
import com.github.zafarkhaja.semver.expr.ExpressionParser;
|
import com.github.zafarkhaja.semver.expr.ExpressionParser;
|
||||||
|
import com.github.zafarkhaja.semver.expr.LexerException;
|
||||||
|
import com.github.zafarkhaja.semver.expr.UnexpectedTokenException;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -310,6 +312,9 @@ public class Version implements Comparable<Version> {
|
|||||||
* @param expr the SemVer Expression
|
* @param expr the SemVer Expression
|
||||||
* @return {@code true} if this version satisfies the specified
|
* @return {@code true} if this version satisfies the specified
|
||||||
* SemVer Expression or {@code false} otherwise
|
* SemVer Expression or {@code false} otherwise
|
||||||
|
* @throws ParseException in case of a general parse error
|
||||||
|
* @throws LexerException when encounters an illegal character
|
||||||
|
* @throws UnexpectedTokenException when comes across an unexpected token
|
||||||
* @since 0.7.0
|
* @since 0.7.0
|
||||||
*/
|
*/
|
||||||
public boolean satisfies(String expr) {
|
public boolean satisfies(String expr) {
|
||||||
|
@ -78,12 +78,14 @@ public class ExpressionParser implements Parser<Expression> {
|
|||||||
* @param input a string representing the SemVer Expression
|
* @param input a string representing the SemVer Expression
|
||||||
* @return the AST for the SemVer Expressions
|
* @return the AST for the SemVer Expressions
|
||||||
* @throws LexerException when encounters an illegal character
|
* @throws LexerException when encounters an illegal character
|
||||||
* @throws UnexpectedElementException when consumes a token of an unexpected type
|
* @throws UnexpectedTokenException when consumes a token of an unexpected type
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Expression parse(String input) {
|
public Expression parse(String input) {
|
||||||
tokens = lexer.tokenize(input);
|
tokens = lexer.tokenize(input);
|
||||||
return parseSemVerExpression();
|
Expression expr = parseSemVerExpression();
|
||||||
|
consumeNextToken(EOL);
|
||||||
|
return expr;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -104,13 +106,13 @@ public class ExpressionParser implements Parser<Expression> {
|
|||||||
Expression expr;
|
Expression expr;
|
||||||
if (tokens.positiveLookahead(NOT)) {
|
if (tokens.positiveLookahead(NOT)) {
|
||||||
tokens.consume();
|
tokens.consume();
|
||||||
tokens.consume(LEFT_PAREN);
|
consumeNextToken(LEFT_PAREN);
|
||||||
expr = new Not(parseSemVerExpression());
|
expr = new Not(parseSemVerExpression());
|
||||||
tokens.consume(RIGHT_PAREN);
|
consumeNextToken(RIGHT_PAREN);
|
||||||
} else if (tokens.positiveLookahead(LEFT_PAREN)) {
|
} else if (tokens.positiveLookahead(LEFT_PAREN)) {
|
||||||
tokens.consume(LEFT_PAREN);
|
consumeNextToken(LEFT_PAREN);
|
||||||
expr = parseSemVerExpression();
|
expr = parseSemVerExpression();
|
||||||
tokens.consume(RIGHT_PAREN);
|
consumeNextToken(RIGHT_PAREN);
|
||||||
} else {
|
} else {
|
||||||
expr = parseExpression();
|
expr = parseExpression();
|
||||||
}
|
}
|
||||||
@ -224,21 +226,21 @@ public class ExpressionParser implements Parser<Expression> {
|
|||||||
* @return the expression AST
|
* @return the expression AST
|
||||||
*/
|
*/
|
||||||
private Expression parseTildeExpression() {
|
private Expression parseTildeExpression() {
|
||||||
tokens.consume(TILDE);
|
consumeNextToken(TILDE);
|
||||||
int major = intOf(tokens.consume(NUMERIC).lexeme);
|
int major = intOf(consumeNextToken(NUMERIC).lexeme);
|
||||||
if (!tokens.positiveLookahead(DOT)) {
|
if (!tokens.positiveLookahead(DOT)) {
|
||||||
return new GreaterOrEqual(versionOf(major, 0, 0));
|
return new GreaterOrEqual(versionOf(major, 0, 0));
|
||||||
}
|
}
|
||||||
tokens.consume(DOT);
|
consumeNextToken(DOT);
|
||||||
int minor = intOf(tokens.consume(NUMERIC).lexeme);
|
int minor = intOf(consumeNextToken(NUMERIC).lexeme);
|
||||||
if (!tokens.positiveLookahead(DOT)) {
|
if (!tokens.positiveLookahead(DOT)) {
|
||||||
return new And(
|
return new And(
|
||||||
new GreaterOrEqual(versionOf(major, minor, 0)),
|
new GreaterOrEqual(versionOf(major, minor, 0)),
|
||||||
new Less(versionOf(major + 1, 0, 0))
|
new Less(versionOf(major + 1, 0, 0))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
tokens.consume(DOT);
|
consumeNextToken(DOT);
|
||||||
int patch = intOf(tokens.consume(NUMERIC).lexeme);
|
int patch = intOf(consumeNextToken(NUMERIC).lexeme);
|
||||||
return new And(
|
return new And(
|
||||||
new GreaterOrEqual(versionOf(major, minor, patch)),
|
new GreaterOrEqual(versionOf(major, minor, patch)),
|
||||||
new Less(versionOf(major, minor + 1, 0))
|
new Less(versionOf(major, minor + 1, 0))
|
||||||
@ -270,8 +272,8 @@ public class ExpressionParser implements Parser<Expression> {
|
|||||||
* @return the expression AST
|
* @return the expression AST
|
||||||
*/
|
*/
|
||||||
private Expression parseVersionExpression() {
|
private Expression parseVersionExpression() {
|
||||||
int major = intOf(tokens.consume(NUMERIC).lexeme);
|
int major = intOf(consumeNextToken(NUMERIC).lexeme);
|
||||||
tokens.consume(DOT);
|
consumeNextToken(DOT);
|
||||||
if (tokens.positiveLookahead(STAR)) {
|
if (tokens.positiveLookahead(STAR)) {
|
||||||
tokens.consume();
|
tokens.consume();
|
||||||
return new And(
|
return new And(
|
||||||
@ -279,9 +281,9 @@ public class ExpressionParser implements Parser<Expression> {
|
|||||||
new Less(versionOf(major + 1, 0, 0))
|
new Less(versionOf(major + 1, 0, 0))
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
int minor = intOf(tokens.consume(NUMERIC).lexeme);
|
int minor = intOf(consumeNextToken(NUMERIC).lexeme);
|
||||||
tokens.consume(DOT);
|
consumeNextToken(DOT);
|
||||||
tokens.consume(STAR);
|
consumeNextToken(STAR);
|
||||||
return new And(
|
return new And(
|
||||||
new GreaterOrEqual(versionOf(major, minor, 0)),
|
new GreaterOrEqual(versionOf(major, minor, 0)),
|
||||||
new Less(versionOf(major, minor + 1, 0))
|
new Less(versionOf(major, minor + 1, 0))
|
||||||
@ -313,7 +315,7 @@ public class ExpressionParser implements Parser<Expression> {
|
|||||||
*/
|
*/
|
||||||
private Expression parseRangeExpression() {
|
private Expression parseRangeExpression() {
|
||||||
Expression ge = new GreaterOrEqual(parseVersion());
|
Expression ge = new GreaterOrEqual(parseVersion());
|
||||||
tokens.consume(HYPHEN);
|
consumeNextToken(HYPHEN);
|
||||||
Expression le = new LessOrEqual(parseVersion());
|
Expression le = new LessOrEqual(parseVersion());
|
||||||
return new And(ge, le);
|
return new And(ge, le);
|
||||||
}
|
}
|
||||||
@ -332,16 +334,16 @@ public class ExpressionParser implements Parser<Expression> {
|
|||||||
* @return the parsed version
|
* @return the parsed version
|
||||||
*/
|
*/
|
||||||
private Version parseVersion() {
|
private Version parseVersion() {
|
||||||
int major = intOf(tokens.consume(NUMERIC).lexeme);
|
int major = intOf(consumeNextToken(NUMERIC).lexeme);
|
||||||
int minor = 0;
|
int minor = 0;
|
||||||
if (tokens.positiveLookahead(DOT)) {
|
if (tokens.positiveLookahead(DOT)) {
|
||||||
tokens.consume();
|
tokens.consume();
|
||||||
minor = intOf(tokens.consume(NUMERIC).lexeme);
|
minor = intOf(consumeNextToken(NUMERIC).lexeme);
|
||||||
}
|
}
|
||||||
int patch = 0;
|
int patch = 0;
|
||||||
if (tokens.positiveLookahead(DOT)) {
|
if (tokens.positiveLookahead(DOT)) {
|
||||||
tokens.consume();
|
tokens.consume();
|
||||||
patch = intOf(tokens.consume(NUMERIC).lexeme);
|
patch = intOf(consumeNextToken(NUMERIC).lexeme);
|
||||||
}
|
}
|
||||||
return versionOf(major, minor, patch);
|
return versionOf(major, minor, patch);
|
||||||
}
|
}
|
||||||
@ -391,4 +393,19 @@ public class ExpressionParser implements Parser<Expression> {
|
|||||||
private int intOf(String value) {
|
private int intOf(String value) {
|
||||||
return Integer.parseInt(value);
|
return Integer.parseInt(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to consume the next token in the stream.
|
||||||
|
*
|
||||||
|
* @param expected the expected types of the next token
|
||||||
|
* @return the next token in the stream
|
||||||
|
* @throws UnexpectedTokenException when encounters an unexpected token type
|
||||||
|
*/
|
||||||
|
private Token consumeNextToken(Token.Type... expected) {
|
||||||
|
try {
|
||||||
|
return tokens.consume(expected);
|
||||||
|
} catch (UnexpectedElementException e) {
|
||||||
|
throw new UnexpectedTokenException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,15 +64,7 @@ class Lexer {
|
|||||||
LEFT_PAREN("\\("),
|
LEFT_PAREN("\\("),
|
||||||
RIGHT_PAREN("\\)"),
|
RIGHT_PAREN("\\)"),
|
||||||
WHITESPACE("\\s+"),
|
WHITESPACE("\\s+"),
|
||||||
EOL("?!") {
|
EOL("?!");
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public boolean isMatchedBy(Token token) {
|
|
||||||
return token == null;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A pattern matching this type.
|
* A pattern matching this type.
|
||||||
@ -123,14 +115,22 @@ class Lexer {
|
|||||||
final String lexeme;
|
final String lexeme;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a {@code Token} instance with the type and lexeme.
|
* The position of this token.
|
||||||
|
*/
|
||||||
|
final int position;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@code Token} instance
|
||||||
|
* with the type, lexeme and position.
|
||||||
*
|
*
|
||||||
* @param type the type of this token
|
* @param type the type of this token
|
||||||
* @param lexeme the lexeme of this token
|
* @param lexeme the lexeme of this token
|
||||||
|
* @param position the position of this token
|
||||||
*/
|
*/
|
||||||
Token(Type type, String lexeme) {
|
Token(Type type, String lexeme, int position) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
this.lexeme = (lexeme == null) ? "" : lexeme;
|
this.lexeme = (lexeme == null) ? "" : lexeme;
|
||||||
|
this.position = position;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -145,7 +145,10 @@ class Lexer {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
Token token = (Token) other;
|
Token token = (Token) other;
|
||||||
return type.equals(token.type) && lexeme.equals(token.lexeme);
|
return
|
||||||
|
type.equals(token.type) &&
|
||||||
|
lexeme.equals(token.lexeme) &&
|
||||||
|
position == token.position;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -156,6 +159,7 @@ class Lexer {
|
|||||||
int hash = 5;
|
int hash = 5;
|
||||||
hash = 71 * hash + type.hashCode();
|
hash = 71 * hash + type.hashCode();
|
||||||
hash = 71 * hash + lexeme.hashCode();
|
hash = 71 * hash + lexeme.hashCode();
|
||||||
|
hash = 71 * hash + position;
|
||||||
return hash;
|
return hash;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,7 +170,11 @@ class Lexer {
|
|||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return type.name() + "(" + lexeme + ")";
|
return String.format(
|
||||||
|
"%s(%s) at position %d",
|
||||||
|
type.name(),
|
||||||
|
lexeme, position
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,6 +194,7 @@ class Lexer {
|
|||||||
*/
|
*/
|
||||||
Stream<Token> tokenize(String input) {
|
Stream<Token> tokenize(String input) {
|
||||||
List<Token> tokens = new ArrayList<Token>();
|
List<Token> tokens = new ArrayList<Token>();
|
||||||
|
int tokenPos = 0;
|
||||||
while (!input.isEmpty()) {
|
while (!input.isEmpty()) {
|
||||||
boolean matched = false;
|
boolean matched = false;
|
||||||
for (Token.Type tokenType : Token.Type.values()) {
|
for (Token.Type tokenType : Token.Type.values()) {
|
||||||
@ -194,8 +203,13 @@ class Lexer {
|
|||||||
matched = true;
|
matched = true;
|
||||||
input = matcher.replaceFirst("");
|
input = matcher.replaceFirst("");
|
||||||
if (tokenType != Token.Type.WHITESPACE) {
|
if (tokenType != Token.Type.WHITESPACE) {
|
||||||
tokens.add(new Token(tokenType, matcher.group()));
|
tokens.add(new Token(
|
||||||
|
tokenType,
|
||||||
|
matcher.group(),
|
||||||
|
tokenPos
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
tokenPos += matcher.end();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -203,6 +217,7 @@ class Lexer {
|
|||||||
throw new LexerException(input);
|
throw new LexerException(input);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
tokens.add(new Token(Token.Type.EOL, null, tokenPos));
|
||||||
return new Stream<Token>(tokens.toArray(new Token[tokens.size()]));
|
return new Stream<Token>(tokens.toArray(new Token[tokens.size()]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,8 @@
|
|||||||
package com.github.zafarkhaja.semver.expr;
|
package com.github.zafarkhaja.semver.expr;
|
||||||
|
|
||||||
import com.github.zafarkhaja.semver.ParseException;
|
import com.github.zafarkhaja.semver.ParseException;
|
||||||
import com.github.zafarkhaja.semver.expr.Lexer.*;
|
import com.github.zafarkhaja.semver.expr.Lexer.Token;
|
||||||
|
import com.github.zafarkhaja.semver.util.UnexpectedElementException;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -45,6 +46,17 @@ public class UnexpectedTokenException extends ParseException {
|
|||||||
*/
|
*/
|
||||||
private final Token.Type[] expected;
|
private final Token.Type[] expected;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs a {@code UnexpectedTokenException} instance with
|
||||||
|
* the wrapped {@code UnexpectedElementException} exception.
|
||||||
|
*
|
||||||
|
* @param cause the wrapped exception
|
||||||
|
*/
|
||||||
|
UnexpectedTokenException(UnexpectedElementException cause) {
|
||||||
|
unexpected = (Token) cause.getUnexpectedElement();
|
||||||
|
expected = (Token.Type[]) cause.getExpectedElementTypes();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a {@code UnexpectedTokenException} instance
|
* Constructs a {@code UnexpectedTokenException} instance
|
||||||
* with the unexpected token and the expected types.
|
* with the unexpected token and the expected types.
|
||||||
@ -57,6 +69,24 @@ public class UnexpectedTokenException extends ParseException {
|
|||||||
this.expected = expected;
|
this.expected = expected;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the unexpected token.
|
||||||
|
*
|
||||||
|
* @return the unexpected token
|
||||||
|
*/
|
||||||
|
Token getUnexpectedToken() {
|
||||||
|
return unexpected;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the expected token types.
|
||||||
|
*
|
||||||
|
* @return an array of expected token types
|
||||||
|
*/
|
||||||
|
Token.Type[] getExpectedTokenTypes() {
|
||||||
|
return expected;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the string representation of this exception
|
* Returns the string representation of this exception
|
||||||
* containing the information about the unexpected
|
* containing the information about the unexpected
|
||||||
|
@ -24,7 +24,6 @@
|
|||||||
package com.github.zafarkhaja.semver.expr;
|
package com.github.zafarkhaja.semver.expr;
|
||||||
|
|
||||||
import com.github.zafarkhaja.semver.Version;
|
import com.github.zafarkhaja.semver.Version;
|
||||||
import com.github.zafarkhaja.semver.util.UnexpectedElementException;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
@ -197,7 +196,7 @@ public class ExpressionParserTest {
|
|||||||
ExpressionParser parser = new ExpressionParser(new Lexer());
|
ExpressionParser parser = new ExpressionParser(new Lexer());
|
||||||
try {
|
try {
|
||||||
parser.parse("((>=1.0.1 & < 2)");
|
parser.parse("((>=1.0.1 & < 2)");
|
||||||
} catch (UnexpectedElementException e) {
|
} catch (UnexpectedTokenException e) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
fail("Should raise error if closing parenthesis is missing");
|
fail("Should raise error if closing parenthesis is missing");
|
||||||
|
@ -38,12 +38,13 @@ public class LexerTest {
|
|||||||
@Test
|
@Test
|
||||||
public void shouldTokenizeVersionString() {
|
public void shouldTokenizeVersionString() {
|
||||||
Token[] expected = {
|
Token[] expected = {
|
||||||
new Token(GREATER, ">"),
|
new Token(GREATER, ">", 0),
|
||||||
new Token(NUMERIC, "1"),
|
new Token(NUMERIC, "1", 1),
|
||||||
new Token(DOT, "."),
|
new Token(DOT, ".", 2),
|
||||||
new Token(NUMERIC, "0"),
|
new Token(NUMERIC, "0", 3),
|
||||||
new Token(DOT, "."),
|
new Token(DOT, ".", 4),
|
||||||
new Token(NUMERIC, "0"),
|
new Token(NUMERIC, "0", 5),
|
||||||
|
new Token(EOL, null, 6),
|
||||||
};
|
};
|
||||||
Lexer lexer = new Lexer();
|
Lexer lexer = new Lexer();
|
||||||
Stream<Token> stream = lexer.tokenize(">1.0.0");
|
Stream<Token> stream = lexer.tokenize(">1.0.0");
|
||||||
@ -53,14 +54,30 @@ public class LexerTest {
|
|||||||
@Test
|
@Test
|
||||||
public void shouldSkipWhitespaces() {
|
public void shouldSkipWhitespaces() {
|
||||||
Token[] expected = {
|
Token[] expected = {
|
||||||
new Token(GREATER, ">"),
|
new Token(GREATER, ">", 0),
|
||||||
new Token(NUMERIC, "1"),
|
new Token(NUMERIC, "1", 2),
|
||||||
|
new Token(EOL, null, 3),
|
||||||
};
|
};
|
||||||
Lexer lexer = new Lexer();
|
Lexer lexer = new Lexer();
|
||||||
Stream<Token> stream = lexer.tokenize("> 1");
|
Stream<Token> stream = lexer.tokenize("> 1");
|
||||||
assertArrayEquals(expected, stream.toArray());
|
assertArrayEquals(expected, stream.toArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldEndWithEol() {
|
||||||
|
Token[] expected = {
|
||||||
|
new Token(NUMERIC, "1", 0),
|
||||||
|
new Token(DOT, ".", 1),
|
||||||
|
new Token(NUMERIC, "2", 2),
|
||||||
|
new Token(DOT, ".", 3),
|
||||||
|
new Token(NUMERIC, "3", 4),
|
||||||
|
new Token(EOL, null, 5),
|
||||||
|
};
|
||||||
|
Lexer lexer = new Lexer();
|
||||||
|
Stream<Token> stream = lexer.tokenize("1.2.3");
|
||||||
|
assertArrayEquals(expected, stream.toArray());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldRaiseErrorOnIllegalCharacter() {
|
public void shouldRaiseErrorOnIllegalCharacter() {
|
||||||
Lexer lexer = new Lexer();
|
Lexer lexer = new Lexer();
|
||||||
|
@ -41,23 +41,23 @@ public class LexerTokenTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldBeReflexive() {
|
public void shouldBeReflexive() {
|
||||||
Token token = new Token(NUMERIC, "1");
|
Token token = new Token(NUMERIC, "1", 0);
|
||||||
assertTrue(token.equals(token));
|
assertTrue(token.equals(token));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldBeSymmetric() {
|
public void shouldBeSymmetric() {
|
||||||
Token t1 = new Token(EQUAL, "=");
|
Token t1 = new Token(EQUAL, "=", 0);
|
||||||
Token t2 = new Token(EQUAL, "=");
|
Token t2 = new Token(EQUAL, "=", 0);
|
||||||
assertTrue(t1.equals(t2));
|
assertTrue(t1.equals(t2));
|
||||||
assertTrue(t2.equals(t1));
|
assertTrue(t2.equals(t1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldBeTransitive() {
|
public void shouldBeTransitive() {
|
||||||
Token t1 = new Token(GREATER, ">");
|
Token t1 = new Token(GREATER, ">", 0);
|
||||||
Token t2 = new Token(GREATER, ">");
|
Token t2 = new Token(GREATER, ">", 0);
|
||||||
Token t3 = new Token(GREATER, ">");
|
Token t3 = new Token(GREATER, ">", 0);
|
||||||
assertTrue(t1.equals(t2));
|
assertTrue(t1.equals(t2));
|
||||||
assertTrue(t2.equals(t3));
|
assertTrue(t2.equals(t3));
|
||||||
assertTrue(t1.equals(t3));
|
assertTrue(t1.equals(t3));
|
||||||
@ -65,8 +65,8 @@ public class LexerTokenTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldBeConsistent() {
|
public void shouldBeConsistent() {
|
||||||
Token t1 = new Token(HYPHEN, "-");
|
Token t1 = new Token(HYPHEN, "-", 0);
|
||||||
Token t2 = new Token(HYPHEN, "-");
|
Token t2 = new Token(HYPHEN, "-", 0);
|
||||||
assertTrue(t1.equals(t2));
|
assertTrue(t1.equals(t2));
|
||||||
assertTrue(t1.equals(t2));
|
assertTrue(t1.equals(t2));
|
||||||
assertTrue(t1.equals(t2));
|
assertTrue(t1.equals(t2));
|
||||||
@ -74,28 +74,35 @@ public class LexerTokenTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldReturnFalseIfOtherVersionIsOfDifferentType() {
|
public void shouldReturnFalseIfOtherVersionIsOfDifferentType() {
|
||||||
Token t1 = new Token(DOT, ".");
|
Token t1 = new Token(DOT, ".", 0);
|
||||||
assertFalse(t1.equals(new String(".")));
|
assertFalse(t1.equals(new String(".")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldReturnFalseIfOtherVersionIsNull() {
|
public void shouldReturnFalseIfOtherVersionIsNull() {
|
||||||
Token t1 = new Token(AND, "&");
|
Token t1 = new Token(AND, "&", 0);
|
||||||
Token t2 = null;
|
Token t2 = null;
|
||||||
assertFalse(t1.equals(t2));
|
assertFalse(t1.equals(t2));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldReturnFalseIfTypesAreDifferent() {
|
public void shouldReturnFalseIfTypesAreDifferent() {
|
||||||
Token t1 = new Token(EQUAL, "=");
|
Token t1 = new Token(EQUAL, "=", 0);
|
||||||
Token t2 = new Token(NOT_EQUAL, "!=");
|
Token t2 = new Token(NOT_EQUAL, "!=", 0);
|
||||||
assertFalse(t1.equals(t2));
|
assertFalse(t1.equals(t2));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldReturnFalseIfLexemesAreDifferent() {
|
public void shouldReturnFalseIfLexemesAreDifferent() {
|
||||||
Token t1 = new Token(NUMERIC, "1");
|
Token t1 = new Token(NUMERIC, "1", 0);
|
||||||
Token t2 = new Token(NUMERIC, "2");
|
Token t2 = new Token(NUMERIC, "2", 0);
|
||||||
|
assertFalse(t1.equals(t2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldReturnFalseIfPositionsAreDifferent() {
|
||||||
|
Token t1 = new Token(NUMERIC, "1", 1);
|
||||||
|
Token t2 = new Token(NUMERIC, "1", 2);
|
||||||
assertFalse(t1.equals(t2));
|
assertFalse(t1.equals(t2));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -104,8 +111,8 @@ public class LexerTokenTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldReturnSameHashCodeIfTokensAreEqual() {
|
public void shouldReturnSameHashCodeIfTokensAreEqual() {
|
||||||
Token t1 = new Token(NUMERIC, "1");
|
Token t1 = new Token(NUMERIC, "1", 0);
|
||||||
Token t2 = new Token(NUMERIC, "1");
|
Token t2 = new Token(NUMERIC, "1", 0);
|
||||||
assertTrue(t1.equals(t2));
|
assertTrue(t1.equals(t2));
|
||||||
assertEquals(t1.hashCode(), t2.hashCode());
|
assertEquals(t1.hashCode(), t2.hashCode());
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
* The MIT License
|
||||||
|
*
|
||||||
|
* Copyright 2014 Zafar Khaja <zafarkhaja@gmail.com>.
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in
|
||||||
|
* all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
* THE SOFTWARE.
|
||||||
|
*/
|
||||||
|
package com.github.zafarkhaja.semver.expr;
|
||||||
|
|
||||||
|
import com.github.zafarkhaja.semver.expr.Lexer.Token;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.Parameterized;
|
||||||
|
import org.junit.runners.Parameterized.Parameters;
|
||||||
|
import static com.github.zafarkhaja.semver.expr.Lexer.Token.Type.*;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Zafar Khaja <zafarkhaja@gmail.com>
|
||||||
|
*/
|
||||||
|
@RunWith(Parameterized.class)
|
||||||
|
public class ParserErrorHandlingTest {
|
||||||
|
|
||||||
|
private final String invalidExpr;
|
||||||
|
private final Token unexpected;
|
||||||
|
private final Token.Type[] expected;
|
||||||
|
|
||||||
|
public ParserErrorHandlingTest(
|
||||||
|
String invalidExpr,
|
||||||
|
Token unexpected,
|
||||||
|
Token.Type[] expected
|
||||||
|
) {
|
||||||
|
this.invalidExpr = invalidExpr;
|
||||||
|
this.unexpected = unexpected;
|
||||||
|
this.expected = expected;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCorrectlyHandleParseErrors() {
|
||||||
|
try {
|
||||||
|
ExpressionParser.newInstance().parse(invalidExpr);
|
||||||
|
} catch (UnexpectedTokenException e) {
|
||||||
|
assertEquals(unexpected, e.getUnexpectedToken());
|
||||||
|
assertArrayEquals(expected, e.getExpectedTokenTypes());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fail("Uncaught exception");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Parameters(name = "{0}")
|
||||||
|
public static Collection<Object[]> parameters() {
|
||||||
|
return Arrays.asList(new Object[][] {
|
||||||
|
{ "1)", new Token(RIGHT_PAREN, ")", 1), new Token.Type[] { EOL } },
|
||||||
|
{ "(>1.0.1", new Token(EOL, null, 7), new Token.Type[] { RIGHT_PAREN } },
|
||||||
|
{ "((>=1 & <2)", new Token(EOL, null, 11), new Token.Type[] { RIGHT_PAREN } },
|
||||||
|
{ ">=1.0.0 &", new Token(EOL, null, 9), new Token.Type[] { NUMERIC } },
|
||||||
|
{ "(>2.0 |)", new Token(RIGHT_PAREN, ")", 7), new Token.Type[] { NUMERIC } },
|
||||||
|
{ "& 1.2", new Token(AND, "&", 0), new Token.Type[] { NUMERIC } },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user