aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMel <einebeere@gmail.com>2022-08-20 02:24:20 +0000
committerMel <einebeere@gmail.com>2022-08-20 02:24:55 +0000
commit24183cd5363b96d83292df17d44fa79d8b47a89f (patch)
tree5fdf4f69f481eddfd77c50838e2fe08a76279384
parent18c6d49cc9982ad4362e82730812e1134478ea64 (diff)
downloadjinx-24183cd5363b96d83292df17d44fa79d8b47a89f.tar.zst
jinx-24183cd5363b96d83292df17d44fa79d8b47a89f.zip
Improved and more versatile RangeMap
-rw-r--r--pkg/lang/vm/code/debug.go4
-rw-r--r--pkg/libs/rangemap/rangemap.go116
-rw-r--r--pkg/libs/rangemap/rangemap_test.go115
3 files changed, 173 insertions, 62 deletions
diff --git a/pkg/lang/vm/code/debug.go b/pkg/lang/vm/code/debug.go
index b96c834..ebf0ea1 100644
--- a/pkg/lang/vm/code/debug.go
+++ b/pkg/lang/vm/code/debug.go
@@ -19,8 +19,8 @@ func (di *DebugInfo) File() string {
}
func (di *DebugInfo) PCToLine(pc int) int {
- line, ok := di.pcToLine.Get(pc)
- if !ok {
+ line := di.pcToLine.Get(pc)
+ if line == nil {
return -1
}
return *line
diff --git a/pkg/libs/rangemap/rangemap.go b/pkg/libs/rangemap/rangemap.go
index 39f97ea..650deec 100644
--- a/pkg/libs/rangemap/rangemap.go
+++ b/pkg/libs/rangemap/rangemap.go
@@ -1,40 +1,110 @@
package rangemap
+import (
+ "fmt"
+ "strings"
+)
+
type RangeMap[D any] struct {
- ranges []rangeEntry
- data []D
+ ranges []rangeEntry[D]
}
func New[D any]() RangeMap[D] {
return RangeMap[D]{
- ranges: []rangeEntry{},
+ // First entry is a sentinel.
+ ranges: []rangeEntry[D]{
+ {
+ from: 0,
+ to: -1,
+ data: nil,
+ },
+ },
}
}
-func (rm *RangeMap[D]) AppendToLast(to int, data D) bool {
- if to < 0 {
- return false
+func (rm *RangeMap[D]) AppendToLast(to int, data D) {
+ lastEntry := &rm.ranges[len(rm.ranges)-1]
+
+ rm.Set(lastEntry.from, to, data)
+}
+
+func (rm *RangeMap[D]) Set(from, to int, data D) {
+ if to < from {
+ return
}
- from := 0
- if len(rm.ranges) != 0 {
- last := rm.ranges[len(rm.ranges)-1]
- if last.to >= to {
- return false
- }
- from = last.to + 1
+ newRanges := make([]rangeEntry[D], 0, len(rm.ranges)+2)
+
+ leftIndex := rm.entry(from)
+ rightIndex := rm.entry(to)
+
+ newRanges = append(newRanges, rm.ranges[:leftIndex]...)
+
+ leftEntry := rm.ranges[leftIndex]
+ if leftEntry.to < from && leftEntry.to != -1 {
+ newRanges = append(newRanges, leftEntry)
+ } else if leftEntry.from < from {
+ newRanges = append(newRanges, rangeEntry[D]{
+ from: leftEntry.from,
+ to: from - 1,
+ data: leftEntry.data,
+ })
}
- rm.ranges = append(rm.ranges, rangeEntry{
+ newRanges = append(newRanges, rangeEntry[D]{
from: from,
to: to,
+ data: &data,
})
- rm.data = append(rm.data, data)
- return true
+ rightEntry := rm.ranges[rightIndex]
+ if rightEntry.from > to {
+ newRanges = append(newRanges, rightEntry)
+ } else if rightEntry.to > to || rightEntry.to == -1 {
+ newRanges = append(newRanges, rangeEntry[D]{
+ from: to + 1,
+ to: rightEntry.to,
+ data: rightEntry.data,
+ })
+ }
+
+ newRanges = append(newRanges, rm.ranges[rightIndex+1:]...)
+
+ rm.ranges = newRanges
+}
+
+func (rm *RangeMap[D]) Get(i int) *D {
+ return rm.ranges[rm.entry(i)].data
}
-func (rm *RangeMap[D]) Get(i int) (*D, bool) {
+func (rm *RangeMap[D]) Len() int {
+ return len(rm.ranges)
+}
+
+func (rm *RangeMap[D]) String() string {
+ ranges := make([]string, 0, len(rm.ranges))
+
+ for _, r := range rm.ranges {
+ if r.data == nil {
+ ranges = append(ranges, fmt.Sprintf("{%d..%d}", r.from, r.to))
+ } else {
+ ranges = append(ranges, fmt.Sprintf("{%d..%d: %v}", r.from, r.to, *r.data))
+ }
+ }
+
+ return fmt.Sprintf("[%s]", strings.Join(ranges, ", "))
+}
+
+func (rm *RangeMap[D]) entry(i int) int {
+ if i < 0 {
+ return 0
+ }
+
+ lastRange := rm.ranges[len(rm.ranges)-1]
+ if i >= lastRange.to && lastRange.to != -1 {
+ return len(rm.ranges) - 1
+ }
+
left := 0
right := len(rm.ranges) - 1
for left <= right {
@@ -42,20 +112,18 @@ func (rm *RangeMap[D]) Get(i int) (*D, bool) {
entry := rm.ranges[mid]
if i < entry.from {
right = mid - 1
- } else if i > entry.to {
+ } else if i > entry.to && entry.to != -1 {
left = mid + 1
} else {
- return &rm.data[mid], true
+ return mid
}
}
- return nil, false
-}
-func (rm *RangeMap[D]) Len() int {
- return len(rm.ranges)
+ panic("unreachable")
}
-type rangeEntry struct {
+type rangeEntry[D any] struct {
from int
to int
+ data *D
}
diff --git a/pkg/libs/rangemap/rangemap_test.go b/pkg/libs/rangemap/rangemap_test.go
index b120efa..deb6dc7 100644
--- a/pkg/libs/rangemap/rangemap_test.go
+++ b/pkg/libs/rangemap/rangemap_test.go
@@ -7,65 +7,108 @@ import (
"github.com/stretchr/testify/assert"
)
-func TestAppend(t *testing.T) {
+func TestAppendAndGet(t *testing.T) {
rm := rangemap.New[string]()
- assert.True(t, rm.AppendToLast(0, "a"))
- assert.True(t, rm.AppendToLast(1, "b"))
- assert.True(t, rm.AppendToLast(4, "c"))
+ rm.AppendToLast(0, "a")
+ rm.AppendToLast(1, "b")
+ rm.AppendToLast(4, "c")
+
+ assert.Equal(t, "a", *rm.Get(0))
+ assert.Equal(t, "b", *rm.Get(1))
+ assert.Equal(t, "c", *rm.Get(2))
+ assert.Equal(t, "c", *rm.Get(3))
+ assert.Equal(t, "c", *rm.Get(4))
}
-func TestAppendAndGet(t *testing.T) {
+func TestEmpty(t *testing.T) {
+ rm := rangemap.New[int]()
+
+ assert.Nil(t, rm.Get(0))
+ assert.Nil(t, rm.Get(1))
+ assert.Nil(t, rm.Get(2))
+ assert.Nil(t, rm.Get(-1))
+}
+
+func TestNoOverlap(t *testing.T) {
+ rm := rangemap.New[string]()
+
+ rm.Set(0, 2, "a")
+ rm.Set(3, 3, "b")
+ rm.Set(4, 7, "c")
+
+ assert.Equal(t, "a", *rm.Get(0))
+ assert.Equal(t, "a", *rm.Get(1))
+ assert.Equal(t, "a", *rm.Get(2))
+
+ assert.Equal(t, "b", *rm.Get(3))
+
+ assert.Equal(t, "c", *rm.Get(4))
+ assert.Equal(t, "c", *rm.Get(5))
+ assert.Equal(t, "c", *rm.Get(6))
+ assert.Equal(t, "c", *rm.Get(7))
+
+ assert.Nil(t, rm.Get(8))
+
+ assert.Equal(t, "[{0..2: a}, {3..3: b}, {4..7: c}, {8..-1}]", rm.String())
+}
+
+func TestOverlap(t *testing.T) {
rm := rangemap.New[string]()
- assert.True(t, rm.AppendToLast(0, "a"))
- assert.True(t, rm.AppendToLast(1, "b"))
- assert.True(t, rm.AppendToLast(4, "c"))
+ rm.Set(0, 2, "a")
+ rm.Set(3, 5, "b")
+ rm.Set(6, 10, "c")
- a, ok := rm.Get(0)
- assert.True(t, ok)
- assert.Equal(t, "a", *a)
+ rm.Set(4, 8, "x")
- b, ok := rm.Get(1)
- assert.True(t, ok)
- assert.Equal(t, "b", *b)
+ assert.Equal(t, "[{0..2: a}, {3..3: b}, {4..8: x}, {9..10: c}, {11..-1}]", rm.String())
+}
- cfrom, ok := rm.Get(2)
- assert.True(t, ok)
- assert.Equal(t, "c", *cfrom)
+func TestOverwrite(t *testing.T) {
+ rm := rangemap.New[string]()
- cmid, ok := rm.Get(3)
- assert.True(t, ok)
- assert.Equal(t, "c", *cmid)
+ rm.Set(0, 2, "a")
+ rm.Set(3, 5, "b")
+ rm.Set(6, 10, "c")
- cto, ok := rm.Get(4)
- assert.True(t, ok)
- assert.Equal(t, "c", *cto)
+ rm.Set(3, 9, "x")
+
+ assert.Equal(t, "[{0..2: a}, {3..9: x}, {10..10: c}, {11..-1}]", rm.String())
}
-func TestWrongAppend(t *testing.T) {
+func TestOverwriteInBetween(t *testing.T) {
rm := rangemap.New[string]()
- assert.True(t, rm.AppendToLast(0, "a"))
- assert.False(t, rm.AppendToLast(0, "b"))
+ rm.Set(0, 2, "a")
+ rm.Set(3, 4, "b")
+ rm.Set(5, 5, "c")
+ rm.Set(6, 10, "d")
+
+ rm.Set(3, 9, "x")
+
+ assert.Equal(t, "[{0..2: a}, {3..9: x}, {10..10: d}, {11..-1}]", rm.String())
}
-func TestUnknownRange(t *testing.T) {
+func TestPerfectFit(t *testing.T) {
rm := rangemap.New[string]()
- assert.True(t, rm.AppendToLast(6, "a"))
+ rm.Set(0, 2, "a")
+ rm.Set(5, 6, "b")
- a, ok := rm.Get(6)
- assert.True(t, ok)
- assert.Equal(t, "a", *a)
+ rm.Set(3, 4, "x")
- b, ok := rm.Get(7)
- assert.False(t, ok)
- assert.Nil(t, b)
+ assert.Equal(t, "[{0..2: a}, {3..4: x}, {5..6: b}, {7..-1}]", rm.String())
}
-func TestNegativeAppend(t *testing.T) {
+func TestSpaceBetween(t *testing.T) {
rm := rangemap.New[string]()
- assert.False(t, rm.AppendToLast(-1, "a"))
+ rm.Set(0, 2, "a")
+ rm.Set(9, 9, "b")
+
+ rm.Set(4, 6, "x")
+
+ // Since our search can never fail, we pad the space with a sentinel value.
+ assert.Equal(t, "[{0..2: a}, {3..3}, {4..6: x}, {7..8}, {9..9: b}, {10..-1}]", rm.String())
}