diff --git a/cmd/gnoland/main.go b/cmd/gnoland/main.go index d5e0ba9fc2d..79b517b3fc8 100644 --- a/cmd/gnoland/main.go +++ b/cmd/gnoland/main.go @@ -134,6 +134,7 @@ func makeGenesisDoc(pvPub crypto.PubKey) *bft.GenesisDoc { for _, path := range []string{ "p/demo/ufmt", "p/demo/avl", + "p/demo/avl/v2", "p/demo/grc/exts", "p/demo/grc/grc20", "p/demo/grc/grc721", diff --git a/examples/gno.land/p/demo/avl/v2/node.gno b/examples/gno.land/p/demo/avl/v2/node.gno new file mode 100644 index 00000000000..4e20eff5bfd --- /dev/null +++ b/examples/gno.land/p/demo/avl/v2/node.gno @@ -0,0 +1,463 @@ +package v2 + +//---------------------------------------- +// Node + +type Node struct { + key string + value interface{} + height int8 + size int + leftNode *Node + rightNode *Node +} + +func NewNode(key string, value interface{}) *Node { + return &Node{ + key: key, + value: value, + height: 0, + size: 1, + } +} + +func (node *Node) Size() int { + if node == nil { + return 0 + } + return node.size +} + +func (node *Node) IsLeaf() bool { + return node.height == 0 +} + +func (node *Node) Key() string { + return node.key +} + +func (node *Node) Value() interface{} { + return node.value +} + +func (node *Node) _copy() *Node { + if node.height == 0 { + panic("Why are you copying a value node?") + } + return &Node{ + key: node.key, + height: node.height, + size: node.size, + leftNode: node.leftNode, + rightNode: node.rightNode, + } +} + +func (node *Node) Has(key string) (has bool) { + if node == nil { + return false + } + if node.key == key { + return true + } + if node.height == 0 { + return false + } else { + if key < node.key { + return node.getLeftNode().Has(key) + } else { + return node.getRightNode().Has(key) + } + } +} + +func (node *Node) Get(key string) (index int, value interface{}, exists bool) { + if node == nil { + return 0, nil, false + } + if node.height == 0 { + if node.key == key { + return 0, node.value, true + } else if node.key < key { + return 1, nil, false + } else { + return 0, nil, false + } + } else { + if key < node.key { + return node.getLeftNode().Get(key) + } else { + rightNode := node.getRightNode() + index, value, exists = rightNode.Get(key) + index += node.size - rightNode.size + return index, value, exists + } + } +} + +func (node *Node) GetByIndex(index int) (key string, value interface{}) { + if node.height == 0 { + if index == 0 { + return node.key, node.value + } else { + panic("GetByIndex asked for invalid index") + return "", nil + } + } else { + // TODO: could improve this by storing the sizes + leftNode := node.getLeftNode() + if index < leftNode.size { + return leftNode.GetByIndex(index) + } else { + return node.getRightNode().GetByIndex(index - leftNode.size) + } + } +} + +// XXX consider a better way to do this... perhaps split Node from Node. +func (node *Node) Set(key string, value interface{}) (newSelf *Node, updated bool) { + if node == nil { + return NewNode(key, value), false + } + if node.height == 0 { + if key < node.key { + return &Node{ + key: node.key, + height: 1, + size: 2, + leftNode: NewNode(key, value), + rightNode: node, + }, false + } else if key == node.key { + return NewNode(key, value), true + } else { + return &Node{ + key: key, + height: 1, + size: 2, + leftNode: node, + rightNode: NewNode(key, value), + }, false + } + } else { + node = node._copy() + if key < node.key { + node.leftNode, updated = node.getLeftNode().Set(key, value) + } else { + node.rightNode, updated = node.getRightNode().Set(key, value) + } + if updated { + return node, updated + } else { + node.calcHeightAndSize() + return node.balance(), updated + } + } +} + +// newNode: The new node to replace node after remove. +// newKey: new leftmost leaf key for node after successfully removing 'key' if changed. +// value: removed value. +func (node *Node) Remove(key string) ( + newNode *Node, newKey string, value interface{}, removed bool, +) { + if node == nil { + return nil, "", nil, false + } + if node.height == 0 { + if key == node.key { + return nil, "", node.value, true + } else { + return node, "", nil, false + } + } else { + if key < node.key { + var newLeftNode *Node + newLeftNode, newKey, value, removed = node.getLeftNode().Remove(key) + if !removed { + return node, "", value, false + } else if newLeftNode == nil { // left node held value, was removed + return node.rightNode, node.key, value, true + } + node = node._copy() + node.leftNode = newLeftNode + node.calcHeightAndSize() + node = node.balance() + return node, newKey, value, true + } else { + var newRightNode *Node + newRightNode, newKey, value, removed = node.getRightNode().Remove(key) + if !removed { + return node, "", value, false + } else if newRightNode == nil { // right node held value, was removed + return node.leftNode, "", value, true + } + node = node._copy() + node.rightNode = newRightNode + if newKey != "" { + node.key = newKey + } + node.calcHeightAndSize() + node = node.balance() + return node, "", value, true + } + } +} + +func (node *Node) getLeftNode() *Node { + return node.leftNode +} + +func (node *Node) getRightNode() *Node { + return node.rightNode +} + +// NOTE: overwrites node +// TODO: optimize balance & rotate +func (node *Node) rotateRight() *Node { + node = node._copy() + l := node.getLeftNode() + _l := l._copy() + + _lrCached := _l.rightNode + _l.rightNode = node + node.leftNode = _lrCached + + node.calcHeightAndSize() + _l.calcHeightAndSize() + + return _l +} + +// NOTE: overwrites node +// TODO: optimize balance & rotate +func (node *Node) rotateLeft() *Node { + node = node._copy() + r := node.getRightNode() + _r := r._copy() + + _rlCached := _r.leftNode + _r.leftNode = node + node.rightNode = _rlCached + + node.calcHeightAndSize() + _r.calcHeightAndSize() + + return _r +} + +// NOTE: mutates height and size +func (node *Node) calcHeightAndSize() { + node.height = maxInt8(node.getLeftNode().height, node.getRightNode().height) + 1 + node.size = node.getLeftNode().size + node.getRightNode().size +} + +func (node *Node) calcBalance() int { + return int(node.getLeftNode().height) - int(node.getRightNode().height) +} + +// NOTE: assumes that node can be modified +// TODO: optimize balance & rotate +func (node *Node) balance() (newSelf *Node) { + balance := node.calcBalance() + if balance > 1 { + if node.getLeftNode().calcBalance() >= 0 { + // Left Left Case + return node.rotateRight() + } else { + // Left Right Case + // node = node._copy() + left := node.getLeftNode() + node.leftNode = left.rotateLeft() + // node.calcHeightAndSize() + return node.rotateRight() + } + } + if balance < -1 { + if node.getRightNode().calcBalance() <= 0 { + // Right Right Case + return node.rotateLeft() + } else { + // Right Left Case + // node = node._copy() + right := node.getRightNode() + node.rightNode = right.rotateRight() + // node.calcHeightAndSize() + return node.rotateLeft() + } + } + // Nothing changed + return node +} + +// Shortcut for TraverseInRange. +func (node *Node) Iterate(start, end string, cb func(*Node) bool) bool { + return node.TraverseInRange(start, end, true, true, cb) +} + +// Shortcut for TraverseInRange. +func (node *Node) ReverseIterate(start, end string, cb func(*Node) bool) bool { + return node.TraverseInRange(start, end, false, true, cb) +} + +// TraverseInRange traverses all nodes, including inner nodes. +// Start is inclusive and end is exclusive when ascending, +// Start and end are inclusive when descending. +// Empty start and empty end denote no start and no end. +// If leavesOnly is true, only visit leaf nodes. +// NOTE: To simulate an exclusive reverse traversal, +// just append 0x00 to start. +func (node *Node) TraverseInRange(start, end string, ascending bool, leavesOnly bool, cb func(*Node) bool) bool { + if node == nil { + return false + } + afterStart := (start == "" || start < node.key) + startOrAfter := (start == "" || start <= node.key) + beforeEnd := false + if ascending { + beforeEnd = (end == "" || node.key < end) + } else { + beforeEnd = (end == "" || node.key <= end) + } + + // Run callback per inner/leaf node. + stop := false + if (!node.IsLeaf() && !leavesOnly) || + (node.IsLeaf() && startOrAfter && beforeEnd) { + stop = cb(node) + if stop { + return stop + } + } + if node.IsLeaf() { + return stop + } + + if ascending { + // check lower nodes, then higher + if afterStart { + stop = node.getLeftNode().TraverseInRange(start, end, ascending, leavesOnly, cb) + } + if stop { + return stop + } + if beforeEnd { + stop = node.getRightNode().TraverseInRange(start, end, ascending, leavesOnly, cb) + } + } else { + // check the higher nodes first + if beforeEnd { + stop = node.getRightNode().TraverseInRange(start, end, ascending, leavesOnly, cb) + } + if stop { + return stop + } + if afterStart { + stop = node.getLeftNode().TraverseInRange(start, end, ascending, leavesOnly, cb) + } + } + + return stop +} + +// TraverseByOffset traverses all nodes, including inner nodes. +// A limit of math.MaxInt means no limit. +func (node *Node) TraverseByOffset(offset, limit int, descending bool, leavesOnly bool, cb func(*Node) bool) bool { + if node == nil { + return false + } + + // fast paths. these happen only if TraverseByOffset is called directly on a leaf. + if limit <= 0 || offset >= node.size { + return false + } + if node.IsLeaf() { + if offset > 0 { + return false + } + return cb(node) + } + + // go to the actual recursive function. + return node.traverseByOffset(offset, limit, descending, leavesOnly, cb) +} + +func (node *Node) traverseByOffset(offset, limit int, descending bool, leavesOnly bool, cb func(*Node) bool) bool { + // caller guarantees: offset < node.size; limit > 0. + + if !leavesOnly { + if cb(node) { + return true + } + } + first, second := node.getLeftNode(), node.getRightNode() + if descending { + first, second = second, first + } + if first.IsLeaf() { + // either run or skip, based on offset + if offset > 0 { + offset-- + } else { + cb(first) + limit-- + if limit <= 0 { + return false + } + } + } else { + // possible cases: + // 1 the offset given skips the first node entirely + // 2 the offset skips none or part of the first node, but the limit requires some of the second node. + // 3 the offset skips none or part of the first node, and the limit stops our search on the first node. + if offset >= first.size { + offset -= first.size // 1 + } else { + if first.traverseByOffset(offset, limit, descending, leavesOnly, cb) { + return true + } + // number of leaves which could actually be called from inside + delta := first.size - offset + offset = 0 + if delta >= limit { + return true // 3 + } + limit -= delta // 2 + } + } + + // because of the caller guarantees and the way we handle the first node, + // at this point we know that limit > 0 and there must be some values in + // this second node that we include. + + // => if the second node is a leaf, it has to be included. + if second.IsLeaf() { + return cb(second) + } + // => if it is not a leaf, it will still be enough to recursively call this + // function with the updated offset and limit + return second.traverseByOffset(offset, limit, descending, leavesOnly, cb) +} + +// Only used in testing... +func (node *Node) lmd() *Node { + if node.height == 0 { + return node + } + return node.getLeftNode().lmd() +} + +// Only used in testing... +func (node *Node) rmd() *Node { + if node.height == 0 { + return node + } + return node.getRightNode().rmd() +} + +func maxInt8(a, b int8) int8 { + if a > b { + return a + } + return b +} diff --git a/examples/gno.land/p/demo/avl/v2/node_test.gno b/examples/gno.land/p/demo/avl/v2/node_test.gno new file mode 100644 index 00000000000..4312270eecc --- /dev/null +++ b/examples/gno.land/p/demo/avl/v2/node_test.gno @@ -0,0 +1,100 @@ +package v2 + +import ( + "sort" + "strings" + "testing" +) + +func TestTraverseByOffset(t *testing.T) { + const testStrings = `Alfa +Alfred +Alpha +Alphabet +Beta +Beth +Book +Browser` + tt := []struct { + name string + desc bool + }{ + {"ascending", false}, + {"descending", true}, + } + + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + sl := strings.Split(testStrings, "\n") + + // sort a first time in the order opposite to how we'll be traversing + // the tree, to ensure that we are not just iterating through with + // insertion order. + sort.Sort(sort.StringSlice(sl)) + if !tc.desc { + reverseSlice(sl) + } + + r := NewNode(sl[0], nil) + for _, v := range sl[1:] { + r, _ = r.Set(v, nil) + } + + // then sort sl in the order we'll be traversing it, so that we can + // compare the result with sl. + reverseSlice(sl) + + var result []string + for i := 0; i < len(sl); i++ { + r.TraverseByOffset(i, 1, tc.desc, true, func(n *Node) bool { + result = append(result, n.Key()) + return false + }) + } + + if !slicesEqual(sl, result) { + t.Errorf("want %v got %v", sl, result) + } + + for l := 2; l <= len(sl); l++ { + // "slices" + for i := 0; i <= len(sl); i++ { + max := i + l + if max > len(sl) { + max = len(sl) + } + exp := sl[i:max] + actual := []string{} + + r.TraverseByOffset(i, l, tc.desc, true, func(tr *Node) bool { + actual = append(actual, tr.Key()) + return false + }) + // t.Log(exp, actual) + if !slicesEqual(exp, actual) { + t.Errorf("want %v got %v", exp, actual) + } + } + } + }) + } +} + +func slicesEqual(w1, w2 []string) bool { + if len(w1) != len(w2) { + return false + } + for i := 0; i < len(w1); i++ { + if w1[0] != w2[0] { + return false + } + } + return true +} + +func reverseSlice(ss []string) { + for i := 0; i < len(ss)/2; i++ { + j := len(ss) - 1 - i + ss[i], ss[j] = ss[j], ss[i] + } +} diff --git a/examples/gno.land/p/demo/avl/v2/tree.gno b/examples/gno.land/p/demo/avl/v2/tree.gno new file mode 100644 index 00000000000..b0c1499f9f5 --- /dev/null +++ b/examples/gno.land/p/demo/avl/v2/tree.gno @@ -0,0 +1,82 @@ +package v2 + +type IterCbFn func(key string, value interface{}) bool + +//---------------------------------------- +// Tree + +// The zero struct can be used as an empty tree. +type Tree struct { + node *Node +} + +func NewTree() *Tree { + return &Tree{ + node: nil, + } +} + +func (tree *Tree) Size() int { + return tree.node.Size() +} + +func (tree *Tree) Has(key string) (has bool) { + return tree.node.Has(key) +} + +func (tree *Tree) Get(key string) (value interface{}, exists bool) { + _, value, exists = tree.node.Get(key) + return +} + +func (tree *Tree) GetByIndex(index int) (key string, value interface{}) { + return tree.node.GetByIndex(index) +} + +func (tree *Tree) Set(key string, value interface{}) (updated bool) { + newnode, updated := tree.node.Set(key, value) + tree.node = newnode + return updated +} + +func (tree *Tree) Remove(key string) (value interface{}, removed bool) { + newnode, _, value, removed := tree.node.Remove(key) + tree.node = newnode + return value, removed +} + +// Shortcut for TraverseInRange. +func (tree *Tree) Iterate(start, end string, cb IterCbFn) bool { + return tree.node.TraverseInRange(start, end, true, true, + func(node *Node) bool { + return cb(node.Key(), node.Value()) + }, + ) +} + +// Shortcut for TraverseInRange. +func (tree *Tree) ReverseIterate(start, end string, cb IterCbFn) bool { + return tree.node.TraverseInRange(start, end, false, true, + func(node *Node) bool { + return cb(node.Key(), node.Value()) + }, + ) +} + +// Shortcut for TraverseByOffset. +func (tree *Tree) IterateByOffset(offset int, count int, cb IterCbFn) bool { + return tree.node.TraverseByOffset(offset, count, true, true, + func(node *Node) bool { + return cb(node.Key(), node.Value()) + }, + ) +} + +// Shortcut for TraverseByOffset. +func (tree *Tree) ReverseIterateByOffset(offset int, count int, cb IterCbFn) bool { + return tree.node.TraverseByOffset(offset, count, false, true, + func(node *Node) bool { + return cb(node.Key(), node.Value()) + }, + ) +} diff --git a/examples/gno.land/p/demo/avl/v2/z_0_filetest.gno b/examples/gno.land/p/demo/avl/v2/z_0_filetest.gno new file mode 100644 index 00000000000..2c40466b575 --- /dev/null +++ b/examples/gno.land/p/demo/avl/v2/z_0_filetest.gno @@ -0,0 +1,348 @@ +// PKGPATH: gno.land/r/test +package test + +import ( + avl "gno.land/p/demo/avl/v2" +) + +var node *avl.Node + +func init() { + node = avl.NewNode("key0", "value0") + // node, _ = node.Set("key0", "value0") +} + +func main() { + var updated bool + node, updated = node.Set("key1", "value1") + // println(node, updated) + println(updated, node.Size()) +} + +// Output: +// false 2 + +// Realm: +// switchrealm["gno.land/r/test"] +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "key0" +// } +// }, +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "value0" +// } +// }, +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "64" +// } +// }, +// { +// "N": "AQAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "32" +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// } +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", +// "ModTime": "5", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "RefCount": "1" +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:6]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "key1" +// } +// }, +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "value1" +// } +// }, +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "64" +// } +// }, +// { +// "N": "AQAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "32" +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// } +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "RefCount": "1" +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:5]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "key1" +// } +// }, +// {}, +// { +// "N": "AQAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "64" +// } +// }, +// { +// "N": "AgAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "32" +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": null, +// "Index": "0", +// "TV": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "26c90e0780984cb934e27728857cda6c6c363075", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4" +// } +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": null, +// "Index": "0", +// "TV": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "5f3fd77a9f92d0b8172d8e5ea4f683ffb637111e", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" +// } +// } +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "RefCount": "1" +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ +// "Blank": {}, +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "IsEscaped": true, +// "ModTime": "4", +// "RefCount": "2" +// }, +// "Parent": null, +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "File": "", +// "Line": "0", +// "Nonce": "0", +// "PkgPath": "gno.land/r/test" +// } +// }, +// "Values": [ +// { +// "T": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// }, +// "V": { +// "@type": "/gno.FuncValue", +// "Closure": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" +// }, +// "FileName": "main.gno", +// "IsMethod": false, +// "Name": "init.0", +// "PkgPath": "gno.land/r/test", +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "File": "main.gno", +// "Line": "10", +// "Nonce": "0", +// "PkgPath": "gno.land/r/test" +// } +// }, +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// }, +// "V": { +// "@type": "/gno.FuncValue", +// "Closure": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" +// }, +// "FileName": "main.gno", +// "IsMethod": false, +// "Name": "main", +// "PkgPath": "gno.land/r/test", +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "File": "main.gno", +// "Line": "15", +// "Nonce": "0", +// "PkgPath": "gno.land/r/test" +// } +// }, +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": null, +// "Index": "0", +// "TV": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "1ad769f5e41891f7780322aa1b66530308b86b91", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" +// } +// } +// } +// } +// ] +// } diff --git a/examples/gno.land/p/demo/avl/v2/z_1_filetest.gno b/examples/gno.land/p/demo/avl/v2/z_1_filetest.gno new file mode 100644 index 00000000000..6006c1314d6 --- /dev/null +++ b/examples/gno.land/p/demo/avl/v2/z_1_filetest.gno @@ -0,0 +1,373 @@ +// PKGPATH: gno.land/r/test +package test + +import ( + avl "gno.land/p/demo/avl/v2" +) + +var node *avl.Node + +func init() { + node = avl.NewNode("key0", "value0") + node, _ = node.Set("key1", "value1") +} + +func main() { + var updated bool + node, updated = node.Set("key2", "value2") + // println(node, updated) + println(updated, node.Size()) +} + +// Output: +// false 3 + +// Realm: +// switchrealm["gno.land/r/test"] +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:9]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "key2" +// } +// }, +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "value2" +// } +// }, +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "64" +// } +// }, +// { +// "N": "AQAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "32" +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// } +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "RefCount": "1" +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:8]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "key2" +// } +// }, +// {}, +// { +// "N": "AQAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "64" +// } +// }, +// { +// "N": "AgAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "32" +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": null, +// "Index": "0", +// "TV": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "b2337cf93c868ce318efb21480f946347580aa6e", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" +// } +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": null, +// "Index": "0", +// "TV": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "fc00d029f59917d26a99d2a92e54158a2e1a8250", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9" +// } +// } +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "RefCount": "1" +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:7]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "key1" +// } +// }, +// {}, +// { +// "N": "AgAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "64" +// } +// }, +// { +// "N": "AwAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "32" +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": null, +// "Index": "0", +// "TV": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "d933d16b221bd73d021da4d5ba2650e4e0e307f3", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:5" +// } +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": null, +// "Index": "0", +// "TV": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "f92d0000e668f4727e8ad13eb3d013db79853365", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8" +// } +// } +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "RefCount": "1" +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:2]={ +// "Blank": {}, +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "IsEscaped": true, +// "ModTime": "6", +// "RefCount": "2" +// }, +// "Parent": null, +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "File": "", +// "Line": "0", +// "Nonce": "0", +// "PkgPath": "gno.land/r/test" +// } +// }, +// "Values": [ +// { +// "T": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// }, +// "V": { +// "@type": "/gno.FuncValue", +// "Closure": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" +// }, +// "FileName": "main.gno", +// "IsMethod": false, +// "Name": "init.0", +// "PkgPath": "gno.land/r/test", +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "File": "main.gno", +// "Line": "10", +// "Nonce": "0", +// "PkgPath": "gno.land/r/test" +// } +// }, +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// }, +// "V": { +// "@type": "/gno.FuncValue", +// "Closure": { +// "@type": "/gno.RefValue", +// "Escaped": true, +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:3" +// }, +// "FileName": "main.gno", +// "IsMethod": false, +// "Name": "main", +// "PkgPath": "gno.land/r/test", +// "Source": { +// "@type": "/gno.RefNode", +// "BlockNode": null, +// "Location": { +// "File": "main.gno", +// "Line": "15", +// "Nonce": "0", +// "PkgPath": "gno.land/r/test" +// } +// }, +// "Type": { +// "@type": "/gno.FuncType", +// "Params": [], +// "Results": [] +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": null, +// "Index": "0", +// "TV": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "f64330eb299a74983b0e3f6c26751a6f4f75fca3", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" +// } +// } +// } +// } +// ] +// } +// d[a8ada09dee16d791fd406d629fe29bb0ed084a30:4] diff --git a/examples/gno.land/p/demo/avl/v2/z_2_filetest.gno b/examples/gno.land/p/demo/avl/v2/z_2_filetest.gno new file mode 100644 index 00000000000..9975f832469 --- /dev/null +++ b/examples/gno.land/p/demo/avl/v2/z_2_filetest.gno @@ -0,0 +1,292 @@ +// PKGPATH: gno.land/r/test +package test + +import ( + avl "gno.land/p/demo/avl/v2" +) + +var tree avl.Tree + +func init() { + tree.Set("key0", "value0") + tree.Set("key1", "value1") +} + +func main() { + var updated bool + updated = tree.Set("key2", "value2") + println(updated, tree.Size()) +} + +// Output: +// false 3 + +// Realm: +// switchrealm["gno.land/r/test"] +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:10]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "key2" +// } +// }, +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "value2" +// } +// }, +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "64" +// } +// }, +// { +// "N": "AQAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "32" +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// } +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", +// "RefCount": "1" +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:9]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "key2" +// } +// }, +// {}, +// { +// "N": "AQAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "64" +// } +// }, +// { +// "N": "AgAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "32" +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": null, +// "Index": "0", +// "TV": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "1b3566e63a02057116fd3e87fecaa397c97da0be", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:7" +// } +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": null, +// "Index": "0", +// "TV": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "d3078e44271bb8dea180fe4b1f0eba33db075a60", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:10" +// } +// } +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "RefCount": "1" +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:8]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "key1" +// } +// }, +// {}, +// { +// "N": "AgAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "64" +// } +// }, +// { +// "N": "AwAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "32" +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": null, +// "Index": "0", +// "TV": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "adad0b935af19942d2389e6a08fa4cfa68ec9392", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" +// } +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": null, +// "Index": "0", +// "TV": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "4a1f471e681956b85f8b3ec8cdb871acd6ca2726", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9" +// } +// } +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", +// "RefCount": "1" +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": null, +// "Index": "0", +// "TV": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "0977a14ca7fbc1abe10e31a9fb92783b4b12fb4e", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8" +// } +// } +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", +// "ModTime": "7", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "RefCount": "1" +// } +// } +// d[a8ada09dee16d791fd406d629fe29bb0ed084a30:5] diff --git a/examples/gno.land/p/demo/avl/v2/z_3_filetest.gno b/examples/gno.land/p/demo/avl/v2/z_3_filetest.gno new file mode 100644 index 00000000000..57742f98f7e --- /dev/null +++ b/examples/gno.land/p/demo/avl/v2/z_3_filetest.gno @@ -0,0 +1,210 @@ +// PKGPATH: gno.land/r/test +package test + +import ( + avl "gno.land/p/demo/avl/v2" + "std" +) + +// var tree avlv2.Tree +var tree = avl.NewTree() + +func init() { + tree.Set("address1", std.Address("1")) + tree.Set("address2", std.Address("2")) +} + +func main() { + var updated bool + updated = tree.Set("address2", std.Address("2+")) + println(updated, tree.Size()) +} + +// Output: +// true 2 + +// Realm: +// switchrealm["gno.land/r/test"] +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:9]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "address2" +// } +// }, +// { +// "T": { +// "@type": "/gno.RefType", +// "ID": "std.Address" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "2+" +// } +// }, +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "64" +// } +// }, +// { +// "N": "AQAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "32" +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// } +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "RefCount": "1" +// } +// } +// c[a8ada09dee16d791fd406d629fe29bb0ed084a30:8]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "16" +// }, +// "V": { +// "@type": "/gno.StringValue", +// "value": "address2" +// } +// }, +// {}, +// { +// "N": "AQAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "64" +// } +// }, +// { +// "N": "AgAAAAAAAAA=", +// "T": { +// "@type": "/gno.PrimitiveType", +// "value": "32" +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": null, +// "Index": "0", +// "TV": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "7429fc97e2a129a67276e39bd16a264fe2aa8557", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:6" +// } +// } +// } +// }, +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": null, +// "Index": "0", +// "TV": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "50070d86b9a14d659fe605ab52450e8d8962bae3", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:9" +// } +// } +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8", +// "ModTime": "0", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", +// "RefCount": "1" +// } +// } +// u[a8ada09dee16d791fd406d629fe29bb0ed084a30:4]={ +// "Fields": [ +// { +// "T": { +// "@type": "/gno.PointerType", +// "Elt": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// } +// }, +// "V": { +// "@type": "/gno.PointerValue", +// "Base": null, +// "Index": "0", +// "TV": { +// "T": { +// "@type": "/gno.RefType", +// "ID": "gno.land/p/demo/avl/v2.Node" +// }, +// "V": { +// "@type": "/gno.RefValue", +// "Hash": "0d149b2743d01552dcfcafa1398383142a39cb6d", +// "ObjectID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:8" +// } +// } +// } +// } +// ], +// "ObjectInfo": { +// "ID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:4", +// "ModTime": "7", +// "OwnerID": "a8ada09dee16d791fd406d629fe29bb0ed084a30:2", +// "RefCount": "1" +// } +// } +// d[a8ada09dee16d791fd406d629fe29bb0ed084a30:5] +// d[a8ada09dee16d791fd406d629fe29bb0ed084a30:7] diff --git a/examples/gno.land/p/demo/ufmt/ufmt.gno b/examples/gno.land/p/demo/ufmt/ufmt.gno index 0f625104168..d116baab07c 100644 --- a/examples/gno.land/p/demo/ufmt/ufmt.gno +++ b/examples/gno.land/p/demo/ufmt/ufmt.gno @@ -34,7 +34,14 @@ func Sprintf(format string, args ...interface{}) string { switch verb { case 's': - buf += arg.(string) + switch v := arg.(type) { + case interface{ String() string }: + buf += v.String() + case string: + buf += v + default: + buf += "(unhandled)" + } case 'd': switch v := arg.(type) { case int: @@ -48,6 +55,17 @@ func Sprintf(format string, args ...interface{}) string { default: buf += "(unhandled)" } + case 't': + switch v := arg.(type) { + case bool: + if v { + buf += "true" + } else { + buf += "false" + } + default: + buf += "(unhandled)" + } case '%': buf += "%" default: diff --git a/examples/gno.land/p/demo/ufmt/ufmt_test.gno b/examples/gno.land/p/demo/ufmt/ufmt_test.gno index 87d196afa2c..07ba76c55b0 100644 --- a/examples/gno.land/p/demo/ufmt/ufmt_test.gno +++ b/examples/gno.land/p/demo/ufmt/ufmt_test.gno @@ -5,6 +5,12 @@ import ( "testing" ) +type stringer struct{} + +func (stringer) String() string { + return "I'm a stringer" +} + func TestSprintf(t *testing.T) { cases := []struct { format string @@ -18,9 +24,14 @@ func TestSprintf(t *testing.T) { {"uint [%d]", []interface{}{uint(42)}, "uint [42]"}, {"int64 [%d]", []interface{}{int64(42)}, "int64 [42]"}, {"uint64 [%d]", []interface{}{uint64(42)}, "uint64 [42]"}, - {"invalid [%d]", []interface{}{"invalid"}, "invalid [(unhandled)]"}, + {"bool [%t]", []interface{}{true}, "bool [true]"}, + {"bool [%t]", []interface{}{false}, "bool [false]"}, + {"invalid bool [%t]", []interface{}{"invalid"}, "invalid bool [(unhandled)]"}, + {"invalid integer [%d]", []interface{}{"invalid"}, "invalid integer [(unhandled)]"}, + {"invalid string [%s]", []interface{}{1}, "invalid string [(unhandled)]"}, {"no args", nil, "no args"}, {"finish with %", nil, "finish with %"}, + {"stringer [%s]", []interface{}{stringer{}}, "stringer [I'm a stringer]"}, } for _, tc := range cases { diff --git a/examples/gno.land/r/gnoland/faucet/admin.gno b/examples/gno.land/r/gnoland/faucet/admin.gno index 03798ba92e7..a4916c5af02 100644 --- a/examples/gno.land/r/gnoland/faucet/admin.gno +++ b/examples/gno.land/r/gnoland/faucet/admin.gno @@ -5,44 +5,77 @@ import ( "std" ) -func AdminSetInPause(inPause bool) error { +func AdminSetInPause(inPause bool) string { if err := assertIsAdmin(); err != nil { - return err + return err.Error() } gInPause = inPause - return nil + return "" } -func AdminSetMessage(message string) error { +func AdminSetMessage(message string) string { if err := assertIsAdmin(); err != nil { - return err + return err.Error() } gMessage = message - return nil + return "" } -func AdminSetPerTransferSend(send std.Coins) error { +func AdminSetTransferLimit(amount int64) string { if err := assertIsAdmin(); err != nil { - return err + return err.Error() } - gPerTransferSend = send - return nil + gLimit = std.Coin{Denom: "ugnot", Amount: amount} + return "" } -func AdminSetAdminAddr(addr std.Address) error { +func AdminSetAdminAddr(addr std.Address) string { if err := assertIsAdmin(); err != nil { - return err + return err.Error() } gAdminAddr = addr - return nil + return "" } -func AdminSetControllerAddr(addr std.Address) error { +func AdminAddController(addr std.Address) string { if err := assertIsAdmin(); err != nil { - return err + return err.Error() } - gControllerAddr = addr - return nil + + size := gControllers.Size() + + if size >= gControllersMaxSize { + return "can not add more controllers than allowed" + } + + if gControllers.Has(addr.String()) { + return addr.String() + " exists, no need to add." + } + + gControllers.Set(addr.String(), addr) + + return "" +} + +func AdminRemoveController(addr std.Address) string { + if err := assertIsAdmin(); err != nil { + return err.Error() + } + + if !gControllers.Has(addr.String()) { + return addr.String() + " is not on the controller list" + } + + _, ok := gControllers.Remove(addr.String()) + + // it not should happen. + // we will check anyway to prevent issues in the underline implementation. + + if !ok { + return addr.String() + " is not on the controller list" + } + + return "" } func assertIsAdmin() error { diff --git a/examples/gno.land/r/gnoland/faucet/faucet.gno b/examples/gno.land/r/gnoland/faucet/faucet.gno index 0d6c8ee37f5..05136f73ea0 100644 --- a/examples/gno.land/r/gnoland/faucet/faucet.gno +++ b/examples/gno.land/r/gnoland/faucet/faucet.gno @@ -4,44 +4,56 @@ import ( "errors" "std" + avl "gno.land/p/demo/avl/v2" "gno.land/p/demo/ufmt" ) var ( // configurable by admin. - gAdminAddr std.Address = "g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq" - gControllerAddr std.Address = "g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq" - gPerTransferSend = std.Coins{std.Coin{"ugnot", 1000000}} - gInPause = false - gMessage = "# Community Faucet.\n\n" + gAdminAddr std.Address = std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + gControllers = avl.NewTree() + gControllersMaxSize = 10 // limit it to 10 + gInPause = false + gMessage = "# Community Faucet.\n\n" // internal vars, for stats. gTotalTransferred std.Coins gTotalTransfers = uint(0) + + // per request limit, 350 gnot + gLimit std.Coin = std.Coin{"ugnot", 350000000} ) -func Transfer(to std.Address) error { +func Transfer(to std.Address, send int64) string { if err := assertIsController(); err != nil { - return err + return err.Error() } if gInPause { - return errors.New("faucet in pause") + return errors.New("faucet in pause").Error() } - send := gPerTransferSend + // limit the per request + if send > gLimit.Amount { + return errors.New("Per request limit " + gLimit.String() + " exceed").Error() + } + sendCoins := std.Coins{std.Coin{Denom: "ugnot", Amount: send}} - gTotalTransferred = gTotalTransferred.Add(send) + gTotalTransferred = gTotalTransferred.Add(sendCoins) gTotalTransfers++ - banker := std.GetBanker(std.BankerTypeOrigSend) + banker := std.GetBanker(std.BankerTypeRealmSend) pkgaddr := std.GetOrigPkgAddr() - banker.SendCoins(pkgaddr, to, send) - return nil + banker.SendCoins(pkgaddr, to, sendCoins) + return "" +} + +func GetPerTransferLimit() int64 { + return gLimit.Amount } -func Render(path string) string { - banker := std.GetBanker(std.BankerTypeOrigSend) +func Render(_ string) string { + banker := std.GetBanker(std.BankerTypeRealmSend) balance := banker.GetCoins(std.GetOrigPkgAddr()) output := gMessage @@ -51,18 +63,29 @@ func Render(path string) string { output += "Status: active.\n" } output += ufmt.Sprintf("Balance: %s.\n", balance.String()) - output += ufmt.Sprintf("Rewarded: %s (in %d times).\n", gTotalTransferred.String(), gTotalTransfers) + output += ufmt.Sprintf("Total transfers: %s (in %d times).\n\n", gTotalTransferred.String(), gTotalTransfers) - if path == "?debug" { - output += ufmt.Sprintf("Admin: %s, Controller: %s\n", gAdminAddr.String(), gControllerAddr.String()) + output += "Package address: " + std.GetOrigPkgAddr().String() + "\n\n" + output += ufmt.Sprintf("Admin: %s\n\n ", gAdminAddr.String()) + output += ufmt.Sprintf("Controllers:\n\n ") + + for i := 0; i < gControllers.Size(); i++ { + _, v := gControllers.GetByIndex(i) + output += ufmt.Sprintf("%s ", v.(std.Address)) } + + output += "\n\n" + output += ufmt.Sprintf("Per request limit: %s\n\n", gLimit.String()) + return output } func assertIsController() error { caller := std.GetOrigCaller() - if caller != gControllerAddr { - return errors.New("restricted for controller") + + ok := gControllers.Has(caller.String()) + if !ok { + return errors.New(caller.String() + " is not on the controller list") } return nil } diff --git a/examples/gno.land/r/gnoland/faucet/faucet_filetest.gno b/examples/gno.land/r/gnoland/faucet/faucet_filetest.gno deleted file mode 100644 index d3e4b5a0ca9..00000000000 --- a/examples/gno.land/r/gnoland/faucet/faucet_filetest.gno +++ /dev/null @@ -1,75 +0,0 @@ -package main - -import ( - "fmt" - "std" - - "gno.land/p/demo/testutils" - "gno.land/r/gnoland/faucet" -) - -func main() { - var ( - adminaddr = std.Address("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq") - controlleraddr = testutils.TestAddress("controller") - mainaddr = std.TestDerivePkgAddr("main") - faucetaddr = std.TestDerivePkgAddr("gno.land/r/faucet") - test1addr = testutils.TestAddress("test1") - test2addr = testutils.TestAddress("test2") - test3addr = testutils.TestAddress("test3") - ) - - banker := std.GetBanker(std.BankerTypeReadonly) - std.TestIssueCoins(faucetaddr, std.Coins{{"ugnot", 1000000000}}) - std.TestSetOrigPkgAddr(faucetaddr) - - showBalances := func() string { - var ( - mainbal = banker.GetCoins(mainaddr) - faucetbal = banker.GetCoins(faucetaddr) - test1bal = banker.GetCoins(test1addr) - test2bal = banker.GetCoins(test2addr) - test3bal = banker.GetCoins(test3addr) - controllerbal = banker.GetCoins(controlleraddr) - adminbal = banker.GetCoins(adminaddr) - ) - return fmt.Sprintf("main=%q, faucet=%q, test1=%q, test2=%q test3=%q controller=%q admin=%q", - mainbal.String(), faucetbal.String(), test1bal.String(), test2bal.String(), - test3bal.String(), controllerbal.String(), adminbal.String()) - } - - println("before:", showBalances()) - - // simulate a Deposit call. - std.TestSetOrigSend(std.Coins{{"ugnot", 2000000000}}, nil) - std.TestSetOrigCaller(adminaddr) - faucet.AdminSetControllerAddr(controlleraddr) - std.TestSetOrigCaller(controlleraddr) - faucet.Transfer(test1addr) - faucet.Transfer(test2addr) - faucet.Transfer(test1addr) - - println("after: ", showBalances()) - - // simulate a Render(). - ret := faucet.Render("") - println("---") - println("Render: ", ret) - println("---") - println("done") -} - -// Output: -// before: main="200000000ugnot", faucet="1000000000ugnot", test1="", test2="" test3="" controller="" admin="" -// after: main="200000000ugnot", faucet="997000000ugnot", test1="2000000ugnot", test2="1000000ugnot" test3="" controller="" admin="" -// --- -// Render: # Community Faucet. -// -// Status: active. -// Balance: 997000000ugnot. -// Rewarded: 3000000ugnot (in 3 times). -// -// --- -// done - -// Realm: diff --git a/examples/gno.land/r/gnoland/faucet/faucet_test.gno b/examples/gno.land/r/gnoland/faucet/faucet_test.gno index 54214dd0ede..c1202e09611 100644 --- a/examples/gno.land/r/gnoland/faucet/faucet_test.gno +++ b/examples/gno.land/r/gnoland/faucet/faucet_test.gno @@ -11,49 +11,103 @@ import ( func TestPackage(t *testing.T) { var ( - adminaddr = std.Address("g1u7y667z64x2h7vc6fmpcprgey4ck233jaww9zq") - controlleraddr = testutils.TestAddress("controller") - test1addr = testutils.TestAddress("test1") + adminaddr = std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + faucetaddr = std.DerivePkgAddr("gno.land/r/faucet") + controlleraddr1 = testutils.TestAddress("controller1") + controlleraddr2 = testutils.TestAddress("controller2") + controlleraddr3 = testutils.TestAddress("controller3") + controlleraddr4 = testutils.TestAddress("controller4") + controlleraddr5 = testutils.TestAddress("controller5") + controlleraddr6 = testutils.TestAddress("controller6") + controlleraddr7 = testutils.TestAddress("controller7") + controlleraddr8 = testutils.TestAddress("controller8") + controlleraddr9 = testutils.TestAddress("controller9") + controlleraddr10 = testutils.TestAddress("controller10") + controlleraddr11 = testutils.TestAddress("controller11") + + test1addr = testutils.TestAddress("test1") ) + // deposit 1000gnot to faucet contract + + std.TestIssueCoins(faucetaddr, std.Coins{{"ugnot", 1000000000}}) + std.TestSetOrigPkgAddr(faucetaddr) + assertBalance(t, faucetaddr, 1000000000) // by default, balance is empty, and as a user I cannot call Transfer, or Admin commands. - std.TestSetOrigSend(std.Coins{{"ugnot", 2000000000}}, nil) + assertBalance(t, test1addr, 0) - assertErr(t, faucet.Transfer(test1addr)) - assertErr(t, faucet.AdminSetControllerAddr(controlleraddr)) - std.TestSetOrigCaller(controlleraddr) - assertErr(t, faucet.Transfer(test1addr)) + std.TestSetOrigCaller(test1addr) + assertErr(t, faucet.Transfer(test1addr, 1000000)) + + assertErr(t, faucet.AdminAddController(controlleraddr1)) + std.TestSetOrigCaller(controlleraddr1) + assertErr(t, faucet.Transfer(test1addr, 1000000)) - // as an admin, set the controller to controlleraddr. + // as an admin, add the controller to contract and deposit more 2000gnot to contract std.TestSetOrigCaller(adminaddr) - assertNoErr(t, faucet.AdminSetControllerAddr(controlleraddr)) + assertNoErr(t, faucet.AdminAddController(controlleraddr1)) + assertBalance(t, faucetaddr, 1000000000) // now, send some tokens as controller. - std.TestSetOrigCaller(controlleraddr) - assertNoErr(t, faucet.Transfer(test1addr)) + std.TestSetOrigCaller(controlleraddr1) + assertNoErr(t, faucet.Transfer(test1addr, 1000000)) assertBalance(t, test1addr, 1000000) - assertNoErr(t, faucet.Transfer(test1addr)) + assertNoErr(t, faucet.Transfer(test1addr, 1000000)) assertBalance(t, test1addr, 2000000) + assertBalance(t, faucetaddr, 998000000) + + // remove controller + // as an admin, remove controller + std.TestSetOrigCaller(adminaddr) + assertNoErr(t, faucet.AdminRemoveController(controlleraddr1)) + std.TestSetOrigCaller(controlleraddr1) + assertErr(t, faucet.Transfer(test1addr, 1000000)) + + // duplicate controller + std.TestSetOrigCaller(adminaddr) + assertNoErr(t, faucet.AdminAddController(controlleraddr1)) + assertErr(t, faucet.AdminAddController(controlleraddr1)) + // add more than more than allowed controllers + assertNoErr(t, faucet.AdminAddController(controlleraddr2)) + assertNoErr(t, faucet.AdminAddController(controlleraddr3)) + assertNoErr(t, faucet.AdminAddController(controlleraddr4)) + assertNoErr(t, faucet.AdminAddController(controlleraddr5)) + assertNoErr(t, faucet.AdminAddController(controlleraddr6)) + assertNoErr(t, faucet.AdminAddController(controlleraddr7)) + assertNoErr(t, faucet.AdminAddController(controlleraddr8)) + assertNoErr(t, faucet.AdminAddController(controlleraddr9)) + assertNoErr(t, faucet.AdminAddController(controlleraddr10)) + assertErr(t, faucet.AdminAddController(controlleraddr11)) + + // send more than per transfer limit + std.TestSetOrigCaller(adminaddr) + faucet.AdminSetTransferLimit(300000000) + std.TestSetOrigCaller(controlleraddr1) + assertErr(t, faucet.Transfer(test1addr, 301000000)) + + // block transefer from the address not on the controllers list. + std.TestSetOrigCaller(controlleraddr11) + assertErr(t, faucet.Transfer(test1addr, 1000000)) } -func assertErr(t *testing.T, err error) { +func assertErr(t *testing.T, err string) { t.Helper() - if err == nil { + + if err == "" { t.Logf("info: got err: %v", err) t.Errorf("expected an error, got nil.") } } -func assertNoErr(t *testing.T, err error) { +func assertNoErr(t *testing.T, err string) { t.Helper() - if err != nil { + if err != "" { t.Errorf("got err: %v.", err) } } func assertBalance(t *testing.T, addr std.Address, expectedBal int64) { t.Helper() - banker := std.GetBanker(std.BankerTypeReadonly) coins := banker.GetCoins(addr) got := coins.AmountOf("ugnot") diff --git a/examples/gno.land/r/gnoland/faucet/z0_filetest.gno b/examples/gno.land/r/gnoland/faucet/z0_filetest.gno new file mode 100644 index 00000000000..d6c3273497f --- /dev/null +++ b/examples/gno.land/r/gnoland/faucet/z0_filetest.gno @@ -0,0 +1,27 @@ +package main + +import ( + "gno.land/r/gnoland/faucet" +) + +// assert render with empty path and no controllers +func main() { + println(faucet.Render("")) +} + +// Output: +// # Community Faucet. +// +// Status: active. +// Balance: 200000000ugnot. +// Total transfers: (in 0 times). +// +// Package address: g17rgsdnfxzza0sdfsdma37sdwxagsz378833ca4 +// +// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 +// +// Controllers: +// +// +// +// Per request limit: 350000000ugnot diff --git a/examples/gno.land/r/gnoland/faucet/z1_filetest.gno b/examples/gno.land/r/gnoland/faucet/z1_filetest.gno new file mode 100644 index 00000000000..54fa750ba5e --- /dev/null +++ b/examples/gno.land/r/gnoland/faucet/z1_filetest.gno @@ -0,0 +1,27 @@ +package main + +import ( + "gno.land/r/gnoland/faucet" +) + +// assert render with a path and no controllers +func main() { + println(faucet.Render("path")) +} + +// Output: +// # Community Faucet. +// +// Status: active. +// Balance: 200000000ugnot. +// Total transfers: (in 0 times). +// +// Package address: g17rgsdnfxzza0sdfsdma37sdwxagsz378833ca4 +// +// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 +// +// Controllers: +// +// +// +// Per request limit: 350000000ugnot diff --git a/examples/gno.land/r/gnoland/faucet/z2_filetest.gno b/examples/gno.land/r/gnoland/faucet/z2_filetest.gno new file mode 100644 index 00000000000..1791cd91989 --- /dev/null +++ b/examples/gno.land/r/gnoland/faucet/z2_filetest.gno @@ -0,0 +1,44 @@ +package main + +import ( + "std" + + "gno.land/p/demo/testutils" + "gno.land/r/gnoland/faucet" +) + +// assert render with empty path and 2 controllers +func main() { + var ( + adminaddr = std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + controlleraddr1 = testutils.TestAddress("controller1") + controlleraddr2 = testutils.TestAddress("controller2") + ) + std.TestSetOrigCaller(adminaddr) + err := faucet.AdminAddController(controlleraddr1) + if err != nil { + panic(err) + } + err = faucet.AdminAddController(controlleraddr2) + if err != nil { + panic(err) + } + println(faucet.Render("")) +} + +// Output: +// # Community Faucet. +// +// Status: active. +// Balance: 200000000ugnot. +// Total transfers: (in 0 times). +// +// Package address: g17rgsdnfxzza0sdfsdma37sdwxagsz378833ca4 +// +// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 +// +// Controllers: +// +// g1vdhkuarjdakxcetjx9047h6lta047h6lsdacav g1vdhkuarjdakxcetjxf047h6lta047h6lnrev3v +// +// Per request limit: 350000000ugnot diff --git a/examples/gno.land/r/gnoland/faucet/z3_filetest.gno b/examples/gno.land/r/gnoland/faucet/z3_filetest.gno new file mode 100644 index 00000000000..1dca56811a9 --- /dev/null +++ b/examples/gno.land/r/gnoland/faucet/z3_filetest.gno @@ -0,0 +1,56 @@ +package main + +import ( + "std" + + "gno.land/p/demo/testutils" + "gno.land/r/gnoland/faucet" +) + +// assert render with 2 controllers and 2 transfers +func main() { + var ( + adminaddr = std.Address("g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5") + controlleraddr1 = testutils.TestAddress("controller1") + controlleraddr2 = testutils.TestAddress("controller2") + testaddr1 = testutils.TestAddress("test1") + testaddr2 = testutils.TestAddress("test2") + ) + std.TestSetOrigCaller(adminaddr) + err := faucet.AdminAddController(controlleraddr1) + if err != nil { + panic(err) + } + err = faucet.AdminAddController(controlleraddr2) + if err != nil { + panic(err) + } + std.TestSetOrigCaller(controlleraddr1) + err = faucet.Transfer(testaddr1, 1000000) + if err != nil { + panic(err) + } + std.TestSetOrigCaller(controlleraddr2) + err = faucet.Transfer(testaddr1, 2000000) + if err != nil { + panic(err) + } + println(faucet.Render("")) +} + +// Output: +// # Community Faucet. +// +// Status: active. +// Balance: 197000000ugnot. +// Total transfers: 3000000ugnot (in 2 times). +// +// Package address: g17rgsdnfxzza0sdfsdma37sdwxagsz378833ca4 +// +// Admin: g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5 +// +// Controllers: +// +// g1vdhkuarjdakxcetjx9047h6lta047h6lsdacav g1vdhkuarjdakxcetjxf047h6lta047h6lnrev3v +// +// Per request limit: 350000000ugnot