Merge pull request #116 from typesafehub/wip/havocp-allow-unquoted-number-chars

Allow unquoted strings consisting entirely of number chars
This commit is contained in:
Havoc Pennington 2013-12-20 05:13:07 -08:00
commit b12a481cd9
5 changed files with 37 additions and 15 deletions

View File

@ -441,6 +441,7 @@ number-to-string library function).
be a two-element path with `foo10` and `0` as the elements.
- `foo"10.0"` is an unquoted then a quoted string which are
concatenated, so this is a single-element path.
- `1.2.3` is the three-element path with `1`,`2`,`3`
Unlike value concatenations, path expressions are _always_
converted to a string, even if they are just a single value.

View File

@ -355,7 +355,15 @@ final class Tokenizer {
return Tokens.newLong(lineOrigin, Long.parseLong(s), s);
}
} catch (NumberFormatException e) {
throw problem(s, "Invalid number: '" + s + "'", true /* suggestQuotes */, e);
// not a number after all, see if it's an unquoted string.
for (char u : s.toCharArray()) {
if (notInUnquotedText.indexOf(u) >= 0)
throw problem(asString(u), "Reserved character '" + asString(u)
+ "' is not allowed outside quotes", true /* suggestQuotes */);
}
// no evil chars so we just decide this was a string and
// not a number.
return Tokens.newUnquotedText(lineOrigin, s);
}
}

View File

@ -124,6 +124,7 @@ class ConfParserTest extends TestUtils {
assertEquals(path("a b"), parsePath(" a b"))
assertEquals(path("a", "b.c", "d"), parsePath("a.\"b.c\".d"))
assertEquals(path("3", "14"), parsePath("3.14"))
assertEquals(path("3", "14", "159"), parsePath("3.14.159"))
assertEquals(path("a3", "14"), parsePath("a3.14"))
assertEquals(path(""), parsePath("\"\""))
assertEquals(path("a", "", "b"), parsePath("a.\"\".b"))
@ -133,6 +134,9 @@ class ConfParserTest extends TestUtils {
assertEquals(path("a-c"), parsePath("a-c"))
assertEquals(path("a_c"), parsePath("a_c"))
assertEquals(path("-"), parsePath("\"-\""))
assertEquals(path("-"), parsePath("-"))
assertEquals(path("-foo"), parsePath("-foo"))
assertEquals(path("-10"), parsePath("-10"))
// here 10.0 is part of an unquoted string
assertEquals(path("foo10", "0"), parsePath("foo10.0"))
@ -140,6 +144,8 @@ class ConfParserTest extends TestUtils {
assertEquals(path("10", "0foo"), parsePath("10.0foo"))
// just a number
assertEquals(path("10", "0"), parsePath("10.0"))
// multiple-decimal number
assertEquals(path("1", "2", "3", "4"), parsePath("1.2.3.4"))
for (invalid <- Seq("", " ", " \n \n ", "a.", ".b", "a..b", "a${b}c", "\"\".", ".\"\"")) {
try {
@ -152,11 +158,6 @@ class ConfParserTest extends TestUtils {
throw e;
}
}
intercept[ConfigException.Parse] {
// this gets parsed as a number since it starts with '-'
parsePath("-")
}
}
@Test
@ -339,11 +340,6 @@ class ConfParserTest extends TestUtils {
lineNumberTest(2, "\n\"foo\"")
lineNumberTest(3, "\n\n\"foo\"")
// newline in middle of number uses the line the number was on
lineNumberTest(1, "1e\n")
lineNumberTest(2, "\n1e\n")
lineNumberTest(3, "\n\n1e\n")
// newlines in triple-quoted string should not hose up the numbering
lineNumberTest(1, "a : \"\"\"foo\"\"\"}")
lineNumberTest(2, "a : \"\"\"foo\n\"\"\"}")
@ -813,4 +809,14 @@ class ConfParserTest extends TestUtils {
val conf = ConfigFactory.parseString("foo= \uFEFFbar\uFEFF")
assertEquals("bar", conf.getString("foo"))
}
@Test
def acceptMultiPeriodNumericPath() {
val conf1 = ConfigFactory.parseString("0.1.2.3=foobar1")
assertEquals("foobar1", conf1.getString("0.1.2.3"))
val conf2 = ConfigFactory.parseString("0.1.2.3.ABC=foobar2")
assertEquals("foobar2", conf2.getString("0.1.2.3.ABC"))
val conf3 = ConfigFactory.parseString("ABC.0.1.2.3=foobar3")
assertEquals("foobar3", conf3.getString("ABC.0.1.2.3"))
}
}

View File

@ -67,6 +67,9 @@ class PathTest extends TestUtils {
RenderTest("\" foo \"", path(" foo ")),
// trailing space only
RenderTest("\"foo \"", path("foo ")))
// numbers with decimal points
RenderTest("1.2", path("1", "2"))
RenderTest("1.2.3.4", path("1", "2", "3", "4"))
for (t <- tests) {
assertEquals(t.expected, t.path.render())

View File

@ -342,8 +342,6 @@ abstract trait TestUtils {
// these two problems are ignored by the lift tokenizer
"[:\"foo\", \"bar\"]", // colon in an array; lift doesn't throw (tokenizer erases it)
"[\"foo\" : \"bar\"]", // colon in an array another way, lift ignores (tokenizer erases it)
"[ 10e3e3 ]", // two exponents. ideally this might parse to a number plus string "e3" but it's hard to implement.
"[ 1-e3 ]", // malformed number but all chars can appear in a number
"[ \"hello ]", // unterminated string
ParseTest(true, "{ \"foo\" , true }"), // comma instead of colon, lift is fine with this
ParseTest(true, "{ \"foo\" : true \"bar\" : false }"), // missing comma between fields, lift fine with this
@ -380,6 +378,7 @@ abstract trait TestUtils {
"[ += ]",
"+= 10",
"10 +=",
"[ 10e+3e ]", // "+" not allowed in unquoted strings, and not a valid number
ParseTest(true, "[ \"foo\nbar\" ]"), // unescaped newline in quoted string, lift doesn't care
"[ # comment ]",
"${ #comment }",
@ -412,7 +411,8 @@ abstract trait TestUtils {
"[ \"//comment\" ]", // quoted // comment
// this long one is mostly to test rendering
"""{ "foo" : { "bar" : "baz", "woo" : "w00t" }, "baz" : { "bar" : "baz", "woo" : [1,2,3,4], "w00t" : true, "a" : false, "b" : 3.14, "c" : null } }""",
"{}")
"{}",
ParseTest(true, "[ 10e+3 ]")) // "+" in a number (lift doesn't handle)
private val validConfInvalidJson = List[ParseTest]("", // empty document
" ", // empty document single space
@ -504,7 +504,11 @@ abstract trait TestUtils {
"a = [], a += b", // += operator with previous init
"{ a = [], a += 10 }", // += in braces object with previous init
"a += b", // += operator without previous init
"{ a += 10 }") // += in braces object without previous init
"{ a += 10 }", // += in braces object without previous init
"[ 10e3e3 ]", // two exponents. this should parse to a number plus string "e3"
"[ 1-e3 ]", // malformed number should end up as a string instead
"[ 1.0.0 ]", // two decimals, should end up as a string
"[ 1.0. ]") // trailing decimal should end up as a string
protected val invalidJson = validConfInvalidJson ++ invalidJsonInvalidConf;