Skip to content

Commit

Permalink
more updates
Browse files Browse the repository at this point in the history
  • Loading branch information
smrz2001 committed Oct 16, 2022
1 parent 27729e2 commit c387c8f
Show file tree
Hide file tree
Showing 7 changed files with 342 additions and 212 deletions.
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ module github.com/ipld/go-ipld-prime
go 1.18

require (
github.com/emirpasic/gods v1.18.1
github.com/frankban/quicktest v1.14.3
github.com/google/go-cmp v0.5.9
github.com/ipfs/go-cid v0.3.2
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE=
github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps=
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
Expand Down
13 changes: 13 additions & 0 deletions node/basicnode/amender.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package basicnode

import "github.com/ipld/go-ipld-prime/datamodel"

func NewAmender(base datamodel.Node, kind datamodel.Kind) datamodel.NodeAmender {
if kind == datamodel.Kind_Map {
return Prototype.Map.amender(base)
} else if kind == datamodel.Kind_List {
return Prototype.List.amender(base)
} else {
return Prototype.Any.amender(base)
}
}
51 changes: 39 additions & 12 deletions node/basicnode/any.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,18 @@ func (Prototype__Any) NewBuilder() datamodel.NodeBuilder {
}

func (p Prototype__Any) AmendingBuilder(base datamodel.Node) datamodel.NodeAmender {
nb := &anyBuilder{}
if base != nil {
switch base.Kind() {
case datamodel.Kind_Map, datamodel.Kind_List:
nb.AssignNull()
default:
nb.AssignNode(base)
}
return p.amender(base)
}

func (Prototype__Any) amender(base datamodel.Node) datamodel.NodeAmender {
nb := &anyBuilder{kind: base.Kind()}
switch base.Kind() {
case datamodel.Kind_Map:
nb.mapBuilder = *(Prototype.Map.AmendingBuilder(base).(*plainMap__Builder))
case datamodel.Kind_List:
nb.listBuilder = *(Prototype.List.AmendingBuilder(base).(*plainList__Builder))
default:
nb.scalarNode = base
}
return nb
}
Expand Down Expand Up @@ -85,13 +89,36 @@ type anyBuilder struct {
}

func (nb *anyBuilder) Get(path datamodel.Path) (datamodel.Node, error) {
//TODO implement me
panic("implement me")
// If the base node is an amender, use it, otherwise return the base node.
switch nb.kind {
case datamodel.Kind_Map:
return nb.mapBuilder.Get(path)
case datamodel.Kind_List:
return nb.listBuilder.Get(path)
}
return nb.scalarNode, nil
}

func (nb *anyBuilder) Transform(path datamodel.Path, transform func(datamodel.Node) (datamodel.Node, error), createParents bool) (datamodel.Node, error) {
//TODO implement me
panic("implement me")
// Allow the base node to be replaced.
if path.Len() == 0 {
if prevNode, err := nb.Get(datamodel.Path{}); err != nil {
return nil, err
} else if newNode, err := transform(prevNode); err != nil {
return nil, err
} else {
*nb = *Prototype.Any.amender(newNode).(*anyBuilder)
return prevNode, nil
}
}
// If the base node is an amender, use it, otherwise panic.
switch nb.kind {
case datamodel.Kind_Map:
return nb.mapBuilder.Transform(path, transform, createParents)
case datamodel.Kind_List:
return nb.listBuilder.Transform(path, transform, createParents)
}
panic("misuse")
}

func (nb *anyBuilder) Reset() {
Expand Down
215 changes: 202 additions & 13 deletions node/basicnode/list.go
Original file line number Diff line number Diff line change
@@ -1,22 +1,26 @@
package basicnode

import (
"fmt"
"reflect"

"github.com/ipld/go-ipld-prime/datamodel"
"github.com/ipld/go-ipld-prime/node/mixins"
)

var (
_ datamodel.Node = &plainList{}
_ datamodel.NodePrototype = Prototype__List{}
_ datamodel.NodeBuilder = &plainList__Builder{}
_ datamodel.NodeAssembler = &plainList__Assembler{}
_ datamodel.Node = &plainList{}
_ datamodel.NodePrototype = Prototype__List{}
_ datamodel.NodePrototypeSupportingAmend = Prototype__List{}
_ datamodel.NodeBuilder = &plainList__Builder{}
_ datamodel.NodeAssembler = &plainList__Assembler{}
)

// plainList is a concrete type that provides a list-kind datamodel.Node.
// It can contain any kind of value.
// plainList is also embedded in the 'any' struct and usable from there.
type plainList struct {
x []datamodel.Node
x []datamodel.NodeAmender
}

// -- Node interface methods -->
Expand All @@ -31,17 +35,31 @@ func (plainList) LookupByNode(datamodel.Node) (datamodel.Node, error) {
return mixins.List{TypeName: "list"}.LookupByNode(nil)
}
func (n *plainList) LookupByIndex(idx int64) (datamodel.Node, error) {
if v, err := n.lookupAmenderByIndex(idx); err != nil {
return nil, err
} else {
return v.Build(), nil
}
}
func (n *plainList) lookupAmenderByIndex(idx int64) (datamodel.NodeAmender, error) {
if n.Length() <= idx {
return nil, datamodel.ErrNotExists{Segment: datamodel.PathSegmentOfInt(idx)}
}
return n.x[idx], nil
}
func (n *plainList) LookupBySegment(seg datamodel.PathSegment) (datamodel.Node, error) {
if v, err := n.lookupAmenderBySegment(seg); err != nil {
return nil, err
} else {
return v.Build(), nil
}
}
func (n *plainList) lookupAmenderBySegment(seg datamodel.PathSegment) (datamodel.NodeAmender, error) {
idx, err := seg.Index()
if err != nil {
return nil, datamodel.ErrInvalidSegmentForList{TroubleSegment: seg, Reason: err}
}
return n.LookupByIndex(idx)
return n.lookupAmenderByIndex(idx)
}
func (plainList) MapIterator() datamodel.MapIterator {
return nil
Expand Down Expand Up @@ -89,7 +107,7 @@ func (itr *plainList_ListIterator) Next() (idx int64, v datamodel.Node, _ error)
if itr.Done() {
return -1, nil, datamodel.ErrIteratorOverread{}
}
v = itr.n.x[itr.idx]
v = itr.n.x[itr.idx].Build()
idx = int64(itr.idx)
itr.idx++
return
Expand All @@ -102,8 +120,27 @@ func (itr *plainList_ListIterator) Done() bool {

type Prototype__List struct{}

func (Prototype__List) NewBuilder() datamodel.NodeBuilder {
return &plainList__Builder{plainList__Assembler{w: &plainList{}}}
func (p Prototype__List) NewBuilder() datamodel.NodeBuilder {
return p.AmendingBuilder(nil)
}

func (p Prototype__List) AmendingBuilder(base datamodel.Node) datamodel.NodeAmender {
var l *plainList
if base != nil {
// If `base` is specified, it MUST be another `plainList`.
if baseList, castOk := base.(*plainList); !castOk {
panic("misuse")
} else {
l = baseList
}
} else {
l = &plainList{}
}
return p.amender(l)
}

func (Prototype__List) amender(base datamodel.Node) datamodel.NodeAmender {
return &plainList__Builder{plainList__Assembler{w: base.(*plainList)}}
}

// -- NodeBuilder -->
Expand All @@ -113,8 +150,8 @@ type plainList__Builder struct {
}

func (nb *plainList__Builder) Build() datamodel.Node {
if nb.state != laState_finished {
panic("invalid state: assembler must be 'finished' before Build can be called!")
if (nb.state != laState_initial) && (nb.state != laState_finished) {
panic("invalid state: assembly in progress must be 'finished' before Build can be called!")
}
return nb.w
}
Expand All @@ -123,6 +160,158 @@ func (nb *plainList__Builder) Reset() {
nb.w = &plainList{}
}

// -- NodeAmender -->

func (nb *plainList__Builder) Get(path datamodel.Path) (datamodel.Node, error) {
// If the root is requested, return the `Node` view of the amender.
if path.Len() == 0 {
return nb.w, nil
}
childSeg, remainingPath := path.Shift()
childAmender, err := nb.w.lookupAmenderBySegment(childSeg)
// Since we're explicitly looking for a node, look for the child node in the current amender state and throw an
// error if it does not exist.
if err != nil {
return nil, err
}
return childAmender.Get(remainingPath)
}

func (nb *plainList__Builder) Transform(path datamodel.Path, transform func(datamodel.Node) (datamodel.Node, error), createParents bool) (datamodel.Node, error) {
// Allow the base node to be replaced.
if path.Len() == 0 {
prevNode := nb.w
if newNode, err := transform(prevNode); err != nil {
return nil, err
} else if newList, castOk := newNode.(*plainList); !castOk { // only use another `plainList` to replace this one
return nil, fmt.Errorf("transform: cannot transform root into incompatible type: %v", reflect.TypeOf(newNode))
} else {
*nb.w = *newList
return prevNode, nil
}
}
childSeg, remainingPath := path.Shift()
atLeaf := remainingPath.Len() == 0
childIdx, err := childSeg.Index()
var childAmender datamodel.NodeAmender
if err != nil {
if childSeg.String() == "-" {
// "-" indicates appending a new element to the end of the list.
childIdx = nb.w.Length()
} else {
return nil, datamodel.ErrInvalidSegmentForList{TroubleSegment: childSeg, Reason: err}
}
} else {
// Don't allow the index to be equal to the length if the segment was not "-".
if childIdx >= nb.w.Length() {
return nil, fmt.Errorf("transform: cannot navigate path segment %q at %q because it is beyond the list bounds", childSeg, path)
}
// Only lookup the segment if it was within range of the list elements. If `childIdx` is equal to the length of
// the list, then we fall-through and append an element to the end of the list.
childAmender, err = nb.w.lookupAmenderByIndex(childIdx)
if err != nil {
// - Return any error other than "not exists".
// - If the child node does not exist and `createParents = true`, create the new hierarchy, otherwise throw
// an error.
// - Even if `createParent = false`, if we're at the leaf, don't throw an error because we don't need to
// create any more intermediate parent nodes.
if _, notFoundErr := err.(datamodel.ErrNotExists); !notFoundErr || !(atLeaf || createParents) {
return nil, fmt.Errorf("transform: parent position at %q did not exist (and createParents was false)", path)
}
}
}
// The default behaviour will be to update the element at the specified index (if it exists). New list elements can
// be added in two cases:
// - If an element is being appended to the end of the list.
// - If the transformation of the target node results in a list of nodes, use the first node in the list to replace
// the target node and then "add" the rest after. This is a bit of an ugly hack but is required for compatibility
// with two conflicting sets of semantics - the current `FocusedTransform`, which (quite reasonably) does an
// in-place replacement of list elements, and JSON Patch (https://datatracker.ietf.org/doc/html/rfc6902), which
// does not specify list element replacement. The only "compliant" way to do this today is to first "remove" the
// target node and then "add" its replacement at the same index, which seems incredibly inefficient.
if atLeaf {
var prevChildVal datamodel.Node = nil
if childAmender != nil {
prevChildVal = childAmender.Build()
}
if newChildVal, err := transform(prevChildVal); err != nil {
return nil, err
} else if newChildVal == nil {
newX := make([]datamodel.NodeAmender, nb.w.Length()-1)
copy(newX, nb.w.x[:childIdx])
copy(newX[:childIdx], nb.w.x[childIdx+1:])
nb.w.x = newX
} else if err = nb.storeChildAmender(childIdx, newChildVal, newChildVal.Kind()); err != nil {
return nil, err
}
return prevChildVal, nil
}
var childVal datamodel.Node = nil
var childKind datamodel.Kind
if childAmender == nil {
// If we're not at the leaf yet, look ahead on the remaining path to determine what kind of intermediate parent
// node we need to create.
nextChildSeg, _ := remainingPath.Shift()
if _, err = nextChildSeg.Index(); err == nil {
// As per the discussion [here](https://github.com/smrz2001/go-ipld-prime/pull/1#issuecomment-1143035685),
// this code assumes that if we're dealing with an integral path segment, it corresponds to a list index.
childKind = datamodel.Kind_List
} else {
// From the same discussion as above, any non-integral, intermediate path can be assumed to be a map key.
childKind = datamodel.Kind_Map
}
} else {
childVal = childAmender.Build()
childKind = childVal.Kind()
}
if err = nb.storeChildAmender(childIdx, childVal, childKind); err != nil {
return nil, err
}
return childAmender.Transform(remainingPath, transform, createParents)
}

func (nb *plainList__Builder) storeChildAmender(childIdx int64, n datamodel.Node, k datamodel.Kind) error {
var elems []datamodel.NodeAmender
if (n != nil) && (k == datamodel.Kind_List) && (n.Length() > 0) {
elems = make([]datamodel.NodeAmender, n.Length())
// The following logic uses a transformed list (if there is one) to perform both insertions (needed by JSON
// Patch) and replacements (needed by `FocusedTransform`), while also providing the flexibility to insert more
// than one element at a particular index in the list.
//
// Rules:
// - If appending to the end of the main list, all elements from the transformed list should be considered
// "created" because they did not exist before.
// - If updating at a particular index in the main list, however, use the first element from the transformed
// list to replace the existing element at that index in the main list, then insert the rest of the
// transformed list elements after.
//
// A special case to consider is that of a list element genuinely being a list itself. If that is the case, the
// transformation MUST wrap the element in another list so that, once unwrapped, the element can be replaced or
// inserted without affecting its semantics. Otherwise, the sub-list's elements will get expanded onto that
// index in the main list.
for i := range elems {
elem, err := n.LookupByIndex(int64(i))
if err != nil {
return err
}
elems[i] = NewAmender(elem, elem.Kind())
}
} else {
elems = []datamodel.NodeAmender{NewAmender(n, k)}
}
if childIdx == nb.w.Length() {
nb.w.x = append(nb.w.x, elems...)
} else {
numElems := int64(len(elems))
newX := make([]datamodel.NodeAmender, nb.w.Length()+numElems)
copy(newX, nb.w.x[:childIdx])
copy(newX[:childIdx], elems)
copy(newX[:childIdx+numElems], nb.w.x[childIdx+1:])
nb.w.x = newX
}
return nil
}

// -- NodeAssembler -->

type plainList__Assembler struct {
Expand Down Expand Up @@ -155,7 +344,7 @@ func (na *plainList__Assembler) BeginList(sizeHint int64) (datamodel.ListAssembl
sizeHint = 0
}
// Allocate storage space.
na.w.x = make([]datamodel.Node, 0, sizeHint)
na.w.x = make([]datamodel.NodeAmender, 0, sizeHint)
// That's it; return self as the ListAssembler. We already have all the right methods on this structure.
return na, nil
}
Expand Down Expand Up @@ -291,7 +480,7 @@ func (lva *plainList__ValueAssembler) AssignLink(v datamodel.Link) error {
return lva.AssignNode(&vb)
}
func (lva *plainList__ValueAssembler) AssignNode(v datamodel.Node) error {
lva.la.w.x = append(lva.la.w.x, v)
lva.la.w.x = append(lva.la.w.x, NewAmender(v, v.Kind()))
lva.la.state = laState_initial
lva.la = nil // invalidate self to prevent further incorrect use.
return nil
Expand Down
Loading

0 comments on commit c387c8f

Please sign in to comment.