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;
/**
* 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(

View File

@ -493,7 +493,11 @@ class VersionParser implements Parser<Version> {
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<Version> {
*/
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
);
}
}
}

View File

@ -112,7 +112,7 @@ public class Stream<E> implements Iterable<E> {
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;
}
/**
* 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.
*

View File

@ -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(

View File

@ -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<Object[]> 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 } },
});
}
}

View File

@ -228,4 +228,17 @@ public class StreamTest {
stream.pushBack();
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());
}
}