Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve docs for AssignNode; and datamodel.Copy function. #264

Merged
merged 2 commits into from
Oct 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
110 changes: 110 additions & 0 deletions datamodel/copy.go
Original file line number Diff line number Diff line change
@@ -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())
}
}
31 changes: 26 additions & 5 deletions datamodel/nodeBuilder.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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.
Expand Down