Commit 5c32434e authored by Eric Myhre's avatar Eric Myhre

Add Kind and Keys methods to Node.

And ReprKind moves from the typed package to the ipld main package.
It's hard to get too much done without the standardization of ReprKind.

Between the Kind() and Keys() methods, it should now be possible to
perform traversals of unknown nodes.

This diff just worries about implementing all the Kind() methods.
Keys() has some additional questions to handle (namely, map ordering?).
Signed-off-by: default avatarEric Myhre <hash@exultant.us>
parent 2a230abe
......@@ -22,6 +22,7 @@ var (
If you're not sure which kind serializable node to use, try `ipldcbor.Node`.
*/
type Node struct {
kind ipld.ReprKind // compute during bind
bound reflect.Value // should already be ptr-unwrapped
atlas atlas.Atlas
}
......@@ -36,11 +37,44 @@ func Bind(bindme interface{}, atl atlas.Atlas) ipld.Node {
rv = rv.Elem()
}
return Node{
kind: determineReprKind(rv),
bound: rv,
atlas: atl,
}
}
func determineReprKind(rv reflect.Value) ipld.ReprKind {
switch rv.Type().Kind() {
case reflect.Bool:
return ipld.ReprKind_Bool
case reflect.String:
return ipld.ReprKind_String
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
return ipld.ReprKind_Int
case reflect.Float32, reflect.Float64:
return ipld.ReprKind_Float
case reflect.Complex64, reflect.Complex128:
panic(fmt.Errorf("invalid ipldbind.Node: ipld has no concept for complex numbers"))
case reflect.Array, reflect.Slice:
return ipld.ReprKind_List
case reflect.Map, reflect.Struct:
return ipld.ReprKind_Map
case reflect.Interface:
determineReprKind(rv.Elem())
case reflect.Chan, reflect.Func, reflect.UnsafePointer:
panic(fmt.Errorf("invalid ipldbind.Node: cannot atlas over type %q; it's a %v", rv.Type().Name(), rv.Kind()))
case reflect.Ptr:
// might've already been traversed during bind, but interface path can find more.
determineReprKind(rv.Elem())
}
panic("unreachable")
}
func (n Node) Kind() ipld.ReprKind {
return n.kind
}
func (n Node) AsBool() (v bool, _ error) {
reflect.ValueOf(&v).Elem().Set(n.bound)
return
......@@ -58,6 +92,11 @@ func (n Node) AsLink() (v cid.Cid, _ error) {
return
}
func (n Node) Keys() ([]string, int) {
return nil, 0 // FIXME
// TODO: REVIEW: structs have clear key order; maps do not. what do?
}
func (n Node) TraverseField(pth string) (ipld.Node, error) {
v := n.bound
......@@ -100,7 +139,7 @@ func (n Node) TraverseField(pth string) (ipld.Node, error) {
reflect.Map,
reflect.Struct,
reflect.Interface:
return Node{v, n.atlas}, nil
return Node{determineReprKind(v), v, n.atlas}, nil
case // esotera: reject with panic
reflect.Chan,
reflect.Func,
......@@ -129,14 +168,14 @@ func (n Node) TraverseField(pth string) (ipld.Node, error) {
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr,
reflect.Float32, reflect.Float64,
reflect.Complex64, reflect.Complex128:
return Node{v, n.atlas}, nil
return Node{determineReprKind(v), v, n.atlas}, nil
case // recursives: wrap in node
reflect.Array,
reflect.Slice,
reflect.Map,
reflect.Struct,
reflect.Interface:
return Node{v, n.atlas}, nil
return Node{determineReprKind(v), v, n.atlas}, nil
case // esotera: reject with panic
reflect.Chan,
reflect.Func,
......
......@@ -27,7 +27,7 @@ var (
If you're not sure which kind serializable node to use, try `ipldcbor.Node`.
*/
type Node struct {
typ typ
kind ipld.ReprKind
_map map[string]ipld.Node // Value union. Only one of these has meaning, depending on the value of 'Type'.
_map2 map[int]ipld.Node // Value union. Only one of these has meaning, depending on the value of 'Type'.
......@@ -40,38 +40,33 @@ type Node struct {
_link cid.Cid // Value union. Only one of these has meaning, depending on the value of 'Type'.
}
type typ struct{ t byte }
var (
tNil = typ{}
tMap = typ{'{'}
tArr = typ{'['}
tBool = typ{'b'}
tStr = typ{'s'}
tInt = typ{'i'}
tFloat = typ{'f'}
tBytes = typ{'x'}
tLink = typ{'/'}
)
func (n *Node) Kind() ipld.ReprKind {
return n.kind
}
func (n *Node) AsBool() (v bool, _ error) {
return n._bool, expectTyp(tBool, n.typ)
return n._bool, expectTyp(ipld.ReprKind_Bool, n.kind)
}
func (n *Node) AsString() (v string, _ error) {
return n._str, expectTyp(tStr, n.typ)
return n._str, expectTyp(ipld.ReprKind_String, n.kind)
}
func (n *Node) AsInt() (v int, _ error) {
return n._int, expectTyp(tInt, n.typ)
return n._int, expectTyp(ipld.ReprKind_Int, n.kind)
}
func (n *Node) AsLink() (v cid.Cid, _ error) {
return n._link, expectTyp(tLink, n.typ)
return n._link, expectTyp(ipld.ReprKind_Link, n.kind)
}
func (n *Node) Keys() ([]string, int) {
return nil, 0 // FIXME
// TODO need to maintain map order now, apparently, sigh
}
func (n *Node) TraverseField(pth string) (ipld.Node, error) {
switch n.typ {
case tNil:
switch n.kind {
case ipld.ReprKind_Null:
return nil, fmt.Errorf("cannot traverse terminals")
case tMap:
case ipld.ReprKind_Map:
switch {
case n._map != nil:
v, _ := n._map[pth]
......@@ -86,7 +81,7 @@ func (n *Node) TraverseField(pth string) (ipld.Node, error) {
default:
panic("unreachable")
}
case tArr:
case ipld.ReprKind_List:
i, err := strconv.Atoi(pth)
if err != nil {
return nil, fmt.Errorf("404")
......@@ -95,7 +90,12 @@ func (n *Node) TraverseField(pth string) (ipld.Node, error) {
return nil, fmt.Errorf("404")
}
return n._arr[i], nil
case tStr, tBytes, tBool, tInt, tFloat, tLink:
case ipld.ReprKind_Bool,
ipld.ReprKind_String,
ipld.ReprKind_Bytes,
ipld.ReprKind_Int,
ipld.ReprKind_Float,
ipld.ReprKind_Link:
return nil, fmt.Errorf("cannot traverse terminals")
default:
panic("unreachable")
......@@ -103,13 +103,13 @@ func (n *Node) TraverseField(pth string) (ipld.Node, error) {
}
func (n *Node) TraverseIndex(idx int) (ipld.Node, error) {
switch n.typ {
case tNil:
switch n.kind {
case ipld.ReprKind_Null:
return nil, fmt.Errorf("cannot traverse terminals")
case tMap:
case ipld.ReprKind_Map:
return nil, fmt.Errorf("cannot traverse map by numeric index")
// REVIEW: there's an argument that maybe we should support this; would be '_map2' code.
case tArr:
case ipld.ReprKind_List:
if idx >= len(n._arr) {
return nil, fmt.Errorf("404")
}
......@@ -117,14 +117,19 @@ func (n *Node) TraverseIndex(idx int) (ipld.Node, error) {
return nil, fmt.Errorf("404")
}
return n._arr[idx], nil
case tStr, tBytes, tBool, tInt, tFloat, tLink:
case ipld.ReprKind_Bool,
ipld.ReprKind_String,
ipld.ReprKind_Bytes,
ipld.ReprKind_Int,
ipld.ReprKind_Float,
ipld.ReprKind_Link:
return nil, fmt.Errorf("cannot traverse terminals")
default:
panic("unreachable")
}
}
func expectTyp(expect, actual typ) error {
func expectTyp(expect, actual ipld.ReprKind) error {
if expect == actual {
return nil
}
......
......@@ -6,11 +6,11 @@ import (
)
func (n *Node) SetField(k string, v ipld.Node) {
n.coerceType(tMap)
n.coerceType(ipld.ReprKind_Map)
n._map[k] = v
}
func (n *Node) SetIndex(k int, v ipld.Node) {
n.coerceType(tArr)
n.coerceType(ipld.ReprKind_List)
// REVIEW: there are implications to serial arrays as we spec'd them.
// Namely, they can't be sparse. It's just not defined.
// And that means we simply have to have a way to define the length.
......@@ -51,31 +51,31 @@ func (n *Node) SetIndex(k int, v ipld.Node) {
//fmt.Printf("len,cap is now %d,%d\n", len(n._arr), cap(n._arr))
}
func (n *Node) SetBool(v bool) {
n.coerceType(tBool)
n.coerceType(ipld.ReprKind_Bool)
n._bool = v
}
func (n *Node) SetString(v string) {
n.coerceType(tStr)
n.coerceType(ipld.ReprKind_String)
n._str = v
}
func (n *Node) SetBytes(v []byte) {
n.coerceType(ipld.ReprKind_Bytes)
n._bytes = v
}
func (n *Node) SetInt(v int) {
n.coerceType(tInt)
n.coerceType(ipld.ReprKind_Int)
n._int = v
}
func (n *Node) SetFloat(v float64) {
n.coerceType(tFloat)
n.coerceType(ipld.ReprKind_Float)
n._float = v
}
func (n *Node) SetBytes(v []byte) {
n.coerceType(tBytes)
n._bytes = v
}
func (n *Node) SetLink(v cid.Cid) {
n.coerceType(tLink)
n.coerceType(ipld.ReprKind_Link)
n._link = v
}
func (n *Node) coerceType(newKind typ) {
func (n *Node) coerceType(newKind ipld.ReprKind) {
// If this node pointer has actually just been nil, initialize.
// (Our arrays sometimes initialize full of nils, so this comes up.)
// TODO
......@@ -84,38 +84,38 @@ func (n *Node) coerceType(newKind typ) {
// Clear previous data, if relevant.
// Don't bother with zeroing finite-size scalars.
switch n.typ {
case tMap:
switch n.kind {
case ipld.ReprKind_Map:
switch newKind {
case tMap:
case ipld.ReprKind_Map:
return
default:
n._map = nil
}
case tArr:
case ipld.ReprKind_List:
switch newKind {
case tArr:
case ipld.ReprKind_List:
return
default:
n._arr = nil
}
case tStr:
case ipld.ReprKind_String:
switch newKind {
case tStr:
case ipld.ReprKind_String:
return
default:
n._str = ""
}
case tBytes:
case ipld.ReprKind_Bytes:
switch newKind {
case tBytes:
case ipld.ReprKind_Bytes:
return
default:
n._bytes = nil
}
case tLink:
case ipld.ReprKind_Link:
switch newKind {
case tLink:
case ipld.ReprKind_Link:
return
default:
n._link = cid.Undef
......@@ -123,9 +123,9 @@ func (n *Node) coerceType(newKind typ) {
}
// Set new type union marker.
// Initialize empty value if necessary (maps).
n.typ = newKind
n.kind = newKind
switch newKind {
case tMap:
case ipld.ReprKind_Map:
n._map = make(map[string]ipld.Node)
}
// You'll still want to set the value itself after this.
......
package ipld
// ReprKind represents the primitive kind in the IPLD data model.
// All of these kinds map directly onto serializable data.
//
// Note that ReprKind contains the concept of "map", but not "struct"
// or "object" -- those are a concepts that could be introduced in a
// type system layers, but are *not* present in the data model layer,
// and therefore they aren't included in the ReprKind enum.
type ReprKind uint8
const (
ReprKind_Invalid = 0
ReprKind_Bool = 'b'
ReprKind_String = 's'
ReprKind_Bytes = 'x'
ReprKind_Int = 'i'
ReprKind_Float = 'f'
ReprKind_Map = '{'
ReprKind_List = '['
ReprKind_Null = '0'
ReprKind_Link = '/'
)
......@@ -3,6 +3,11 @@ package ipld
import "github.com/ipfs/go-cid"
type Node interface {
// Kind returns a value from the ReprKind enum describing what the
// essential serializable kind of this node is (map, list, int, etc).
// Most other handling of a node requires first switching upon the kind.
Kind() ReprKind
// GetField resolves a path in the the object and returns
// either a primitive (e.g. string, int, etc), a link (type CID),
// or another Node.
......@@ -14,6 +19,13 @@ type Node interface {
// or another Node.
TraverseIndex(idx int) (Node, error)
// Keys returns instructions for traversing the node.
// If the node kind is a map, the keys slice has content;
// if it's a list, the length int will be positive
// (and if it's a zero length list, there's not to traverse, right?);
// and if it's a primitive type the returned values are nil and zero.
Keys() ([]string, int)
AsBool() (bool, error)
AsString() (string, error)
AsInt() (int, error)
......
package typed
// ReprKind represents the primitive kind in the IPLD data model.
// Note that it contains the concept of "map", but not "object" --
// "object" is a concept up in our type system layers, and *not*
// present in the data model layer.
type ReprKind uint8
const (
ReprKind_Invalid = 0
ReprKind_Bool = 'b'
ReprKind_String = 's'
ReprKind_Bytes = 'x'
ReprKind_Int = 'i'
ReprKind_Float = 'f'
ReprKind_Map = '{' // still feel these should be string-only keys. consider the Type.Fields behavior if it's an int-keyed map: insane?
ReprKind_List = '['
ReprKind_Null = '-'
ReprKind_Link = '/'
)
// Kind is our type level kind. It includes "object", "union", and other
// advanced concepts. "map" at this layer also contains additional constraints;
// it must be a single type of element.
type Kind uint8
// REVIEW: unclear if this is needed. Can switch on `Type.(type)`.
const (
Kind_Invalid = 0
Kind_Bool = 'b'
......
package typed
import (
"github.com/ipld/go-ipld-prime"
)
type Node struct {
// FUTURE: proxies most methods, plus adds just-in-time type checking on reads.
// You can use a Validate call to force checking of the entire tree.
// "Advanced Layouts" (e.g. HAMTs, etc) can be seamlessly presented as a plain map through this interface.
}
type MutableNode struct {
// FUTURE: another impl of ipld.MutableNode we can return which checks all things at change time.
// This can proxy to some other implementation type that does real storage.
}
func Validate(ts Universe, t Type, node ipld.Node) []error {
// TODO need more methods to enable traversal, then come back here
return nil
}
package typed
func (TypeBool) ReprKind() ReprKind {
return ReprKind_Bool
import (
"github.com/ipld/go-ipld-prime"
)
func (TypeBool) ReprKind() ipld.ReprKind {
return ipld.ReprKind_Bool
}
func (TypeString) ReprKind() ReprKind {
return ReprKind_String
func (TypeString) ReprKind() ipld.ReprKind {
return ipld.ReprKind_String
}
func (TypeBytes) ReprKind() ReprKind {
return ReprKind_Bytes
func (TypeBytes) ReprKind() ipld.ReprKind {
return ipld.ReprKind_Bytes
}
func (TypeInt) ReprKind() ReprKind {
return ReprKind_Int
func (TypeInt) ReprKind() ipld.ReprKind {
return ipld.ReprKind_Int
}
func (TypeFloat) ReprKind() ReprKind {
return ReprKind_Float
func (TypeFloat) ReprKind() ipld.ReprKind {
return ipld.ReprKind_Float
}
func (TypeMap) ReprKind() ReprKind {
return ReprKind_Map
func (TypeMap) ReprKind() ipld.ReprKind {
return ipld.ReprKind_Map
}
func (TypeList) ReprKind() ReprKind {
return ReprKind_List
func (TypeList) ReprKind() ipld.ReprKind {
return ipld.ReprKind_List
}
func (TypeLink) ReprKind() ReprKind {
return ReprKind_Link
func (TypeLink) ReprKind() ipld.ReprKind {
return ipld.ReprKind_Link
}
func (tv TypeUnion) ReprKind() ReprKind {
func (tv TypeUnion) ReprKind() ipld.ReprKind {
// REVIEW: this may fib; has the bizarre property of being dependent on the *concrete value* for kinded unions!
if tv.Style == UnionStyle_Kinded {
return ReprKind_Invalid
return ipld.ReprKind_Invalid
} else {
return ReprKind_Map
return ipld.ReprKind_Map
}
}
func (tv TypeObject) ReprKind() ReprKind {
func (tv TypeObject) ReprKind() ipld.ReprKind {
if tv.TupleStyle {
return ReprKind_List
return ipld.ReprKind_List
} else {
return ReprKind_Map
return ipld.ReprKind_Map
}
}
func (TypeEnum) ReprKind() ReprKind {
return ReprKind_String
func (TypeEnum) ReprKind() ipld.ReprKind {
return ipld.ReprKind_String
}
package typed
import (
"github.com/ipld/go-ipld-prime"
)
type TypeName string
type Type interface {
// Name() TypeName // annoying name collision.
ReprKind() ReprKind
ReprKind() ipld.ReprKind
}
var (
......@@ -57,10 +61,10 @@ type TypeLink struct {
type TypeUnion struct {
Name TypeName
Style UnionStyle
ValuesKinded map[ReprKind]Type // for Style==Kinded
Values map[TypeName]Type // for Style!=Kinded
TypeHintKey string // for Style==Envelope|Inline
ContentKey string // for Style==Envelope
ValuesKinded map[ipld.ReprKind]Type // for Style==Kinded
Values map[TypeName]Type // for Style!=Kinded
TypeHintKey string // for Style==Envelope|Inline
ContentKey string // for Style==Envelope
}
type UnionStyle struct{ x string }
......
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