Unmarshal into maps

This commit is contained in:
Thomas Pelletier
2021-03-15 09:49:10 -04:00
parent 1718142ede
commit 37d06dabcf
5 changed files with 213 additions and 74 deletions
+1 -1
View File
@@ -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
+109 -41
View File
@@ -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()
+8 -8
View File
@@ -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)
})
}
+6 -6
View File
@@ -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
}
+89 -18
View File
@@ -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,