stdlib enum map
This commit is contained in:
@@ -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"
|
||||||
|
|
||||||
|
|||||||
@@ -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(
|
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)
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -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 "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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user