summaryrefslogtreecommitdiff
path: root/vendor/codeberg.org/gruf/go-maps
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/codeberg.org/gruf/go-maps')
-rw-r--r--vendor/codeberg.org/gruf/go-maps/LICENSE9
-rw-r--r--vendor/codeberg.org/gruf/go-maps/README.md7
-rw-r--r--vendor/codeberg.org/gruf/go-maps/common.go289
-rw-r--r--vendor/codeberg.org/gruf/go-maps/list.go154
-rw-r--r--vendor/codeberg.org/gruf/go-maps/lru.go153
-rw-r--r--vendor/codeberg.org/gruf/go-maps/ordered.go159
6 files changed, 771 insertions, 0 deletions
diff --git a/vendor/codeberg.org/gruf/go-maps/LICENSE b/vendor/codeberg.org/gruf/go-maps/LICENSE
new file mode 100644
index 000000000..e4163ae35
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-maps/LICENSE
@@ -0,0 +1,9 @@
+MIT License
+
+Copyright (c) 2022 gruf
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/vendor/codeberg.org/gruf/go-maps/README.md b/vendor/codeberg.org/gruf/go-maps/README.md
new file mode 100644
index 000000000..cf1aea644
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-maps/README.md
@@ -0,0 +1,7 @@
+# go-maps
+
+Provides a selection of hashmaps (or, "dictionaries") with features exceeding that of the default Go runtime hashmap.
+
+Includes:
+- OrderedMap
+- LRUMap
diff --git a/vendor/codeberg.org/gruf/go-maps/common.go b/vendor/codeberg.org/gruf/go-maps/common.go
new file mode 100644
index 000000000..f5877ee3a
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-maps/common.go
@@ -0,0 +1,289 @@
+package maps
+
+import (
+ "fmt"
+ "reflect"
+
+ "codeberg.org/gruf/go-byteutil"
+ "codeberg.org/gruf/go-kv"
+)
+
+// ordered provides a common ordered hashmap base, storing order in a doubly-linked list.
+type ordered[K comparable, V any] struct {
+ hmap map[K]*elem[K, V]
+ list list[K, V]
+ pool []*elem[K, V]
+ rnly bool
+}
+
+// write_check panics if map is not in a safe-state to write to.
+func (m ordered[K, V]) write_check() {
+ if m.rnly {
+ panic("map write during read loop")
+ }
+}
+
+// Has returns whether key exists in map.
+func (m *ordered[K, V]) Has(key K) bool {
+ _, ok := m.hmap[key]
+ return ok
+}
+
+// Delete will delete given key from map, returns false if not found.
+func (m *ordered[K, V]) Delete(key K) bool {
+ // Ensure safe
+ m.write_check()
+
+ // Look for existing elem
+ elem, ok := m.hmap[key]
+ if !ok {
+ return false
+ }
+
+ // Drop from list
+ m.list.Unlink(elem)
+
+ // Delete from map
+ delete(m.hmap, key)
+
+ // Return to pool
+ m.free(elem)
+
+ return true
+}
+
+// Range passes given function over the requested range of the map.
+func (m *ordered[K, V]) Range(start, length int, fn func(int, K, V)) {
+ // Disallow writes
+ m.rnly = true
+ defer func() {
+ m.rnly = false
+ }()
+
+ // Nil check
+ _ = fn
+
+ switch end := start + length; {
+ // No loop to iterate
+ case length == 0:
+ if start < 0 || (m.list.len > 0 && start >= m.list.len) {
+ panic("index out of bounds")
+ }
+
+ // Step backwards
+ case length < 0:
+ // Check loop indices are within map bounds
+ if end < -1 || start >= m.list.len || m.list.len == 0 {
+ panic("index out of bounds")
+ }
+
+ // Get starting index elem
+ elem := m.list.Index(start)
+
+ for i := start; i > end; i-- {
+ fn(i, elem.K, elem.V)
+ elem = elem.prev
+ }
+
+ // Step forwards
+ case length > 0:
+ // Check loop indices are within map bounds
+ if start < 0 || end > m.list.len || m.list.len == 0 {
+ panic("index out of bounds")
+ }
+
+ // Get starting index elem
+ elem := m.list.Index(start)
+
+ for i := start; i < end; i++ {
+ fn(i, elem.K, elem.V)
+ elem = elem.next
+ }
+ }
+}
+
+// RangeIf passes given function over the requested range of the map. Returns early on 'fn' -> false.
+func (m *ordered[K, V]) RangeIf(start, length int, fn func(int, K, V) bool) {
+ // Disallow writes
+ m.rnly = true
+ defer func() {
+ m.rnly = false
+ }()
+
+ // Nil check
+ _ = fn
+
+ switch end := start + length; {
+ // No loop to iterate
+ case length == 0:
+ if start < 0 || (m.list.len > 0 && start >= m.list.len) {
+ panic("index out of bounds")
+ }
+
+ // Step backwards
+ case length < 0:
+ // Check loop indices are within map bounds
+ if end < -1 || start >= m.list.len || m.list.len == 0 {
+ panic("index out of bounds")
+ }
+
+ // Get starting index elem
+ elem := m.list.Index(start)
+
+ for i := start; i > end; i-- {
+ if !fn(i, elem.K, elem.V) {
+ return
+ }
+ elem = elem.prev
+ }
+
+ // Step forwards
+ case length > 0:
+ // Check loop indices are within map bounds
+ if start < 0 || end > m.list.len || m.list.len == 0 {
+ panic("index out of bounds")
+ }
+
+ // Get starting index elem
+ elem := m.list.Index(start)
+
+ for i := start; i < end; i++ {
+ if !fn(i, elem.K, elem.V) {
+ return
+ }
+ elem = elem.next
+ }
+ }
+}
+
+// Truncate will truncate the map from the back by given amount, passing dropped elements to given function.
+func (m *ordered[K, V]) Truncate(sz int, fn func(K, V)) {
+ // Check size withing bounds
+ if sz > m.list.len {
+ panic("index out of bounds")
+ }
+
+ if fn == nil {
+ // move nil check out of loop
+ fn = func(K, V) {}
+ }
+
+ // Disallow writes
+ m.rnly = true
+ defer func() {
+ m.rnly = false
+ }()
+
+ for i := 0; i < sz; i++ {
+ // Pop current tail
+ elem := m.list.tail
+ m.list.Unlink(elem)
+
+ // Delete from map
+ delete(m.hmap, elem.K)
+
+ // Pass dropped to func
+ fn(elem.K, elem.V)
+
+ // Release to pool
+ m.free(elem)
+ }
+}
+
+// Len returns the current length of the map.
+func (m *ordered[K, V]) Len() int {
+ return m.list.len
+}
+
+// format implements fmt.Formatter, allowing performant string formatting of map.
+func (m *ordered[K, V]) format(rtype reflect.Type, state fmt.State, verb rune) {
+ var (
+ kvbuf byteutil.Buffer
+ field kv.Field
+ vbose bool
+ )
+
+ switch {
+ // Only handle 'v' verb
+ case verb != 'v':
+ panic("invalid verb '" + string(verb) + "' for map")
+
+ // Prefix with type when verbose
+ case state.Flag('#'):
+ state.Write([]byte(rtype.String()))
+ }
+
+ // Disallow writes
+ m.rnly = true
+ defer func() {
+ m.rnly = false
+ }()
+
+ // Write map opening brace
+ state.Write([]byte{'{'})
+
+ if m.list.len > 0 {
+ // Preallocate buffer
+ kvbuf.Guarantee(64)
+
+ // Start at index 0
+ elem := m.list.head
+
+ for i := 0; i < m.list.len-1; i++ {
+ // Append formatted key-val pair to state
+ field.K = fmt.Sprint(elem.K)
+ field.V = elem.V
+ field.AppendFormat(&kvbuf, vbose)
+ _, _ = state.Write(kvbuf.B)
+ kvbuf.Reset()
+
+ // Prepare buffer with comma separator
+ kvbuf.B = append(kvbuf.B, `, `...)
+
+ // Jump to next in list
+ elem = elem.next
+ }
+
+ // Append formatted key-val pair to state
+ field.K = fmt.Sprint(elem.K)
+ field.V = elem.V
+ field.AppendFormat(&kvbuf, vbose)
+ _, _ = state.Write(kvbuf.B)
+ }
+
+ // Write map closing brace
+ state.Write([]byte{'}'})
+}
+
+// Std returns a clone of map's data in the standard library equivalent map type.
+func (m *ordered[K, V]) Std() map[K]V {
+ std := make(map[K]V, m.list.len)
+ for _, elem := range m.hmap {
+ std[elem.K] = elem.V
+ }
+ return std
+}
+
+// alloc will acquire list element from pool, or allocate new.
+func (m *ordered[K, V]) alloc() *elem[K, V] {
+ if len(m.pool) == 0 {
+ return &elem[K, V]{}
+ }
+ idx := len(m.pool) - 1
+ elem := m.pool[idx]
+ m.pool = m.pool[:idx]
+ return elem
+}
+
+// free will reset elem fields and place back in pool.
+func (m *ordered[K, V]) free(elem *elem[K, V]) {
+ var (
+ zk K
+ zv V
+ )
+ elem.K = zk
+ elem.V = zv
+ elem.next = nil
+ elem.prev = nil
+ m.pool = append(m.pool, elem)
+}
diff --git a/vendor/codeberg.org/gruf/go-maps/list.go b/vendor/codeberg.org/gruf/go-maps/list.go
new file mode 100644
index 000000000..2d960976b
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-maps/list.go
@@ -0,0 +1,154 @@
+package maps
+
+// list is a doubly-linked list containing elemnts with key-value pairs of given generic parameter types.
+type list[K comparable, V any] struct {
+ head *elem[K, V]
+ tail *elem[K, V]
+ len int
+}
+
+// Index returns the element at index from list.
+func (l *list[K, V]) Index(idx int) *elem[K, V] {
+ switch {
+ // Idx in first half
+ case idx < l.len/2:
+ elem := l.head
+ for i := 0; i < idx; i++ {
+ elem = elem.next
+ }
+ return elem
+
+ // Index in last half
+ default:
+ elem := l.tail
+ for i := l.len - 1; i > idx; i-- {
+ elem = elem.prev
+ }
+ return elem
+ }
+}
+
+// PushFront will push the given element to the front of the list.
+func (l *list[K, V]) PushFront(elem *elem[K, V]) {
+ if l.len == 0 {
+ // Set new tail + head
+ l.head = elem
+ l.tail = elem
+
+ // Link elem to itself
+ elem.next = elem
+ elem.prev = elem
+ } else {
+ oldHead := l.head
+
+ // Link to old head
+ elem.next = oldHead
+ oldHead.prev = elem
+
+ // Link up to tail
+ elem.prev = l.tail
+ l.tail.next = elem
+
+ // Set new head
+ l.head = elem
+ }
+
+ // Incr count
+ l.len++
+}
+
+// PushBack will push the given element to the back of the list.
+func (l *list[K, V]) PushBack(elem *elem[K, V]) {
+ if l.len == 0 {
+ // Set new tail + head
+ l.head = elem
+ l.tail = elem
+
+ // Link elem to itself
+ elem.next = elem
+ elem.prev = elem
+ } else {
+ oldTail := l.tail
+
+ // Link up to head
+ elem.next = l.head
+ l.head.prev = elem
+
+ // Link to old tail
+ elem.prev = oldTail
+ oldTail.next = elem
+
+ // Set new tail
+ l.tail = elem
+ }
+
+ // Incr count
+ l.len++
+}
+
+// PopTail will pop the current tail of the list, returns nil if empty.
+func (l *list[K, V]) PopTail() *elem[K, V] {
+ if l.len == 0 {
+ return nil
+ }
+ elem := l.tail
+ l.Unlink(elem)
+ return elem
+}
+
+// Unlink will unlink the given element from the doubly-linked list chain.
+func (l *list[K, V]) Unlink(elem *elem[K, V]) {
+ if l.len <= 1 {
+ // Drop elem's links
+ elem.next = nil
+ elem.prev = nil
+
+ // Only elem in list
+ l.head = nil
+ l.tail = nil
+ l.len = 0
+ return
+ }
+
+ // Get surrounding elems
+ next := elem.next
+ prev := elem.prev
+
+ // Relink chain
+ next.prev = prev
+ prev.next = next
+
+ switch elem {
+ // Set new head
+ case l.head:
+ l.head = next
+
+ // Set new tail
+ case l.tail:
+ l.tail = prev
+ }
+
+ // Drop elem's links
+ elem.next = nil
+ elem.prev = nil
+
+ // Decr count
+ l.len--
+}
+
+// elem represents an element in a doubly-linked list.
+type elem[K comparable, V any] struct {
+ next *elem[K, V]
+ prev *elem[K, V]
+ K K
+ V V
+}
+
+// allocElems will allocate a slice of empty elements of length.
+func allocElems[K comparable, V any](i int) []*elem[K, V] {
+ s := make([]*elem[K, V], i)
+ for i := range s {
+ s[i] = &elem[K, V]{}
+ }
+ return s
+}
diff --git a/vendor/codeberg.org/gruf/go-maps/lru.go b/vendor/codeberg.org/gruf/go-maps/lru.go
new file mode 100644
index 000000000..06ea2ab10
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-maps/lru.go
@@ -0,0 +1,153 @@
+package maps
+
+import (
+ "fmt"
+ "reflect"
+)
+
+// LRU provides an ordered hashmap implementation that keeps elements ordered according to last recently used (hence, LRU).
+type LRUMap[K comparable, V any] struct {
+ ordered[K, V]
+ size int
+}
+
+// NewLRU returns a new instance of LRUMap with given initializing length and maximum capacity.
+func NewLRU[K comparable, V any](len, cap int) *LRUMap[K, V] {
+ m := new(LRUMap[K, V])
+ m.Init(len, cap)
+ return m
+}
+
+// Init will initialize this map with initial length and maximum capacity.
+func (m *LRUMap[K, V]) Init(len, cap int) {
+ if cap <= 0 {
+ panic("lru cap must be greater than zero")
+ } else if m.pool != nil {
+ panic("lru map already initialized")
+ }
+ m.ordered.hmap = make(map[K]*elem[K, V], len)
+ m.ordered.pool = allocElems[K, V](len)
+ m.size = cap
+}
+
+// Get will fetch value for given key from map, in the process pushing it to the front of the map. Returns false if not found.
+func (m *LRUMap[K, V]) Get(key K) (V, bool) {
+ if elem, ok := m.hmap[key]; ok {
+ // Ensure safe
+ m.write_check()
+
+ // Unlink elem from list
+ m.list.Unlink(elem)
+
+ // Push to front of list
+ m.list.PushFront(elem)
+
+ return elem.V, true
+ }
+ var z V // zero value
+ return z, false
+}
+
+// Add will add the given key-value pair to the map, pushing them to the front of the map. Returns false if already exists. Evicts old at maximum capacity.
+func (m *LRUMap[K, V]) Add(key K, value V) bool {
+ return m.AddWithHook(key, value, nil)
+}
+
+// AddWithHook performs .Add() but passing any evicted entry to given hook function.
+func (m *LRUMap[K, V]) AddWithHook(key K, value V, evict func(K, V)) bool {
+ // Ensure safe
+ m.write_check()
+
+ // Look for existing elem
+ elem, ok := m.hmap[key]
+ if ok {
+ return false
+ }
+
+ if m.list.len >= m.size {
+ // We're at capacity, sir!
+ // Pop current tail elem
+ elem = m.list.PopTail()
+
+ if evict != nil {
+ // Pass to evict hook
+ evict(elem.K, elem.V)
+ }
+
+ // Delete key from map
+ delete(m.hmap, elem.K)
+ } else {
+ // Allocate elem
+ elem = m.alloc()
+ }
+
+ // Set elem
+ elem.K = key
+ elem.V = value
+
+ // Add element map entry
+ m.hmap[key] = elem
+
+ // Push to front of list
+ m.list.PushFront(elem)
+ return true
+}
+
+// Set will ensure that given key-value pair exists in the map, by either adding new or updating existing, pushing them to the front of the map. Evicts old at maximum capacity.
+func (m *LRUMap[K, V]) Set(key K, value V) {
+ m.SetWithHook(key, value, nil)
+}
+
+// SetWithHook performs .Set() but passing any evicted entry to given hook function.
+func (m *LRUMap[K, V]) SetWithHook(key K, value V, evict func(K, V)) {
+ // Ensure safe
+ m.write_check()
+
+ // Look for existing elem
+ elem, ok := m.hmap[key]
+
+ if ok {
+ // Unlink elem from list
+ m.list.Unlink(elem)
+
+ // Update existing
+ elem.V = value
+ } else {
+ if m.list.len >= m.size {
+ // We're at capacity, sir!
+ // Pop current tail elem
+ elem = m.list.PopTail()
+
+ if evict != nil {
+ // Pass to evict hook
+ evict(elem.K, elem.V)
+ }
+
+ // Delete key from map
+ delete(m.hmap, elem.K)
+ } else {
+ // Allocate elem
+ elem = m.alloc()
+ }
+
+ // Set elem
+ elem.K = key
+ elem.V = value
+
+ // Add element map entry
+ m.hmap[key] = elem
+ }
+
+ // Push to front of list
+ m.list.PushFront(elem)
+}
+
+// Cap returns the maximum capacity of this LRU map.
+func (m *LRUMap[K, V]) Cap() int {
+ return m.size
+}
+
+// Format implements fmt.Formatter, allowing performant string formatting of map.
+func (m *LRUMap[K, V]) Format(state fmt.State, verb rune) {
+ m.format(reflect.TypeOf(m), state, verb)
+}
diff --git a/vendor/codeberg.org/gruf/go-maps/ordered.go b/vendor/codeberg.org/gruf/go-maps/ordered.go
new file mode 100644
index 000000000..ca8ebe8a0
--- /dev/null
+++ b/vendor/codeberg.org/gruf/go-maps/ordered.go
@@ -0,0 +1,159 @@
+package maps
+
+import (
+ "fmt"
+ "reflect"
+)
+
+// OrderedMap provides a hashmap implementation that tracks the order in which keys are added.
+type OrderedMap[K comparable, V any] struct {
+ ordered[K, V]
+}
+
+// NewOrdered returns a new instance of LRUMap with given initializing length and maximum capacity.
+func NewOrdered[K comparable, V any](len int) *OrderedMap[K, V] {
+ m := new(OrderedMap[K, V])
+ m.Init(len)
+ return m
+}
+
+// Init will initialize this map with initial length.
+func (m *OrderedMap[K, V]) Init(len int) {
+ if m.pool != nil {
+ panic("ordered map already initialized")
+ }
+ m.ordered.hmap = make(map[K]*elem[K, V], len)
+ m.ordered.pool = allocElems[K, V](len)
+}
+
+// Get will fetch value for given key from map. Returns false if not found.
+func (m *OrderedMap[K, V]) Get(key K) (V, bool) {
+ if elem, ok := m.hmap[key]; ok {
+ return elem.V, true
+ }
+ var z V // zero value
+ return z, false
+}
+
+// Add will add the given key-value pair to the map, returns false if already exists.
+func (m *OrderedMap[K, V]) Add(key K, value V) bool {
+ // Ensure safe
+ m.write_check()
+
+ // Look for existing elem
+ elem, ok := m.hmap[key]
+ if ok {
+ return false
+ }
+
+ // Allocate elem
+ elem = m.alloc()
+ elem.K = key
+ elem.V = value
+
+ // Add element map entry
+ m.hmap[key] = elem
+
+ // Push to back of list
+ m.list.PushBack(elem)
+ return true
+}
+
+// Set will ensure that given key-value pair exists in the map, by either adding new or updating existing.
+func (m *OrderedMap[K, V]) Set(key K, value V) {
+ // Ensure safe
+ m.write_check()
+
+ // Look for existing elem
+ elem, ok := m.hmap[key]
+
+ if ok {
+ // Update existing
+ elem.V = value
+ } else {
+ // Allocate elem
+ elem = m.alloc()
+ elem.K = key
+ elem.V = value
+
+ // Add element map entry
+ m.hmap[key] = elem
+
+ // Push to back of list
+ m.list.PushBack(elem)
+ }
+}
+
+// Index returns the key-value pair at index from map. Returns false if index out of range.
+func (m *OrderedMap[K, V]) Index(idx int) (K, V, bool) {
+ if idx < 0 || idx >= m.list.len {
+ var (
+ zk K
+ zv V
+ ) // zero values
+ return zk, zv, false
+ }
+ elem := m.list.Index(idx)
+ return elem.K, elem.V, true
+}
+
+// Push will insert the given key-value pair at index in the map. Panics if index out of range.
+func (m *OrderedMap[K, V]) Push(idx int, key K, value V) {
+ // Check index within bounds of map
+ if idx < 0 || idx >= m.list.len {
+ panic("index out of bounds")
+ }
+
+ // Ensure safe
+ m.write_check()
+
+ // Get element at index
+ next := m.list.Index(idx)
+
+ // Allocate new elem
+ elem := m.alloc()
+ elem.K = key
+ elem.V = value
+
+ // Add element map entry
+ m.hmap[key] = elem
+
+ // Move next forward
+ elem.next = next
+ elem.prev = next.prev
+
+ // Link up elem in chain
+ next.prev.next = elem
+ next.prev = elem
+}
+
+// Pop will remove and return the key-value pair at index in the map. Panics if index out of range.
+func (m *OrderedMap[K, V]) Pop(idx int) (K, V) {
+ // Check index within bounds of map
+ if idx < 0 || idx >= m.list.len {
+ panic("index out of bounds")
+ }
+
+ // Ensure safe
+ m.write_check()
+
+ // Get element at index
+ elem := m.list.Index(idx)
+
+ // Unlink elem from list
+ m.list.Unlink(elem)
+
+ // Get elem values
+ k := elem.K
+ v := elem.V
+
+ // Release to pool
+ m.free(elem)
+
+ return k, v
+}
+
+// Format implements fmt.Formatter, allowing performant string formatting of map.
+func (m *OrderedMap[K, V]) Format(state fmt.State, verb rune) {
+ m.format(reflect.TypeOf(m), state, verb)
+}