diff --git a/Makefile b/Makefile index 4969bd1..aa2718e 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,11 @@ -all: +all: build + +build: @(cd cmd/toml-test-decoder; go build) test: - @(cd ast; go test) - @(cd parse; go test) + @(cd ast; go test | grep -v ^PASS) + @(cd parse; go test | grep -v ^PASS) -sushi-test: +sushi-test: build @(cd cmd/toml-test-decoder; go build; ${GOPATH}/bin/toml-test ./toml-test-decoder) diff --git a/ast/ast.go b/ast/ast.go index acabb84..52d4d14 100644 --- a/ast/ast.go +++ b/ast/ast.go @@ -69,10 +69,10 @@ const ( TypeBoolean ValueType = "boolean" // TypeOffsetDateTime identifies a date/time value, including timezone info (2019-06-18 10:32:15.173645362+0200). - TypeOffsetDateTime ValueType = "offset datetime" + TypeOffsetDateTime ValueType = "datetime" // TypeLocalDateTime identifies a date/time value, without timezone info (2018-12-25 12:12:18.876772533). - TypeLocalDateTime ValueType = "datetime" + TypeLocalDateTime ValueType = "datetime-local" // TypeLocalDate identifies a date value (2017-05-17). TypeLocalDate ValueType = "date" @@ -81,13 +81,18 @@ const ( TypeLocalTime ValueType = "time" // TypeArrayOfTables identifies an [[array.of.tables]]. - TypeArrayOfTables ValueType = "arrayOfTables" + TypeArrayOfTables ValueType = "array-of-tables" // TypeArray identifies ["an", "inline", "static", "array"]. TypeArray ValueType = "array" // TypeTable identifies an { "inline" = "table" } or [standard.table]. TypeTable ValueType = "table" + + // TypeImplicitTable identifies an intermediate table that was created + // using a table definition that looks like [a.b.c.d] (where a, b and c + // would be implicit tables when they hadn't been created explicitly before). + TypeImplicitTable ValueType = "implicit-table" ) // SetKeyValuePair is used to set a key and an accompanying value in the @@ -115,22 +120,30 @@ func (doc *Document) OpenTable(key Key) error { doc.CurrentKey = nil doc.Current = doc.Root // Go over all requested levels of the key. For all levels, except the last - // one, it is okay if a Table or TableArray already exists. For at least the - // last level, no table or value must exist, because that would mean we are - // overwriting an existing key/value pair, which is not allowed. + // one, it is okay if a Table, implicit Table or TableArray already exists. + // For at least the last level, no table or value must exist, or an implicit + // table must exist. Otherwise we are overwriting an existing value, which + // is not allowed. node, lastKeyPart, err := doc.makeTablePath(key) if err != nil { return fmt.Errorf("invalid table: %s", err) } - // Check if the key is still free for use. + // Check if the key is already in use. if existing, ok := node[lastKeyPart]; ok { - path := doc.formatKeyPath(key, len(key)-1) - return fmt.Errorf("invalid table: %s value already exists at key %s", existing.Type, path) + // It is, but it is an implicit table. Upgrade it to an explicit and use it. + if existing.Type == TypeImplicitTable { + existing.Type = TypeTable + node = existing.Data[0].(Table) + } else { + path := doc.formatKeyPath(key, len(key)-1) + return fmt.Errorf("invalid table: %s value already exists at key %s", existing.Type, path) + } + } else { + // The subtable does not exist yet. Create the subtable. + subTable := make(Table) + node[lastKeyPart] = NewValue(TypeTable, subTable) + node = subTable } - // The subtable does not exist yet. Create the subtable. - subTable := make(Table) - node[lastKeyPart] = NewValue(TypeTable, subTable) - node = subTable // From here on, key/value pairs are added to the newly defined table. doc.Current = node @@ -193,10 +206,10 @@ func (doc *Document) makeTablePath(key Key) (Table, string, error) { } if subValue, ok := node[keyPart]; ok { // You cannot overwrite an already defined key, regardless its value. - // When a value already exists at the current key, this can only be a table - // or an array of tables. In case of an array of tables, the last created - // table will be used. - if subValue.Type == TypeTable { + // When a value already exists at the current key, this can only be a table, + // an implicit table or an array of tables. In case of an array of tables, + // the table that was added last to the array will be used. + if subValue.Type == TypeTable || subValue.Type == TypeImplicitTable { // A table was found, traverse to that table. node = subValue.Data[0].(Table) } else if subValue.Type == TypeArrayOfTables { @@ -209,9 +222,9 @@ func (doc *Document) makeTablePath(key Key) (Table, string, error) { return nil, "", fmt.Errorf("%s value already exists at key %s", subValue.Type, path) } } else { - // The subtable does not exist yet. Create the subtable. + // The subtable does not exist yet. Create the subtable as an implicit table. subTable := make(Table) - node[keyPart] = NewValue(TypeTable, subTable) + node[keyPart] = NewValue(TypeImplicitTable, subTable) node = subTable } } diff --git a/ast/ast_test.go b/ast/ast_test.go index e962274..3de9859 100644 --- a/ast/ast_test.go +++ b/ast/ast_test.go @@ -73,9 +73,9 @@ func Test_ConstructExplicitTableAfterImplicitSubtable(t *testing.T) { p := ast.NewDocument() p.OpenTable(ast.NewKey("a", "b", "c")) p.SetKeyValuePair(ast.NewKey("answer"), ast.NewValue(ast.TypeString, "42")) - p.OpenTable(ast.NewKey("a")) + err := p.OpenTable(ast.NewKey("a")) p.SetKeyValuePair(ast.NewKey("better"), ast.NewValue(ast.TypeString, "43")) - return nil, p + return err, p }, "", `{"a": {"b": {"c": {"answer": "42"}}, "better": "43"}}`) diff --git a/ast/string.go b/ast/string.go index d658143..fa881f8 100644 --- a/ast/string.go +++ b/ast/string.go @@ -35,6 +35,8 @@ func (value Value) String() string { values[i] = value.(*Value).String() } return fmt.Sprintf("[%s]", strings.Join(values, ", ")) + case TypeImplicitTable: + fallthrough case TypeTable: pairs := value.Data[0].(Table) keys := make([]string, len(pairs)) diff --git a/cmd/toml-test-decoder/main.go b/cmd/toml-test-decoder/main.go index ef52012..0bc155d 100644 --- a/cmd/toml-test-decoder/main.go +++ b/cmd/toml-test-decoder/main.go @@ -77,6 +77,8 @@ func makeSushi(value *ast.Value) string { } else { return fmt.Sprintf(`{"type": "array", "value": [%s]}`, strings.Join(values, ", ")) } + case ast.TypeImplicitTable: + fallthrough case ast.TypeTable: pairs := value.Data[0].(ast.Table) keys := make([]string, len(pairs)) diff --git a/parse/value_array_test.go b/parse/value_array_test.go index 806e54c..09fc8d1 100644 --- a/parse/value_array_test.go +++ b/parse/value_array_test.go @@ -39,7 +39,7 @@ func TestArray(t *testing.T) { {`x=[[1],['a']]`, `{"x": [[1], ["a"]]}`, ``}, {`x=[[[],[]],[]]`, `{"x": [[[], []], []]}`, ``}, {"x=[\r\n\r\n \t\n [\r\n\r\n\t [],[\t]],\t\n[]\t \t \n ]", `{"x": [[[], []], []]}`, ``}, - {`x=[[1],'a']`, `{}`, `type mismatch in array of static arrays: found an item of type string at line 1, column 11`}, + {`x=[[1],'a']`, `{}`, `type mismatch in array of arrays: found an item of type string at line 1, column 11`}, } { p := newParser() testParse(t, p, p.startDocument, test)