From e4bc5d99cae1765db0abacb182aff77b2660344f Mon Sep 17 00:00:00 2001 From: Gabriel Lopes Veiga Date: Wed, 31 Jul 2024 22:02:06 -0300 Subject: [PATCH 1/2] Return []any for hash arrays too --- decode_test.go | 4 ++-- internal/tag/add.go | 6 ------ internal/toml-test/toml.go | 23 +++------------------- parse.go | 39 ++++++++++++++++++++++++-------------- 4 files changed, 30 insertions(+), 42 deletions(-) diff --git a/decode_test.go b/decode_test.go index b547990c..5b43730d 100644 --- a/decode_test.go +++ b/decode_test.go @@ -642,9 +642,9 @@ func (d *dish) UnmarshalTOML(p any) error { data, _ := p.(map[string]any) d.Name, _ = data["name"].(string) d.Price, _ = data["price"].(float32) - ingredients, _ := data["ingredients"].([]map[string]any) + ingredients, _ := data["ingredients"].([]any) for _, e := range ingredients { - n, _ := any(e).(map[string]any) + n, _ := e.(map[string]any) name, _ := n["name"].(string) i := ingredient{name} d.Ingredients = append(d.Ingredients, i) diff --git a/internal/tag/add.go b/internal/tag/add.go index f80ddb87..7d79f550 100644 --- a/internal/tag/add.go +++ b/internal/tag/add.go @@ -27,12 +27,6 @@ func Add(key string, tomlData any) any { // An array: we don't need to add any tags, just recurse for every table // entry. - case []map[string]any: - typed := make([]map[string]any, len(orig)) - for i, v := range orig { - typed[i] = Add("", v).(map[string]any) - } - return typed case []any: typed := make([]any, len(orig)) for i, v := range orig { diff --git a/internal/toml-test/toml.go b/internal/toml-test/toml.go index 8786af64..3383e8d5 100644 --- a/internal/toml-test/toml.go +++ b/internal/toml-test/toml.go @@ -38,12 +38,6 @@ func (r Test) CompareTOML(want, have any) Test { switch w := want.(type) { case map[string]any: return r.cmpTOMLMap(w, have) - case []map[string]any: - ww := make([]any, 0, len(w)) - for _, v := range w { - ww = append(ww, v) - } - return r.cmpTOMLArrays(ww, have) case []any: return r.cmpTOMLArrays(w, have) default: @@ -83,21 +77,10 @@ func (r Test) cmpTOMLMap(want map[string]any, have any) Test { } func (r Test) cmpTOMLArrays(want []any, have any) Test { - // Slice can be decoded to []any for an array of primitives, or - // []map[string]any for an array of tables. - // - // TODO: it would be nicer if it could always decode to []any? haveSlice, ok := have.([]any) - if !ok { - tblArray, ok := have.([]map[string]any) - if !ok { - return r.mismatch("array", want, have) - } - haveSlice = make([]any, len(tblArray)) - for i := range tblArray { - haveSlice[i] = tblArray[i] - } + if !ok { + return r.mismatch("array", want, have) } if len(want) != len(haveSlice) { @@ -146,7 +129,7 @@ func deepEqual(want, have any) bool { func isTomlValue(v any) bool { switch v.(type) { - case map[string]any, []map[string]any, []any: + case map[string]any, []any: return false } return true diff --git a/parse.go b/parse.go index 3f2c090c..7ea580d0 100644 --- a/parse.go +++ b/parse.go @@ -29,6 +29,7 @@ type parser struct { type keyInfo struct { pos Position tomlType tomlType + locked bool // Writing to inline arrays after definition is forbidden. } func parse(data string) (p *parser, err error) { @@ -173,7 +174,7 @@ func (p *parser) topLevel(item item) { p.assertEqual(itemTableEnd, name.typ) p.addContext(key, false) - p.setType("", tomlHash, item.pos) + p.setType("", tomlHash, item.pos, false) p.ordered = append(p.ordered, key) case itemArrayTableStart: // [[ .. ]] name := p.nextPos() @@ -185,7 +186,7 @@ func (p *parser) topLevel(item item) { p.assertEqual(itemArrayTableEnd, name.typ) p.addContext(key, true) - p.setType("", tomlArrayHash, item.pos) + p.setType("", tomlArrayHash, item.pos, false) p.ordered = append(p.ordered, key) case itemKeyStart: // key = .. outerContext := p.context @@ -212,7 +213,7 @@ func (p *parser) topLevel(item item) { vItem := p.next() val, typ := p.value(vItem, false) p.setValue(p.currentKey, val) - p.setType(p.currentKey, typ, vItem.pos) + p.setType(p.currentKey, typ, vItem.pos, true) /// Remove the context we added (preserving any context from [tbl] lines). p.context = outerContext @@ -408,7 +409,7 @@ func missingLeadingZero(d, l string) bool { } func (p *parser) valueArray(it item) (any, tomlType) { - p.setType(p.currentKey, tomlArray, it.pos) + p.setType(p.currentKey, tomlArray, it.pos, false) var ( // Initialize to a non-nil slice to make it consistent with how S = [] @@ -478,7 +479,7 @@ func (p *parser) valueInlineTable(it item, parentIsArray bool) (any, tomlType) { /// Set the value. val, typ := p.value(p.next(), false) p.setValue(p.currentKey, val) - p.setType(p.currentKey, typ, it.pos) + p.setType(p.currentKey, typ, it.pos, true) hash := topHash for _, c := range context { @@ -575,8 +576,12 @@ func (p *parser) addContext(key Key, array bool) { // Otherwise, it better be a table, since this MUST be a key group (by // virtue of it not being the last element in a key). switch t := hashContext[k].(type) { - case []map[string]any: - hashContext = t[len(t)-1] + case []any: + if !p.isLocked(keyContext) { + hashContext = t[len(t)-1].(map[string]any) + } else { + p.panicf("Key '%s' was already created as a hash.", keyContext) + } case map[string]any: hashContext = t default: @@ -590,13 +595,17 @@ func (p *parser) addContext(key Key, array bool) { // list of tables for it. k := key.last() if _, ok := hashContext[k]; !ok { - hashContext[k] = make([]map[string]any, 0, 4) + hashContext[k] = make([]any, 0, 4) } // Add a new table. But make sure the key hasn't already been used // for something else. - if hash, ok := hashContext[k].([]map[string]any); ok { - hashContext[k] = append(hash, make(map[string]any)) + if hash, ok := hashContext[k].([]any); ok { + if !p.isLocked(append(keyContext, k)) { + hashContext[k] = append(hash, make(map[string]any)) + } else { + p.panicf("Key '%s' was already created and cannot be used as an array.", key) + } } else { p.panicf("Key '%s' was already created and cannot be used as an array.", key) } @@ -622,10 +631,10 @@ func (p *parser) setValue(key string, value any) { p.bug("Context for key '%s' has not been established.", keyContext) } switch t := tmpHash.(type) { - case []map[string]any: + case []any: // The context is a table of hashes. Pick the most recent table // defined as the current hash. - hash = t[len(t)-1] + hash = t[len(t)-1].(map[string]any) case map[string]any: hash = t default: @@ -666,7 +675,7 @@ func (p *parser) setValue(key string, value any) { // // Note that if `key` is empty, then the type given will be applied to the // current context (which is either a table or an array of tables). -func (p *parser) setType(key string, typ tomlType, pos Position) { +func (p *parser) setType(key string, typ tomlType, pos Position, locked bool) { keyContext := make(Key, 0, len(p.context)+1) keyContext = append(keyContext, p.context...) if len(key) > 0 { // allow type setting for hashes @@ -678,7 +687,8 @@ func (p *parser) setType(key string, typ tomlType, pos Position) { if len(keyContext) == 0 { keyContext = Key{""} } - p.keyInfo[keyContext.String()] = keyInfo{tomlType: typ, pos: pos} + + p.keyInfo[keyContext.String()] = keyInfo{tomlType: typ, pos: pos, locked: locked} } // Implicit keys need to be created when tables are implied in "a.b.c.d = 1" and @@ -687,6 +697,7 @@ func (p *parser) addImplicit(key Key) { p.implicits[key.String()] = struc func (p *parser) removeImplicit(key Key) { delete(p.implicits, key.String()) } func (p *parser) isImplicit(key Key) bool { _, ok := p.implicits[key.String()]; return ok } func (p *parser) isArray(key Key) bool { return p.keyInfo[key.String()].tomlType == tomlArray } +func (p *parser) isLocked(key Key) bool { return p.keyInfo[key.String()].locked } func (p *parser) addImplicitContext(key Key) { p.addImplicit(key); p.addContext(key, false) } // current returns the full key name of the current context. From 6c4cdb8c768f20be81c5c7953ecf6b69d57489f7 Mon Sep 17 00:00:00 2001 From: Gabriel Lopes Veiga Date: Thu, 1 Aug 2024 15:43:38 -0300 Subject: [PATCH 2/2] Add specific function to setting hash type --- parse.go | 20 +++++++------------- 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/parse.go b/parse.go index 7ea580d0..2208025a 100644 --- a/parse.go +++ b/parse.go @@ -174,7 +174,7 @@ func (p *parser) topLevel(item item) { p.assertEqual(itemTableEnd, name.typ) p.addContext(key, false) - p.setType("", tomlHash, item.pos, false) + p.setTypeContext(tomlHash, item.pos, false) p.ordered = append(p.ordered, key) case itemArrayTableStart: // [[ .. ]] name := p.nextPos() @@ -186,7 +186,7 @@ func (p *parser) topLevel(item item) { p.assertEqual(itemArrayTableEnd, name.typ) p.addContext(key, true) - p.setType("", tomlArrayHash, item.pos, false) + p.setTypeContext(tomlArrayHash, item.pos, false) p.ordered = append(p.ordered, key) case itemKeyStart: // key = .. outerContext := p.context @@ -676,18 +676,12 @@ func (p *parser) setValue(key string, value any) { // Note that if `key` is empty, then the type given will be applied to the // current context (which is either a table or an array of tables). func (p *parser) setType(key string, typ tomlType, pos Position, locked bool) { - keyContext := make(Key, 0, len(p.context)+1) - keyContext = append(keyContext, p.context...) - if len(key) > 0 { // allow type setting for hashes - keyContext = append(keyContext, key) - } - // Special case to make empty keys ("" = 1) work. - // Without it it will set "" rather than `""`. - // TODO: why is this needed? And why is this only needed here? - if len(keyContext) == 0 { - keyContext = Key{""} - } + keyContext := p.context.add(key) + p.keyInfo[keyContext.String()] = keyInfo{tomlType: typ, pos: pos, locked: locked} +} +func (p *parser) setTypeContext(typ tomlType, pos Position, locked bool) { + keyContext := p.context p.keyInfo[keyContext.String()] = keyInfo{tomlType: typ, pos: pos, locked: locked} }