Commit 34a8b3c7 authored by Eric Myhre's avatar Eric Myhre

Node for all. Last phase deref.

Finally got the bind and free impls on the same page.

Surprisingly, the bind node can work without being a ptr itself.
I'm not sure if that'll last, but let's try to roll with it.
If we *can* keep that property, it might reduce GC impact in a pretty
significant way.

Added a 'fluent' package.  It's just a twinkle in my eye so far, but it
might represent the nicest way through all the discussed issues.
Nodes shouldn't have to be panicful; and users shouldn't have to do all
error handling manually either.  A package full of fluent interfaces
that know what's going on seems to be the only way to get both.
But we'll see how this shakes out.  Maybe typeful traversers will
make the whole thing end up more coreward than being relegated to a
helper package.  I have no idea.
Signed-off-by: default avatarEric Myhre <hash@exultant.us>
parent c35f4393
...@@ -10,7 +10,7 @@ import ( ...@@ -10,7 +10,7 @@ import (
) )
var ( var (
_ ipld.Node = &Node{} _ ipld.Node = Node{}
) )
/* /*
...@@ -22,7 +22,7 @@ var ( ...@@ -22,7 +22,7 @@ var (
If you're not sure which kind serializable node to use, try `ipldcbor.Node`. If you're not sure which kind serializable node to use, try `ipldcbor.Node`.
*/ */
type Node struct { type Node struct {
bound reflect.Value bound reflect.Value // should already be ptr-unwrapped
atlas atlas.Atlas atlas atlas.Atlas
} }
...@@ -31,54 +31,40 @@ type Node struct { ...@@ -31,54 +31,40 @@ type Node struct {
atlas to understand how to traverse it. atlas to understand how to traverse it.
*/ */
func Bind(bindme interface{}, atl atlas.Atlas) ipld.Node { func Bind(bindme interface{}, atl atlas.Atlas) ipld.Node {
return &Node{ rv := reflect.ValueOf(bindme)
bound: reflect.ValueOf(bindme), for rv.Kind() == reflect.Ptr {
rv = rv.Elem()
}
return Node{
bound: rv,
atlas: atl, atlas: atl,
} }
} }
func (n *Node) GetField(pth string) (v interface{}, _ error) { func (n Node) AsBool() (v bool, _ error) {
return v, traverseField(n.bound, pth, n.atlas, reflect.ValueOf(v)) reflect.ValueOf(&v).Elem().Set(n.bound)
} return
func (n *Node) GetFieldBool(pth string) (v bool, _ error) {
return v, traverseField(n.bound, pth, n.atlas, reflect.ValueOf(&v).Elem())
}
func (n *Node) GetFieldString(pth string) (v string, _ error) {
return v, traverseField(n.bound, pth, n.atlas, reflect.ValueOf(&v).Elem())
}
func (n *Node) GetFieldInt(pth string) (v int, _ error) {
return v, traverseField(n.bound, pth, n.atlas, reflect.ValueOf(&v).Elem())
}
func (n *Node) GetFieldLink(pth string) (v cid.Cid, _ error) {
return v, traverseField(n.bound, pth, n.atlas, reflect.ValueOf(&v).Elem())
}
func (n *Node) GetIndex(pth int) (v interface{}, _ error) {
return v, traverseIndex(n.bound, pth, n.atlas, reflect.ValueOf(v))
}
func (n *Node) GetIndexBool(pth int) (v bool, _ error) {
return v, traverseIndex(n.bound, pth, n.atlas, reflect.ValueOf(&v).Elem())
} }
func (n *Node) GetIndexString(pth int) (v string, _ error) { func (n Node) AsString() (v string, _ error) {
return v, traverseIndex(n.bound, pth, n.atlas, reflect.ValueOf(&v).Elem()) reflect.ValueOf(&v).Elem().Set(n.bound)
return
} }
func (n *Node) GetIndexInt(pth int) (v int, _ error) { func (n Node) AsInt() (v int, _ error) {
return v, traverseIndex(n.bound, pth, n.atlas, reflect.ValueOf(&v).Elem()) reflect.ValueOf(&v).Elem().Set(n.bound)
return
} }
func (n *Node) GetIndexLink(pth int) (v cid.Cid, _ error) { func (n Node) AsLink() (v cid.Cid, _ error) {
return v, traverseIndex(n.bound, pth, n.atlas, reflect.ValueOf(&v).Elem()) reflect.ValueOf(&v).Elem().Set(n.bound)
return
} }
func traverseField(v reflect.Value, pth string, atl atlas.Atlas, assignTo reflect.Value) error { func (n Node) TraverseField(pth string) (ipld.Node, error) {
// Unwrap any pointers. v := n.bound
for v.Kind() == reflect.Ptr {
v = v.Elem()
}
// Traverse. // Traverse.
// Honor any atlent overrides if present; // Honor any atlent overrides if present;
// Use kind-based fallbacks if necessary. // Use kind-based fallbacks if necessary.
atlent, exists := atl.Get(reflect.ValueOf(v.Type()).Pointer()) atlent, exists := n.atlas.Get(reflect.ValueOf(v.Type()).Pointer())
if exists { if exists {
switch { switch {
case atlent.MarshalTransformFunc != nil: case atlent.MarshalTransformFunc != nil:
...@@ -90,7 +76,7 @@ func traverseField(v reflect.Value, pth string, atl atlas.Atlas, assignTo reflec ...@@ -90,7 +76,7 @@ func traverseField(v reflect.Value, pth string, atl atlas.Atlas, assignTo reflec
break break
} }
} }
return fmt.Errorf("traverse failed: type %q has no field named %q", v.Type().Name(), pth) return Node{}, fmt.Errorf("traverse failed: type %q has no field named %q", v.Type().Name(), pth)
case atlent.UnionKeyedMorphism != nil: case atlent.UnionKeyedMorphism != nil:
panic(fmt.Errorf("invalid ipldbind.Node: type %q atlas specifies union, but ipld doesn't know how to make sense of this", v.Type().Name())) panic(fmt.Errorf("invalid ipldbind.Node: type %q atlas specifies union, but ipld doesn't know how to make sense of this", v.Type().Name()))
case atlent.MapMorphism != nil: case atlent.MapMorphism != nil:
...@@ -114,7 +100,7 @@ func traverseField(v reflect.Value, pth string, atl atlas.Atlas, assignTo reflec ...@@ -114,7 +100,7 @@ func traverseField(v reflect.Value, pth string, atl atlas.Atlas, assignTo reflec
reflect.Map, reflect.Map,
reflect.Struct, reflect.Struct,
reflect.Interface: reflect.Interface:
assignTo.Set(reflect.ValueOf(Node{v, atl})) return Node{v, n.atlas}, nil
case // esotera: reject with panic case // esotera: reject with panic
reflect.Chan, reflect.Chan,
reflect.Func, reflect.Func,
...@@ -136,23 +122,21 @@ func traverseField(v reflect.Value, pth string, atl atlas.Atlas, assignTo reflec ...@@ -136,23 +122,21 @@ func traverseField(v reflect.Value, pth string, atl atlas.Atlas, assignTo reflec
// Or wrap with a Node and assign that (for recursives). // Or wrap with a Node and assign that (for recursives).
// TODO decide what to do with typedef'd primitives. // TODO decide what to do with typedef'd primitives.
switch v.Type().Kind() { switch v.Type().Kind() {
case // primitives: set them case // primitives: wrap in node
reflect.Bool, reflect.Bool,
reflect.String, reflect.String,
reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
reflect.Float32, reflect.Float64, reflect.Float32, reflect.Float64,
reflect.Complex64, reflect.Complex128: reflect.Complex64, reflect.Complex128:
assignTo.Set(v) return Node{v, n.atlas}, nil
return nil
case // recursives: wrap in node case // recursives: wrap in node
reflect.Array, reflect.Array,
reflect.Slice, reflect.Slice,
reflect.Map, reflect.Map,
reflect.Struct, reflect.Struct,
reflect.Interface: reflect.Interface:
assignTo.Set(reflect.ValueOf(Node{v, atl})) return Node{v, n.atlas}, nil
return nil
case // esotera: reject with panic case // esotera: reject with panic
reflect.Chan, reflect.Chan,
reflect.Func, reflect.Func,
...@@ -166,6 +150,6 @@ func traverseField(v reflect.Value, pth string, atl atlas.Atlas, assignTo reflec ...@@ -166,6 +150,6 @@ func traverseField(v reflect.Value, pth string, atl atlas.Atlas, assignTo reflec
} }
} }
func traverseIndex(v reflect.Value, pth int, atl atlas.Atlas, assignTo reflect.Value) error { func (n Node) TraverseIndex(idx int) (ipld.Node, error) {
panic("NYI") panic("NYI")
} }
package ipldfree package ipldfree
import ( import (
"fmt"
"strconv"
"github.com/ipfs/go-cid"
"github.com/ipld/go-ipld-prime" "github.com/ipld/go-ipld-prime"
) )
...@@ -25,22 +29,24 @@ var ( ...@@ -25,22 +29,24 @@ var (
type Node struct { type Node struct {
typ typ typ typ
_map map[string]interface{} // Value union. Only one of these has meaning, depending on the value of 'Type'. // n.b. maps values are all ptr so the returned node interface can be mutable.
_map2 map[int]interface{} // Value union. Only one of these has meaning, depending on the value of 'Type'.
_arr []interface{} // Value union. Only one of these has meaning, depending on the value of 'Type'. _map map[string]*Node // Value union. Only one of these has meaning, depending on the value of 'Type'.
_str string // Value union. Only one of these has meaning, depending on the value of 'Type'. _map2 map[int]*Node // Value union. Only one of these has meaning, depending on the value of 'Type'.
_bytes []byte // Value union. Only one of these has meaning, depending on the value of 'Type'. _arr []Node // Value union. Only one of these has meaning, depending on the value of 'Type'.
_bool bool // Value union. Only one of these has meaning, depending on the value of 'Type'. _str string // Value union. Only one of these has meaning, depending on the value of 'Type'.
_int int64 // Value union. Only one of these has meaning, depending on the value of 'Type'. _bytes []byte // Value union. Only one of these has meaning, depending on the value of 'Type'.
_uint uint64 // Value union. Only one of these has meaning, depending on the value of 'Type'. _bool bool // Value union. Only one of these has meaning, depending on the value of 'Type'.
_float float64 // Value union. Only one of these has meaning, depending on the value of 'Type'. _int int // Value union. Only one of these has meaning, depending on the value of 'Type'.
_uint uint // Value union. Only one of these has meaning, depending on the value of 'Type'.
_float float64 // Value union. Only one of these has meaning, depending on the value of 'Type'.
} }
type typ struct{ t byte } type typ struct{ t byte }
var ( var (
tZero = typ{} // treat as tMap tNil = typ{}
tMap = typ{'{'} tMap = typ{'{'}
tArr = typ{'['} tArr = typ{'['}
tStr = typ{'s'} tStr = typ{'s'}
...@@ -49,8 +55,65 @@ var ( ...@@ -49,8 +55,65 @@ var (
tInt = typ{'i'} tInt = typ{'i'}
tUint = typ{'u'} tUint = typ{'u'}
tFloat = typ{'f'} tFloat = typ{'f'}
tLink = typ{'/'}
) )
func (n *Node) GetField(pth []string) (v interface{}, _ error) { func (n *Node) AsBool() (v bool, _ error) {
return v, traverse(n.bound, pth, n.atlas, reflect.ValueOf(v)) return n._bool, expectTyp(tBool, n.typ)
}
func (n *Node) AsString() (v string, _ error) {
return n._str, expectTyp(tStr, n.typ)
}
func (n *Node) AsInt() (v int, _ error) {
return n._int, expectTyp(tInt, n.typ)
}
func (n *Node) AsLink() (v cid.Cid, _ error) {
return cid.Cid{}, expectTyp(tLink, n.typ) // TODO ??
}
func (n *Node) TraverseField(pth string) (ipld.Node, error) {
switch n.typ {
case tNil:
return nil, fmt.Errorf("cannot traverse terminals")
case tMap:
switch {
case n._map != nil:
v, _ := n._map[pth]
return v, nil
case n._map2 != nil:
i, err := strconv.Atoi(pth)
if err != nil {
return nil, fmt.Errorf("404")
}
v, _ := n._map2[i]
return v, nil
default:
panic("unreachable")
}
case tArr:
i, err := strconv.Atoi(pth)
if err != nil {
return nil, fmt.Errorf("404")
}
if i >= len(n._arr) {
return nil, fmt.Errorf("404")
}
v := &n._arr[i]
return v, nil
case tStr, tBytes, tBool, tInt, tUint, tFloat, tLink:
return nil, fmt.Errorf("cannot traverse terminals")
default:
panic("unreachable")
}
}
func (n *Node) TraverseIndex(idx int) (ipld.Node, error) {
panic("NYI")
}
func expectTyp(expect, actual typ) error {
if expect == actual {
return nil
}
return fmt.Errorf("woof")
} }
...@@ -12,36 +12,18 @@ type Node interface { ...@@ -12,36 +12,18 @@ type Node interface {
// originally stored as a full node, you cannot immediately take // originally stored as a full node, you cannot immediately take
// a link to it for embedding in other objects (you'd have to make // a link to it for embedding in other objects (you'd have to make
// a new RootNode with the same content first, then store that). // a new RootNode with the same content first, then store that).
// TraverseField(path string) (Node, error)
// A variety of GetField{Foo} methods exists to yield specific types,
// and will panic upon encountering unexpected types.
GetField(path string) (interface{}, error)
GetFieldBool(path string) (bool, error) // See GetField docs.
GetFieldString(path string) (string, error) // See GetField docs.
GetFieldInt(path string) (int, error) // See GetField docs.
GetFieldLink(path string) (cid.Cid, error) // See GetField docs.
// GetIndex is the equivalent of GetField but for indexing into an array // GetIndex is the equivalent of GetField but for indexing into an array
// (or a numerically-keyed map). Like GetField, it returns // (or a numerically-keyed map). Like GetField, it returns
// either a primitive (e.g. string, int, etc), a link (type CID), // either a primitive (e.g. string, int, etc), a link (type CID),
// or another Node. // or another Node.
// TraverseIndex(idx int) (Node, error)
// A variety of GetIndex{Foo} methods exists to yield specific types,
// and will panic upon encountering unexpected types.
GetIndex(idx int) (interface{}, error)
GetIndexBool(idx int) (bool, error) // See GetIndex docs.
GetIndexString(idx int) (string, error) // See GetIndex docs.
GetIndexInt(idx int) (int, error) // See GetIndex docs.
GetIndexLink(idx int) (cid.Cid, error) // See GetIndex docs.
// REVIEW this whole interface is still *very* unfriendly to chaining. AsBool() (bool, error)
// Friendly would be returning `(Node)` at all times, and having final AsString() (string, error)
// dereferencing options on leaf nodes, and treating it as a Maybe at all AsInt() (int, error)
// other times. Multireturn methods are antithetical to graceful fluent AsLink() (cid.Cid, error)
// chaining in golang, syntactically.
// Panics might also be a viable option. I suspect we do quite usually
// want to do large amounts of these traversal operations, and bailing at
// any point is desired to be terse. We could provide a thunkwrapper.
} }
type SerializableNode interface { type SerializableNode interface {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment