Add syntax error positions to the error reporting
This commit is contained in:
parent
228d2274c4
commit
399df4d267
|
@ -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(
|
||||
|
|
|
@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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 } },
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user