sync stdlib from honey
This commit is contained in:
@@ -7,7 +7,6 @@ handles_append test {
|
||||
|
||||
try append(&list, 42)
|
||||
|
||||
try testing.expect_type(usize, list.items.len)
|
||||
try testing.expect_equal(1, list.items.len)
|
||||
try testing.expect_equal(42, list.items[0])
|
||||
}
|
||||
|
||||
+5
-5
@@ -7,11 +7,11 @@
|
||||
# Declarative and literal-only: one executable per build. List fields take an
|
||||
# address-of an array literal (`&["raylib"]`) and default to empty.
|
||||
BuildConfig :: struct {
|
||||
name []u8 # output executable name under root/build
|
||||
source []u8 # program package directory, relative to build.bro
|
||||
name []u8 # output executable name under root/build
|
||||
source []u8 # program package directory, relative to build.bro
|
||||
libraries [][]u8 = &[] # library names to link (-l)
|
||||
lib_paths [][]u8 = &[] # library search directories (-L)
|
||||
includes [][]u8 = &[] # C include directories (-I)
|
||||
defines [][]u8 = &[] # C preprocessor defines (name or name=value)
|
||||
links [][]u8 = &[] # extra linker inputs (object/source files, -framework pairs)
|
||||
includes [][]u8 = &[] # C include directories (-I)
|
||||
defines [][]u8 = &[] # C preprocessor defines (name or name=value)
|
||||
links [][]u8 = &[] # extra linker inputs (object/source files, -framework pairs)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
import "@std/meta"
|
||||
|
||||
EnumMap func($E, $V type) type {
|
||||
match typeinfo!(E) {
|
||||
.enum |info|: return struct {
|
||||
present [info.fields.len]mut bool # fixme: replace with bitset
|
||||
values [info.fields.len]mut V
|
||||
}
|
||||
else: compile_error!("EnumMap key must be an enum")
|
||||
}
|
||||
}
|
||||
|
||||
init func(
|
||||
$E, $V type,
|
||||
values meta.EnumFieldStruct(E, ?V, some!(null)),
|
||||
) EnumMap(E, V) {
|
||||
map EnumMap(E, V) = undefined
|
||||
|
||||
match typeinfo!(E) {
|
||||
.enum |info|: expand for info.fields |field, i| {
|
||||
map.present[i] = false
|
||||
|
||||
if field!(values, field.name) |value| {
|
||||
map.present[i] = true
|
||||
map.values[i] = value
|
||||
}
|
||||
}
|
||||
else: compile_error!("EnumMap key must be an enum")
|
||||
}
|
||||
|
||||
return map
|
||||
}
|
||||
|
||||
get func($E, $V type, map @EnumMap(E, V), key E) ?V {
|
||||
# fixme: linear lookup; implement an enum index/discriminant map for O(1) lookup
|
||||
match typeinfo!(E) {
|
||||
.enum |info|: expand for info.fields |field, i| {
|
||||
if key == field!(E, field.name) {
|
||||
if (map.present[i]) return map.values[i]
|
||||
return null
|
||||
}
|
||||
}
|
||||
else: compile_error!("EnumMap key must be an enum")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import "@std/mem"
|
||||
import "@std/testing"
|
||||
|
||||
TestEnum :: enum(u8) {
|
||||
ident = 3
|
||||
int = 8
|
||||
eof = 21
|
||||
}
|
||||
|
||||
handles_sparse_enum_get test {
|
||||
names EnumMap(TestEnum, []u8) = init({
|
||||
ident = "identifier",
|
||||
int = "integer",
|
||||
})
|
||||
|
||||
ident :: get(&names, TestEnum.ident)
|
||||
|
||||
try testing.expect_type(?[]u8, ident)
|
||||
try testing.expect(mem.eql("identifier", ident?))
|
||||
|
||||
eof :: get(&names, TestEnum.eof)
|
||||
|
||||
try testing.expect_type(?[]u8, eof)
|
||||
try testing.expect_equal(null, eof)
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
import "@std/mem"
|
||||
|
||||
PutError :: enum { key_exists }
|
||||
|
||||
Entry func($K, $V type) type {
|
||||
return struct {
|
||||
# hash = 0 means empty
|
||||
hash usize = 0
|
||||
key K
|
||||
value V
|
||||
}
|
||||
}
|
||||
|
||||
HashMap func(
|
||||
$K, $V type,
|
||||
$hash_key func(key K) usize,
|
||||
$keys_eql func(a, b K) bool,
|
||||
) type {
|
||||
return struct {
|
||||
entries []mut Entry(K, V)
|
||||
count usize
|
||||
allocator mem.Allocator
|
||||
}
|
||||
}
|
||||
|
||||
StringHashMap func($V type) type {
|
||||
return HashMap([]u8, V, str_hash, str_eql)
|
||||
}
|
||||
|
||||
init func(
|
||||
$K, $V type,
|
||||
$hash_key func(key K) usize,
|
||||
$keys_eql func(a, b K) bool,
|
||||
allocator mem.Allocator,
|
||||
) HashMap(K, V, hash_key, keys_eql) {
|
||||
return HashMap(K, V, hash_key, keys_eql){
|
||||
entries = mem.empty(Entry(K, V)),
|
||||
count = 0,
|
||||
allocator = allocator,
|
||||
}
|
||||
}
|
||||
|
||||
# free the entries in the hash map.
|
||||
# note: this operation invalidates the map.
|
||||
deinit func(
|
||||
$K, $V type,
|
||||
$hash_key func(key K) usize,
|
||||
$keys_eql func(a, b K) bool,
|
||||
map @HashMap(K, V, hash_key, keys_eql),
|
||||
) void { mem.free(map.allocator, map.entries) }
|
||||
|
||||
get func(
|
||||
$K, $V type,
|
||||
$hash_key func(key K) usize,
|
||||
$keys_eql func(a, b K) bool,
|
||||
map @HashMap(K, V, hash_key, keys_eql),
|
||||
key K,
|
||||
) ?V {
|
||||
if (map.count == 0) return null
|
||||
|
||||
hash :: normalize(hash_key(key))
|
||||
idx usize = hash & (map.entries.len - 1)
|
||||
|
||||
while true {
|
||||
entry :: map.entries[idx]
|
||||
if (entry.hash == 0) return null
|
||||
|
||||
if (entry.hash == hash and keys_eql(entry.key, key)) {
|
||||
return entry.value
|
||||
}
|
||||
|
||||
idx = (idx + 1) & (map.entries.len - 1)
|
||||
}
|
||||
}
|
||||
|
||||
put func(
|
||||
$K, $V type,
|
||||
$hash_key func(key K) usize,
|
||||
$keys_eql func(a, b K) bool,
|
||||
map @mut HashMap(K, V, hash_key, keys_eql),
|
||||
key K,
|
||||
value V,
|
||||
) void ! (PutError | mem.AllocError) {
|
||||
# check grow
|
||||
threshold :: map.entries.len - divtrunc!(map.entries.len, 4)
|
||||
if (map.entries.len == 0 or map.count + 1 > threshold) {
|
||||
old_entries :: map.entries
|
||||
new_size :: if (old_entries.len > 0) old_entries.len * 2 else 8
|
||||
new_entries :: try mem.alloc(Entry(K, V), map.allocator, new_size)
|
||||
|
||||
# zero new entries
|
||||
for 0..new_entries.len |i| {
|
||||
new_entries[i].hash = 0
|
||||
}
|
||||
|
||||
# move old entries
|
||||
for old_entries |entry| {
|
||||
if (entry.hash == 0) continue
|
||||
|
||||
# find an empty slot
|
||||
idx usize = entry.hash & (new_entries.len - 1)
|
||||
while new_entries[idx].hash != 0 {
|
||||
idx = (idx + 1) & (new_entries.len - 1)
|
||||
}
|
||||
|
||||
new_entries[idx] = entry
|
||||
}
|
||||
|
||||
map.entries = new_entries
|
||||
mem.free(map.allocator, old_entries)
|
||||
}
|
||||
|
||||
# put new entry
|
||||
hash :: normalize(hash_key(key))
|
||||
idx usize = hash & (map.entries.len - 1)
|
||||
|
||||
while true {
|
||||
entry :: map.entries[idx]
|
||||
if entry.hash == 0 {
|
||||
map.entries[idx] = Entry(K, V){
|
||||
hash = hash,
|
||||
key = key,
|
||||
value = value,
|
||||
}
|
||||
map.count += 1
|
||||
return
|
||||
}
|
||||
|
||||
if (entry.hash == hash and keys_eql(entry.key, key)) {
|
||||
return .key_exists
|
||||
}
|
||||
|
||||
idx = (idx + 1) & (map.entries.len - 1)
|
||||
}
|
||||
}
|
||||
|
||||
hide normalize func(hash usize) usize {
|
||||
# mapping both 0 and 1 to 1 is safe because equality resolves
|
||||
# collisions (since hash and key must both be equal).
|
||||
if (hash == 0) return 1
|
||||
return hash
|
||||
}
|
||||
|
||||
#! FNV-1a hash implementation.
|
||||
#! note: vulnerable to collision attacks.
|
||||
hide str_hash func(key []u8) usize {
|
||||
hash u32 = 2166136261 # offset basis
|
||||
prime u32 = 16777619
|
||||
|
||||
for key |byte| {
|
||||
product u64 :: u64(hash xor u32(byte)) * prime
|
||||
hash = u32(product & u64(maxval!(u32)))
|
||||
}
|
||||
|
||||
return usize(hash)
|
||||
}
|
||||
|
||||
hide str_eql func(a, b []u8) bool {
|
||||
return mem.eql(a, b)
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import "@std/mem"
|
||||
import "@std/testing"
|
||||
|
||||
handles_put_and_get test {
|
||||
map StringHashMap(u32) = init(mem.c_allocator)
|
||||
defer deinit(&map)
|
||||
|
||||
try put(&map, "key", 42)
|
||||
value :: get(&map, "key")
|
||||
|
||||
try testing.expect_type(?u32, value)
|
||||
try testing.expect_equal(42, value?)
|
||||
}
|
||||
+21
-11
@@ -41,17 +41,7 @@ eql func($T type, left, right []T) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
hide empty_storage [1]mut u64 = [0]
|
||||
|
||||
hide empty_slice func($T type, count usize) []mut T {
|
||||
pointer *mut T :: ptrcast!(T, (&empty_storage).ptr)
|
||||
return pointer[..count]
|
||||
}
|
||||
|
||||
empty func($T type) []mut T {
|
||||
return empty_slice(T, 0)
|
||||
}
|
||||
|
||||
# allocate memory for a slice of type `T` with `count` elements.
|
||||
alloc func($T type, allocator Allocator, count usize) []mut T ! AllocError {
|
||||
if count == 0 {
|
||||
return empty_slice(T, 0)
|
||||
@@ -73,10 +63,14 @@ alloc func($T type, allocator Allocator, count usize) []mut T ! AllocError {
|
||||
return .out_of_memory
|
||||
}
|
||||
|
||||
# reallocate memory for a slice of type `T` with `new_count` elements.
|
||||
# reallocating with `new_count == 0` will free the memory and return an empty slice.
|
||||
# note: memory must be reallocated with the same allocator that was used to allocate it.
|
||||
realloc func($T type, allocator Allocator, memory []mut T, new_count usize) []mut T ! AllocError {
|
||||
if new_count == memory.len {
|
||||
return memory
|
||||
}
|
||||
|
||||
if new_count == 0 {
|
||||
free(allocator, memory)
|
||||
return empty_slice(T, 0)
|
||||
@@ -107,9 +101,12 @@ realloc func($T type, allocator Allocator, memory []mut T, new_count usize) []mu
|
||||
pointer *mut T :: ptrcast!(T, bytes)
|
||||
return pointer[..new_count]
|
||||
}
|
||||
|
||||
return .out_of_memory
|
||||
}
|
||||
|
||||
# free memory allocated for a slice of type `T`.
|
||||
# note: memory must be freed with the same allocator that was used to allocate it.
|
||||
free func($T type, allocator Allocator, memory []T) void {
|
||||
if memory.len != 0 and sizeof!(T) != 0 {
|
||||
mutable_memory []mut T :: constcast!(memory)
|
||||
@@ -117,6 +114,19 @@ free func($T type, allocator Allocator, memory []T) void {
|
||||
}
|
||||
}
|
||||
|
||||
# get an empty slice of type `T` with `count` elements.
|
||||
empty_slice func($T type, count usize) []mut T {
|
||||
pointer *mut T :: ptrcast!(T, (&empty_storage).ptr)
|
||||
return pointer[..count]
|
||||
}
|
||||
|
||||
# get an empty slice of type `T` with 0 elements.
|
||||
empty func($T type) []mut T {
|
||||
return empty_slice(T, 0)
|
||||
}
|
||||
|
||||
hide empty_storage [1]mut u64 = [0]
|
||||
|
||||
hide malloc_alignment usize :: 16 # ponytail: aarch64-macos libc malloc alignment assumption.
|
||||
|
||||
hide power_of_two func(value usize) bool {
|
||||
|
||||
+3
-3
@@ -44,9 +44,9 @@ TypeInfo :: union(enum) {
|
||||
EnumFieldStruct func($E, $Field type, $default ?Field) type {
|
||||
match typeinfo!(E) {
|
||||
.enum |info|: {
|
||||
names [info.fields.len]mut []u8 = undefined
|
||||
field_types [info.fields.len]mut type = undefined
|
||||
defaults [info.fields.len]mut ?Field = undefined
|
||||
names [info.fields.len]mut []u8 = undefined
|
||||
field_types [info.fields.len]mut type = undefined
|
||||
defaults [info.fields.len]mut ?Field = undefined
|
||||
expand for info.fields |field, index| {
|
||||
names[index] = field.name
|
||||
field_types[index] = Field
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import "io"
|
||||
import "enums"
|
||||
import "arraylist"
|
||||
|
||||
Io :: alias io.Io
|
||||
ArrayList :: alias arraylist.ArrayList
|
||||
EnumMap :: alias enums.EnumMap
|
||||
|
||||
@@ -38,9 +38,11 @@ expect_equal func($T type, expected, actual T, location SourceLocation) void ! E
|
||||
debug.print("{s}:{d}:{d}: expected and actual slices differ\n", {location.file, location.line, location.column})
|
||||
return .expectation_failed
|
||||
}
|
||||
else: if expected != actual {
|
||||
debug.print("{s}:{d}:{d}: expected {}, found {}\n", {location.file, location.line, location.column, expected, actual})
|
||||
return .expectation_failed
|
||||
else: {
|
||||
if expected != actual {
|
||||
debug.print("{s}:{d}:{d}: expected {}, found {}\n", {location.file, location.line, location.column, expected, actual})
|
||||
return .expectation_failed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user