Skip to content

Commit

Permalink
Make character parsers have readable error messages, not just the new…
Browse files Browse the repository at this point in the history
…line parser.
  • Loading branch information
renggli committed Aug 1, 2020
1 parent 96c9a1d commit 1c2a6e1
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ public int hashCode() {
* Returns a parser for that detects newlines platform independently.
*/
public static final Parser NEWLINE_PARSER =
of('\n').or(of('\r', "'NEWLINE' expected").seq(of('\n').optional()));
of('\n').or(of('\r').seq(of('\n').optional()));

/**
* Converts the {@code position} index in a {@code buffer} to a line and
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public static CharacterParser of(
* Returns a parser that accepts a specific {@code character}.
*/
public static CharacterParser of(char character) {
return of(character, "'" + character + "' expected");
return of(character, "'" + toReadableString(character) + "' expected");
}

public static CharacterParser of(char character, String message) {
Expand All @@ -44,8 +44,8 @@ public static CharacterParser any(String message) {
/**
* Returns a parser that accepts any of the provided characters.
*/
public static CharacterParser anyOf(String chars) {
return anyOf(chars, "any of '" + chars + "' expected");
public static CharacterParser anyOf(String characters) {
return anyOf(characters, "any of '" + toReadableString(characters) + "' expected");
}

public static CharacterParser anyOf(String chars, String message) {
Expand All @@ -66,8 +66,8 @@ public static CharacterParser none(String message) {
/**
* Returns a parser that accepts none of the provided characters.
*/
public static CharacterParser noneOf(String chars) {
return noneOf(chars, "none of '" + chars + "' expected");
public static CharacterParser noneOf(String characters) {
return noneOf(characters, "none of '" + toReadableString(characters) + "' expected");
}

public static CharacterParser noneOf(String chars, String message) {
Expand Down Expand Up @@ -111,11 +111,10 @@ public static CharacterParser lowerCase(String message) {
* Returns a parser that accepts a specific character pattern.
*
* <p>Characters match themselves. A dash {@code -} between two characters
* matches the range of those characters. A caret {@code ^} at the beginning
* negates the pattern.
* matches the range of those characters. A caret {@code ^} at the beginning negates the pattern.
*/
public static CharacterParser pattern(String pattern) {
return pattern(pattern, "[" + pattern + "] expected");
return pattern(pattern, "[" + toReadableString(pattern) + "] expected");
}

public static CharacterParser pattern(String pattern, String message) {
Expand All @@ -126,7 +125,8 @@ public static CharacterParser pattern(String pattern, String message) {
* Returns a parser that accepts a specific character range.
*/
public static CharacterParser range(char start, char stop) {
return range(start, stop, start + ".." + stop + " expected");
return range(start, stop,
toReadableString(start) + ".." + toReadableString(stop) + " expected");
}

public static CharacterParser range(char start, char stop, String message) {
Expand Down Expand Up @@ -215,4 +215,35 @@ public CharacterParser copy() {
public String toString() {
return super.toString() + "[" + message + "]";
}

private static String toReadableString(String characters) {
StringBuilder buffer = new StringBuilder();
for (int i = 0; i < characters.length(); i++) {
buffer.append(toReadableString(characters.charAt(i)));
}
return buffer.toString();
}

private static String toReadableString(char character) {
switch (character) {
case '\b':
return "\\b"; // backspace
case '\t':
return "\\t"; // horizontal tab
case '\n':
return "\\n"; // new line
case '\f':
return "\\f"; // form feed
case '\r':
return "\\r"; // carriage return
}
if (Character.isISOControl(character)) {
String escape = Integer.toHexString(character);
while (escape.length() < 4) {
escape = "0" + escape;
}
return "\\u" + escape;
}
return Character.toString(character);
}
}
28 changes: 26 additions & 2 deletions petitparser-core/src/test/java/org/petitparser/CharacterTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,15 @@ public void testAnyOf() {
assertFailure(parser, "x", "any of 'uncopyrightable' expected");
}

@Test
public void testAnyOfSpecial() {
Parser parser = anyOf("\n\r\t");
assertSuccess(parser, "\n", '\n');
assertSuccess(parser, "\r", '\r');
assertSuccess(parser, "\t", '\t');
assertFailure(parser, "x", "any of '\\n\\r\\t' expected");
}

@Test
public void testAnyOfWithMessage() {
Parser parser = anyOf("uncopyrightable", "wrong");
Expand Down Expand Up @@ -110,6 +119,14 @@ public void testNoneOf() {
assertFailure(parser, "y", "none of 'uncopyrightable' expected");
}

@Test
public void testNoneOfSpecial() {
Parser parser = noneOf("\b\f");
assertSuccess(parser, "a", 'a');
assertFailure(parser, "\b", "none of '\\b\\f' expected");
assertFailure(parser, "\f", "none of '\\b\\f' expected");
}

@Test
public void testNoneOfWithMessage() {
Parser parser = noneOf("uncopyrightable", "wrong");
Expand All @@ -134,15 +151,22 @@ public void testNoneOfEmpty() {
}

@Test
public void testIs() {
public void testOf() {
Parser parser = of('a');
assertSuccess(parser, "a", 'a');
assertFailure(parser, "b", "'a' expected");
assertFailure(parser, "", "'a' expected");
}

@Test
public void testIsWithMessage() {
public void testOfSpecial() {
Parser parser = of('\u0001');
assertSuccess(parser, "\u0001", '\u0001');
assertFailure(parser, "a", "'\\u0001' expected");
}

@Test
public void testOfWithMessage() {
Parser parser = of('a', "wrong");
assertSuccess(parser, "a", 'a');
assertFailure(parser, "b", "wrong");
Expand Down

0 comments on commit 1c2a6e1

Please sign in to comment.