improve error messages for reserved chars

Requires passing the char through the tokenizer to the parser
since the parser will have more contextual information to
include in the error.
This commit is contained in:
Havoc Pennington 2011-12-05 14:25:22 -05:00
parent 236a49fe3e
commit 6254a1bccd
5 changed files with 80 additions and 4 deletions

View File

@ -4,5 +4,18 @@
package com.typesafe.config.impl; package com.typesafe.config.impl;
enum TokenType { enum TokenType {
START, END, COMMA, EQUALS, COLON, OPEN_CURLY, CLOSE_CURLY, OPEN_SQUARE, CLOSE_SQUARE, VALUE, NEWLINE, UNQUOTED_TEXT, SUBSTITUTION; START,
END,
COMMA,
EQUALS,
COLON,
OPEN_CURLY,
CLOSE_CURLY,
OPEN_SQUARE,
CLOSE_SQUARE,
VALUE,
NEWLINE,
UNQUOTED_TEXT,
SUBSTITUTION,
RESERVED_CHAR;
} }

View File

@ -482,9 +482,7 @@ final class Tokenizer {
if (firstNumberChars.indexOf(c) >= 0) { if (firstNumberChars.indexOf(c) >= 0) {
t = pullNumber(c); t = pullNumber(c);
} else if (notInUnquotedText.indexOf(c) >= 0) { } else if (notInUnquotedText.indexOf(c) >= 0) {
throw parseError(String t = Tokens.newReservedChar(lineOrigin(), c);
.format("Character '%c' is not the start of any valid token",
c));
} else { } else {
putBack(c); putBack(c);
t = pullUnquotedText(); t = pullUnquotedText();

View File

@ -119,6 +119,45 @@ final class Tokens {
} }
} }
static private class ReservedChar extends Token {
final private ConfigOrigin origin;
final private int value;
ReservedChar(ConfigOrigin origin, int c) {
super(TokenType.RESERVED_CHAR);
this.origin = origin;
this.value = c;
}
ConfigOrigin origin() {
return origin;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append('\'');
sb.appendCodePoint(value);
sb.append('\'');
return sb.toString();
}
@Override
protected boolean canEqual(Object other) {
return other instanceof ReservedChar;
}
@Override
public boolean equals(Object other) {
return super.equals(other) && ((ReservedChar) other).value == value;
}
@Override
public int hashCode() {
return 41 * (41 + super.hashCode()) + value;
}
}
// This is not a Value, because it requires special processing // This is not a Value, because it requires special processing
static private class Substitution extends Token { static private class Substitution extends Token {
final private ConfigOrigin origin; final private ConfigOrigin origin;
@ -200,6 +239,18 @@ final class Tokens {
} }
} }
static boolean isReservedChar(Token token) {
return token instanceof ReservedChar;
}
static ConfigOrigin getReservedCharOrigin(Token token) {
if (token instanceof ReservedChar) {
return ((ReservedChar) token).origin();
} else {
throw new ConfigException.BugOrBroken("tried to get reserved char origin from " + token);
}
}
static boolean isUnquotedText(Token token) { static boolean isUnquotedText(Token token) {
return token instanceof UnquotedText; return token instanceof UnquotedText;
} }
@ -267,6 +318,10 @@ final class Tokens {
return new Line(lineNumberJustEnded); return new Line(lineNumberJustEnded);
} }
static Token newReservedChar(ConfigOrigin origin, int codepoint) {
return new ReservedChar(origin, codepoint);
}
static Token newUnquotedText(ConfigOrigin origin, String s) { static Token newUnquotedText(ConfigOrigin origin, String s) {
return new UnquotedText(origin, s); return new UnquotedText(origin, s);
} }

View File

@ -383,6 +383,7 @@ abstract trait TestUtils {
def tokenTrue = Tokens.newBoolean(fakeOrigin(), true) def tokenTrue = Tokens.newBoolean(fakeOrigin(), true)
def tokenFalse = Tokens.newBoolean(fakeOrigin(), false) def tokenFalse = Tokens.newBoolean(fakeOrigin(), false)
def tokenNull = Tokens.newNull(fakeOrigin()) def tokenNull = Tokens.newNull(fakeOrigin())
def tokenReserved(c: Int) = Tokens.newReservedChar(fakeOrigin(), c)
def tokenUnquoted(s: String) = Tokens.newUnquotedText(fakeOrigin(), s) def tokenUnquoted(s: String) = Tokens.newUnquotedText(fakeOrigin(), s)
def tokenString(s: String) = Tokens.newString(fakeOrigin(), s) def tokenString(s: String) = Tokens.newString(fakeOrigin(), s)
def tokenDouble(d: Double) = Tokens.newDouble(fakeOrigin(), d, null) def tokenDouble(d: Double) = Tokens.newDouble(fakeOrigin(), d, null)

View File

@ -214,4 +214,13 @@ class TokenizerTest extends TestUtils {
tokenizerTest(List(tokenDouble(3.14)), "3.14//comment") tokenizerTest(List(tokenDouble(3.14)), "3.14//comment")
tokenizerTest(List(tokenDouble(3.14)), "3.14#comment") tokenizerTest(List(tokenDouble(3.14)), "3.14#comment")
} }
@Test
def tokenizeReservedChars() {
val tokenized = tokenizeAsList("+`^?!@*&\\")
assertEquals(Seq(Tokens.START, tokenReserved('+'), tokenReserved('`'),
tokenReserved('^'), tokenReserved('?'), tokenReserved('!'), tokenReserved('@'),
tokenReserved('*'), tokenReserved('&'), tokenReserved('\\'),
Tokens.END), tokenized)
}
} }