Skip to content

Commit 152f67d

Browse files
committed
make lexer work as an iterator
1 parent 436b8ae commit 152f67d

File tree

4 files changed

+315
-59
lines changed

4 files changed

+315
-59
lines changed

internal/ring/ring.go

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
package ring
2+
3+
// Ring is a very simple ring buffer implementation that uses a slice. The
4+
// internal slice will only grow, never shrink. When it grows, it grows in
5+
// chunks of "chunkSize" (given as argument in the [New] function). Pointer and
6+
// reference types can be safely used because memory is cleared.
7+
type Ring[T any] struct {
8+
data []T
9+
back, len, chunkSize int
10+
}
11+
12+
func New[T any](chunkSize int) *Ring[T] {
13+
if chunkSize < 1 {
14+
panic("chunkSize must be greater than zero")
15+
}
16+
return &Ring[T]{
17+
chunkSize: chunkSize,
18+
}
19+
}
20+
21+
func (r *Ring[T]) Len() int {
22+
return r.len
23+
}
24+
25+
func (r *Ring[T]) Cap() int {
26+
return len(r.data)
27+
}
28+
29+
func (r *Ring[T]) Reset() {
30+
var zero T
31+
for i := range r.data {
32+
r.data[i] = zero // clear mem, optimized by the compiler, in Go 1.21 the "clear" builtin can be used
33+
}
34+
r.back = 0
35+
r.len = 0
36+
}
37+
38+
// Nth returns the n-th oldest value (zero-based) in the ring without making
39+
// any change.
40+
func (r *Ring[T]) Nth(n int) (v T, ok bool) {
41+
if n < 0 || n >= r.len || len(r.data) == 0 {
42+
return v, false
43+
}
44+
n = (n + r.back) % len(r.data)
45+
return r.data[n], true
46+
}
47+
48+
// Dequeue returns the oldest value.
49+
func (r *Ring[T]) Dequeue() (v T, ok bool) {
50+
if r.len == 0 {
51+
return v, false
52+
}
53+
v, r.data[r.back] = r.data[r.back], v // retrieve and clear mem
54+
r.len--
55+
r.back = (r.back + 1) % len(r.data)
56+
return v, true
57+
}
58+
59+
// Enqueue adds an item to the ring.
60+
func (r *Ring[T]) Enqueue(v T) {
61+
if r.len == len(r.data) {
62+
r.grow()
63+
}
64+
writePos := (r.back + r.len) % len(r.data)
65+
r.data[writePos] = v
66+
r.len++
67+
}
68+
69+
func (r *Ring[T]) grow() {
70+
s := make([]T, len(r.data)+r.chunkSize)
71+
if r.len > 0 {
72+
chunk1 := r.back + r.len
73+
if chunk1 > len(r.data) {
74+
chunk1 = len(r.data)
75+
}
76+
copied := copy(s, r.data[r.back:chunk1])
77+
78+
if copied < r.len { // wrapped slice
79+
chunk2 := r.len - copied
80+
copy(s[copied:], r.data[:chunk2])
81+
}
82+
}
83+
r.back = 0
84+
r.data = s
85+
}

