Merge branch 'master' into pelletier/inline-tables
diff --git a/lexer.go b/lexer.go index 95beb09..25635b4 100644 --- a/lexer.go +++ b/lexer.go
@@ -158,13 +158,17 @@ 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 @@ 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
diff --git a/lexer_test.go b/lexer_test.go index d8abb35..df2cac9 100644 --- a/lexer_test.go +++ b/lexer_test.go
@@ -8,11 +8,12 @@ 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 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"},
diff --git a/parser.go b/parser.go index bd33e0d..c3afa04 100644 --- a/parser.go +++ b/parser.go
@@ -171,6 +171,7 @@ 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 @@ 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 @@ 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 @@ 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 }
diff --git a/parser_test.go b/parser_test.go index 9b827b1..365c247 100644 --- a/parser_test.go +++ b/parser_test.go
@@ -330,6 +330,52 @@ }) } +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 TestDuplicateGroups(t *testing.T) { _, err := Load("[foo]\na=2\n[foo]b=3") if err.Error() != "(3, 2): duplicated tables" { @@ -545,3 +591,10 @@ 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()) + } +}
diff --git a/token.go b/token.go index f0fbac9..ebbf960 100644 --- a/token.go +++ b/token.go
@@ -26,6 +26,8 @@ tokenEqual tokenLeftBracket tokenRightBracket + tokenLeftCurlyBrace + tokenRightCurlyBrace tokenLeftParen tokenRightParen tokenDoubleLeftBracket @@ -44,6 +46,7 @@ ) var tokenTypeNames = []string{ + "Error", "EOF", "Comment", "Key", @@ -54,7 +57,9 @@ "Float", "=", "[", - "[", + "]", + "{", + "}", "(", ")", "]]",