Add syntax error positions to the error reporting

This commit is contained in:
Zafar Khaja 2014-06-30 15:28:57 +03:00
parent 228d2274c4
commit 399df4d267
6 changed files with 120 additions and 41 deletions

View File

@ -42,6 +42,11 @@ public class UnexpectedCharacterException extends ParseException {
*/ */
private final Character unexpected; private final Character unexpected;
/**
* The position of the unexpected character.
*/
private final int position;
/** /**
* The array of expected character types. * The array of expected character types.
*/ */
@ -54,19 +59,27 @@ public class UnexpectedCharacterException extends ParseException {
* @param cause the wrapped exception * @param cause the wrapped exception
*/ */
UnexpectedCharacterException(UnexpectedElementException cause) { UnexpectedCharacterException(UnexpectedElementException cause) {
position = cause.getPosition();
unexpected = (Character) cause.getUnexpectedElement(); unexpected = (Character) cause.getUnexpectedElement();
expected = (CharType[]) cause.getExpectedElementTypes(); expected = (CharType[]) cause.getExpectedElementTypes();
} }
/** /**
* Constructs a {@code UnexpectedCharacterException} instance * 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.unexpected = unexpected;
this.expected = expected; this.position = position;
this.expected = expected;
} }
/** /**
@ -78,6 +91,15 @@ public class UnexpectedCharacterException extends ParseException {
return unexpected; 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. * Gets the expected character types.
* *
@ -97,9 +119,10 @@ public class UnexpectedCharacterException extends ParseException {
@Override @Override
public String toString() { public String toString() {
String message = String.format( String message = String.format(
"Unexpected character '%s(%s)'", "Unexpected character '%s(%s)' at position '%d'",
CharType.forCharacter(unexpected), CharType.forCharacter(unexpected),
unexpected unexpected,
position
); );
if (expected.length > 0) { if (expected.length > 0) {
message += String.format( message += String.format(

View File

@ -493,7 +493,11 @@ class VersionParser implements Parser<Version> {
if (DOT.isMatchedBy(la) || PLUS.isMatchedBy(la) || EOL.isMatchedBy(la)) { if (DOT.isMatchedBy(la) || PLUS.isMatchedBy(la) || EOL.isMatchedBy(la)) {
throw new ParseException( throw new ParseException(
"Identifiers MUST NOT be empty", "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<Version> {
*/ */
private void ensureValidLookahead(CharType... expected) { private void ensureValidLookahead(CharType... expected) {
if (!chars.positiveLookahead(expected)) { if (!chars.positiveLookahead(expected)) {
throw new UnexpectedCharacterException(chars.lookahead(1), expected); throw new UnexpectedCharacterException(
chars.lookahead(1),
chars.currentOffset(),
expected
);
} }
} }
} }

View File

