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:
Zafar Khaja 2013-11-04 21:06:55 +04:00
parent 87bb03dd7f
commit 2dc8bd8930
12 changed files with 847 additions and 243 deletions

View File

@ -29,11 +29,11 @@ package com.github.zafarkhaja.semver;
*/
public class ParserException extends RuntimeException {
ParserException(String message) {
public ParserException(String message) {
super(message);
}
ParserException() {
public ParserException() {
}
}

View File

@ -23,7 +23,7 @@
*/
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.List;
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> {
static class CharStream {
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 {
static enum Char implements Stream.ElementType<Character> {
DIGIT {
@Override
public boolean isMatchedBy(char chr) {
public boolean isMatchedBy(Character chr) {
if (chr == null) {
return false;
}
return chr >= '0' && chr <= '9';
}
},
LETTER {
@Override
public boolean isMatchedBy(char chr) {
public boolean isMatchedBy(Character chr) {
if (chr == null) {
return false;
}
return (chr >= 'a' && chr <= 'z')
|| (chr >= 'A' && chr <= 'Z');
}
},
DOT {
@Override
public boolean isMatchedBy(char chr) {
public boolean isMatchedBy(Character chr) {
if (chr == null) {
return false;
}
return chr == '.';
}
},
HYPHEN {
@Override
public boolean isMatchedBy(char chr) {
public boolean isMatchedBy(Character chr) {
if (chr == null) {
return false;
}
return chr == '-';
}
},
PLUS {
@Override
public boolean isMatchedBy(char chr) {
public boolean isMatchedBy(Character chr) {
if (chr == null) {
return false;
}
return chr == '+';
}
},
EOL {
@Override
public boolean isMatchedBy(char chr) {
return chr == CharStream.EOL;
public boolean isMatchedBy(Character chr) {
return chr == null;
}
};
}
private final CharStream chars;
private final Stream<Character> chars;
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
@ -298,8 +241,8 @@ class VersionParser implements Parser<Version> {
}
private void checkForLeadingZeroes() {
char la1 = chars.lookahead(1);
char la2 = chars.lookahead(2);
Character la1 = chars.lookahead(1);
Character la2 = chars.lookahead(2);
if (la1 == '0' && DIGIT.isMatchedBy(la2)) {
throw new GrammarException(
"Numeric identifier MUST NOT contain leading zeroes"

View 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()]));
}
}

View File

@ -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 + "'";
}
}

View File

@ -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;
}
}

View 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);
}
}

View File

@ -21,28 +21,28 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* 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;
/**
*
* @author Zafar Khaja <zafarkhaja@gmail.com>
*/
public class UnexpectedCharacterException extends ParserException {
public class UnexpectedElementTypeException extends RuntimeException {
private final char unexpected;
private final CharType[] expected;
private final Object unexpected;
private final ElementType<?>[] expected;
UnexpectedCharacterException(char chr, CharType... expected) {
unexpected = chr;
UnexpectedElementTypeException(Object element, ElementType<?>... expected) {
unexpected = element;
this.expected = expected;
}
@Override
public String toString() {
String message = "Unexpected character '" + unexpected + "'";
String message = "Unexpected element '" + unexpected + "'";
if (expected.length > 0) {
message += ", expecting '" + Arrays.toString(expected) + "'";
}

View File

@ -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));
}
}

View File

@ -23,9 +23,8 @@
*/
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 com.github.zafarkhaja.semver.VersionParser.Char.*;
import static org.junit.Assert.*;
/**
@ -36,49 +35,49 @@ public class VersionParserCharTest {
@Test
public void shouldBeMatchedByDigit() {
assertTrue(Char.DIGIT.isMatchedBy('0'));
assertTrue(Char.DIGIT.isMatchedBy('9'));
assertFalse(Char.DIGIT.isMatchedBy('a'));
assertFalse(Char.DIGIT.isMatchedBy('A'));
assertTrue(DIGIT.isMatchedBy('0'));
assertTrue(DIGIT.isMatchedBy('9'));
assertFalse(DIGIT.isMatchedBy('a'));
assertFalse(DIGIT.isMatchedBy('A'));
}
@Test
public void shouldBeMatchedByLetter() {
assertTrue(Char.LETTER.isMatchedBy('a'));
assertTrue(Char.LETTER.isMatchedBy('A'));
assertFalse(Char.LETTER.isMatchedBy('0'));
assertFalse(Char.LETTER.isMatchedBy('9'));
assertTrue(LETTER.isMatchedBy('a'));
assertTrue(LETTER.isMatchedBy('A'));
assertFalse(LETTER.isMatchedBy('0'));
assertFalse(LETTER.isMatchedBy('9'));
}
@Test
public void shouldBeMatchedByDot() {
assertTrue(Char.DOT.isMatchedBy('.'));
assertFalse(Char.DOT.isMatchedBy('-'));
assertFalse(Char.DOT.isMatchedBy('0'));
assertFalse(Char.DOT.isMatchedBy('9'));
assertTrue(DOT.isMatchedBy('.'));
assertFalse(DOT.isMatchedBy('-'));
assertFalse(DOT.isMatchedBy('0'));
assertFalse(DOT.isMatchedBy('9'));
}
@Test
public void shouldBeMatchedByHyphen() {
assertTrue(Char.HYPHEN.isMatchedBy('-'));
assertFalse(Char.HYPHEN.isMatchedBy('+'));
assertFalse(Char.HYPHEN.isMatchedBy('a'));
assertFalse(Char.HYPHEN.isMatchedBy('0'));
assertTrue(HYPHEN.isMatchedBy('-'));
assertFalse(HYPHEN.isMatchedBy('+'));
assertFalse(HYPHEN.isMatchedBy('a'));
assertFalse(HYPHEN.isMatchedBy('0'));
}
@Test
public void shouldBeMatchedByPlus() {
assertTrue(Char.PLUS.isMatchedBy('+'));
assertFalse(Char.PLUS.isMatchedBy('-'));
assertFalse(Char.PLUS.isMatchedBy('a'));
assertFalse(Char.PLUS.isMatchedBy('0'));
assertTrue(PLUS.isMatchedBy('+'));
assertFalse(PLUS.isMatchedBy('-'));
assertFalse(PLUS.isMatchedBy('a'));
assertFalse(PLUS.isMatchedBy('0'));
}
@Test
public void shouldBeMatchedByEol() {
assertTrue(Char.EOL.isMatchedBy(CharStream.EOL));
assertFalse(Char.EOL.isMatchedBy('-'));
assertFalse(Char.EOL.isMatchedBy('a'));
assertFalse(Char.EOL.isMatchedBy('0'));
assertTrue(EOL.isMatchedBy(null));
assertFalse(EOL.isMatchedBy('-'));
assertFalse(EOL.isMatchedBy('a'));
assertFalse(EOL.isMatchedBy('0'));
}
}

View File

@ -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");
}
}

View File

@ -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());
}
}
}

View 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';
}
}
));
}
}