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

feat(examples): add p/moul/ulist/lplist (layered proxy list) #3413

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
Draft
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
1 change: 1 addition & 0 deletions examples/gno.land/p/moul/ulist/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module gno.land/p/moul/ulist
1 change: 1 addition & 0 deletions examples/gno.land/p/moul/ulist/lplist/gno.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
gno.land/p/moul/ulist/lplist
201 changes: 201 additions & 0 deletions examples/gno.land/p/moul/ulist/lplist/layered_proxy_list.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
package ulist

import (
"errors"
)

// MigratorFn is a function type that lazily converts values from source to target
type MigratorFn func(interface{}) interface{}

// LayeredProxyList represents a wrapper around an existing List that handles migration
type LayeredProxyList struct {
source IList
target *List
migrator MigratorFn
sourceHeight int // Store initial source size to optimize lookups
}

// NewLayeredProxyList creates a new LayeredProxyList instance that wraps an existing List
func NewLayeredProxyList(source IList, migrator MigratorFn) *LayeredProxyList {
sourceHeight := source.TotalSize()
target := New()
target.totalSize = sourceHeight
return &LayeredProxyList{
source: source,
target: target,
migrator: migrator,
sourceHeight: sourceHeight,
}
}

// Get retrieves the value at the specified index
// Uses sourceHeight to efficiently route requests
func (l *LayeredProxyList) Get(index int) interface{} {
if index < l.sourceHeight {
// Direct access to source for indices below sourceHeight
val := l.source.Get(index)
if val == nil {
return nil
}
// Only apply migrator if it exists
if l.migrator != nil {
return l.migrator(val)
}
return val
}
// Access target list directly for new indices
return l.target.Get(index)
}

// Append adds one or more values to the target list
func (l *LayeredProxyList) Append(values ...interface{}) {
l.target.Append(values...)
}

// Delete marks elements as deleted in the appropriate list
func (l *LayeredProxyList) Delete(indices ...int) error {
for _, index := range indices {
if index < l.sourceHeight {
return errors.New("cannot delete from source list")
}
}
return l.target.Delete(indices...)
}

// Size returns the total number of active elements
func (l *LayeredProxyList) Size() int {
return l.source.Size() + l.target.Size()
}

// TotalSize returns the total number of elements ever added
func (l *LayeredProxyList) TotalSize() int {
return l.target.TotalSize()
}

// MustDelete deletes elements, panicking on error
func (l *LayeredProxyList) MustDelete(indices ...int) {
if err := l.Delete(indices...); err != nil {
panic(err)
}
}

// MustGet retrieves a value, panicking if not found
func (l *LayeredProxyList) MustGet(index int) interface{} {
val := l.Get(index)
if val == nil {
panic(ErrDeleted)
}
return val
}

// GetRange returns elements between start and end indices
func (l *LayeredProxyList) GetRange(start, end int) []Entry {
var entries []Entry
l.Iterator(start, end, func(index int, value interface{}) bool {
entries = append(entries, Entry{Index: index, Value: value})
return false
})
return entries
}

// GetRangeByOffset returns elements starting from offset
func (l *LayeredProxyList) GetRangeByOffset(offset int, count int) []Entry {
var entries []Entry
l.IteratorByOffset(offset, count, func(index int, value interface{}) bool {
entries = append(entries, Entry{Index: index, Value: value})
return false
})
return entries
}

// Iterator performs iteration between start and end indices
func (l *LayeredProxyList) Iterator(start, end int, cb IterCbFn) bool {
// For empty list or invalid range
if start < 0 && end < 0 {
return false
}

// Normalize indices
if start < 0 {
start = 0
}
if end < 0 {
end = 0
}

totalSize := l.TotalSize()
if end >= totalSize {
end = totalSize - 1
}
if start >= totalSize {
start = totalSize - 1
}

// Handle reverse iteration
if start > end {
for i := start; i >= end; i-- {
val := l.Get(i)
if val != nil {
if cb(i, val) {
return true
}
}
}
return false
}

// Handle forward iteration
for i := start; i <= end; i++ {
val := l.Get(i)
if val != nil {
if cb(i, val) {
return true
}
}
}
return false
}

// IteratorByOffset performs iteration starting from offset
func (l *LayeredProxyList) IteratorByOffset(offset int, count int, cb IterCbFn) bool {
if count == 0 {
return false
}

// Normalize offset
if offset < 0 {
offset = 0
}
totalSize := l.TotalSize()
if offset >= totalSize {
offset = totalSize - 1
}

// Determine end based on count direction
var end int
if count > 0 {
end = totalSize - 1
} else {
end = 0
}

wrapperReturned := false
remaining := abs(count)
wrapper := func(index int, value interface{}) bool {
if remaining <= 0 {
wrapperReturned = true
return true
}
remaining--
return cb(index, value)
}

ret := l.Iterator(offset, end, wrapper)
if wrapperReturned {
return false
}
return ret
}

