diff --git a/datamodel/copy.go b/datamodel/copy.go new file mode 100644 index 00000000..b237fd4d --- /dev/null +++ b/datamodel/copy.go @@ -0,0 +1,110 @@ +package datamodel + +import ( + "fmt" +) + +// Copy does an explicit shallow copy of a Node's data into a NodeAssembler. +// +// This can be used to flip data from one memory layout to another +// (for example, from basicnode to using using bindnode, +// or to codegenerated node implementations, +// or to or from ADL nodes, etc). +// +// The copy is implemented by ranging over the contents if it's a recursive kind, +// and for each of them, using `AssignNode` on the child values; +// for scalars, it's just calling the appropriate `Assign*` method. +// +// Many NodeAssembler implementations use this as a fallback behavior in their +// `AssignNode` method (that is, they call to this function after all other special +// faster shortcuts they might prefer to employ, such as direct struct copying +// if they share internal memory layouts, etc, have been tried already). +// +func Copy(n Node, na NodeAssembler) error { + switch n.Kind() { + case Kind_Null: + if n.IsAbsent() { + return fmt.Errorf("copying an absent node makes no sense") + } + return na.AssignNull() + case Kind_Bool: + v, err := n.AsBool() + if err != nil { + return fmt.Errorf("node violated contract: promised to be %v kind, but AsBool method returned %w", n.Kind(), err) + } + return na.AssignBool(v) + case Kind_Int: + v, err := n.AsInt() + if err != nil { + return fmt.Errorf("node violated contract: promised to be %v kind, but AsInt method returned %w", n.Kind(), err) + } + return na.AssignInt(v) + case Kind_Float: + v, err := n.AsFloat() + if err != nil { + return fmt.Errorf("node violated contract: promised to be %v kind, but AsFloat method returned %w", n.Kind(), err) + } + return na.AssignFloat(v) + case Kind_String: + v, err := n.AsString() + if err != nil { + return fmt.Errorf("node violated contract: promised to be %v kind, but AsString method returned %w", n.Kind(), err) + } + return na.AssignString(v) + case Kind_Bytes: + v, err := n.AsBytes() + if err != nil { + return fmt.Errorf("node violated contract: promised to be %v kind, but AsBytes method returned %w", n.Kind(), err) + } + return na.AssignBytes(v) + case Kind_Link: + v, err := n.AsLink() + if err != nil { + return fmt.Errorf("node violated contract: promised to be %v kind, but AsLink method returned %w", n.Kind(), err) + } + return na.AssignLink(v) + case Kind_Map: + ma, err := na.BeginMap(n.Length()) + if err != nil { + return err + } + itr := n.MapIterator() + for !itr.Done() { + k, v, err := itr.Next() + if err != nil { + return err + } + if v.IsAbsent() { + continue + } + if err := ma.AssembleKey().AssignNode(k); err != nil { + return err + } + if err := ma.AssembleValue().AssignNode(v); err != nil { + return err + } + } + return ma.Finish() + case Kind_List: + la, err := na.BeginList(n.Length()) + if err != nil { + return err + } + itr := n.ListIterator() + for !itr.Done() { + _, v, err := itr.Next() + if err != nil { + return err + } + if v.IsAbsent() { + continue + } + if err := la.AssembleValue().AssignNode(v); err != nil { + return err + } + } + return la.Finish() + default: + return fmt.Errorf("node has invalid kind %v", n.Kind()) + } +} diff --git a/datamodel/nodeBuilder.go b/datamodel/nodeBuilder.go index c29bea1c..ed3f9558 100644 --- a/datamodel/nodeBuilder.go +++ b/datamodel/nodeBuilder.go @@ -3,16 +3,36 @@ package datamodel // NodeAssembler is the interface that describes all the ways we can set values // in a node that's under construction. // -// To create a Node, you should start with a NodeBuilder (which contains a +// A NodeAssembler is about filling in data. +// To create a new Node, you should start with a NodeBuilder (which contains a // superset of the NodeAssembler methods, and can return the finished Node // from its `Build` method). +// While continuing to build a recursive structure from there, +// you'll see NodeAssembler for all the child values. +// +// For filling scalar data, there's a `Assign{Kind}` method for each kind; +// after calling one of these methods, the data is filled in, and the assembler is done. +// For recursives, there are `BeginMap` and `BeginList` methods, +// which return an object that needs further manipulation to fill in the contents. +// +// There is also one special method: `AssignNode`. +// `AssignNode` takes another `Node` as a parameter, +// and should should internally call one of the other `Assign*` or `Begin*` (and subsequent) functions +// as appropriate for the kind of the `Node` it is given. +// This is roughly equivalent to using the `Copy` function (and is often implemented using it!), but +// `AssignNode` may also try to take faster shortcuts in some implementations, when it detects they're possible. +// (For example, for typed nodes, if they're the same type, lots of checking can be skipped. +// For nodes implemented with pointers, lots of copying can be skipped. +// For nodes that can detect the argument has the same memory layout, faster copy mechanisms can be used; etc.) // // Why do both this and the NodeBuilder interface exist? -// When creating trees of nodes, recursion works over the NodeAssembler interface. -// This is important to efficient library internals, because avoiding the +// In short: NodeBuilder is when you want to cause an allocation; +// NodeAssembler can be used to just "fill in" memory. +// (In the internal gritty details: separate interfaces, one of which lacks a +// `Build` method, helps us write efficient library internals: avoiding the // requirement to be able to return a Node at any random point in the process // relieves internals from needing to implement 'freeze' features. -// (This is useful in turn because implementing those 'freeze' features in a +// This is useful in turn because implementing those 'freeze' features in a // language without first-class/compile-time support for them (as golang is) // would tend to push complexity and costs to execution time; we'd rather not.) type NodeAssembler interface { @@ -69,7 +89,8 @@ type MapAssembler interface { // just feed data and check errors), but it's here. // // For all Data Model maps, this will answer with a basic concept of "string". - // For Schema typed maps, this may answer with a more complex type (potentially even a struct type). + // For Schema typed maps, this may answer with a more complex type + // (potentially even a struct type or union type -- anything that can have a string representation). KeyPrototype() NodePrototype // ValuePrototype returns a NodePrototype that knows how to build values this map can contain.