From 1a82ecb2ca20a8f88316d4555ce3c14b03cc3b17 Mon Sep 17 00:00:00 2001 From: Arpad Ryszka Date: Tue, 9 Jun 2026 21:04:04 +0200 Subject: [PATCH] create arena structure --- arena.go | 352 ++++++++++++++ arena_test.go | 1242 ++++++++++++++++++++++++++++++++++++++++++++++++ choice.go | 1 - mmlexp_test.go | 6 - pool.go | 25 + syntaxhead.go | 1 - 6 files changed, 1619 insertions(+), 8 deletions(-) create mode 100644 arena.go create mode 100644 arena_test.go create mode 100644 pool.go diff --git a/arena.go b/arena.go new file mode 100644 index 0000000..e957e8f --- /dev/null +++ b/arena.go @@ -0,0 +1,352 @@ +package treerack + +type arena struct { + + // indicates fixed size pages from the pool + f bool + + // tolerates pages of different size, but expects the page size to be min w + 1 + p *pool[[]int] + + // the max size of the stored data blocks. Expected to be larger than 0 + w int + + // pages storing the cursors for the indexes. The cursors point to the position of the first record + // for an index in a. The total range of indexes can be larger then the sum of the page lengths, + // depending on the offset. Initialized to -1, which means a cursor not pointing to a data item + c [][]int + + // pages storing the records. Every record belongs to an index in c. The size of a record is w + // + 1, where w fields represent the data, and one field hold the cursor pointing to the next record + // belonging to the same index. The cursors point to the cursor position, the data fields are before the + // cursor position. This means 0 is not a valid value for a cursor, and represents an empty record + // placeholder. The pages in a are initialized to 0. Records are not split between pages + a [][]int + + // virtual size of c. The actual size is l - o + l int + + // offset in between the total virtual range of the indexes and the first index available in c. By + // default it is 0, until one or more pages are removed from the beginning of c as a result of prune. + // All the other indicators and the input indexes are interpreted for the total virtual range of the + // arena + o int + + // available size for records. The virtual total length of the pages in a starting from the original + // zero. + m int + + // virutal total size occupied by records. Effectively, the next available position in a, unless s >= m - w - + // 1 + s int + + // total size of pages trimmed from the arena + t int +} + +func newArena(w int, p *pool[[]int], f bool) *arena { + return &arena{ + f: f, + p: p, + w: w, + } +} + +func (a *arena) findPosition(pages [][]int, p int) ([]int, int) { + if a.f { + if len(pages) == 0 { + return nil, -1 + } + + s := len(pages[0]) + if p >= s*len(pages) { + return nil, -1 + } + + return pages[p/s], p % s + } + + for i := 0; i < len(pages); i++ { + if p < len(pages[i]) { + return pages[i], p + } + + p -= len(pages[i]) + } + + return nil, -1 +} + +func matchValues(v, q []int) bool { + for i := 0; i < len(q); i++ { + if v[i] != q[i] { + return false + } + } + + return true +} + +func (a *arena) cursor(index int) *int { + index -= a.o + if index < 0 { + return nil + } + + p, i := a.findPosition(a.c, index) + if i < 0 { + return nil + } + + return &p[i] +} + +func (a *arena) stepCursor(c *int) *int { + p, i := a.findPosition(a.a, *c-a.t) + return &p[i] +} + +func (a *arena) recordAt(c int) []int { + p, i := a.findPosition(a.a, c-a.t) + return p[i-a.w : i+1] +} + +func (a *arena) allocCursor(index int) { + for index >= a.l { + p := a.p.get() + for i := range p { + p[i] = -1 + } + + a.c = append(a.c, p) + a.l += len(p) + } +} + +func (a *arena) allocArena() { + if a.s+a.w+1 <= a.m { + return + } + + p := a.p.get() + for i := range p { + p[i] = 0 + } + + a.a = append(a.a, p) + a.m += len(p) +} + +func (a *arena) append(c int, v ...int) int { + p := a.a[len(a.a)-1] + i := a.s - a.m + len(p) + if i < 0 { + i = 0 + a.s = a.m - len(p) + } + + copy(p[i:i+a.w], v) + a.s += a.w + 1 + p[i+a.w] = c + return a.s - 1 +} + +func (a *arena) clearCursor(c *int, q ...int) { + if len(q) > a.w { + return + } + + for c != nil && *c > 0 { + r := a.recordAt(*c) + if matchValues(r[:len(r)-1], q) { + *c, r[a.w] = r[a.w], 0 + continue + } + + c = a.stepCursor(c) + } +} + +func (a *arena) lastActivePosition(p []int) int { + for i := len(p) - len(p)%(a.w+1) - 1; i > 0; i -= a.w + 1 { + if p[i] == 0 { + continue + } + + return i + } + + return -1 +} + +func (a *arena) trim() { + for { + if len(a.a) == 0 { + break + } + + if a.lastActivePosition(a.a[0]) > 0 { + break + } + + a.p.put(a.a[0]) + a.t += len(a.a[0]) + a.a = a.a[1:] + } + + for { + if len(a.a) == 0 { + break + } + + last := len(a.a) - 1 + p := a.lastActivePosition(a.a[last]) + if p > 0 { + a.s = a.m - len(a.a[last]) + p + 1 + break + } + + a.p.put(a.a[last]) + a.m -= len(a.a[last]) + a.s = a.m + a.a = a.a[:last] + } +} + +func (a *arena) len() int { + return a.l +} + +func (a *arena) has(index int, q ...int) bool { + if len(q) == 0 { + return true + } + + if len(q) > a.w { + return false + } + + for c := a.cursor(index); c != nil && *c > 0; c = a.stepCursor(c) { + r := a.recordAt(*c) + if matchValues(r[:len(r)-1], q) { + return true + } + } + + return false +} + +// it does not copy the data +func (a *arena) get(index int, q ...int) [][]int { + if len(q) > a.w { + return nil + } + + var values [][]int + for c := a.cursor(index); c != nil && *c > 0; c = a.stepCursor(c) { + r := a.recordAt(*c) + v := r[:len(r)-1] + if matchValues(v, q) { + values = append(values, v) + } + } + + return values +} + +// it does not copy the data +func (a *arena) set(index int, v ...int) { + if a.has(index, v...) { + return + } + + a.allocCursor(index) + c := a.cursor(index) + + // index can be in pruned range + if c == nil { + return + } + + a.allocArena() + *c = a.append(*c, v...) +} + +func (a *arena) del(index int, q ...int) { + c := a.cursor(index) + a.clearCursor(c, q...) + a.trim() +} + +func (a *arena) prune(from, to int) { + var p int + for _, c := range a.c { + if p+len(c) <= from-a.o { + p += len(c) + continue + } + + if p >= to-a.o { + break + } + + for i := max(0, from-a.o-p); i < min(len(c), to-a.o-p); i++ { + a.clearCursor(&c[i]) + } + + p += len(c) + } + + for { + if len(a.c) == 0 { + break + } + + var used bool + c := a.c[0] + if a.o+len(c) > to { + break + } + + for _, ci := range c { + if ci > 0 { + used = true + break + } + } + + if used { + break + } + + a.o += len(c) + a.p.put(c) + a.c = a.c[1:] + } + + for { + if len(a.c) == 0 { + break + } + + var used bool + last := len(a.c) - 1 + c := a.c[last] + for _, ci := range c { + if ci > 0 { + used = true + break + } + } + + if used { + break + } + + a.l -= len(c) + a.p.put(c) + a.c = a.c[:last] + } + + a.trim() +} diff --git a/arena_test.go b/arena_test.go new file mode 100644 index 0000000..ead0983 --- /dev/null +++ b/arena_test.go @@ -0,0 +1,1242 @@ +package treerack + +import "testing" + +func veq(a [][]int, b [][]int) bool { + if len(a) != len(b) { + return false + } + + for i := range a { + if len(a[i]) != len(b[i]) { + return false + } + + for j := range a[i] { + if a[i][j] != b[i][j] { + return false + } + } + } + + return true +} + +func TestArena(t *testing.T) { + test := func(fixedPage bool) func(*testing.T) { + return func(t *testing.T) { + t.Run("len", func(t *testing.T) { + t.Run("empty", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(2, p, fixedPage) + if a.len() != 0 { + t.Fatal(a.len()) + } + }) + + t.Run("length", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(2, p, fixedPage) + a.set(42, 1, 2) + if a.len() != 1<<6 { + t.Fatal(a.len()) + } + }) + + t.Run("length over multiple pages", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(2, p, fixedPage) + a.set(84, 1, 2) + if a.len() != 1<<7 { + t.Fatal(a.len()) + } + }) + }) + + t.Run("has", func(t *testing.T) { + t.Run("empty query", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(2, p, fixedPage) + if !a.has(42) { + t.Fatal() + } + }) + + t.Run("query too long", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(2, p, fixedPage) + a.set(42, 1, 2) + if a.has(42, 1, 2, 3) { + t.Fatal() + } + }) + + t.Run("no values at index", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(2, p, fixedPage) + a.set(84, 1, 2) + if a.has(42, 1, 2) { + t.Fatal() + } + }) + + t.Run("query does not match single", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(2, p, fixedPage) + a.set(42, 1, 2) + a.set(42, 1, 3) + if a.has(42, 1, 1) { + t.Fatal() + } + }) + + t.Run("query does not match multiple", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(2, p, fixedPage) + a.set(42, 1, 2) + a.set(42, 1, 3) + if a.has(42, 2) { + t.Fatal() + } + }) + + t.Run("match single", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(2, p, fixedPage) + a.set(42, 1, 2) + a.set(42, 1, 3) + if !a.has(42, 1, 2) { + t.Fatal() + } + }) + + t.Run("match multiple", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(2, p, fixedPage) + a.set(42, 1, 2) + a.set(42, 1, 3) + if !a.has(42, 1) { + t.Fatal() + } + }) + + t.Run("after trim start", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 4) }) + a := newArena(2, p, fixedPage) + a.set(3, 1, 2) + a.set(3, 1, 3) + a.del(3, 1, 2) + if !a.has(3, 1) { + t.Fatal() + } + }) + + t.Run("empty", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(2, p, fixedPage) + if a.has(3, 1, 2) { + t.Fatal() + } + }) + + t.Run("negative index", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(2, p, fixedPage) + a.set(3, 1, 2) + if a.has(-3, 1, 2) { + t.Fatal() + } + }) + + t.Run("beyond available range", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(2, p, fixedPage) + a.set(3, 1, 2) + if a.has(84, 1, 2) { + t.Fatal() + } + }) + }) + + t.Run("get", func(t *testing.T) { + t.Run("query too long", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(2, p, fixedPage) + a.set(42, 1, 2) + a.set(42, 1, 3) + r := a.get(42, 1, 2, 3) + if len(r) != 0 { + t.Fatal(r) + } + }) + + t.Run("zero query no items at index", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(2, p, fixedPage) + a.set(84, 1, 2) + a.set(84, 1, 3) + r := a.get(42) + if len(r) != 0 { + t.Fatal(r) + } + }) + + t.Run("zero query one item", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(2, p, fixedPage) + a.set(42, 1, 2) + r := a.get(42) + if !veq(r, [][]int{{1, 2}}) { + t.Fatal(r) + } + }) + + t.Run("zero query multiple items", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(2, p, fixedPage) + a.set(42, 1, 2) + a.set(42, 1, 3) + r := a.get(42) + if !veq(r, [][]int{{1, 3}, {1, 2}}) { + t.Fatal(r) + } + }) + + t.Run("query does not match single", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(2, p, fixedPage) + a.set(42, 1, 2) + r := a.get(42, 1, 1) + if len(r) != 0 { + t.Fatal(r) + } + }) + + t.Run("query does not match multiple", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(2, p, fixedPage) + a.set(42, 1, 2) + a.set(42, 1, 3) + r := a.get(42, 1, 1) + if len(r) != 0 { + t.Fatal(r) + } + }) + + t.Run("match single", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(2, p, fixedPage) + a.set(42, 1, 2) + a.set(42, 1, 3) + r := a.get(42, 1, 2) + if !veq(r, [][]int{{1, 2}}) { + t.Fatal(r) + } + }) + + t.Run("match multiple", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(2, p, fixedPage) + a.set(42, 1, 2) + a.set(42, 1, 3) + r := a.get(42, 1) + if !veq(r, [][]int{{1, 3}, {1, 2}}) { + t.Fatal(r) + } + }) + + t.Run("after trim start", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 4) }) + a := newArena(2, p, fixedPage) + a.set(3, 1, 2) + a.set(3, 1, 3) + a.del(3, 1, 2) + r := a.get(3, 1) + if !veq(r, [][]int{{1, 3}}) { + t.Fatal(r) + } + }) + + t.Run("empty", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(2, p, fixedPage) + r := a.get(3, 1, 2) + if len(r) != 0 { + t.Fatal() + } + }) + + t.Run("negative index", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(2, p, fixedPage) + a.set(3, 1, 2) + r := a.get(-3, 1, 2) + if len(r) != 0 { + t.Fatal() + } + }) + + t.Run("beyond available range", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(2, p, fixedPage) + a.set(3, 1, 2) + r := a.get(84, 1, 2) + if len(r) != 0 { + t.Fatal() + } + }) + }) + + t.Run("set", func(t *testing.T) { + t.Run("alloc from zero", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(2, p, fixedPage) + a.set(42, 1, 2) + r := a.get(42) + if !veq(r, [][]int{{1, 2}}) { + t.Fatal(r) + } + }) + + t.Run("alloc multiple from zero", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(2, p, fixedPage) + a.set(84, 1, 2) + r := a.get(84) + if !veq(r, [][]int{{1, 2}}) { + t.Fatal(r) + } + }) + + t.Run("alloc", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(2, p, fixedPage) + a.set(42, 1, 2) + r := a.get(42) + if !veq(r, [][]int{{1, 2}}) { + t.Fatal(r) + } + + a.set(84, 1, 3) + r = a.get(42) + if !veq(r, [][]int{{1, 2}}) { + t.Fatal(r) + } + + r = a.get(84) + if !veq(r, [][]int{{1, 3}}) { + t.Fatal(r) + } + }) + + t.Run("same value", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(2, p, fixedPage) + a.set(3, 1, 2) + a.set(3, 1, 2) + r := a.get(3) + if !veq(r, [][]int{{1, 2}}) { + t.Fatal(r) + } + }) + + t.Run("alloc multiple", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(2, p, fixedPage) + a.set(42, 1, 2) + r := a.get(42) + if !veq(r, [][]int{{1, 2}}) { + t.Fatal(r) + } + + a.set(160, 1, 3) + r = a.get(42) + if !veq(r, [][]int{{1, 2}}) { + t.Fatal(r) + } + + r = a.get(160) + if !veq(r, [][]int{{1, 3}}) { + t.Fatal(r) + } + }) + + t.Run("do not split records", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 8) }) + a := newArena(2, p, fixedPage) + a.set(0, 1, 2) + r := a.get(0) + if !veq(r, [][]int{{1, 2}}) { + t.Fatal(r) + } + + a.set(1, 1, 3) + r = a.get(0) + if !veq(r, [][]int{{1, 2}}) { + t.Fatal(r) + } + + r = a.get(1) + if !veq(r, [][]int{{1, 3}}) { + t.Fatal(r) + } + }) + + t.Run("after trim start", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 4) }) + a := newArena(2, p, fixedPage) + a.set(1, 1, 2) + a.set(1, 1, 3) + a.del(1, 1, 2) + a.set(1, 1, 4) + r := a.get(1) + if !veq(r, [][]int{{1, 4}, {1, 3}}) { + t.Fatal(r) + } + }) + + t.Run("negative index", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(2, p, fixedPage) + a.set(-3, 1, 2) + r := a.get(-3, 1, 2) + if len(r) != 0 { + t.Fatal() + } + }) + }) + + t.Run("del", func(t *testing.T) { + t.Run("query too long", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(3, p, fixedPage) + a.set(42, 1, 2, 3) + a.del(42, 1, 2, 3, 4) + r := a.get(42) + if !veq(r, [][]int{{1, 2, 3}}) { + t.Fatal(r) + } + }) + + t.Run("empty", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(3, p, fixedPage) + a.del(42, 1, 2, 3) + r := a.get(42) + if len(r) != 0 { + t.Fatal(r) + } + }) + + t.Run("empty at index", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(3, p, fixedPage) + a.set(42, 1, 2, 3) + a.del(84, 1, 2, 3) + r := a.get(42) + if !veq(r, [][]int{{1, 2, 3}}) { + t.Fatal(r) + } + }) + + t.Run("no match", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(3, p, fixedPage) + a.set(42, 1, 2, 3) + a.del(42, 1, 2, 4) + r := a.get(42) + if !veq(r, [][]int{{1, 2, 3}}) { + t.Fatal(r) + } + }) + + t.Run("match single", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(3, p, fixedPage) + a.set(42, 1, 2, 3) + a.set(42, 1, 2, 4) + a.del(42, 1, 2, 3) + r := a.get(42) + if !veq(r, [][]int{{1, 2, 4}}) { + t.Fatal(r) + } + }) + + t.Run("match multiple", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(3, p, fixedPage) + a.set(42, 1, 2, 3) + a.set(42, 1, 2, 4) + a.set(42, 2, 2, 4) + a.del(42, 1, 2) + r := a.get(42) + if !veq(r, [][]int{{2, 2, 4}}) { + t.Fatal(r) + } + }) + + t.Run("match all", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(3, p, fixedPage) + a.set(42, 1, 2, 3) + a.set(42, 1, 2, 4) + a.set(42, 2, 2, 4) + a.del(42) + r := a.get(42) + if len(r) != 0 { + t.Fatal(r) + } + }) + + t.Run("trim start", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 4) }) + a := newArena(2, p, fixedPage) + a.set(3, 1, 1) + a.set(3, 1, 2) + a.set(3, 2, 1) + if len(a.a) != 3 { + t.Fatal(a.a) + } + + a.del(3, 1, 1) + r := a.get(3) + if !veq(r, [][]int{{2, 1}, {1, 2}}) { + t.Fatal(r) + } + + if len(a.a) != 2 { + t.Fatal(len(a.a)) + } + }) + + t.Run("trim end", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 4) }) + a := newArena(2, p, fixedPage) + a.set(3, 1, 1) + a.set(3, 1, 2) + a.set(3, 2, 1) + if len(a.a) != 3 { + t.Fatal(a.a) + } + + a.del(3, 2, 1) + r := a.get(3) + if !veq(r, [][]int{{1, 2}, {1, 1}}) { + t.Fatal(r) + } + + if len(a.a) != 2 { + t.Fatal(len(a.a)) + } + }) + + t.Run("trim start and end", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 4) }) + a := newArena(2, p, fixedPage) + a.set(3, 1, 1) + a.set(3, 2, 1) + a.set(3, 1, 2) + if len(a.a) != 3 { + t.Fatal(a.a) + } + + a.del(3, 1) + r := a.get(3) + if !veq(r, [][]int{{2, 1}}) { + t.Fatal(r) + } + + if len(a.a) != 1 { + t.Fatal(len(a.a)) + } + }) + + t.Run("trim all", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 4) }) + a := newArena(2, p, fixedPage) + a.set(3, 1, 1) + a.set(3, 2, 1) + a.set(3, 1, 2) + if len(a.a) != 3 { + t.Fatal(a.a) + } + + a.del(3) + r := a.get(3) + if len(r) != 0 { + t.Fatal(r) + } + + if len(a.a) != 0 { + t.Fatal(len(a.a)) + } + }) + + t.Run("after trim start", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 4) }) + a := newArena(2, p, fixedPage) + a.set(3, 1, 1) + a.set(3, 2, 1) + a.set(3, 1, 2) + a.del(3, 1, 1) + a.del(3, 1, 2) + r := a.get(3) + if !veq(r, [][]int{{2, 1}}) { + t.Fatal(r) + } + }) + + t.Run("empty", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(2, p, fixedPage) + a.del(3, 1, 2) + r := a.get(3, 1, 2) + if len(r) != 0 { + t.Fatal() + } + }) + + t.Run("negative index", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(2, p, fixedPage) + a.set(3, 1, 2) + a.del(-3, 1, 2) + r := a.get(3, 1, 2) + if !veq(r, [][]int{{1, 2}}) { + t.Fatal() + } + }) + + t.Run("beyond available range", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(2, p, fixedPage) + a.set(3, 1, 2) + a.del(84, 1, 2) + r := a.get(3) + if !veq(r, [][]int{{1, 2}}) { + t.Fatal() + } + }) + }) + + t.Run("prune", func(t *testing.T) { + t.Run("empty", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 8) }) + a := newArena(2, p, fixedPage) + a.prune(3, 72) + if a.o != 0 || a.l != 0 { + t.Fatal(a.o, a.l) + } + + r := a.get(3) + if len(r) != 0 { + t.Fatal(r) + } + }) + + t.Run("zero range", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 8) }) + a := newArena(2, p, fixedPage) + for i := 0; i < 12; i++ { + a.set(i, 1, i) + } + + a.prune(3, 3) + for i := 0; i < 12; i++ { + r := a.get(i) + if len(r) != 1 || len(r[0]) != 2 { + t.Fatal(r) + } + } + }) + + t.Run("zero range below available", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 8) }) + a := newArena(2, p, fixedPage) + for i := 0; i < 12; i++ { + a.set(i, 1, i) + } + + a.prune(-3, -3) + for i := 0; i < 12; i++ { + r := a.get(i) + if len(r) != 1 || len(r[0]) != 2 { + t.Fatal(r) + } + } + }) + + t.Run("zero range above available", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 8) }) + a := newArena(2, p, fixedPage) + for i := 0; i < 12; i++ { + a.set(i, 1, i) + } + + a.prune(a.len()+3, a.len()+3) + for i := 0; i < 12; i++ { + r := a.get(i) + if len(r) != 1 || len(r[0]) != 2 { + t.Fatal(r) + } + } + }) + + t.Run("invalid range", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 8) }) + a := newArena(2, p, fixedPage) + for i := 0; i < 12; i++ { + a.set(i, 1, i) + } + + a.prune(3, -3) + for i := 0; i < 12; i++ { + r := a.get(i) + if len(r) != 1 || len(r[0]) != 2 { + t.Fatal(r) + } + } + }) + + t.Run("start", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 8) }) + a := newArena(2, p, fixedPage) + for i := 0; i < 12; i++ { + a.set(i, 1, i) + } + + a.prune(0, 9) + if a.o != 8 || a.l != 16 { + t.Fatal(a.o, a.l) + } + + for i := 0; i < 9; i++ { + r := a.get(i) + if len(r) != 0 { + t.Fatal(r) + } + } + + for i := 9; i < 12; i++ { + r := a.get(i) + if len(r) != 1 || len(r[0]) != 2 { + t.Fatal(r) + } + } + }) + + t.Run("start from negative range", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 8) }) + a := newArena(2, p, fixedPage) + for i := 0; i < 12; i++ { + a.set(i, 1, i) + } + + a.prune(-3, 9) + if a.o != 8 || a.l != 16 { + t.Fatal(a.o, a.l) + } + + for i := 0; i < 9; i++ { + r := a.get(i) + if len(r) != 0 { + t.Fatal(r) + } + } + + for i := 9; i < 12; i++ { + r := a.get(i) + if len(r) != 1 || len(r[0]) != 2 { + t.Fatal(r) + } + } + }) + + t.Run("end", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 8) }) + a := newArena(2, p, fixedPage) + for i := 0; i < 12; i++ { + a.set(i, 1, i) + } + + a.prune(3, a.len()) + if a.o != 0 || a.l != 8 { + t.Fatal(a.o, a.l) + } + + for i := 0; i < 3; i++ { + r := a.get(i) + if len(r) != 1 || len(r[0]) != 2 { + t.Fatal(r) + } + } + + for i := 3; i < 12; i++ { + r := a.get(i) + if len(r) != 0 { + t.Fatal(r) + } + } + }) + + t.Run("end without dropping a page", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 8) }) + a := newArena(2, p, fixedPage) + for i := 0; i < 12; i++ { + a.set(i, 1, i) + } + + a.prune(9, a.len()) + if a.o != 0 || a.l != 16 { + t.Fatal(a.o, a.l) + } + + for i := 0; i < 9; i++ { + r := a.get(i) + if len(r) != 1 || len(r[0]) != 2 { + t.Fatal(r) + } + } + + for i := 9; i < 12; i++ { + r := a.get(i) + if len(r) != 0 { + t.Fatal(r) + } + } + }) + + t.Run("end to out of range", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 8) }) + a := newArena(2, p, fixedPage) + for i := 0; i < 12; i++ { + a.set(i, 1, i) + } + + a.prune(3, a.len()+3) + if a.o != 0 || a.l != 8 { + t.Fatal(a.o, a.l) + } + + for i := 0; i < 3; i++ { + r := a.get(i) + if len(r) != 1 || len(r[0]) != 2 { + t.Fatal(r) + } + } + + for i := 3; i < 12; i++ { + r := a.get(i) + if len(r) != 0 { + t.Fatal(r) + } + } + }) + + t.Run("middle", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 8) }) + a := newArena(2, p, fixedPage) + for i := 0; i < 12; i++ { + a.set(i, 1, i) + } + + a.prune(3, 9) + if a.o != 0 || a.l != 16 { + t.Fatal(a.o, a.l) + } + + for i := 0; i < 3; i++ { + r := a.get(i) + if len(r) != 1 || len(r[0]) != 2 { + t.Fatal(r) + } + } + + for i := 3; i < 9; i++ { + r := a.get(i) + if len(r) != 0 { + t.Fatal(r) + } + } + + for i := 9; i < 12; i++ { + r := a.get(i) + if len(r) != 1 || len(r[0]) != 2 { + t.Fatal(r) + } + } + }) + + t.Run("multiple pages from start", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 8) }) + a := newArena(2, p, fixedPage) + for i := 0; i < 84; i++ { + a.set(i, 1, i) + } + + a.prune(0, 36) + if a.o != 32 || a.l != 88 { + t.Fatal(a.o, a.l) + } + + for i := 0; i < 36; i++ { + r := a.get(i) + if len(r) != 0 { + t.Fatal(r) + } + } + + for i := 36; i < 84; i++ { + r := a.get(i) + if len(r) != 1 || len(r[0]) != 2 { + t.Fatal(r) + } + } + }) + + t.Run("end", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 8) }) + a := newArena(2, p, fixedPage) + for i := 0; i < 84; i++ { + a.set(i, 1, i) + } + + a.prune(36, a.len()) + if a.o != 0 || a.l != 40 { + t.Fatal(a.o, a.l) + } + + for i := 0; i < 36; i++ { + r := a.get(i) + if len(r) != 1 || len(r[0]) != 2 { + t.Fatal(r) + } + } + + for i := 36; i < 84; i++ { + r := a.get(i) + if len(r) != 0 { + t.Fatal(r) + } + } + }) + + t.Run("middle", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 8) }) + a := newArena(2, p, fixedPage) + for i := 0; i < 84; i++ { + a.set(i, 1, i) + } + + a.prune(36, 54) + if a.o != 0 || a.l != 88 { + t.Fatal(a.o, a.l) + } + + for i := 0; i < 36; i++ { + r := a.get(i) + if len(r) != 1 || len(r[0]) != 2 { + t.Fatal(r) + } + } + + for i := 36; i < 54; i++ { + r := a.get(i) + if len(r) != 0 { + t.Fatal(r) + } + } + + for i := 54; i < 84; i++ { + r := a.get(i) + if len(r) != 1 || len(r[0]) != 2 { + t.Fatal(r) + } + } + }) + + t.Run("again", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 8) }) + a := newArena(2, p, fixedPage) + for i := 0; i < 84; i++ { + a.set(i, 1, i) + } + + a.prune(0, 36) + a.prune(9, 42) + if a.o != 40 || a.l != 88 { + t.Fatal(a.o, a.l) + } + + for i := 0; i < 42; i++ { + r := a.get(i) + if len(r) != 0 { + t.Fatal(r) + } + } + + for i := 42; i < 84; i++ { + r := a.get(i) + if len(r) != 1 || len(r[0]) != 2 { + t.Fatal(r) + } + } + }) + + t.Run("again within pruned range", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 8) }) + a := newArena(2, p, fixedPage) + for i := 0; i < 84; i++ { + a.set(i, 1, i) + } + + a.prune(0, 36) + a.prune(9, 27) + if a.o != 32 || a.l != 88 { + t.Fatal(a.o, a.l) + } + + for i := 0; i < 36; i++ { + r := a.get(i) + if len(r) != 0 { + t.Fatal(r) + } + } + + for i := 36; i < 84; i++ { + r := a.get(i) + if len(r) != 1 || len(r[0]) != 2 { + t.Fatal(r) + } + } + }) + + t.Run("ops after pruned start", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 8) }) + a := newArena(2, p, fixedPage) + for i := 0; i < 84; i++ { + a.set(i, 1, i) + } + + a.prune(0, 36) + if a.len() != 88 { + t.Fatal(a.len()) + } + + if a.has(18, 1) { + t.Fatal() + } + + if !a.has(42, 1) { + t.Fatal() + } + + r := a.get(18) + if len(r) != 0 { + t.Fatal(r) + } + + r = a.get(42) + if !veq(r, [][]int{{1, 42}}) { + t.Fatal(r) + } + + a.set(18, 1, 18) + r = a.get(18) + if len(r) != 0 { + t.Fatal(r) + } + + a.set(42, 1, 24) + r = a.get(42) + if !veq(r, [][]int{{1, 24}, {1, 42}}) { + t.Fatal(r) + } + + a.del(18, 1, 18) + r = a.get(18) + if len(r) != 0 { + t.Fatal(r) + } + + a.del(42, 1) + r = a.get(42) + if len(r) != 0 { + t.Fatal(r) + } + }) + + t.Run("ops after pruned end", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 8) }) + a := newArena(2, p, fixedPage) + for i := 0; i < 84; i++ { + a.set(i, 1, i) + } + + a.prune(54, a.len()) + if a.len() != 56 { + t.Fatal(a.len()) + } + + if a.has(72, 1) { + t.Fatal() + } + + if !a.has(42, 1) { + t.Fatal() + } + + r := a.get(72) + if len(r) != 0 { + t.Fatal(r) + } + + r = a.get(42) + if !veq(r, [][]int{{1, 42}}) { + t.Fatal(r) + } + + a.set(72, 1, 72) + r = a.get(72) + if !veq(r, [][]int{{1, 72}}) { + t.Fatal(r) + } + + a.set(42, 1, 24) + r = a.get(42) + if !veq(r, [][]int{{1, 24}, {1, 42}}) { + t.Fatal(r) + } + + a.del(72, 1, 72) + r = a.get(72) + if len(r) != 0 { + t.Fatal(r) + } + + a.del(42, 1) + r = a.get(42) + if len(r) != 0 { + t.Fatal(r) + } + }) + + t.Run("after trim start within trimmed", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 8) }) + a := newArena(2, p, fixedPage) + for i := 0; i < 84; i++ { + a.set(i, 1, i) + } + + for i := 0; i < 54; i++ { + a.del(i) + } + + a.prune(0, 36) + if a.o != 32 || a.l != 88 { + t.Fatal(a.o, a.l) + } + + for i := 0; i < 54; i++ { + r := a.get(i) + if len(r) != 0 { + t.Fatal(r) + } + } + + for i := 54; i < 84; i++ { + r := a.get(i) + if !veq(r, [][]int{{1, i}}) { + t.Fatal(r) + } + } + }) + + t.Run("after trim start beyond trimmed", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 8) }) + a := newArena(2, p, fixedPage) + for i := 0; i < 84; i++ { + a.set(i, 1, i) + } + + for i := 0; i < 36; i++ { + a.del(i) + } + + a.prune(0, 54) + if a.o != 48 || a.l != 88 { + t.Fatal(a.o, a.l) + } + + for i := 0; i < 54; i++ { + r := a.get(i) + if len(r) != 0 { + t.Fatal(r) + } + } + + for i := 54; i < 84; i++ { + r := a.get(i) + if !veq(r, [][]int{{1, i}}) { + t.Fatal(r) + } + } + }) + }) + + t.Run("example", func(t *testing.T) { + p := newPool(func() []int { return make([]int, 1<<6) }) + a := newArena(2, p, fixedPage) + v := a.get(42) + if len(v) != 0 { + t.Fatal("unexpected values") + } + + if a.has(3, 1, 2) { + t.Fatal("unexpected values") + } + + a.set(3, 1, 2, 3) + v = a.get(3) + if !veq(v, [][]int{{1, 2}}) { + t.Fatal(v) + } + + if !a.has(3, 1, 2) { + t.Fatal("values not found") + } + + v = a.get(3, 1) + if !veq(v, [][]int{{1, 2}}) { + t.Fatal(v) + } + + if !a.has(3, 1) { + t.Fatal("values not found") + } + + v = a.get(3, 1, 3) + if !veq(v, nil) { + t.Fatal(v) + } + + if a.has(3, 1, 3) { + t.Fatal("unexpected values") + } + + a.del(3, 2) + if !a.has(3, 1) { + t.Fatal("values not found") + } + + a.del(3, 1, 2) + if a.has(3, 1, 2) { + t.Fatal("unexpected values") + } + }) + } + } + + t.Run("fixed page", test(true)) + t.Run("variable page", test(false)) +} diff --git a/choice.go b/choice.go index 463787e..cfa5980 100644 --- a/choice.go +++ b/choice.go @@ -136,7 +136,6 @@ func (b *choiceBuilder) build(c *context) ([]Node, bool) { from := c.offset parsed := to > from - if parsed { c.results.dropMatchTo(c.offset, b.id, to) for _, g := range b.generalizations { diff --git a/mmlexp_test.go b/mmlexp_test.go index ca12a4f..3149831 100644 --- a/mmlexp_test.go +++ b/mmlexp_test.go @@ -2980,12 +2980,7 @@ func TestMMLExp(t *testing.T) { } func TestMMLFile(t *testing.T) { - if testing.Short() { - t.Skip() - } - const n = 180 - s, err := openSyntaxFile("doc/example/mml-exp.treerack") if err != nil { t.Error(err) @@ -2993,7 +2988,6 @@ func TestMMLFile(t *testing.T) { } s.Init() - b, err := os.ReadFile("doc/example/test.mml") if err != nil { t.Fatal(err) diff --git a/pool.go b/pool.go new file mode 100644 index 0000000..2b04b1a --- /dev/null +++ b/pool.go @@ -0,0 +1,25 @@ +package treerack + +type pool[T any] struct { + create func() T + values []T +} + +func newPool[T any](create func() T) *pool[T] { + return &pool[T]{create: create} +} + +func (p *pool[T]) get() T { + if len(p.values) == 0 { + return p.create() + } + + last := len(p.values) - 1 + v := p.values[last] + p.values = p.values[:last] + return v +} + +func (p *pool[T]) put(v T) { + p.values = append(p.values, v) +} diff --git a/syntaxhead.go b/syntaxhead.go index f740113..59d6964 100644 --- a/syntaxhead.go +++ b/syntaxhead.go @@ -216,7 +216,6 @@ func parseInput(r io.Reader, p parser, b builder, kw []parser, maxTraceLength in c.offset = 0 c.results.resetPending() - n, _ := b.build(c) return n[0], nil }