-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmodel.iterator.go
More file actions
169 lines (155 loc) · 5.05 KB
/
model.iterator.go
File metadata and controls
169 lines (155 loc) · 5.05 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
package model
import (
"github.com/bdlm/cast/v2"
"github.com/bdlm/errors/v2"
)
// Cur reads the key and value at the current cursor position into *pK and *pV
// and returns true. It returns false — and leaves *pK and *pV unchanged — when
// the cursor is before the first element (position -1) or beyond the last
// element.
//
// The cursor is at -1 immediately after construction, after [Model.Reset], and
// after any successful mutation (Delete, SetData, Sort, Reverse). Cur returns
// false in all those states.
//
// For HASH models *pK is set to the string key; for LIST models *pK is set to
// the integer index.
//
// Cur acquires a read lock and may run concurrently with other read operations.
// Note that the cursor (pos) is a shared mutable value written by Next, Prev,
// Reset, and Seek; concurrent iteration from multiple goroutines will
// interleave cursor movements non-deterministically.
func (mdl *Model) Cur(pK, pV *any) bool {
mdl.mux.RLock()
defer mdl.mux.RUnlock()
if mdl.pos < 0 || mdl.pos >= len(mdl.data) {
return false
}
*pK = mdl.pos
if HASH == mdl.GetType() {
*pK = mdl.idxHash[mdl.pos]
}
*pV = toValue(mdl.data[mdl.pos])
return true
}
// Next advances the cursor by one position and reads the key and value at the
// new position into *pK and *pV. It returns true if an element was written, or
// false when the cursor moves past the last element. When Next returns false it
// also resets the cursor to -1, so the next Next call restarts from the
// beginning of the data.
//
// A typical forward-iteration loop:
//
// var key, val any
// for m.Next(&key, &val) {
// // process key and val.(stdModel.Value)
// }
//
// For HASH models *pK is set to the string key; for LIST models *pK is set to
// the integer index.
//
// Next acquires an exclusive write lock to update the cursor atomically with
// the read of the current element.
func (mdl *Model) Next(pK, pV *any) bool {
mdl.mux.Lock()
mdl.pos++
// at the end of the data, reset.
if len(mdl.data) <= mdl.pos {
mdl.pos = -1
mdl.mux.Unlock()
return false
}
*pK = mdl.pos
if HASH == mdl.GetType() {
*pK = mdl.idxHash[mdl.pos]
}
*pV = toValue(mdl.data[mdl.pos])
mdl.mux.Unlock()
return true
}
// Prev retreats the cursor by one position and reads the key and value at the
// new position into *pK and *pV. It returns true if an element was written, or
// false when the cursor would move before the first element. When Prev returns
// false it clamps the cursor to -1 so that a subsequent [Model.Next] call
// restarts from the beginning of the data rather than attempting a negative
// slice index.
//
// A typical backward-iteration loop:
//
// m.Seek(m.Len() - 1) // start at the last element
// var key, val any
// for m.Prev(&key, &val) {
// // process key and val.(stdModel.Value)
// }
//
// For HASH models *pK is set to the string key; for LIST models *pK is set to
// the integer index.
//
// Prev acquires an exclusive write lock to update the cursor atomically with
// the read of the current element.
func (mdl *Model) Prev(pK, pV *any) bool {
mdl.mux.Lock()
mdl.pos--
// at the beginning of the data, stop and clamp to -1 so that a
// subsequent Next() does not attempt a negative slice index.
if mdl.pos < 0 {
mdl.pos = -1
mdl.mux.Unlock()
return false
}
*pK = mdl.pos
if HASH == mdl.GetType() {
*pK = mdl.idxHash[mdl.pos]
}
*pV = toValue(mdl.data[mdl.pos])
mdl.mux.Unlock()
return true
}
// Reset sets the cursor to position -1, which is before the first element.
// After Reset, [Model.Cur] returns false and the next [Model.Next] call
// returns the first element.
//
// Reset acquires an exclusive write lock to update the cursor atomically.
func (mdl *Model) Reset() {
mdl.mux.Lock()
mdl.pos = -1
mdl.mux.Unlock()
}
// Seek positions the cursor at pos so that [Model.Cur] immediately returns the
// element at that position and the next [Model.Next] call returns the following
// element.
//
// For LIST models, pos is converted to int via cast.ToE[int]; non-integer types
// that cannot be cast return [InvalidIndexType]. A negative value or a value
// >= [Model.Len] returns [InvalidIndex].
//
// For HASH models, pos is cast to string via bdlm/cast. If no element with
// that key exists, [InvalidIndex] is returned.
//
// Seek acquires an exclusive write lock to update the cursor atomically.
func (mdl *Model) Seek(pos any) error {
mdl.mux.Lock()
defer mdl.mux.Unlock()
// List model
if LIST == mdl.GetType() {
idx, err := cast.ToE[int](pos)
if err != nil {
return errors.WrapE(InvalidIndexType, errors.Errorf("position '%v' must be an integer", pos))
}
if idx < 0 {
return errors.WrapE(InvalidIndex, errors.Errorf("invalid index '%d'", idx))
}
if idx >= len(mdl.data) {
return errors.WrapE(InvalidIndex, errors.Errorf("the specified position '%d' is beyond the end of the data", idx))
}
mdl.pos = idx
return nil
}
// Hash model
hashKey := cast.To[string](pos)
if idx, ok := mdl.hashIdx[hashKey]; ok {
mdl.pos = idx
return nil
}
return errors.WrapE(InvalidIndex, errors.Errorf("the specified position '%s' does not exist", hashKey))
}