From 37d06dabcf67055efa4598f04d506422d88db2c3 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Mon, 15 Mar 2021 09:49:10 -0400 Subject: [PATCH] Unmarshal into maps --- README.md | 2 +- targets.go | 150 ++++++++++++++++++++++++++++++++------------ targets_test.go | 16 ++--- unmarshaler.go | 12 ++-- unmarshaler_test.go | 107 +++++++++++++++++++++++++------ 5 files changed, 213 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index 9f3678a..75a1106 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Development branch. Probably does not work. ## Must do -- [ ] Unmarshal into maps. +- [x] Unmarshal into maps. - [ ] Attach comments to AST (gated by parser flag). - [ ] Abstract AST. - [ ] Support Array Tables diff --git a/targets.go b/targets.go index 11ccd3d..bff9970 100644 --- a/targets.go +++ b/targets.go @@ -6,8 +6,8 @@ import ( ) type target interface { - // Ensure the target's value is compatible with a slice and initialized. - ensureSlice() error + // Dereferences the target. + get() reflect.Value // Store a string at the target. setString(v string) error @@ -21,12 +21,8 @@ type target interface { // Store a float64 at the target setFloat64(v float64) error - // Creates a new value of the container's element type, and returns a - // target to it. - pushNew() (target, error) - - // Dereferences the target. - get() reflect.Value + // Stores any value at the target + set(v reflect.Value) error } // valueTarget just contains a reflect.Value that can be set. @@ -37,21 +33,76 @@ func (t valueTarget) get() reflect.Value { return reflect.Value(t) } -func (t valueTarget) ensureSlice() error { +func (t valueTarget) set(v reflect.Value) error { + reflect.Value(t).Set(v) + return nil +} + +func (t valueTarget) setString(v string) error { + t.get().SetString(v) + return nil +} + +func (t valueTarget) setBool(v bool) error { + t.get().SetBool(v) + return nil +} + +func (t valueTarget) setInt64(v int64) error { + t.get().SetInt(v) + return nil +} + +func (t valueTarget) setFloat64(v float64) error { + t.get().SetFloat(v) + return nil +} + +// mapTarget targets a specific key of a map. +type mapTarget struct { + v reflect.Value + k reflect.Value +} + +func (t mapTarget) get() reflect.Value { + return t.v.MapIndex(t.k) +} + +func (t mapTarget) set(v reflect.Value) error { + t.v.SetMapIndex(t.k, v) + return nil +} + +func (t mapTarget) setString(v string) error { + return t.set(reflect.ValueOf(v)) +} + +func (t mapTarget) setBool(v bool) error { + return t.set(reflect.ValueOf(v)) +} + +func (t mapTarget) setInt64(v int64) error { + return t.set(reflect.ValueOf(v)) +} + +func (t mapTarget) setFloat64(v float64) error { + return t.set(reflect.ValueOf(v)) +} + +func ensureSlice(t target) error { f := t.get() switch f.Type().Kind() { case reflect.Slice: if f.IsNil() { - f.Set(reflect.MakeSlice(f.Type(), 0, 0)) + return t.set(reflect.MakeSlice(f.Type(), 0, 0)) } case reflect.Interface: if f.IsNil() { - f.Set(reflect.MakeSlice(reflect.TypeOf([]interface{}{}), 0, 0)) - } else { - if f.Type().Elem().Kind() != reflect.Slice { - return fmt.Errorf("interface is pointing to a %s, not a slice", f.Kind()) - } + return t.set(reflect.MakeSlice(reflect.TypeOf([]interface{}{}), 0, 0)) + } + if f.Type().Elem().Kind() != reflect.Slice { + return fmt.Errorf("interface is pointing to a %s, not a slice", f.Kind()) } default: return fmt.Errorf("cannot initialize a slice in %s", f.Kind()) @@ -59,76 +110,71 @@ func (t valueTarget) ensureSlice() error { return nil } -func (t valueTarget) setString(v string) error { +func setString(t target, v string) error { f := t.get() switch f.Kind() { case reflect.String: - f.SetString(v) + return t.setString(v) case reflect.Interface: - f.Set(reflect.ValueOf(v)) + return t.set(reflect.ValueOf(v)) default: - return fmt.Errorf("cannot assign string to a %s", f.String()) + return fmt.Errorf("cannot assign string to a %s", f.Kind()) } - - return nil } -func (t valueTarget) setBool(v bool) error { +func setBool(t target, v bool) error { f := t.get() switch f.Kind() { case reflect.Bool: - f.SetBool(v) + return t.setBool(v) case reflect.Interface: - f.Set(reflect.ValueOf(v)) + return t.set(reflect.ValueOf(v)) default: return fmt.Errorf("cannot assign bool to a %s", f.String()) } - - return nil } -func (t valueTarget) setInt64(v int64) error { +func setInt64(t target, v int64) error { f := t.get() switch f.Kind() { case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: // TODO: overflow checks - f.SetInt(v) + return t.setInt64(v) case reflect.Interface: - f.Set(reflect.ValueOf(v)) + return t.set(reflect.ValueOf(v)) default: return fmt.Errorf("cannot assign int64 to a %s", f.String()) } - - return nil } -func (t valueTarget) setFloat64(v float64) error { +func setFloat64(t target, v float64) error { f := t.get() switch f.Kind() { case reflect.Float32, reflect.Float64: // TODO: overflow checks - f.SetFloat(v) + return t.setFloat64(v) case reflect.Interface: - f.Set(reflect.ValueOf(v)) + return t.set(reflect.ValueOf(v)) default: return fmt.Errorf("cannot assign float64 to a %s", f.String()) } - - return nil } -func (t valueTarget) pushNew() (target, error) { +func pushNew(t target) (target, error) { f := t.get() switch f.Kind() { case reflect.Slice: idx := f.Len() - f.Set(reflect.Append(f, reflect.New(f.Type().Elem()).Elem())) - return valueTarget(f.Index(idx)), nil + err := t.set(reflect.Append(f, reflect.New(f.Type().Elem()).Elem())) + if err != nil { + return nil, err + } + return valueTarget(t.get().Index(idx)), nil case reflect.Interface: if f.IsNil() { panic("interface should have been initialized") @@ -140,8 +186,11 @@ func (t valueTarget) pushNew() (target, error) { idx := ifaceElem.Len() newElem := reflect.New(ifaceElem.Type().Elem()).Elem() newSlice := reflect.Append(ifaceElem, newElem) - f.Set(newSlice) - return valueTarget(f.Elem().Index(idx)), nil + err := t.set(newSlice) + if err != nil { + return nil, err + } + return valueTarget(t.get().Elem().Index(idx)), nil default: return nil, fmt.Errorf("cannot pushNew on a %s", f.Kind()) } @@ -162,11 +211,30 @@ func scope(v reflect.Value, name string) (target, error) { } else { return scope(v.Elem(), name) } + case reflect.Map: + return scopeMap(v, name) default: panic(fmt.Errorf("can't scope on a %s", v.Kind())) } } +func scopeMap(v reflect.Value, name string) (target, error) { + if v.IsNil() { + v.Set(reflect.MakeMap(v.Type())) + } + + k := reflect.ValueOf(name) + if !v.MapIndex(k).IsValid() { + newElem := reflect.New(v.Type().Elem()) + v.SetMapIndex(k, newElem.Elem()) + } + + return mapTarget{ + v: v, + k: k, + }, nil +} + func scopeStruct(v reflect.Value, name string) (target, error) { // TODO: cache this t := v.Type() diff --git a/targets_test.go b/targets_test.go index 25d6358..1522d22 100644 --- a/targets_test.go +++ b/targets_test.go @@ -41,7 +41,7 @@ func TestStructTarget_Ensure(t *testing.T) { t.Run(e.desc, func(t *testing.T) { target, err := scope(e.input, e.name) require.NoError(t, err) - err = target.ensureSlice() + err = ensureSlice(target) v := target.get() e.test(v, err) }) @@ -88,7 +88,7 @@ func TestStructTarget_SetString(t *testing.T) { t.Run(e.desc, func(t *testing.T) { target, err := scope(e.input, e.name) require.NoError(t, err) - err = target.setString(str) + err = setString(target, str) v := target.get() e.test(v, err) }) @@ -105,12 +105,12 @@ func TestPushNew(t *testing.T) { x, err := scope(reflect.ValueOf(&d).Elem(), "A") require.NoError(t, err) - n, err := x.pushNew() + n, err := pushNew(x) require.NoError(t, err) require.NoError(t, n.setString("hello")) require.Equal(t, []string{"hello"}, d.A) - n, err = x.pushNew() + n, err = pushNew(x) require.NoError(t, err) require.NoError(t, n.setString("world")) require.Equal(t, []string{"hello", "world"}, d.A) @@ -125,14 +125,14 @@ func TestPushNew(t *testing.T) { x, err := scope(reflect.ValueOf(&d).Elem(), "A") require.NoError(t, err) - n, err := x.pushNew() + n, err := pushNew(x) require.NoError(t, err) - require.NoError(t, n.setString("hello")) + require.NoError(t, setString(n, "hello")) require.Equal(t, []interface{}{"hello"}, d.A) - n, err = x.pushNew() + n, err = pushNew(x) require.NoError(t, err) - require.NoError(t, n.setString("world")) + require.NoError(t, setString(n, "world")) require.Equal(t, []interface{}{"hello", "world"}, d.A) }) } diff --git a/unmarshaler.go b/unmarshaler.go index e40449d..2859c53 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -95,13 +95,13 @@ func unmarshalValue(x target, node *ast.Node) error { func unmarshalString(x target, node *ast.Node) error { assertNode(ast.String, node) - return x.setString(string(node.Data)) + return setString(x, string(node.Data)) } func unmarshalBool(x target, node *ast.Node) error { assertNode(ast.Bool, node) v := node.Data[0] == 't' - return x.setBool(v) + return setBool(x, v) } func unmarshalInteger(x target, node *ast.Node) error { @@ -110,7 +110,7 @@ func unmarshalInteger(x target, node *ast.Node) error { if err != nil { return err } - return x.setInt64(v) + return setInt64(x, v) } func unmarshalFloat(x target, node *ast.Node) error { @@ -119,7 +119,7 @@ func unmarshalFloat(x target, node *ast.Node) error { if err != nil { return err } - return x.setFloat64(v) + return setFloat64(x, v) } func unmarshalInlineTable(x target, node *ast.Node) error { @@ -137,13 +137,13 @@ func unmarshalInlineTable(x target, node *ast.Node) error { func unmarshalArray(x target, node *ast.Node) error { assertNode(ast.Array, node) - err := x.ensureSlice() + err := ensureSlice(x) if err != nil { return err } for _, n := range node.Children { - v, err := x.pushNew() + v, err := pushNew(x) if err != nil { return err } diff --git a/unmarshaler_test.go b/unmarshaler_test.go index f9df67e..deb0532 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -172,6 +172,7 @@ func TestUnmarshal(t *testing.T) { type test struct { target interface{} expected interface{} + err bool } examples := []struct { desc string @@ -186,8 +187,8 @@ func TestUnmarshal(t *testing.T) { A string } return test{ - &doc{}, - &doc{A: "foo"}, + target: &doc{}, + expected: &doc{A: "foo"}, } }, }, @@ -199,8 +200,8 @@ func TestUnmarshal(t *testing.T) { A bool } return test{ - &doc{}, - &doc{A: true}, + target: &doc{}, + expected: &doc{A: true}, } }, }, @@ -212,8 +213,8 @@ func TestUnmarshal(t *testing.T) { A bool } return test{ - &doc{A: true}, - &doc{A: false}, + target: &doc{A: true}, + expected: &doc{A: false}, } }, }, @@ -225,8 +226,8 @@ func TestUnmarshal(t *testing.T) { A []string } return test{ - &doc{}, - &doc{A: []string{"foo", "bar"}}, + target: &doc{}, + expected: &doc{A: []string{"foo", "bar"}}, } }, }, @@ -242,8 +243,8 @@ B = "data"`, A A } return test{ - &doc{}, - &doc{A: A{B: "data"}}, + target: &doc{}, + expected: &doc{A: A{B: "data"}}, } }, }, @@ -259,8 +260,8 @@ B = "data"`, Name name } return test{ - &doc{}, - &doc{Name: name{ + target: &doc{}, + expected: &doc{Name: name{ First: "hello", Last: "world", }}, @@ -279,8 +280,8 @@ B = "data"`, Names []name } return test{ - &doc{}, - &doc{ + target: &doc{}, + expected: &doc{ Names: []name{ { First: "hello", @@ -295,14 +296,86 @@ B = "data"`, } }, }, + { + desc: "into map[string]interface{}", + input: `A = "foo"`, + gen: func() test { + doc := map[string]interface{}{} + return test{ + target: &doc, + expected: &map[string]interface{}{ + "A": "foo", + }, + } + }, + }, + { + desc: "multi keys of different types into map[string]interface{}", + input: `A = "foo" +B = 42`, + gen: func() test { + doc := map[string]interface{}{} + return test{ + target: &doc, + expected: &map[string]interface{}{ + "A": "foo", + "B": int64(42), + }, + } + }, + }, + { + desc: "slice in a map[string]interface{}", + input: `A = ["foo", "bar"]`, + gen: func() test { + doc := map[string]interface{}{} + return test{ + target: &doc, + expected: &map[string]interface{}{ + "A": []interface{}{"foo", "bar"}, + }, + } + }, + }, + { + desc: "string into map[string]string", + input: `A = "foo"`, + gen: func() test { + doc := map[string]string{} + return test{ + target: &doc, + expected: &map[string]string{ + "A": "foo", + }, + } + }, + }, + { + desc: "float64 into map[string]string", + input: `A = 42.0`, + gen: func() test { + doc := map[string]string{} + return test{ + target: &doc, + err: true, + } + }, + }, } for _, e := range examples { t.Run(e.desc, func(t *testing.T) { test := e.gen() + if test.err && test.expected != nil { + panic("invalid test: cannot expect both an error and a value") + } err := Unmarshal([]byte(e.input), test.target) - require.NoError(t, err) - assert.Equal(t, test.expected, test.target) + if test.err { + require.Error(t, err) + } else { + require.NoError(t, err) + assert.Equal(t, test.expected, test.target) + } }) } } @@ -437,8 +510,6 @@ func TestFromAst_Table(t *testing.T) { func TestFromAst_InlineTable(t *testing.T) { t.Run("one level of strings", func(t *testing.T) { - // name = { first = "Tom", last = "Preston-Werner" } - root := ast.Root{ ast.Node{ Kind: ast.KeyValue,