// Verify that LayeredProxyList implements IList
var _ IList = (*LayeredProxyList)(nil)
161 changes: 161 additions & 0 deletions examples/gno.land/p/moul/ulist/lplist/layered_proxy_list_test.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
package ulist

import (
"testing"
)

// TestLayeredProxyListBasicOperations tests the basic operations of LayeredProxyList
func TestLayeredProxyListBasicOperations(t *testing.T) {
// Create source list with initial data
source := New()
source.Append(1, 2, 3)

// Create proxy list with a simple multiplier migrator
migrator := func(v interface{}) interface{} {
return v.(int) * 2
}
proxy := NewLayeredProxyList(source, migrator)

// Test initial state
if got := proxy.Size(); got != 3 {
t.Errorf("initial Size() = %v, want %v", got, 3)
}
if got := proxy.TotalSize(); got != 3 {
t.Errorf("initial TotalSize() = %v, want %v", got, 3)
}

// Test Get with migration
tests := []struct {
index int
want interface{}
}{
{0, 2}, // 1 * 2
{1, 4}, // 2 * 2
{2, 6}, // 3 * 2
}

for _, tt := range tests {
if got := proxy.Get(tt.index); got != tt.want {
t.Errorf("Get(%v) = %v, want %v", tt.index, got, tt.want)
}
}

// Test Append to target
proxy.Append(7, 8)
if got := proxy.Size(); got != 5 {
t.Errorf("Size() after append = %v, want %v", got, 5)
}

// Test Get from target (no migration)
if got := proxy.Get(3); got != 7 {
t.Errorf("Get(3) = %v, want %v", got, 7)
}
}

// TestLayeredProxyListDelete tests delete operations
func TestLayeredProxyListDelete(t *testing.T) {
source := New()
source.Append(1, 2, 3)
proxy := NewLayeredProxyList(source, nil)
proxy.Append(4, 5)

// Test delete from source (should fail)
if err := proxy.Delete(1); err == nil {
t.Error("Delete from source should return error")
}

// Test delete from target (should succeed)
if err := proxy.Delete(3); err != nil {
t.Errorf("Delete from target failed: %v", err)
}

// Verify deletion
if got := proxy.Get(3); got != nil {
t.Errorf("Get(3) after delete = %v, want nil", got)
}
}

// TestLayeredProxyListIteration tests iteration methods
func TestLayeredProxyListIteration(t *testing.T) {
source := New()
source.Append(1, 2, 3)
proxy := NewLayeredProxyList(source, nil)
proxy.Append(4, 5)

// Test GetRange
entries := proxy.GetRange(0, 4)
if len(entries) != 5 {
t.Errorf("GetRange returned %v entries, want 5", len(entries))
}

// Test reverse iteration
entries = proxy.GetRange(4, 0)
if len(entries) != 5 {
t.Errorf("Reverse GetRange returned %v entries, want 5", len(entries))
}

// Test IteratorByOffset with positive count
var values []interface{}
proxy.IteratorByOffset(1, 3, func(index int, value interface{}) bool {
values = append(values, value)
return false
})
if len(values) != 3 {
t.Errorf("IteratorByOffset returned %v values, want 3", len(values))
}
}

// TestLayeredProxyListMustOperations tests must operations
func TestLayeredProxyListMustOperations(t *testing.T) {
source := New()
source.Append(1, 2)
proxy := NewLayeredProxyList(source, nil)

// Test MustGet success
defer func() {
if r := recover(); r != nil {
t.Errorf("MustGet panicked unexpectedly: %v", r)
}
}()
if got := proxy.MustGet(1); got != 2 {
t.Errorf("MustGet(1) = %v, want 2", got)
}

// Test MustGet panic
defer func() {
if r := recover(); r == nil {
t.Error("MustGet should have panicked")
}
}()
proxy.MustGet(99) // Should panic
}

// TestLayeredProxyListWithNilMigrator tests behavior without a migrator
func TestLayeredProxyListWithNilMigrator(t *testing.T) {
source := New()
source.Append(1, 2)
proxy := NewLayeredProxyList(source, nil)

if got := proxy.Get(0); got != 1 {
t.Errorf("Get(0) with nil migrator = %v, want 1", got)
}
}

// TestLayeredProxyListEmpty tests operations on empty lists
func TestLayeredProxyListEmpty(t *testing.T) {
source := New()
proxy := NewLayeredProxyList(source, nil)

if got := proxy.Size(); got != 0 {
t.Errorf("Size() of empty list = %v, want 0", got)
}

if got := proxy.Get(0); got != nil {
t.Errorf("Get(0) of empty list = %v, want nil", got)
}

entries := proxy.GetRange(0, 10)
if len(entries) != 0 {
t.Errorf("GetRange on empty list returned %v entries, want 0", len(entries))
}
}
Loading
Loading