@ -112,7 +112,7 @@ public class Stream<E> implements Iterable<E> {
return consume(); return consume();
} }
} }
throw new UnexpectedElementException(lookahead, expected); throw new UnexpectedElementException(lookahead, offset, expected);
} }
/** /**
@ -149,6 +149,15 @@ public class Stream<E> implements Iterable<E> {
return null; 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. * Checks if the next element in this stream is of the expected types.
* *

View File

@ -40,6 +40,11 @@ public class UnexpectedElementException extends RuntimeException {
*/ */
private final Object unexpected; private final Object unexpected;
/**
* The position of the unexpected element in the stream.
*/
private final int position;
/** /**
* The array of the expected element types. * The array of the expected element types.
*/ */
@ -50,10 +55,16 @@ public class UnexpectedElementException extends RuntimeException {
* with the unexpected element and the expected types. * with the unexpected element and the expected types.
* *
* @param element the unexpected element in the stream * @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 * @param expected an array of the expected element types
*/ */
UnexpectedElementException(Object element, ElementType<?>... expected) { UnexpectedElementException(
unexpected = element; Object element,
int position,
ElementType<?>... expected
) {
unexpected = element;
this.position = position;
this.expected = expected; this.expected = expected;
} }
@ -66,6 +77,15 @@ public class UnexpectedElementException extends RuntimeException {
return unexpected; 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. * Gets the expected element types.
* *
@ -85,8 +105,9 @@ public class UnexpectedElementException extends RuntimeException {
@Override @Override
public String toString() { public String toString() {
String message = String.format( String message = String.format(
"Unexpected element '%s'", "Unexpected element '%s' at position '%d'",
unexpected unexpected,
position
); );
if (expected.length > 0) { if (expected.length > 0) {
message += String.format( message += String.format(

View File

@ -42,15 +42,18 @@ public class ParserErrorHandlingTest {
private final String invalidVersion; private final String invalidVersion;
private final Character unexpected; private final Character unexpected;
private final int position;
private final CharType[] expected; private final CharType[] expected;
public ParserErrorHandlingTest( public ParserErrorHandlingTest(
String invalidVersion, String invalidVersion,
Character unexpected, Character unexpected,
int position,
CharType[] expected CharType[] expected
) { ) {
this.invalidVersion = invalidVersion; this.invalidVersion = invalidVersion;
this.unexpected = unexpected; this.unexpected = unexpected;
this.position = position;
this.expected = expected; this.expected = expected;
} }
@ -60,12 +63,14 @@ public class ParserErrorHandlingTest {
VersionParser.parseValidSemVer(invalidVersion); VersionParser.parseValidSemVer(invalidVersion);
} catch (UnexpectedCharacterException e) { } catch (UnexpectedCharacterException e) {
assertEquals(unexpected, e.getUnexpectedCharacter()); assertEquals(unexpected, e.getUnexpectedCharacter());
assertEquals(position, e.getPosition());
assertArrayEquals(expected, e.getExpectedCharTypes()); assertArrayEquals(expected, e.getExpectedCharTypes());
return; return;
} catch (ParseException e) { } catch (ParseException e) {
if (e.getCause() != null) { if (e.getCause() != null) {
UnexpectedCharacterException cause = (UnexpectedCharacterException) e.getCause(); UnexpectedCharacterException cause = (UnexpectedCharacterException) e.getCause();
assertEquals(unexpected, cause.getUnexpectedCharacter()); assertEquals(unexpected, cause.getUnexpectedCharacter());
assertEquals(position, cause.getPosition());
assertArrayEquals(expected, cause.getExpectedCharTypes()); assertArrayEquals(expected, cause.getExpectedCharTypes());
} }
return; return;
@ -76,33 +81,33 @@ public class ParserErrorHandlingTest {
@Parameters(name = "{0}") @Parameters(name = "{0}")
public static Collection<Object[]> parameters() { public static Collection<Object[]> parameters() {
return Arrays.asList(new Object[][] { return Arrays.asList(new Object[][] {
{ "1", null, new CharType[] { DOT } }, { "1", null, 1, new CharType[] { DOT } },
{ "1 ", ' ', new CharType[] { DOT } }, { "1 ", ' ', 1, new CharType[] { DOT } },
{ "1.", null, new CharType[] { DIGIT } }, { "1.", null, 2, new CharType[] { DIGIT } },
{ "1.2", null, new CharType[] { DOT } }, { "1.2", null, 3, new CharType[] { DOT } },
{ "1.2.", null, new CharType[] { DIGIT } }, { "1.2.", null, 4, new CharType[] { DIGIT } },
{ "a.b.c", 'a', new CharType[] { DIGIT } }, { "a.b.c", 'a', 0, new CharType[] { DIGIT } },
{ "1.b.c", 'b', new CharType[] { DIGIT } }, { "1.b.c", 'b', 2, new CharType[] { DIGIT } },
{ "1.2.c", 'c', new CharType[] { DIGIT } }, { "1.2.c", 'c', 4, new CharType[] { DIGIT } },
{ "!.2.3", '!', new CharType[] { DIGIT } }, { "!.2.3", '!', 0, new CharType[] { DIGIT } },
{ "1.!.3", '!', new CharType[] { DIGIT } }, { "1.!.3", '!', 2, new CharType[] { DIGIT } },
{ "1.2.!", '!', new CharType[] { DIGIT } }, { "1.2.!", '!', 4, new CharType[] { DIGIT } },
{ "v1.2.3", 'v', new CharType[] { DIGIT } }, { "v1.2.3", 'v', 0, new CharType[] { DIGIT } },
{ "1.2.3-", null, new CharType[] { DIGIT, LETTER, HYPHEN } }, { "1.2.3-", null, 6, new CharType[] { DIGIT, LETTER, HYPHEN } },
{ "1.2. 3", ' ', new CharType[] { DIGIT } }, { "1.2. 3", ' ', 4, new CharType[] { DIGIT } },
{ "1.2.3=alpha", '=', new CharType[] { HYPHEN, PLUS, EOL } }, { "1.2.3=alpha", '=', 5, new CharType[] { HYPHEN, PLUS, EOL } },
{ "1.2.3~beta", '~', new CharType[] { HYPHEN, PLUS, EOL } }, { "1.2.3~beta", '~', 5, new CharType[] { HYPHEN, PLUS, EOL } },
{ "1.2.3-be$ta", '$', new CharType[] { PLUS, EOL } }, { "1.2.3-be$ta", '$', 8, new CharType[] { PLUS, EOL } },
{ "1.2.3+b1+b2", '+', new CharType[] { EOL } }, { "1.2.3+b1+b2", '+', 8, new CharType[] { EOL } },
{ "1.2.3-rc!", '!', new CharType[] { PLUS, EOL } }, { "1.2.3-rc!", '!', 8, new CharType[] { PLUS, EOL } },
{ "1.2.3-+", '+', new CharType[] { DIGIT, LETTER, HYPHEN } }, { "1.2.3-+", '+', 6, new CharType[] { DIGIT, LETTER, HYPHEN } },
{ "1.2.3-@", '@', new CharType[] { DIGIT, LETTER, HYPHEN } }, { "1.2.3-@", '@', 6, new CharType[] { DIGIT, LETTER, HYPHEN } },
{ "1.2.3+@", '@', new CharType[] { DIGIT, LETTER, HYPHEN } }, { "1.2.3+@", '@', 6, new CharType[] { DIGIT, LETTER, HYPHEN } },
{ "1.2.3-rc1.", null, new CharType[] { DIGIT, LETTER, HYPHEN } }, { "1.2.3-rc.", null, 9, new CharType[] { DIGIT, LETTER, HYPHEN } },
{ "1.2.3+20140620.", null, new CharType[] { DIGIT, LETTER, HYPHEN } }, { "1.2.3+b.", null, 8, new CharType[] { DIGIT, LETTER, HYPHEN } },
{ "1.2.3-b.+b", '+', new CharType[] { DIGIT, LETTER, HYPHEN } }, { "1.2.3-b.+b", '+', 8, new CharType[] { DIGIT, LETTER, HYPHEN } },
{ "1.2.3-rc..", '.', new CharType[] { DIGIT, LETTER, HYPHEN } }, { "1.2.3-rc..", '.', 9, new CharType[] { DIGIT, LETTER, HYPHEN } },
{ "1.2.3-rc+bld..", '.', new CharType[] { DIGIT, LETTER, HYPHEN } }, { "1.2.3-a+b..", '.', 10, new CharType[] { DIGIT, LETTER, HYPHEN } },
}); });
} }
} }

View File

@ -228,4 +228,17 @@ public class StreamTest {
stream.pushBack(); stream.pushBack();
assertEquals(Character.valueOf('a'), stream.consume()); assertEquals(Character.valueOf('a'), stream.consume());
} }
@Test
public void shouldKeepTrackOfCurrentOffset() {
Stream<Character> stream = new Stream<Character>(
new Character[] {'a', 'b', 'c'}
);
assertEquals(0, stream.currentOffset());
stream.consume();
assertEquals(1, stream.currentOffset());
stream.consume();
stream.consume();
assertEquals(3, stream.currentOffset());
}
} }