From 399df4d2677c709ff9c127f011835fb98e10d8d5 Mon Sep 17 00:00:00 2001 From: Zafar Khaja Date: Mon, 30 Jun 2014 15:28:57 +0300 Subject: [PATCH] Add syntax error positions to the error reporting --- .../semver/UnexpectedCharacterException.java | 37 +++++++++--- .../zafarkhaja/semver/VersionParser.java | 12 +++- .../github/zafarkhaja/semver/util/Stream.java | 11 +++- .../util/UnexpectedElementException.java | 29 +++++++-- .../semver/ParserErrorHandlingTest.java | 59 ++++++++++--------- .../zafarkhaja/semver/util/StreamTest.java | 13 ++++ 6 files changed, 120 insertions(+), 41 deletions(-) diff --git a/src/main/java/com/github/zafarkhaja/semver/UnexpectedCharacterException.java b/src/main/java/com/github/zafarkhaja/semver/UnexpectedCharacterException.java index 485d7c6..30fb99b 100644 --- a/src/main/java/com/github/zafarkhaja/semver/UnexpectedCharacterException.java +++ b/src/main/java/com/github/zafarkhaja/semver/UnexpectedCharacterException.java @@ -42,6 +42,11 @@ public class UnexpectedCharacterException extends ParseException { */ private final Character unexpected; + /** + * The position of the unexpected character. + */ + private final int position; + /** * The array of expected character types. */ @@ -54,19 +59,27 @@ public class UnexpectedCharacterException extends ParseException { * @param cause the wrapped exception */ UnexpectedCharacterException(UnexpectedElementException cause) { + position = cause.getPosition(); unexpected = (Character) cause.getUnexpectedElement(); - expected = (CharType[]) cause.getExpectedElementTypes(); + expected = (CharType[]) cause.getExpectedElementTypes(); } /** * Constructs a {@code UnexpectedCharacterException} instance - * with the unexpected character and the expected types. + * with the unexpected character, its position and the expected types. * - * @param cause the wrapped exception + * @param unexpected the unexpected character + * @param position the position of the unexpected character + * @param expected an array of the expected character types */ - UnexpectedCharacterException(Character unexpected, CharType... expected) { + UnexpectedCharacterException( + Character unexpected, + int position, + CharType... expected + ) { this.unexpected = unexpected; - this.expected = expected; + this.position = position; + this.expected = expected; } /** @@ -78,6 +91,15 @@ public class UnexpectedCharacterException extends ParseException { return unexpected; } + /** + * Gets the position of the unexpected character. + * + * @return the position of the unexpected character + */ + int getPosition() { + return position; + } + /** * Gets the expected character types. * @@ -97,9 +119,10 @@ public class UnexpectedCharacterException extends ParseException { @Override public String toString() { String message = String.format( - "Unexpected character '%s(%s)'", + "Unexpected character '%s(%s)' at position '%d'", CharType.forCharacter(unexpected), - unexpected + unexpected, + position ); if (expected.length > 0) { message += String.format( diff --git a/src/main/java/com/github/zafarkhaja/semver/VersionParser.java b/src/main/java/com/github/zafarkhaja/semver/VersionParser.java index facc8d4..3cba10a 100644 --- a/src/main/java/com/github/zafarkhaja/semver/VersionParser.java +++ b/src/main/java/com/github/zafarkhaja/semver/VersionParser.java @@ -493,7 +493,11 @@ class VersionParser implements Parser { if (DOT.isMatchedBy(la) || PLUS.isMatchedBy(la) || EOL.isMatchedBy(la)) { throw new ParseException( "Identifiers MUST NOT be empty", - new UnexpectedCharacterException(la, DIGIT, LETTER, HYPHEN) + new UnexpectedCharacterException( + la, + chars.currentOffset(), + DIGIT, LETTER, HYPHEN + ) ); } } @@ -521,7 +525,11 @@ class VersionParser implements Parser { */ private void ensureValidLookahead(CharType... expected) { if (!chars.positiveLookahead(expected)) { - throw new UnexpectedCharacterException(chars.lookahead(1), expected); + throw new UnexpectedCharacterException( + chars.lookahead(1), + chars.currentOffset(), + expected + ); } } } diff --git a/src/main/java/com/github/zafarkhaja/semver/util/Stream.java b/src/main/java/com/github/zafarkhaja/semver/util/Stream.java index 5b3ed77..3021c12 100644 --- a/src/main/java/com/github/zafarkhaja/semver/util/Stream.java +++ b/src/main/java/com/github/zafarkhaja/semver/util/Stream.java @@ -112,7 +112,7 @@ public class Stream implements Iterable { return consume(); } } - throw new UnexpectedElementException(lookahead, expected); + throw new UnexpectedElementException(lookahead, offset, expected); } /** @@ -149,6 +149,15 @@ public class Stream implements Iterable { return null; } + /** + * Returns the current offset of this stream. + * + * @return the current offset of this stream + */ + public int currentOffset() { + return offset; + } + /** * Checks if the next element in this stream is of the expected types. * diff --git a/src/main/java/com/github/zafarkhaja/semver/util/UnexpectedElementException.java b/src/main/java/com/github/zafarkhaja/semver/util/UnexpectedElementException.java index f7bcaa4..30d4835 100644 --- a/src/main/java/com/github/zafarkhaja/semver/util/UnexpectedElementException.java +++ b/src/main/java/com/github/zafarkhaja/semver/util/UnexpectedElementException.java @@ -40,6 +40,11 @@ public class UnexpectedElementException extends RuntimeException { */ private final Object unexpected; + /** + * The position of the unexpected element in the stream. + */ + private final int position; + /** * The array of the expected element types. */ @@ -50,10 +55,16 @@ public class UnexpectedElementException extends RuntimeException { * with the unexpected element and the expected types. * * @param element the unexpected element in the stream + * @param position the position of the unexpected element * @param expected an array of the expected element types */ - UnexpectedElementException(Object element, ElementType... expected) { - unexpected = element; + UnexpectedElementException( + Object element, + int position, + ElementType... expected + ) { + unexpected = element; + this.position = position; this.expected = expected; } @@ -66,6 +77,15 @@ public class UnexpectedElementException extends RuntimeException { return unexpected; } + /** + * Gets the position of the unexpected element. + * + * @return the position of the unexpected element + */ + public int getPosition() { + return position; + } + /** * Gets the expected element types. * @@ -85,8 +105,9 @@ public class UnexpectedElementException extends RuntimeException { @Override public String toString() { String message = String.format( - "Unexpected element '%s'", - unexpected + "Unexpected element '%s' at position '%d'", + unexpected, + position ); if (expected.length > 0) { message += String.format( diff --git a/src/test/java/com/github/zafarkhaja/semver/ParserErrorHandlingTest.java b/src/test/java/com/github/zafarkhaja/semver/ParserErrorHandlingTest.java index d3e0738..dc8b65b 100644 --- a/src/test/java/com/github/zafarkhaja/semver/ParserErrorHandlingTest.java +++ b/src/test/java/com/github/zafarkhaja/semver/ParserErrorHandlingTest.java @@ -42,15 +42,18 @@ public class ParserErrorHandlingTest { private final String invalidVersion; private final Character unexpected; + private final int position; private final CharType[] expected; public ParserErrorHandlingTest( String invalidVersion, Character unexpected, + int position, CharType[] expected ) { this.invalidVersion = invalidVersion; this.unexpected = unexpected; + this.position = position; this.expected = expected; } @@ -60,12 +63,14 @@ public class ParserErrorHandlingTest { VersionParser.parseValidSemVer(invalidVersion); } catch (UnexpectedCharacterException e) { assertEquals(unexpected, e.getUnexpectedCharacter()); + assertEquals(position, e.getPosition()); assertArrayEquals(expected, e.getExpectedCharTypes()); return; } catch (ParseException e) { if (e.getCause() != null) { UnexpectedCharacterException cause = (UnexpectedCharacterException) e.getCause(); assertEquals(unexpected, cause.getUnexpectedCharacter()); + assertEquals(position, cause.getPosition()); assertArrayEquals(expected, cause.getExpectedCharTypes()); } return; @@ -76,33 +81,33 @@ public class ParserErrorHandlingTest { @Parameters(name = "{0}") public static Collection parameters() { return Arrays.asList(new Object[][] { - { "1", null, new CharType[] { DOT } }, - { "1 ", ' ', new CharType[] { DOT } }, - { "1.", null, new CharType[] { DIGIT } }, - { "1.2", null, new CharType[] { DOT } }, - { "1.2.", null, new CharType[] { DIGIT } }, - { "a.b.c", 'a', new CharType[] { DIGIT } }, - { "1.b.c", 'b', new CharType[] { DIGIT } }, - { "1.2.c", 'c', new CharType[] { DIGIT } }, - { "!.2.3", '!', new CharType[] { DIGIT } }, - { "1.!.3", '!', new CharType[] { DIGIT } }, - { "1.2.!", '!', new CharType[] { DIGIT } }, - { "v1.2.3", 'v', new CharType[] { DIGIT } }, - { "1.2.3-", null, new CharType[] { DIGIT, LETTER, HYPHEN } }, - { "1.2. 3", ' ', new CharType[] { DIGIT } }, - { "1.2.3=alpha", '=', new CharType[] { HYPHEN, PLUS, EOL } }, - { "1.2.3~beta", '~', new CharType[] { HYPHEN, PLUS, EOL } }, - { "1.2.3-be$ta", '$', new CharType[] { PLUS, EOL } }, - { "1.2.3+b1+b2", '+', new CharType[] { EOL } }, - { "1.2.3-rc!", '!', new CharType[] { PLUS, EOL } }, - { "1.2.3-+", '+', new CharType[] { DIGIT, LETTER, HYPHEN } }, - { "1.2.3-@", '@', new CharType[] { DIGIT, LETTER, HYPHEN } }, - { "1.2.3+@", '@', new CharType[] { DIGIT, LETTER, HYPHEN } }, - { "1.2.3-rc1.", null, new CharType[] { DIGIT, LETTER, HYPHEN } }, - { "1.2.3+20140620.", null, new CharType[] { DIGIT, LETTER, HYPHEN } }, - { "1.2.3-b.+b", '+', new CharType[] { DIGIT, LETTER, HYPHEN } }, - { "1.2.3-rc..", '.', new CharType[] { DIGIT, LETTER, HYPHEN } }, - { "1.2.3-rc+bld..", '.', new CharType[] { DIGIT, LETTER, HYPHEN } }, + { "1", null, 1, new CharType[] { DOT } }, + { "1 ", ' ', 1, new CharType[] { DOT } }, + { "1.", null, 2, new CharType[] { DIGIT } }, + { "1.2", null, 3, new CharType[] { DOT } }, + { "1.2.", null, 4, new CharType[] { DIGIT } }, + { "a.b.c", 'a', 0, new CharType[] { DIGIT } }, + { "1.b.c", 'b', 2, new CharType[] { DIGIT } }, + { "1.2.c", 'c', 4, new CharType[] { DIGIT } }, + { "!.2.3", '!', 0, new CharType[] { DIGIT } }, + { "1.!.3", '!', 2, new CharType[] { DIGIT } }, + { "1.2.!", '!', 4, new CharType[] { DIGIT } }, + { "v1.2.3", 'v', 0, new CharType[] { DIGIT } }, + { "1.2.3-", null, 6, new CharType[] { DIGIT, LETTER, HYPHEN } }, + { "1.2. 3", ' ', 4, new CharType[] { DIGIT } }, + { "1.2.3=alpha", '=', 5, new CharType[] { HYPHEN, PLUS, EOL } }, + { "1.2.3~beta", '~', 5, new CharType[] { HYPHEN, PLUS, EOL } }, + { "1.2.3-be$ta", '$', 8, new CharType[] { PLUS, EOL } }, + { "1.2.3+b1+b2", '+', 8, new CharType[] { EOL } }, + { "1.2.3-rc!", '!', 8, new CharType[] { PLUS, EOL } }, + { "1.2.3-+", '+', 6, new CharType[] { DIGIT, LETTER, HYPHEN } }, + { "1.2.3-@", '@', 6, new CharType[] { DIGIT, LETTER, HYPHEN } }, + { "1.2.3+@", '@', 6, new CharType[] { DIGIT, LETTER, HYPHEN } }, + { "1.2.3-rc.", null, 9, new CharType[] { DIGIT, LETTER, HYPHEN } }, + { "1.2.3+b.", null, 8, new CharType[] { DIGIT, LETTER, HYPHEN } }, + { "1.2.3-b.+b", '+', 8, new CharType[] { DIGIT, LETTER, HYPHEN } }, + { "1.2.3-rc..", '.', 9, new CharType[] { DIGIT, LETTER, HYPHEN } }, + { "1.2.3-a+b..", '.', 10, new CharType[] { DIGIT, LETTER, HYPHEN } }, }); } } diff --git a/src/test/java/com/github/zafarkhaja/semver/util/StreamTest.java b/src/test/java/com/github/zafarkhaja/semver/util/StreamTest.java index 4a43aaa..1a00831 100644 --- a/src/test/java/com/github/zafarkhaja/semver/util/StreamTest.java +++ b/src/test/java/com/github/zafarkhaja/semver/util/StreamTest.java @@ -228,4 +228,17 @@ public class StreamTest { stream.pushBack(); assertEquals(Character.valueOf('a'), stream.consume()); } + + @Test + public void shouldKeepTrackOfCurrentOffset() { + Stream stream = new Stream( + new Character[] {'a', 'b', 'c'} + ); + assertEquals(0, stream.currentOffset()); + stream.consume(); + assertEquals(1, stream.currentOffset()); + stream.consume(); + stream.consume(); + assertEquals(3, stream.currentOffset()); + } }