allow omitting curly braces on root object

This commit is contained in:
Havoc Pennington 2011-11-12 15:02:20 -05:00
parent 39de5aa444
commit 4051d07870
4 changed files with 87 additions and 17 deletions

View File

@ -265,7 +265,7 @@ final class Parser {
if (Tokens.isValue(token)) {
return Tokens.getValue(token);
} else if (token == Tokens.OPEN_CURLY) {
return parseObject();
return parseObject(true);
} else if (token == Tokens.OPEN_SQUARE) {
return parseArray();
} else {
@ -379,8 +379,8 @@ final class Parser {
}
}
private AbstractConfigObject parseObject() {
// invoked just after the OPEN_CURLY
private AbstractConfigObject parseObject(boolean hadOpenCurly) {
// invoked just after the OPEN_CURLY (or START, if !hadOpenCurly)
Map<String, AbstractConfigValue> values = new HashMap<String, AbstractConfigValue>();
ConfigOrigin objectOrigin = lineOrigin();
boolean afterComma = false;
@ -389,8 +389,13 @@ final class Parser {
if (t == Tokens.CLOSE_CURLY) {
if (afterComma) {
throw parseError("expecting a field name after comma, got a close brace }");
} else if (!hadOpenCurly) {
throw parseError("unbalanced close brace '}' with no open brace");
}
break;
} else if (t == Tokens.END && !hadOpenCurly) {
putBack(t);
break;
} else if (flavor != SyntaxFlavor.JSON && isIncludeKeyword(t)) {
parseInclude(values);
@ -405,7 +410,7 @@ final class Parser {
&& afterKey == Tokens.OPEN_CURLY) {
// can omit the ':' or '=' before an object value
valueToken = afterKey;
newValue = parseObject();
newValue = parseObject(true);
} else {
if (!isKeyValueSeparatorToken(afterKey)) {
throw parseError("Key may not be followed by token: "
@ -458,13 +463,24 @@ final class Parser {
t = nextTokenIgnoringNewline();
if (t == Tokens.CLOSE_CURLY) {
if (!hadOpenCurly) {
throw parseError("unbalanced close brace '}' with no open brace");
}
break;
} else if (t == Tokens.COMMA) {
// continue looping
afterComma = true;
} else {
} else if (hadOpenCurly) {
throw parseError("Expecting close brace } or a comma, got "
+ t);
} else {
if (t == Tokens.END) {
putBack(t);
break;
} else {
throw parseError("Expecting end of input or a comma, got "
+ t);
}
}
}
return new SimpleConfigObject(objectOrigin,
@ -487,7 +503,7 @@ final class Parser {
} else if (Tokens.isValue(t)) {
values.add(parseValue(t));
} else if (t == Tokens.OPEN_CURLY) {
values.add(parseObject());
values.add(parseObject(true));
} else if (t == Tokens.OPEN_SQUARE) {
values.add(parseArray());
} else {
@ -515,7 +531,7 @@ final class Parser {
if (Tokens.isValue(t)) {
values.add(parseValue(t));
} else if (t == Tokens.OPEN_CURLY) {
values.add(parseObject());
values.add(parseObject(true));
} else if (t == Tokens.OPEN_SQUARE) {
values.add(parseArray());
} else {
@ -537,14 +553,24 @@ final class Parser {
t = nextTokenIgnoringNewline();
AbstractConfigValue result = null;
if (t == Tokens.OPEN_CURLY) {
result = parseObject();
result = parseObject(true);
} else if (t == Tokens.OPEN_SQUARE) {
result = parseArray();
} else if (t == Tokens.END) {
throw parseError("Empty document");
} else {
throw parseError("Document must have an object or array at root, unexpected token: "
+ t);
if (flavor == SyntaxFlavor.JSON) {
if (t == Tokens.END) {
throw parseError("Empty document");
} else {
throw parseError("Document must have an object or array at root, unexpected token: "
+ t);
}
} else {
// the root object can omit the surrounding braces.
// this token should be the first field's key, or part
// of it, so put it back.
putBack(t);
result = parseObject(false);
}
}
t = nextTokenIgnoringNewline();

View File

@ -0,0 +1,40 @@
"ints" : {
"fortyTwo" : 42,
"fortyTwoAgain" : 42
},
"floats" : {
"fortyTwoPointOne" : 42.1,
"fortyTwoPointOneAgain" : 42.1
},
"strings" : {
"abcd" : "abcd",
"abcdAgain" : "abcd",
"a" : "a",
"b" : "b",
"c" : "c",
"d" : "d",
"concatenated" : "null bar 42 baz true 3.14 hi"
},
"arrays" : {
"empty" : [],
"1" : [ 1 ],
"12" : [1, 2],
"123" : [1, 2, 3],
"ofString" : [ "a", "b", "c" ]
},
"booleans" : {
"true" : true,
"trueAgain" : true,
"false" : false,
"falseAgain" : false
},
"nulls" : {
"null" : null,
"nullAgain" : null
}

View File

@ -87,6 +87,6 @@ class EquivalentsTest extends TestUtils {
// it breaks every time you add a file, so you have to update it.
assertEquals(2, dirCount)
// this is the number of files not named original.*
assertEquals(9, fileCount)
assertEquals(10, fileCount)
}
}

View File

@ -92,7 +92,7 @@ abstract trait TestUtils {
// note: it's important to put {} or [] at the root if you
// want to test "invalidity reasons" other than "wrong root"
private val invalidJsonInvalidConf = List[ParseTest]("", // empty document
private val invalidJsonInvalidConf = List[ParseTest](
"{",
"}",
"[",
@ -102,6 +102,10 @@ abstract trait TestUtils {
"\"", // single quote by itself
"{ \"foo\" : }", // no value in object
"{ : 10 }", // no key in object
" \"foo\" : ", // no value in object with no braces
" : 10 ", // no key in object with no braces
" \"foo\" : 10 } ", // close brace but no open
" \"foo\" : 10 [ ", // no-braces object with trailing gunk
"{ \"foo\" }", // no value or colon
"{ \"a\" : [ }", // [ is not a valid value
ParseTest(true, "{ \"foo\" : 10, }"), // extra trailing comma (lift fails to throw)
@ -142,8 +146,7 @@ abstract trait TestUtils {
"${ #comment }",
"{ include \"bar\" : 10 }", // include with a value after it
"{ include foo }", // include with unquoted string
"{ include : { \"a\" : 1 } }", // include used as unquoted key
"") // empty document again, just for clean formatting of this list ;-)
"{ include : { \"a\" : 1 } }") // include used as unquoted key
// We'll automatically try each of these with whitespace modifications
// so no need to add every possible whitespace variation
@ -170,10 +173,11 @@ abstract trait TestUtils {
"""{ "foo" : { "bar" : "baz", "woo" : "w00t" }, "baz" : { "bar" : "baz", "woo" : [1,2,3,4], "w00t" : true, "a" : false, "b" : 3.14, "c" : null } }""",
"{}")
private val validConfInvalidJson = List[ParseTest](
private val validConfInvalidJson = List[ParseTest]("", // empty document
"""{ "foo" = 42 }""", // equals rather than colon
"""{ foo { "bar" : 42 } }""", // omit the colon for object value
"""{ foo baz { "bar" : 42 } }""", // omit the colon with unquoted key with spaces
""" "foo" : 42 """, // omit braces on root object
"""{ "foo" : bar }""", // no quotes on value
"""{ "foo" : null bar 42 baz true 3.14 "hi" }""", // bunch of values to concat into a string
"{ foo : \"bar\" }", // no quotes on key