Merge pull request #46 from pelletier/pelletier/inline-tables

Implement inline tables
This commit is contained in:
Thomas Pelletier
2015-11-03 16:05:32 +01:00
5 changed files with 172 additions and 15 deletions
+19 -1
View File
@@ -158,13 +158,17 @@ func (l *tomlLexer) lexRvalue() tomlLexStateFn {
case '.':
return l.errorf("cannot start float with a dot")
case '=':
return l.errorf("cannot have multiple equals for the same key")
return l.lexEqual
case '[':
l.depth++
return l.lexLeftBracket
case ']':
l.depth--
return l.lexRightBracket
case '{':
return l.lexLeftCurlyBrace
case '}':
return l.lexRightCurlyBrace
case '#':
return l.lexComment
case '"':
@@ -218,6 +222,20 @@ func (l *tomlLexer) lexRvalue() tomlLexStateFn {
return nil
}
func (l *tomlLexer) lexLeftCurlyBrace() tomlLexStateFn {
l.ignore()
l.pos++
l.emit(tokenLeftCurlyBrace)
return l.lexRvalue
}
func (l *tomlLexer) lexRightCurlyBrace() tomlLexStateFn {
l.ignore()
l.pos++
l.emit(tokenRightCurlyBrace)
return l.lexRvalue
}
func (l *tomlLexer) lexDate() tomlLexStateFn {
l.emit(tokenDate)
return l.lexRvalue
+5 -12
View File
@@ -8,11 +8,12 @@ func testFlow(t *testing.T, input string, expectedFlow []token) {
token := <-ch
if token != expected {
t.Log("While testing: ", input)
t.Log("compared (got)", token, "to (expected)", expected)
t.Log("\tvalue:", token.val, "<->", expected.val)
t.Log("\ttype:", token.typ.String(), "<->", expected.typ.String())
t.Log("\tline:", token.Line, "<->", expected.Line)
t.Log("\tcolumn:", token.Col, "<->", expected.Col)
t.Log("compared", token, "to", expected)
t.Log(token.val, "<->", expected.val)
t.Log(token.typ, "<->", expected.typ)
t.Log(token.Line, "<->", expected.Line)
t.Log(token.Col, "<->", expected.Col)
t.FailNow()
}
}
@@ -371,14 +372,6 @@ func TestFloatWithExponent5(t *testing.T) {
})
}
func TestDoubleEqualKey(t *testing.T) {
testFlow(t, "foo= = 2", []token{
token{Position{1, 1}, tokenKey, "foo"},
token{Position{1, 4}, tokenEqual, "="},
token{Position{1, 5}, tokenError, "cannot have multiple equals for the same key"},
})
}
func TestInvalidEsquapeSequence(t *testing.T) {
testFlow(t, `foo = "\x"`, []token{
token{Position{1, 1}, tokenKey, "foo"},
+61 -1
View File
@@ -171,6 +171,7 @@ func (p *tomlParser) parseGroup() tomlParserStateFn {
func (p *tomlParser) parseAssign() tomlParserStateFn {
key := p.getToken()
p.assume(tokenEqual)
value := p.parseRvalue()
var groupKey []string
if len(p.currentGroup) > 0 {
@@ -245,6 +246,10 @@ func (p *tomlParser) parseRvalue() interface{} {
return val
case tokenLeftBracket:
return p.parseArray()
case tokenLeftCurlyBrace:
return p.parseInlineTable()
case tokenEqual:
p.raiseError(tok, "cannot have multiple equals for the same key")
case tokenError:
p.raiseError(tok, "%s", tok)
}
@@ -254,7 +259,51 @@ func (p *tomlParser) parseRvalue() interface{} {
return nil
}
func (p *tomlParser) parseArray() []interface{} {
func tokenIsComma(t *token) bool {
return t != nil && t.typ == tokenComma
}
func (p *tomlParser) parseInlineTable() *TomlTree {
tree := newTomlTree()
var previous *token
Loop:
for {
follow := p.peek()
if follow == nil || follow.typ == tokenEOF {
p.raiseError(follow, "unterminated inline table")
}
switch follow.typ {
case tokenRightCurlyBrace:
p.getToken()
break Loop
case tokenKey:
if !tokenIsComma(previous) && previous != nil {
p.raiseError(follow, "comma expected between fields in inline table")
}
key := p.getToken()
p.assume(tokenEqual)
value := p.parseRvalue()
tree.Set(key.val, value)
case tokenComma:
if previous == nil {
p.raiseError(follow, "inline table cannot start with a comma")
}
if tokenIsComma(previous) {
p.raiseError(follow, "need field between two commas in inline table")
}
p.getToken()
default:
p.raiseError(follow, "unexpected token type in inline table: %s", follow.typ.String())
}
previous = follow
}
if tokenIsComma(previous) {
p.raiseError(previous, "trailing comma at the end of inline table")
}
return tree
}
func (p *tomlParser) parseArray() interface{} {
var array []interface{}
arrayType := reflect.TypeOf(nil)
for {
@@ -285,6 +334,17 @@ func (p *tomlParser) parseArray() []interface{} {
p.getToken()
}
}
// An array of TomlTrees is actually an array of inline
// tables, which is a shorthand for a table array. If the
// array was not converted from []interface{} to []*TomlTree,
// the two notations would not be equivalent.
if arrayType == reflect.TypeOf(newTomlTree()) {
tomlArray := make([]*TomlTree, len(array))
for i, v := range array {
tomlArray[i] = v.(*TomlTree)
}
return tomlArray
}
return array
}
+81
View File
@@ -330,6 +330,80 @@ func TestArrayWithExtraCommaComment(t *testing.T) {
})
}
func TestSimpleInlineGroup(t *testing.T) {
tree, err := Load("key = {a = 42}")
assertTree(t, tree, err, map[string]interface{}{
"key": map[string]interface{}{
"a": int64(42),
},
})
}
func TestDoubleInlineGroup(t *testing.T) {
tree, err := Load("key = {a = 42, b = \"foo\"}")
assertTree(t, tree, err, map[string]interface{}{
"key": map[string]interface{}{
"a": int64(42),
"b": "foo",
},
})
}
func TestExampleInlineGroup(t *testing.T) {
tree, err := Load(`name = { first = "Tom", last = "Preston-Werner" }
point = { x = 1, y = 2 }`)
assertTree(t, tree, err, map[string]interface{}{
"name": map[string]interface{}{
"first": "Tom",
"last": "Preston-Werner",
},
"point": map[string]interface{}{
"x": int64(1),
"y": int64(2),
},
})
}
func TestExampleInlineGroupInArray(t *testing.T) {
tree, err := Load(`points = [{ x = 1, y = 2 }]`)
assertTree(t, tree, err, map[string]interface{}{
"points": []map[string]interface{}{
map[string]interface{}{
"x": int64(1),
"y": int64(2),
},
},
})
}
func TestInlineTableUnterminated(t *testing.T) {
_, err := Load("foo = {")
if err.Error() != "(1, 8): unterminated inline table" {
t.Error("Bad error message:", err.Error())
}
}
func TestInlineTableCommaExpected(t *testing.T) {
_, err := Load("foo = {hello = 53 test = foo}")
if err.Error() != "(1, 19): comma expected between fields in inline table" {
t.Error("Bad error message:", err.Error())
}
}
func TestInlineTableCommaStart(t *testing.T) {
_, err := Load("foo = {, hello = 53}")
if err.Error() != "(1, 8): inline table cannot start with a comma" {
t.Error("Bad error message:", err.Error())
}
}
func TestInlineTableDoubleComma(t *testing.T) {
_, err := Load("foo = {hello = 53,, foo = 17}")
if err.Error() != "(1, 19): need field between two commas in inline table" {
t.Error("Bad error message:", err.Error())
}
}
func TestDuplicateGroups(t *testing.T) {
_, err := Load("[foo]\na=2\n[foo]b=3")
if err.Error() != "(3, 2): duplicated tables" {
@@ -545,3 +619,10 @@ func TestInvalidGroupArray(t *testing.T) {
t.Error("Should error")
}
}
func TestDoubleEqual(t *testing.T) {
_, err := Load("foo= = 2")
if err.Error() != "(1, 6): cannot have multiple equals for the same key" {
t.Error("Bad error message:", err.Error())
}
}
+6 -1
View File
@@ -26,6 +26,8 @@ const (
tokenEqual
tokenLeftBracket
tokenRightBracket
tokenLeftCurlyBrace
tokenRightCurlyBrace
tokenLeftParen
tokenRightParen
tokenDoubleLeftBracket
@@ -44,6 +46,7 @@ const (
)
var tokenTypeNames = []string{
"Error",
"EOF",
"Comment",
"Key",
@@ -54,7 +57,9 @@ var tokenTypeNames = []string{
"Float",
"=",
"[",
"[",
"]",
"{",
"}",
"(",
")",
"]]",