stdlib enum map
This commit is contained in:
@@ -3,6 +3,7 @@ import "@std/debug"
|
||||
import "@std/mem"
|
||||
import "@std/arraylist"
|
||||
|
||||
test import "@std/enums"
|
||||
test import "@std/arraylist"
|
||||
test import "@std/hashmap"
|
||||
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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(
|
||||
$K, $V type,
|
||||
$hash_key func(key K) usize,
|
||||
@@ -80,9 +81,9 @@ put func(
|
||||
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) {
|
||||
# grow entries array
|
||||
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)
|
||||
@@ -109,6 +110,7 @@ put func(
|
||||
mem.free(map.allocator, old_entries)
|
||||
}
|
||||
|
||||
# put new entry
|
||||
hash :: normalize(hash_key(key))
|
||||
idx usize = hash & (map.entries.len - 1)
|
||||
|
||||
|
||||
@@ -39,3 +39,20 @@ TypeInfo :: union(enum) {
|
||||
fallible 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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,7 @@
|
||||
import "io"
|
||||
import "enums"
|
||||
import "arraylist"
|
||||
|
||||
Io :: alias io.Io
|
||||
ArrayList :: alias arraylist.ArrayList
|
||||
EnumMap :: alias enums.EnumMap
|
||||
|
||||
+26
-3
@@ -1,4 +1,5 @@
|
||||
import "@std/debug"
|
||||
import "@std/mem"
|
||||
|
||||
Error :: enum {
|
||||
expectation_failed
|
||||
@@ -18,9 +19,31 @@ expect func(condition bool, location SourceLocation) void ! Error {
|
||||
}
|
||||
|
||||
expect_equal func($T type, expected, actual T, location SourceLocation) void ! Error {
|
||||
if expected != actual {
|
||||
debug.print("{s}:{d}:{d}: expected {}, found {}\n", {location.file, location.line, location.column, expected, actual})
|
||||
return .expectation_failed
|
||||
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 {
|
||||
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