diff --git a/toml_testgen_test.go b/toml_testgen_test.go index f98f7fb..d060496 100644 --- a/toml_testgen_test.go +++ b/toml_testgen_test.go @@ -743,25 +743,8 @@ func TestTOMLTest_Invalid_Tests_Invalid_InlineTable_Empty3(t *testing.T) { testgenInvalid(t, input) } -func TestTOMLTest_Invalid_Tests_Invalid_InlineTable_Linebreak1(t *testing.T) { - input := "# No newlines are allowed between the curly braces unless they are valid within\n# a value.\nsimple = { a = 1 \n}\n" - testgenInvalid(t, input) -} - -func TestTOMLTest_Invalid_Tests_Invalid_InlineTable_Linebreak2(t *testing.T) { - input := "t = {a=1,\nb=2}\n" - testgenInvalid(t, input) -} - -func TestTOMLTest_Invalid_Tests_Invalid_InlineTable_Linebreak3(t *testing.T) { - input := "t = {a=1\n,b=2}\n" - testgenInvalid(t, input) -} - -func TestTOMLTest_Invalid_Tests_Invalid_InlineTable_Linebreak4(t *testing.T) { - input := "json_like = {\n first = \"Tom\",\n last = \"Preston-Werner\"\n}\n" - testgenInvalid(t, input) -} +// TestTOMLTest_Invalid_Tests_Invalid_InlineTable_Linebreak1 through Linebreak4 +// are removed because TOML v1.1.0 allows newlines in inline tables. func TestTOMLTest_Invalid_Tests_Invalid_InlineTable_NoClose1(t *testing.T) { input := "a={\n" @@ -833,10 +816,8 @@ func TestTOMLTest_Invalid_Tests_Invalid_InlineTable_Overwrite10(t *testing.T) { testgenInvalid(t, input) } -func TestTOMLTest_Invalid_Tests_Invalid_InlineTable_TrailingComma(t *testing.T) { - input := "# A terminating comma (also called trailing comma) is not permitted after the\n# last key/value pair in an inline table\nabc = { abc = 123, }\n" - testgenInvalid(t, input) -} +// TestTOMLTest_Invalid_Tests_Invalid_InlineTable_TrailingComma is removed +// because TOML v1.1.0 allows trailing commas in inline tables. func TestTOMLTest_Invalid_Tests_Invalid_Integer_CapitalBin(t *testing.T) { input := "capital-bin = 0B0\n" diff --git a/unmarshaler_test.go b/unmarshaler_test.go index 0a9d679..5fb2120 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -1094,6 +1094,87 @@ B = "data"`, } }, }, + { + desc: "multiline inline table", + input: "Name = {\n First = \"hello\",\n Last = \"world\"\n}", + gen: func() test { + type name struct { + First string + Last string + } + type doc struct { + Name name + } + + return test{ + target: &doc{}, + expected: &doc{Name: name{ + First: "hello", + Last: "world", + }}, + } + }, + }, + { + desc: "inline table with trailing comma", + input: `Name = {First = "hello", Last = "world",}`, + gen: func() test { + type name struct { + First string + Last string + } + type doc struct { + Name name + } + + return test{ + target: &doc{}, + expected: &doc{Name: name{ + First: "hello", + Last: "world", + }}, + } + }, + }, + { + desc: "multiline inline table with trailing comma and comments", + input: "Name = {\n # first name\n First = \"hello\",\n # last name\n Last = \"world\",\n}", + gen: func() test { + type name struct { + First string + Last string + } + type doc struct { + Name name + } + + return test{ + target: &doc{}, + expected: &doc{Name: name{ + First: "hello", + Last: "world", + }}, + } + }, + }, + { + desc: "nested multiline inline tables", + input: "A = {\n B = {\n C = 1,\n },\n}", + gen: func() test { + var v map[string]interface{} + + return test{ + target: &v, + expected: &map[string]interface{}{ + "A": map[string]interface{}{ + "B": map[string]interface{}{ + "C": int64(1), + }, + }, + }, + } + }, + }, { desc: "inline table inside array", input: `Names = [{First = "hello", Last = "world"}, {First = "ab", Last = "cd"}]`, diff --git a/unstable/parser.go b/unstable/parser.go index 1a08397..55ea50a 100644 --- a/unstable/parser.go +++ b/unstable/parser.go @@ -460,12 +460,14 @@ func (p *Parser) parseLiteralString(b []byte) ([]byte, []byte, []byte, error) { return v, v[1 : len(v)-1], rest, nil } +//nolint:funlen,cyclop func (p *Parser) parseInlineTable(b []byte) (reference, []byte, error) { // inline-table = inline-table-open [ inline-table-keyvals ] inline-table-close // inline-table-open = %x7B ws ; { // inline-table-close = ws %x7D ; } // inline-table-sep = ws %x2C ws ; , Comma // inline-table-keyvals = keyval [ inline-table-sep inline-table-keyvals ] + tableStart := b parent := p.builder.Push(Node{ Kind: InlineTable, Raw: p.rangeOfToken(b[:1], b[1:]), @@ -473,45 +475,77 @@ func (p *Parser) parseInlineTable(b []byte) (reference, []byte, error) { first := true - var child reference + lastChild := invalidReference + + addChild := func(ref reference) { + if lastChild == invalidReference { + p.builder.AttachChild(parent, ref) + } else { + p.builder.Chain(lastChild, ref) + } + lastChild = ref + } b = b[1:] var err error for len(b) > 0 { - previousB := b - b = p.parseWhitespace(b) + var cref reference + cref, b, err = p.parseOptionalWhitespaceCommentNewline(b) + if err != nil { + return parent, nil, err + } + + if cref != invalidReference { + addChild(cref) + } if len(b) == 0 { - return parent, nil, NewParserError(previousB[:1], "inline table is incomplete") + return parent, nil, NewParserError(tableStart[:1], "inline table is incomplete") } if b[0] == '}' { break } - if !first { - b, err = expect(',', b) + if b[0] == ',' { + if first { + return parent, nil, NewParserError(b[0:1], "inline table cannot start with comma") + } + b = b[1:] + + cref, b, err = p.parseOptionalWhitespaceCommentNewline(b) if err != nil { return parent, nil, err } - b = p.parseWhitespace(b) + if cref != invalidReference { + addChild(cref) + } + } else if !first { + return parent, nil, NewParserError(b[0:1], "inline table entries must be separated by commas") + } + + // trailing comma: if '}' follows, stop + if len(b) > 0 && b[0] == '}' { + break } var kv reference - kv, b, err = p.parseKeyval(b) if err != nil { return parent, nil, err } - if first { - p.builder.AttachChild(parent, kv) - } else { - p.builder.Chain(child, kv) + addChild(kv) + + cref, b, err = p.parseOptionalWhitespaceCommentNewline(b) + if err != nil { + return parent, nil, err + } + if cref != invalidReference { + addChild(cref) } - child = kv first = false } diff --git a/unstable/parser_test.go b/unstable/parser_test.go index 2f5f9ec..47cfca3 100644 --- a/unstable/parser_test.go +++ b/unstable/parser_test.go @@ -331,6 +331,87 @@ func TestParser_AST(t *testing.T) { }, }, }, + { + desc: "multiline inline table", + input: "name = {\n first = \"Tom\",\n last = \"Preston-Werner\"\n}", + ast: astNode{ + Kind: KeyValue, + Children: []astNode{ + { + Kind: InlineTable, + Children: []astNode{ + { + Kind: KeyValue, + Children: []astNode{ + {Kind: String, Data: []byte(`Tom`)}, + {Kind: Key, Data: []byte(`first`)}, + }, + }, + { + Kind: KeyValue, + Children: []astNode{ + {Kind: String, Data: []byte(`Preston-Werner`)}, + {Kind: Key, Data: []byte(`last`)}, + }, + }, + }, + }, + { + Kind: Key, + Data: []byte(`name`), + }, + }, + }, + }, + { + desc: "inline table with trailing comma", + input: `name = { first = "Tom", last = "Preston-Werner", }`, + ast: astNode{ + Kind: KeyValue, + Children: []astNode{ + { + Kind: InlineTable, + Children: []astNode{ + { + Kind: KeyValue, + Children: []astNode{ + {Kind: String, Data: []byte(`Tom`)}, + {Kind: Key, Data: []byte(`first`)}, + }, + }, + { + Kind: KeyValue, + Children: []astNode{ + {Kind: String, Data: []byte(`Preston-Werner`)}, + {Kind: Key, Data: []byte(`last`)}, + }, + }, + }, + }, + { + Kind: Key, + Data: []byte(`name`), + }, + }, + }, + }, + { + desc: "empty inline table with newline", + input: "name = {\n}", + ast: astNode{ + Kind: KeyValue, + Children: []astNode{ + { + Kind: InlineTable, + Children: nil, + }, + { + Kind: Key, + Data: []byte(`name`), + }, + }, + }, + }, } for _, e := range examples {