Create Lexer for Expression parser
Factored out VersionParser.CharStream into a separate class and made it generic to use in the Lexer. Did some refactoring of VersionParser.
This commit is contained in:
parent
87bb03dd7f
commit
2dc8bd8930
|
@ -29,11 +29,11 @@ package com.github.zafarkhaja.semver;
|
||||||
*/
|
*/
|
||||||
public class ParserException extends RuntimeException {
|
public class ParserException extends RuntimeException {
|
||||||
|
|
||||||
ParserException(String message) {
|
public ParserException(String message) {
|
||||||
super(message);
|
super(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
ParserException() {
|
public ParserException() {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@
|
||||||
*/
|
*/
|
||||||
package com.github.zafarkhaja.semver;
|
package com.github.zafarkhaja.semver;
|
||||||
|
|
||||||
import com.github.zafarkhaja.semver.VersionParser.CharStream;
|
import com.github.zafarkhaja.semver.util.Stream;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import static com.github.zafarkhaja.semver.VersionParser.Char.*;
|
import static com.github.zafarkhaja.semver.VersionParser.Char.*;
|
||||||
|
@ -34,127 +34,70 @@ import static com.github.zafarkhaja.semver.VersionParser.Char.*;
|
||||||
*/
|
*/
|
||||||
class VersionParser implements Parser<Version> {
|
class VersionParser implements Parser<Version> {
|
||||||
|
|
||||||
static class CharStream {
|
static enum Char implements Stream.ElementType<Character> {
|
||||||
|
|
||||||
static interface CharType {
|
|
||||||
boolean isMatchedBy(char chr);
|
|
||||||
}
|
|
||||||
|
|
||||||
private final char[] data;
|
|
||||||
|
|
||||||
private int offset = 0;
|
|
||||||
|
|
||||||
static final char EOL = (char) -1;
|
|
||||||
|
|
||||||
CharStream(String input) {
|
|
||||||
data = input.toCharArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
char consume() {
|
|
||||||
if (offset + 1 <= data.length) {
|
|
||||||
return data[offset++];
|
|
||||||
}
|
|
||||||
return EOL;
|
|
||||||
}
|
|
||||||
|
|
||||||
char consume(CharType... expected) {
|
|
||||||
char la = lookahead(1);
|
|
||||||
for (CharType charType : expected) {
|
|
||||||
if (charType.isMatchedBy(la)) {
|
|
||||||
return consume();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new UnexpectedCharacterException(la, expected);
|
|
||||||
}
|
|
||||||
|
|
||||||
char lookahead() {
|
|
||||||
return lookahead(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
char lookahead(int pos) {
|
|
||||||
int idx = offset + pos - 1;
|
|
||||||
if (idx < data.length) {
|
|
||||||
return data[idx];
|
|
||||||
}
|
|
||||||
return EOL;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean positiveLookahead(CharType... expected) {
|
|
||||||
char la = lookahead(1);
|
|
||||||
for (CharType charType : expected) {
|
|
||||||
if (charType.isMatchedBy(la)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean positiveLookaheadBefore(CharType before, CharType... expected) {
|
|
||||||
char la;
|
|
||||||
for (int i = 1; i <= data.length; i++) {
|
|
||||||
la = lookahead(i);
|
|
||||||
if (before.isMatchedBy(la)) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
for (CharType charType : expected) {
|
|
||||||
if (charType.isMatchedBy(la)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
char[] toArray() {
|
|
||||||
return data.clone();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static enum Char implements CharStream.CharType {
|
|
||||||
|
|
||||||
DIGIT {
|
DIGIT {
|
||||||
@Override
|
@Override
|
||||||
public boolean isMatchedBy(char chr) {
|
public boolean isMatchedBy(Character chr) {
|
||||||
|
if (chr == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return chr >= '0' && chr <= '9';
|
return chr >= '0' && chr <= '9';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
LETTER {
|
LETTER {
|
||||||
@Override
|
@Override
|
||||||
public boolean isMatchedBy(char chr) {
|
public boolean isMatchedBy(Character chr) {
|
||||||
|
if (chr == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return (chr >= 'a' && chr <= 'z')
|
return (chr >= 'a' && chr <= 'z')
|
||||||
|| (chr >= 'A' && chr <= 'Z');
|
|| (chr >= 'A' && chr <= 'Z');
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
DOT {
|
DOT {
|
||||||
@Override
|
@Override
|
||||||
public boolean isMatchedBy(char chr) {
|
public boolean isMatchedBy(Character chr) {
|
||||||
|
if (chr == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return chr == '.';
|
return chr == '.';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
HYPHEN {
|
HYPHEN {
|
||||||
@Override
|
@Override
|
||||||
public boolean isMatchedBy(char chr) {
|
public boolean isMatchedBy(Character chr) {
|
||||||
|
if (chr == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return chr == '-';
|
return chr == '-';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
PLUS {
|
PLUS {
|
||||||
@Override
|
@Override
|
||||||
public boolean isMatchedBy(char chr) {
|
public boolean isMatchedBy(Character chr) {
|
||||||
|
if (chr == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return chr == '+';
|
return chr == '+';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
EOL {
|
EOL {
|
||||||
@Override
|
@Override
|
||||||
public boolean isMatchedBy(char chr) {
|
public boolean isMatchedBy(Character chr) {
|
||||||
return chr == CharStream.EOL;
|
return chr == null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private final CharStream chars;
|
private final Stream<Character> chars;
|
||||||
|
|
||||||
VersionParser(String input) {
|
VersionParser(String input) {
|
||||||
chars = new CharStream(input);
|
Character[] elements = new Character[input.length()];
|
||||||
|
for (int i = 0; i < input.length(); i++) {
|
||||||
|
elements[i] = Character.valueOf(input.charAt(i));
|
||||||
|
}
|
||||||
|
chars = new Stream<Character>(elements);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -298,8 +241,8 @@ class VersionParser implements Parser<Version> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkForLeadingZeroes() {
|
private void checkForLeadingZeroes() {
|
||||||
char la1 = chars.lookahead(1);
|
Character la1 = chars.lookahead(1);
|
||||||
char la2 = chars.lookahead(2);
|
Character la2 = chars.lookahead(2);
|
||||||
if (la1 == '0' && DIGIT.isMatchedBy(la2)) {
|
if (la1 == '0' && DIGIT.isMatchedBy(la2)) {
|
||||||
throw new GrammarException(
|
throw new GrammarException(
|
||||||
"Numeric identifier MUST NOT contain leading zeroes"
|
"Numeric identifier MUST NOT contain leading zeroes"
|
||||||
|
|
145
src/main/java/com/github/zafarkhaja/semver/expr/Lexer.java
Normal file
145
src/main/java/com/github/zafarkhaja/semver/expr/Lexer.java
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
/*
|
||||||
|
* The MIT License
|
||||||
|
*
|
||||||
|
* Copyright 2013 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.util.Stream;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Zafar Khaja <zafarkhaja@gmail.com>
|
||||||
|
*/
|
||||||
|
class Lexer {
|
||||||
|
|
||||||
|
static class Token {
|
||||||
|
|
||||||
|
enum Type implements Stream.ElementType<Token> {
|
||||||
|
|
||||||
|
NUMERIC("0|[1-9][0-9]*"),
|
||||||
|
DOT("\\."),
|
||||||
|
HYPHEN("-"),
|
||||||
|
EQUAL("="),
|
||||||
|
NOT_EQUAL("!="),
|
||||||
|
GREATER(">(?!=)"),
|
||||||
|
GREATER_EQUAL(">="),
|
||||||
|
LESS("<(?!=)"),
|
||||||
|
LESS_EQUAL("<="),
|
||||||
|
TILDE("~"),
|
||||||
|
STAR("\\*"),
|
||||||
|
AND("&"),
|
||||||
|
OR("\\|"),
|
||||||
|
NOT("!(?!=)"),
|
||||||
|
LEFT_PAREN("\\("),
|
||||||
|
RIGHT_PAREN("\\)"),
|
||||||
|
WHITESPACE("\\s+"),
|
||||||
|
EOL("?!") {
|
||||||
|
@Override
|
||||||
|
public boolean isMatchedBy(Token token) {
|
||||||
|
return token == null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
final Pattern pattern;
|
||||||
|
|
||||||
|
private Type(String regexp) {
|
||||||
|
pattern = Pattern.compile("^(" + regexp + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return name() + "(" + pattern + ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isMatchedBy(Token token) {
|
||||||
|
if (token == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this == token.type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final Type type;
|
||||||
|
final String lexeme;
|
||||||
|
|
||||||
|
Token(Type type, String lexeme) {
|
||||||
|
this.type = type;
|
||||||
|
this.lexeme = (lexeme == null) ? "" : lexeme;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object other) {
|
||||||
|
if (this == other) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(other instanceof Token)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
Token token = (Token) other;
|
||||||
|
return type.equals(token.type) && lexeme.equals(token.lexeme);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int hash = 5;
|
||||||
|
hash = 71 * hash + type.hashCode();
|
||||||
|
hash = 71 * hash + lexeme.hashCode();
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return type.name() + "(" + lexeme + ")";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Lexer() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream<Token> tokenize(String input) {
|
||||||
|
List<Token> tokens = new ArrayList<Token>();
|
||||||
|
while (!input.isEmpty()) {
|
||||||
|
boolean matched = false;
|
||||||
|
for (Token.Type tokenType : Token.Type.values()) {
|
||||||
|
Matcher matcher = tokenType.pattern.matcher(input);
|
||||||
|
if (matcher.find()) {
|
||||||
|
matched = true;
|
||||||
|
input = matcher.replaceFirst("");
|
||||||
|
if (tokenType != Token.Type.WHITESPACE) {
|
||||||
|
tokens.add(new Token(tokenType, matcher.group()));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!matched) {
|
||||||
|
throw new LexerException(input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Stream<Token>(tokens.toArray(new Token[tokens.size()]));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
/*
|
||||||
|
* The MIT License
|
||||||
|
*
|
||||||
|
* Copyright 2013 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Zafar Khaja <zafarkhaja@gmail.com>
|
||||||
|
*/
|
||||||
|
public class LexerException extends RuntimeException {
|
||||||
|
|
||||||
|
private final String expr;
|
||||||
|
|
||||||
|
LexerException(String expr) {
|
||||||
|
this.expr = expr;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Illegal character near '" + expr + "'";
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* The MIT License
|
||||||
|
*
|
||||||
|
* Copyright 2013 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.ParserException;
|
||||||
|
import com.github.zafarkhaja.semver.expr.Lexer.*;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Zafar Khaja <zafarkhaja@gmail.com>
|
||||||
|
*/
|
||||||
|
public class UnexpectedTokenException extends ParserException {
|
||||||
|
|
||||||
|
private final Token unexpected;
|
||||||
|
private final Token.Type[] expected;
|
||||||
|
|
||||||
|
UnexpectedTokenException(Token token, Token.Type... expected) {
|
||||||
|
unexpected = token;
|
||||||
|
this.expected = expected;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
String message = "Unexpected token '" + unexpected + "'";
|
||||||
|
if (expected.length > 0) {
|
||||||
|
message += ", expecting '" + Arrays.toString(expected) + "'";
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
}
|
148
src/main/java/com/github/zafarkhaja/semver/util/Stream.java
Normal file
148
src/main/java/com/github/zafarkhaja/semver/util/Stream.java
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
/*
|
||||||
|
* The MIT License
|
||||||
|
*
|
||||||
|
* Copyright 2013 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.util;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.NoSuchElementException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Zafar Khaja <zafarkhaja@gmail.com>
|
||||||
|
*/
|
||||||
|
public class Stream<E> implements Iterable<E> {
|
||||||
|
|
||||||
|
public static interface ElementType<E> {
|
||||||
|
boolean isMatchedBy(E element);
|
||||||
|
}
|
||||||
|
|
||||||
|
private final E[] elements;
|
||||||
|
|
||||||
|
private int offset = 0;
|
||||||
|
|
||||||
|
public Stream(E[] elements) {
|
||||||
|
this.elements = elements.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
public E consume() {
|
||||||
|
if (offset >= elements.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return elements[offset++];
|
||||||
|
}
|
||||||
|
|
||||||
|
public E consume(ElementType<E>... expected) {
|
||||||
|
E lookahead = lookahead(1);
|
||||||
|
for (ElementType<E> type : expected) {
|
||||||
|
if (type.isMatchedBy(lookahead)) {
|
||||||
|
return consume();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new UnexpectedElementTypeException(lookahead, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
public E lookahead() {
|
||||||
|
return lookahead(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public E lookahead(int position) {
|
||||||
|
int idx = offset + position - 1;
|
||||||
|
if (idx < elements.length) {
|
||||||
|
return elements[idx];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean positiveLookahead(ElementType<E>... expected) {
|
||||||
|
for (ElementType<E> type : expected) {
|
||||||
|
if (type.isMatchedBy(lookahead(1))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean positiveLookaheadBefore(
|
||||||
|
ElementType<E> before,
|
||||||
|
ElementType<E>... expected
|
||||||
|
) {
|
||||||
|
E lookahead;
|
||||||
|
for (int i = 1; i <= elements.length; i++) {
|
||||||
|
lookahead = lookahead(i);
|
||||||
|
if (before.isMatchedBy(lookahead)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
for (ElementType<E> type : expected) {
|
||||||
|
if (type.isMatchedBy(lookahead)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean positiveLookaheadUntil(
|
||||||
|
int until,
|
||||||
|
ElementType<E>... expected
|
||||||
|
) {
|
||||||
|
for (int i = 1; i <= until; i++) {
|
||||||
|
for (ElementType<E> type : expected) {
|
||||||
|
if (type.isMatchedBy(lookahead(i))) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Iterator<E> iterator() {
|
||||||
|
return new Iterator<E>() {
|
||||||
|
|
||||||
|
private int index = offset;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasNext() {
|
||||||
|
return index < elements.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public E next() {
|
||||||
|
if (index >= elements.length) {
|
||||||
|
throw new NoSuchElementException();
|
||||||
|
}
|
||||||
|
return elements[index++];
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void remove() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public E[] toArray() {
|
||||||
|
return Arrays.copyOfRange(elements, offset, elements.length);
|
||||||
|
}
|
||||||
|
}
|
|
@ -21,28 +21,28 @@
|
||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
* THE SOFTWARE.
|
* THE SOFTWARE.
|
||||||
*/
|
*/
|
||||||
package com.github.zafarkhaja.semver;
|
package com.github.zafarkhaja.semver.util;
|
||||||
|
|
||||||
import com.github.zafarkhaja.semver.VersionParser.CharStream.CharType;
|
import com.github.zafarkhaja.semver.util.Stream.ElementType;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Zafar Khaja <zafarkhaja@gmail.com>
|
* @author Zafar Khaja <zafarkhaja@gmail.com>
|
||||||
*/
|
*/
|
||||||
public class UnexpectedCharacterException extends ParserException {
|
public class UnexpectedElementTypeException extends RuntimeException {
|
||||||
|
|
||||||
private final char unexpected;
|
private final Object unexpected;
|
||||||
private final CharType[] expected;
|
private final ElementType<?>[] expected;
|
||||||
|
|
||||||
UnexpectedCharacterException(char chr, CharType... expected) {
|
UnexpectedElementTypeException(Object element, ElementType<?>... expected) {
|
||||||
unexpected = chr;
|
unexpected = element;
|
||||||
this.expected = expected;
|
this.expected = expected;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
String message = "Unexpected character '" + unexpected + "'";
|
String message = "Unexpected element '" + unexpected + "'";
|
||||||
if (expected.length > 0) {
|
if (expected.length > 0) {
|
||||||
message += ", expecting '" + Arrays.toString(expected) + "'";
|
message += ", expecting '" + Arrays.toString(expected) + "'";
|
||||||
}
|
}
|
|
@ -1,118 +0,0 @@
|
||||||
/*
|
|
||||||
* The MIT License
|
|
||||||
*
|
|
||||||
* Copyright 2013 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;
|
|
||||||
|
|
||||||
import com.github.zafarkhaja.semver.VersionParser.Char;
|
|
||||||
import com.github.zafarkhaja.semver.VersionParser.CharStream;
|
|
||||||
import org.junit.Test;
|
|
||||||
import static org.junit.Assert.*;
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* @author Zafar Khaja <zafarkhaja@gmail.com>
|
|
||||||
*/
|
|
||||||
public class VersionParserCharStreamTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldBeBackedByCharArray() {
|
|
||||||
String input = "abc";
|
|
||||||
CharStream chars = new CharStream(input);
|
|
||||||
assertArrayEquals(input.toCharArray(), chars.toArray());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldNotReturnRealCharArray() {
|
|
||||||
CharStream chars = new CharStream("abc");
|
|
||||||
char[] charArray = chars.toArray();
|
|
||||||
charArray[0] = 'z';
|
|
||||||
assertEquals('z', charArray[0]);
|
|
||||||
assertEquals('a', chars.lookahead());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldConsumeCharactersOneByOne() {
|
|
||||||
CharStream chars = new CharStream("abc");
|
|
||||||
assertEquals('a', chars.consume());
|
|
||||||
assertEquals('b', chars.consume());
|
|
||||||
assertEquals('c', chars.consume());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldReturnEolWhenNothingLeftToConsume() {
|
|
||||||
CharStream chars = new CharStream("abc");
|
|
||||||
assertEquals('a', chars.consume());
|
|
||||||
assertEquals('b', chars.consume());
|
|
||||||
assertEquals('c', chars.consume());
|
|
||||||
assertEquals(CharStream.EOL, chars.consume());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldRaiseErrorWhenUnexpectedCharTypeConsumed() {
|
|
||||||
CharStream chars = new CharStream("abc");
|
|
||||||
try {
|
|
||||||
chars.consume(Char.DIGIT);
|
|
||||||
} catch (UnexpectedCharacterException e) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
fail("Should raise error when unexpected character type is consumed");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldLookaheadWithoutConsuming() {
|
|
||||||
CharStream chars = new CharStream("abc");
|
|
||||||
assertEquals('a', chars.lookahead());
|
|
||||||
assertEquals('a', chars.lookahead());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldLookaheadArbitraryNumberOfCharacters() {
|
|
||||||
CharStream chars = new CharStream("abc");
|
|
||||||
assertEquals('a', chars.lookahead(1));
|
|
||||||
assertEquals('b', chars.lookahead(2));
|
|
||||||
assertEquals('c', chars.lookahead(3));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldReturnEolWhenNothingLeftToLookahead() {
|
|
||||||
CharStream chars = new CharStream("abc");
|
|
||||||
assertEquals('a', chars.consume());
|
|
||||||
assertEquals('b', chars.consume());
|
|
||||||
assertEquals('c', chars.consume());
|
|
||||||
assertEquals(CharStream.EOL, chars.lookahead());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldCheckIfLookaheadIsOfExpectedTypes() {
|
|
||||||
CharStream chars = new CharStream("abc");
|
|
||||||
assertTrue(chars.positiveLookahead(Char.LETTER));
|
|
||||||
assertFalse(chars.positiveLookahead(Char.DIGIT, Char.PLUS));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void shouldCheckIfCharOfExpectedTypesExistBeforeGivenType() {
|
|
||||||
CharStream chars = new CharStream("1.0.0");
|
|
||||||
assertTrue(chars.positiveLookaheadBefore(Char.EOL, Char.DOT));
|
|
||||||
assertFalse(chars.positiveLookaheadBefore(Char.EOL, Char.LETTER));
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -23,9 +23,8 @@
|
||||||
*/
|
*/
|
||||||
package com.github.zafarkhaja.semver;
|
package com.github.zafarkhaja.semver;
|
||||||
|
|
||||||
import com.github.zafarkhaja.semver.VersionParser.Char;
|
|
||||||
import com.github.zafarkhaja.semver.VersionParser.CharStream;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import static com.github.zafarkhaja.semver.VersionParser.Char.*;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -36,49 +35,49 @@ public class VersionParserCharTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldBeMatchedByDigit() {
|
public void shouldBeMatchedByDigit() {
|
||||||
assertTrue(Char.DIGIT.isMatchedBy('0'));
|
assertTrue(DIGIT.isMatchedBy('0'));
|
||||||
assertTrue(Char.DIGIT.isMatchedBy('9'));
|
assertTrue(DIGIT.isMatchedBy('9'));
|
||||||
assertFalse(Char.DIGIT.isMatchedBy('a'));
|
assertFalse(DIGIT.isMatchedBy('a'));
|
||||||
assertFalse(Char.DIGIT.isMatchedBy('A'));
|
assertFalse(DIGIT.isMatchedBy('A'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldBeMatchedByLetter() {
|
public void shouldBeMatchedByLetter() {
|
||||||
assertTrue(Char.LETTER.isMatchedBy('a'));
|
assertTrue(LETTER.isMatchedBy('a'));
|
||||||
assertTrue(Char.LETTER.isMatchedBy('A'));
|
assertTrue(LETTER.isMatchedBy('A'));
|
||||||
assertFalse(Char.LETTER.isMatchedBy('0'));
|
assertFalse(LETTER.isMatchedBy('0'));
|
||||||
assertFalse(Char.LETTER.isMatchedBy('9'));
|
assertFalse(LETTER.isMatchedBy('9'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldBeMatchedByDot() {
|
public void shouldBeMatchedByDot() {
|
||||||
assertTrue(Char.DOT.isMatchedBy('.'));
|
assertTrue(DOT.isMatchedBy('.'));
|
||||||
assertFalse(Char.DOT.isMatchedBy('-'));
|
assertFalse(DOT.isMatchedBy('-'));
|
||||||
assertFalse(Char.DOT.isMatchedBy('0'));
|
assertFalse(DOT.isMatchedBy('0'));
|
||||||
assertFalse(Char.DOT.isMatchedBy('9'));
|
assertFalse(DOT.isMatchedBy('9'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldBeMatchedByHyphen() {
|
public void shouldBeMatchedByHyphen() {
|
||||||
assertTrue(Char.HYPHEN.isMatchedBy('-'));
|
assertTrue(HYPHEN.isMatchedBy('-'));
|
||||||
assertFalse(Char.HYPHEN.isMatchedBy('+'));
|
assertFalse(HYPHEN.isMatchedBy('+'));
|
||||||
assertFalse(Char.HYPHEN.isMatchedBy('a'));
|
assertFalse(HYPHEN.isMatchedBy('a'));
|
||||||
assertFalse(Char.HYPHEN.isMatchedBy('0'));
|
assertFalse(HYPHEN.isMatchedBy('0'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldBeMatchedByPlus() {
|
public void shouldBeMatchedByPlus() {
|
||||||
assertTrue(Char.PLUS.isMatchedBy('+'));
|
assertTrue(PLUS.isMatchedBy('+'));
|
||||||
assertFalse(Char.PLUS.isMatchedBy('-'));
|
assertFalse(PLUS.isMatchedBy('-'));
|
||||||
assertFalse(Char.PLUS.isMatchedBy('a'));
|
assertFalse(PLUS.isMatchedBy('a'));
|
||||||
assertFalse(Char.PLUS.isMatchedBy('0'));
|
assertFalse(PLUS.isMatchedBy('0'));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldBeMatchedByEol() {
|
public void shouldBeMatchedByEol() {
|
||||||
assertTrue(Char.EOL.isMatchedBy(CharStream.EOL));
|
assertTrue(EOL.isMatchedBy(null));
|
||||||
assertFalse(Char.EOL.isMatchedBy('-'));
|
assertFalse(EOL.isMatchedBy('-'));
|
||||||
assertFalse(Char.EOL.isMatchedBy('a'));
|
assertFalse(EOL.isMatchedBy('a'));
|
||||||
assertFalse(Char.EOL.isMatchedBy('0'));
|
assertFalse(EOL.isMatchedBy('0'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
/*
|
||||||
|
* The MIT License
|
||||||
|
*
|
||||||
|
* Copyright 2013 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.*;
|
||||||
|
import com.github.zafarkhaja.semver.util.Stream;
|
||||||
|
import org.junit.Test;
|
||||||
|
import static com.github.zafarkhaja.semver.expr.Lexer.Token.Type.*;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Zafar Khaja <zafarkhaja@gmail.com>
|
||||||
|
*/
|
||||||
|
public class LexerTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldTokenizeVersionString() {
|
||||||
|
Token[] expected = {
|
||||||
|
new Token(GREATER, ">"),
|
||||||
|
new Token(NUMERIC, "1"),
|
||||||
|
new Token(DOT, "."),
|
||||||
|
new Token(NUMERIC, "0"),
|
||||||
|
new Token(DOT, "."),
|
||||||
|
new Token(NUMERIC, "0"),
|
||||||
|
};
|
||||||
|
Lexer lexer = new Lexer();
|
||||||
|
Stream<Token> stream = lexer.tokenize(">1.0.0");
|
||||||
|
assertArrayEquals(expected, stream.toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldSkipWhitespaces() {
|
||||||
|
Token[] expected = {
|
||||||
|
new Token(GREATER, ">"),
|
||||||
|
new Token(NUMERIC, "1"),
|
||||||
|
};
|
||||||
|
Lexer lexer = new Lexer();
|
||||||
|
Stream<Token> stream = lexer.tokenize("> 1");
|
||||||
|
assertArrayEquals(expected, stream.toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldRaiseErrorOnIllegalCharacter() {
|
||||||
|
Lexer lexer = new Lexer();
|
||||||
|
try {
|
||||||
|
lexer.tokenize("@1.0.0");
|
||||||
|
} catch (LexerException e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fail("Should raise error on illegal character");
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,113 @@
|
||||||
|
/*
|
||||||
|
* The MIT License
|
||||||
|
*
|
||||||
|
* Copyright 2013 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 org.junit.Test;
|
||||||
|
import org.junit.experimental.runners.Enclosed;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import static com.github.zafarkhaja.semver.expr.Lexer.Token.Type.*;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Zafar Khaja <zafarkhaja@gmail.com>
|
||||||
|
*/
|
||||||
|
@RunWith(Enclosed.class)
|
||||||
|
public class LexerTokenTest {
|
||||||
|
|
||||||
|
public static class EqualsMethodTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldBeReflexive() {
|
||||||
|
Token token = new Token(NUMERIC, "1");
|
||||||
|
assertTrue(token.equals(token));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldBeSymmetric() {
|
||||||
|
Token t1 = new Token(EQUAL, "=");
|
||||||
|
Token t2 = new Token(EQUAL, "=");
|
||||||
|
assertTrue(t1.equals(t2));
|
||||||
|
assertTrue(t2.equals(t1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldBeTransitive() {
|
||||||
|
Token t1 = new Token(GREATER, ">");
|
||||||
|
Token t2 = new Token(GREATER, ">");
|
||||||
|
Token t3 = new Token(GREATER, ">");
|
||||||
|
assertTrue(t1.equals(t2));
|
||||||
|
assertTrue(t2.equals(t3));
|
||||||
|
assertTrue(t1.equals(t3));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldBeConsistent() {
|
||||||
|
Token t1 = new Token(HYPHEN, "-");
|
||||||
|
Token t2 = new Token(HYPHEN, "-");
|
||||||
|
assertTrue(t1.equals(t2));
|
||||||
|
assertTrue(t1.equals(t2));
|
||||||
|
assertTrue(t1.equals(t2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldReturnFalseIfOtherVersionIsOfDifferentType() {
|
||||||
|
Token t1 = new Token(DOT, ".");
|
||||||
|
assertFalse(t1.equals(new String(".")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldReturnFalseIfOtherVersionIsNull() {
|
||||||
|
Token t1 = new Token(AND, "&");
|
||||||
|
Token t2 = null;
|
||||||
|
assertFalse(t1.equals(t2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldReturnFalseIfTypesAreDifferent() {
|
||||||
|
Token t1 = new Token(EQUAL, "=");
|
||||||
|
Token t2 = new Token(NOT_EQUAL, "!=");
|
||||||
|
assertFalse(t1.equals(t2));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldReturnFalseIfLexemesAreDifferent() {
|
||||||
|
Token t1 = new Token(NUMERIC, "1");
|
||||||
|
Token t2 = new Token(NUMERIC, "2");
|
||||||
|
assertFalse(t1.equals(t2));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class HashCodeMethodTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldReturnSameHashCodeIfTokensAreEqual() {
|
||||||
|
Token t1 = new Token(NUMERIC, "1");
|
||||||
|
Token t2 = new Token(NUMERIC, "1");
|
||||||
|
assertTrue(t1.equals(t2));
|
||||||
|
assertEquals(t1.hashCode(), t2.hashCode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
206
src/test/java/com/github/zafarkhaja/semver/util/StreamTest.java
Normal file
206
src/test/java/com/github/zafarkhaja/semver/util/StreamTest.java
Normal file
|
@ -0,0 +1,206 @@
|
||||||
|
/*
|
||||||
|
* The MIT License
|
||||||
|
*
|
||||||
|
* Copyright 2013 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.util;
|
||||||
|
|
||||||
|
import com.github.zafarkhaja.semver.util.Stream.ElementType;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import org.junit.Test;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author Zafar Khaja <zafarkhaja@gmail.com>
|
||||||
|
*/
|
||||||
|
public class StreamTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldBeBackedByArray() {
|
||||||
|
Character[] input = {'a', 'b', 'c'};
|
||||||
|
Stream<Character> stream = new Stream<Character>(input);
|
||||||
|
assertArrayEquals(input, stream.toArray());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldImplementIterable() {
|
||||||
|
Character[] input = {'a', 'b', 'c'};
|
||||||
|
Stream<Character> stream = new Stream<Character>(input);
|
||||||
|
Iterator<Character> it = stream.iterator();
|
||||||
|
for (Character chr : input) {
|
||||||
|
assertEquals(chr, it.next());
|
||||||
|
}
|
||||||
|
assertFalse(it.hasNext());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldNotReturnRealElementsArray() {
|
||||||
|
Stream<Character> stream = new Stream<Character>(
|
||||||
|
new Character[] {'a', 'b', 'c'}
|
||||||
|
);
|
||||||
|
Character[] charArray = stream.toArray();
|
||||||
|
charArray[0] = Character.valueOf('z');
|
||||||
|
assertEquals(Character.valueOf('z'), charArray[0]);
|
||||||
|
assertEquals(Character.valueOf('a'), stream.toArray()[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldReturnArrayOfElementsThatAreLeftInStream() {
|
||||||
|
Stream<Character> stream = new Stream<Character>(
|
||||||
|
new Character[] {'a', 'b', 'c'}
|
||||||
|
);
|
||||||
|
stream.consume();
|
||||||
|
stream.consume();
|
||||||
|
assertEquals(1, stream.toArray().length);
|
||||||
|
assertEquals(Character.valueOf('c'), stream.toArray()[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldConsumeElementsOneByOne() {
|
||||||
|
Stream<Character> stream = new Stream<Character>(
|
||||||
|
new Character[] {'a', 'b', 'c'}
|
||||||
|
);
|
||||||
|
assertEquals(Character.valueOf('a'), stream.consume());
|
||||||
|
assertEquals(Character.valueOf('b'), stream.consume());
|
||||||
|
assertEquals(Character.valueOf('c'), stream.consume());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldRaiseErrorWhenUnexpectedElementTypeConsumed() {
|
||||||
|
Stream<Character> stream = new Stream<Character>(
|
||||||
|
new Character[] {'a', 'b', 'c'}
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
stream.consume(new ElementType<Character>() {
|
||||||
|
@Override
|
||||||
|
public boolean isMatchedBy(Character element) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (UnexpectedElementTypeException e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fail("Should raise error when unexpected element type is consumed");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldLookaheadWithoutConsuming() {
|
||||||
|
Stream<Character> stream = new Stream<Character>(
|
||||||
|
new Character[] {'a', 'b', 'c'}
|
||||||
|
);
|
||||||
|
assertEquals(Character.valueOf('a'), stream.lookahead());
|
||||||
|
assertEquals(Character.valueOf('a'), stream.lookahead());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldLookaheadArbitraryNumberOfElements() {
|
||||||
|
Stream<Character> stream = new Stream<Character>(
|
||||||
|
new Character[] {'a', 'b', 'c'}
|
||||||
|
);
|
||||||
|
assertEquals(Character.valueOf('a'), stream.lookahead(1));
|
||||||
|
assertEquals(Character.valueOf('b'), stream.lookahead(2));
|
||||||
|
assertEquals(Character.valueOf('c'), stream.lookahead(3));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCheckIfLookaheadIsOfExpectedTypes() {
|
||||||
|
Stream<Character> stream = new Stream<Character>(
|
||||||
|
new Character[] {'a', 'b', 'c'}
|
||||||
|
);
|
||||||
|
assertTrue(stream.positiveLookahead(
|
||||||
|
new ElementType<Character>() {
|
||||||
|
@Override
|
||||||
|
public boolean isMatchedBy(Character element) {
|
||||||
|
return element == 'a';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
));
|
||||||
|
assertFalse(stream.positiveLookahead(
|
||||||
|
new ElementType<Character>() {
|
||||||
|
@Override
|
||||||
|
public boolean isMatchedBy(Character element) {
|
||||||
|
return element == 'c';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCheckIfElementOfExpectedTypesExistBeforeGivenType() {
|
||||||
|
Stream<Character> stream = new Stream<Character>(
|
||||||
|
new Character[] {'1', '.', '0', '.', '0'}
|
||||||
|
);
|
||||||
|
assertTrue(stream.positiveLookaheadBefore(
|
||||||
|
new ElementType<Character>() {
|
||||||
|
@Override
|
||||||
|
public boolean isMatchedBy(Character element) {
|
||||||
|
return element == '.';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new ElementType<Character>() {
|
||||||
|
@Override
|
||||||
|
public boolean isMatchedBy(Character element) {
|
||||||
|
return element == '1';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
));
|
||||||
|
assertFalse(stream.positiveLookaheadBefore(
|
||||||
|
new ElementType<Character>() {
|
||||||
|
@Override
|
||||||
|
public boolean isMatchedBy(Character element) {
|
||||||
|
return element == '1';
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new ElementType<Character>() {
|
||||||
|
@Override
|
||||||
|
public boolean isMatchedBy(Character element) {
|
||||||
|
return element == '.';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCheckIfElementOfExpectedTypesExistUntilGivenPosition() {
|
||||||
|
Stream<Character> stream = new Stream<Character>(
|
||||||
|
new Character[] {'1', '.', '0', '.', '0'}
|
||||||
|
);
|
||||||
|
assertTrue(stream.positiveLookaheadUntil(
|
||||||
|
3,
|
||||||
|
new ElementType<Character>() {
|
||||||
|
@Override
|
||||||
|
public boolean isMatchedBy(Character element) {
|
||||||
|
return element == '0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
));
|
||||||
|
assertFalse(stream.positiveLookaheadUntil(
|
||||||
|
3,
|
||||||
|
new ElementType<Character>() {
|
||||||
|
@Override
|
||||||
|
public boolean isMatchedBy(Character element) {
|
||||||
|
return element == 'a';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user