From 939f8896661a72582149ec35351c517734e3bde8 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Wed, 17 Mar 2021 09:57:50 -0400 Subject: [PATCH] wip: figuring out unmarshaling to interfaces --- targets.go | 56 +++++++++++++++++++++++++++++++++------------ unmarshaler.go | 53 +++++++++++++++++++++++++++++------------- unmarshaler_test.go | 1 - 3 files changed, 79 insertions(+), 31 deletions(-) diff --git a/targets.go b/targets.go index 417cdb5..b0f9db2 100644 --- a/targets.go +++ b/targets.go @@ -196,21 +196,54 @@ func pushNew(t target) (target, error) { } } -func scopeTarget(t target, name string) (target, error) { - x := t.get() - return scope(x, name) -} - func scopeTableTarget(append bool, t target, name string) (target, error) { x := t.get() + + if x.Kind() == reflect.Interface { + t, err := initInterface(append, t) + if err != nil { + return t, err + } + x = t.get() + } + + if x.Kind() == reflect.Slice { + return scopeSlice(t, append) + } + t, err := scope(x, name) if err != nil { return t, err } - x = t.get() - if x.Kind() == reflect.Slice { - return scopeSlice(t, append) + return t, nil +} + +// initInterface makes sure that the interface pointed at by the target is not +// nil. +// Returns the target to the initialized value of the target. +func initInterface(append bool, t target) (target, error) { + x := t.get() + + if x.Kind() != reflect.Interface { + panic("this should only be called on interfaces") } + + if x.IsNil() { + var newElement reflect.Value + if append { + newElement = reflect.MakeSlice(reflect.TypeOf([]interface{}{}), 0, 0) + } else { + newElement = reflect.MakeMap(reflect.TypeOf(map[string]interface{}{})) + } + err := t.set(newElement) + if err != nil { + return t, err + } + x = t.get() + } + + x = x.Elem() + t = valueTarget(x) return t, nil } @@ -218,12 +251,6 @@ func scope(v reflect.Value, name string) (target, error) { switch v.Kind() { case reflect.Struct: return scopeStruct(v, name) - case reflect.Interface: - if v.IsNil() { - panic("not implemented") // TODO - } else { - return scope(v.Elem(), name) - } case reflect.Map: return scopeMap(v, name) default: @@ -233,6 +260,7 @@ func scope(v reflect.Value, name string) (target, error) { func scopeSlice(t target, append bool) (target, error) { v := t.get() + if append { newElem := reflect.New(v.Type().Elem()) newSlice := reflect.Append(v, newElem.Elem()) diff --git a/unmarshaler.go b/unmarshaler.go index 309fd38..478a127 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -42,18 +42,26 @@ func fromAst(tree ast.Root, v interface{}) error { // unchanged, except by table and array table. func unmarshalTopLevelNode(root target, x target, node *ast.Node) (target, error) { switch node.Kind { - case ast.Table: - return scopeWithTable(root, node.Key()) - case ast.ArrayTable: - return scopeWithArrayTable(root, node.Key()) case ast.KeyValue: return x, unmarshalKeyValue(x, node) + case ast.Table: + return scopeWithKey(root, node.Key()) + case ast.ArrayTable: + return scopeWithArrayTable(root, node.Key()) default: panic(fmt.Errorf("this should not be a top level node type: %s", node.Kind)) } } -func scopeWithTable(x target, key []ast.Node) (target, error) { +// scopeWithKey performs target scoping when unmarshaling an ast.KeyValue node. +// +// The goal is to hop from target to target recursively using the names in key. +// Parts of the key should be used to resolve field names for structs, and as +// keys when targeting maps. +// +// When encountering slices, it should always use its last element, and error +// if the slice does not have any. +func scopeWithKey(x target, key []ast.Node) (target, error) { var err error for _, n := range key { x, err = scopeTableTarget(false, x, string(n.Data)) @@ -64,6 +72,11 @@ func scopeWithTable(x target, key []ast.Node) (target, error) { return x, nil } +// scopeWithArrayTable performs target scoping when unmarshaling an +// ast.ArrayTable node. +// +// It is the same as scopeWithKey, but when scoping the last part of the key +// it creates a new element in the array instead of using the last one. func scopeWithArrayTable(x target, key []ast.Node) (target, error) { var err error if len(key) > 1 { @@ -74,18 +87,26 @@ func scopeWithArrayTable(x target, key []ast.Node) (target, error) { } } } - return scopeTableTarget(true, x, string(key[len(key)-1].Data)) -} - -func scopeWithKey(x target, key []ast.Node) (target, error) { - var err error - for _, n := range key { - x, err = scopeTarget(x, string(n.Data)) - if err != nil { - return nil, err - } + x, err = scopeTableTarget(true, x, string(key[len(key)-1].Data)) + if err != nil { + return x, err } - return x, nil + + v := x.get() + + if v.Kind() == reflect.Interface { + x, err = initInterface(true, x) + if err != nil { + return x, err + } + v = x.get() + } + + if v.Kind() == reflect.Slice { + return scopeSlice(x, true) + } + + return x, err } func unmarshalKeyValue(x target, node *ast.Node) error { diff --git a/unmarshaler_test.go b/unmarshaler_test.go index 8c682ed..e748d0c 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -420,7 +420,6 @@ B = "data"`, }, }, { - skip: true, // TODO desc: "one-level multi-element array table to map", input: `[[Products]] Name = "Hammer"