internal/ring/ring_test.go

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
package ring
2+
3+
import (
4+
"fmt"
5+
"testing"
6+
)
7+
8+
func TestRing(t *testing.T) {
9+
type op = ringOp[int]
10+
testRing(t, New[int](3),
11+
// noops on empty ring
12+
op{cap: 0, opType: opRst, value: 0, items: []int{}},
13+
op{cap: 0, opType: opDeq, value: 0, items: []int{}},
14+
15+
// basic
16+
op{cap: 3, opType: opEnq, value: 1, items: []int{1}},
17+
op{cap: 3, opType: opDeq, value: 1, items: []int{}},
18+
19+
// wrapping
20+
op{cap: 3, opType: opEnq, value: 2, items: []int{2}},
21+
op{cap: 3, opType: opEnq, value: 3, items: []int{2, 3}},
22+
op{cap: 3, opType: opEnq, value: 4, items: []int{2, 3, 4}},
23+
op{cap: 3, opType: opDeq, value: 2, items: []int{3, 4}},
24+
op{cap: 3, opType: opDeq, value: 3, items: []int{4}},
25+
op{cap: 3, opType: opDeq, value: 4, items: []int{}},
26+
27+
// resetting
28+
op{cap: 3, opType: opEnq, value: 2, items: []int{2}},
29+
op{cap: 3, opType: opRst, value: 0, items: []int{}},
30+
op{cap: 3, opType: opDeq, value: 0, items: []int{}},
31+
32+
// growing without wrapping
33+
op{cap: 3, opType: opEnq, value: 5, items: []int{5}},
34+
op{cap: 3, opType: opEnq, value: 6, items: []int{5, 6}},
35+
op{cap: 3, opType: opEnq, value: 7, items: []int{5, 6, 7}},
36+
op{cap: 6, opType: opEnq, value: 8, items: []int{5, 6, 7, 8}},
37+
op{cap: 6, opType: opRst, value: 0, items: []int{}},
38+
op{cap: 6, opType: opDeq, value: 0, items: []int{}},
39+
40+
// growing and wrapping
41+
op{cap: 6, opType: opEnq, value: 9, items: []int{9}},
42+
op{cap: 6, opType: opEnq, value: 10, items: []int{9, 10}},
43+
op{cap: 6, opType: opEnq, value: 11, items: []int{9, 10, 11}},
44+
op{cap: 6, opType: opEnq, value: 12, items: []int{9, 10, 11, 12}},
45+
op{cap: 6, opType: opEnq, value: 13, items: []int{9, 10, 11, 12, 13}},
46+
op{cap: 6, opType: opEnq, value: 14, items: []int{9, 10, 11, 12, 13, 14}},
47+
op{cap: 6, opType: opDeq, value: 9, items: []int{10, 11, 12, 13, 14}},
48+
op{cap: 6, opType: opDeq, value: 10, items: []int{11, 12, 13, 14}},
49+
op{cap: 6, opType: opEnq, value: 15, items: []int{11, 12, 13, 14, 15}},
50+
op{cap: 6, opType: opEnq, value: 16, items: []int{11, 12, 13, 14, 15, 16}},
51+
op{cap: 9, opType: opEnq, value: 17, items: []int{11, 12, 13, 14, 15, 16, 17}}, // grows wrapped
52+
op{cap: 9, opType: opDeq, value: 11, items: []int{12, 13, 14, 15, 16, 17}},
53+
op{cap: 9, opType: opDeq, value: 12, items: []int{13, 14, 15, 16, 17}},
54+
op{cap: 9, opType: opDeq, value: 13, items: []int{14, 15, 16, 17}},
55+
op{cap: 9, opType: opDeq, value: 14, items: []int{15, 16, 17}},
56+
op{cap: 9, opType: opDeq, value: 15, items: []int{16, 17}},
57+
op{cap: 9, opType: opDeq, value: 16, items: []int{17}},
58+
op{cap: 9, opType: opDeq, value: 17, items: []int{}},
59+
op{cap: 9, opType: opDeq, value: 0, items: []int{}},
60+
)
61+
62+
t.Run("should panic on invalid chunkSize", func(t *testing.T) {
63+
defer func() {
64+
if r := recover(); r == nil {
65+
t.Fatalf("should have panicked")
66+
}
67+
}()
68+
New[int](0)
69+
})
70+
}
71+
72+
const (
73+
opEnq = iota // enqueue an item
74+
opDeq // dequeue an item and an item was available
75+
opRst // reset
76+
)
77+
78+
type ringOp[T comparable] struct {
79+
cap int // expected values
80+
opType int // opEnq or opDeq
81+
value T // value to enqueue or value expected for dequeue; ignored for opRst
82+
items []T // items left
83+
}
84+
85+
func testRing[T comparable](t *testing.T, r *Ring[T], ops ...ringOp[T]) {
86+
for i, op := range ops {
87+
testOK := t.Run(fmt.Sprintf("opIndex=%v", i), func(t *testing.T) {
88+
testRingOp(t, r, op)
89+
})
90+
if !testOK {
91+
return
92+
}
93+
}
94+
}
95+
96+
func testRingOp[T comparable](t *testing.T, r *Ring[T], op ringOp[T]) {
97+
var zero T
98+
switch op.opType {
99+
case opEnq:
100+
r.Enqueue(op.value)
101+
case opDeq:
102+
shouldSucceed := r.Len() > 0
103+
v, ok := r.Dequeue()
104+
switch {
105+
case ok != shouldSucceed:
106+
t.Fatalf("should have succeeded: %v", shouldSucceed)
107+
case ok && v != op.value:
108+
t.Fatalf("expected value: %v; got: %v", op.value, v)
109+
case !ok && v != zero:
110+
t.Fatalf("expected zero value; got: %v", v)
111+
}
112+
case opRst:
113+
r.Reset()
114+
}
115+
if c := r.Cap(); c != op.cap {
116+
t.Fatalf("expected cap: %v; got: %v", op.cap, c)
117+
}
118+
if l := r.Len(); l != len(op.items) {
119+
t.Errorf("expected Len(): %v; got: %v", len(op.items), l)
120+
}
121+
var got []T
122+
for i := 0; ; i++ {
123+
v, ok := r.Nth(i)
124+
if !ok {
125+
break
126+
}
127+
got = append(got, v)
128+
}
129+
if l := len(got); l != len(op.items) {
130+
t.Errorf("expected items: %v\ngot items: %v", op.items, got)
131+
}
132+
for i := range op.items {
133+
if op.items[i] != got[i] {
134+
t.Fatalf("expected items: %v\ngot items: %v", op.items, got)
135+
}
136+
}
137+
if v, ok := r.Nth(len(op.items)); ok || v != zero {
138+
t.Fatalf("expected no more items, got: v=%v; ok=%v", v, ok)
139+
}
140+
}

0 commit comments

Comments
 (0)