stdlib enum map

This commit is contained in:
2026-07-19 13:02:36 +02:00
parent 709d977436
commit c1225f4ac2
8 changed files with 154 additions and 5 deletions
+1
View File
@@ -3,6 +3,7 @@ import "@std/debug"
import "@std/mem" import "@std/mem"
import "@std/arraylist" import "@std/arraylist"
test import "@std/enums"
test import "@std/arraylist" test import "@std/arraylist"
test import "@std/hashmap" test import "@std/hashmap"
+45
View File
@@ -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!(none)),
) 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 none
}
}
else: compile_error!("EnumMap key must be an enum")
}
}
+25
View File
@@ -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(none, eof)
}
+4 -2
View File
@@ -40,7 +40,8 @@ init func(
} }
} }
# frees the entries in the hash map and invalidates it. # free the entries in the hash map.
# note: this operation invalidates the map.
deinit func( deinit func(
$K, $V type, $K, $V type,
$hash_key func(key K) usize, $hash_key func(key K) usize,
@@ -80,9 +81,9 @@ put func(
key K, key K,
value V, value V,
) void ! (PutError | mem.AllocError) { ) void ! (PutError | mem.AllocError) {
# check grow
threshold :: map.entries.len - divtrunc!(map.entries.len, 4) threshold :: map.entries.len - divtrunc!(map.entries.len, 4)
if (map.entries.len == 0 or map.count + 1 > threshold) { if (map.entries.len == 0 or map.count + 1 > threshold) {
# grow entries array
old_entries :: map.entries old_entries :: map.entries
new_size :: if (old_entries.len > 0) old_entries.len * 2 else 8 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) new_entries :: try mem.alloc(Entry(K, V), map.allocator, new_size)
@@ -109,6 +110,7 @@ put func(
mem.free(map.allocator, old_entries) mem.free(map.allocator, old_entries)
} }
# put new entry
hash :: normalize(hash_key(key)) hash :: normalize(hash_key(key))
idx usize = hash & (map.entries.len - 1) idx usize = hash & (map.entries.len - 1)
+17
View File
@@ -39,3 +39,20 @@ TypeInfo :: union(enum) {
fallible void fallible void
distinct void distinct void
} }
EnumFieldStruct func($E, $Field type, $default ?Field) type {
match typeinfo!(E) {
.enum |info|: {
names [field!(typeinfo!(E), "enum").fields.len]mut []u8 = undefined
field_types [field!(typeinfo!(E), "enum").fields.len]mut type = undefined
defaults [field!(typeinfo!(E), "enum").fields.len]mut ?Field = undefined
expand for info.fields |field, index| {
names[index] = field.name
field_types[index] = Field
defaults[index] = default
}
return struct_type!(.auto, names, field_types, defaults)
}
else: compile_error!("EnumFieldStruct key must be an enum")
}
}
+34
View File
@@ -0,0 +1,34 @@
testing :: import "@std/testing"
TestTokenKind :: enum(u8) {
ident = 3
int = 8
eof = 21
}
TestNames :: alias EnumFieldStruct(TestTokenKind, ?[]u8, some!(none))
enum_field_struct_defaults test {
names TestNames = {
ident = "identifier",
int = "integer",
}
if field!(names, "ident") |value| {
try testing.expect(value.len == 10)
} else {
try testing.expect(false)
}
if field!(names, "int") |value| {
try testing.expect(value.len == 7)
} else {
try testing.expect(false)
}
if field!(names, "eof") |_| {
try testing.expect(false)
}
empty TestNames = {}
if field!(empty, "ident") |_| {
try testing.expect(false)
}
}
+2
View File
@@ -1,5 +1,7 @@
import "io" import "io"
import "enums"
import "arraylist" import "arraylist"
Io :: alias io.Io Io :: alias io.Io
ArrayList :: alias arraylist.ArrayList ArrayList :: alias arraylist.ArrayList
EnumMap :: alias enums.EnumMap
+23
View File
@@ -1,4 +1,5 @@
import "@std/debug" import "@std/debug"
import "@std/mem"
Error :: enum { Error :: enum {
expectation_failed expectation_failed
@@ -18,11 +19,33 @@ expect func(condition bool, location SourceLocation) void ! Error {
} }
expect_equal func($T type, expected, actual T, location SourceLocation) void ! Error { expect_equal func($T type, expected, actual T, location SourceLocation) void ! Error {
match typeinfo!(T) {
.optional: {
if expected |expected_value| {
if actual |actual_value| {
try expect_equal(expected_value, actual_value, location)
return
}
debug.print("{s}:{d}:{d}: expected an optional value, found none\n", {location.file, location.line, location.column})
return .expectation_failed
}
if actual |_| {
debug.print("{s}:{d}:{d}: expected none, found an optional value\n", {location.file, location.line, location.column})
return .expectation_failed
}
}
.slice: if !mem.eql(expected, actual) {
debug.print("{s}:{d}:{d}: expected and actual slices differ\n", {location.file, location.line, location.column})
return .expectation_failed
}
else: {
if expected != actual { if expected != actual {
debug.print("{s}:{d}:{d}: expected {}, found {}\n", {location.file, location.line, location.column, expected, actual}) debug.print("{s}:{d}:{d}: expected {}, found {}\n", {location.file, location.line, location.column, expected, actual})
return .expectation_failed return .expectation_failed
} }
} }
}
}
expect_type func($Expected, $Actual type, _ Actual, location SourceLocation) void ! Error { expect_type func($Expected, $Actual type, _ Actual, location SourceLocation) void ! Error {
try expect($(Expected == Actual), location) try expect($(Expected == Actual), location)