Implement inline tables
diff --git a/.travis.yml b/.travis.yml index 83e84ad..eeaf72b 100644 --- a/.travis.yml +++ b/.travis.yml
@@ -2,10 +2,10 @@ script: "./test.sh" sudo: false go: - - 1.1 - 1.2 - 1.3 - 1.4.1 + - 1.5 - tip before_install: - go get github.com/axw/gocov/gocov
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 82946bf..3509367 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 59f513b..9075002 100644 --- a/parser.go +++ b/parser.go
@@ -244,6 +244,8 @@ return val case tokenLeftBracket: return p.parseArray() + case tokenLeftCurlyBrace: + return p.parseInlineTable() case tokenError: p.raiseError(tok, "%s", tok) } @@ -253,7 +255,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 { @@ -263,6 +309,13 @@ } if follow.typ == tokenRightBracket { p.getToken() + if arrayType == reflect.TypeOf(newTomlTree()) { + tomlArray := make([]*TomlTree, len(array)) + for i, v := range array { + tomlArray[i] = v.(*TomlTree) + } + return tomlArray + } return array } val := p.parseRvalue()
diff --git a/parser_test.go b/parser_test.go index ebf1626..c507a23 100644 --- a/parser_test.go +++ b/parser_test.go
@@ -310,6 +310,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" {
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", "=", "[", - "[", + "]", + "{", + "}", "(", ")", "]]",