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;
|
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(
|
||||||
|
|
|
@ -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
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
*
|
*
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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 } },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user