From c1225f4ac2586f6ba63a3be50664b85e4a431ae2 Mon Sep 17 00:00:00 2001 From: hl-valdemar Date: Sun, 19 Jul 2026 13:02:36 +0200 Subject: [PATCH] stdlib enum map --- source/main.hon | 1 + std/enums/enums.hon | 45 ++++++++++++++++++++++++++++++++++++++++ std/enums/enums.test.hon | 25 ++++++++++++++++++++++ std/hashmap/hashmap.hon | 6 ++++-- std/meta/meta.hon | 17 +++++++++++++++ std/meta/meta.test.hon | 34 ++++++++++++++++++++++++++++++ std/std.hon | 2 ++ std/testing/testing.hon | 29 +++++++++++++++++++++++--- 8 files changed, 154 insertions(+), 5 deletions(-) create mode 100644 std/enums/enums.hon create mode 100644 std/enums/enums.test.hon create mode 100644 std/meta/meta.test.hon diff --git a/source/main.hon b/source/main.hon index 790bcfb..a521b2a 100644 --- a/source/main.hon +++ b/source/main.hon @@ -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" diff --git a/std/enums/enums.hon b/std/enums/enums.hon new file mode 100644 index 0000000..7e1b2e4 --- /dev/null +++ b/std/enums/enums.hon @@ -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") + } +} diff --git a/std/enums/enums.test.hon b/std/enums/enums.test.hon new file mode 100644 index 0000000..ba4a2b8 --- /dev/null +++ b/std/enums/enums.test.hon @@ -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) +} diff --git a/std/hashmap/hashmap.hon b/std/hashmap/hashmap.hon index 4b7c518..dbc6e2d 100644 --- a/std/hashmap/hashmap.hon +++ b/std/hashmap/hashmap.hon @@ -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) diff --git a/std/meta/meta.hon b/std/meta/meta.hon index 709df7d..170818d 100644 --- a/std/meta/meta.hon +++ b/std/meta/meta.hon @@ -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") + } +} diff --git a/std/meta/meta.test.hon b/std/meta/meta.test.hon new file mode 100644 index 0000000..1c357a2 --- /dev/null +++ b/std/meta/meta.test.hon @@ -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) + } +} diff --git a/std/std.hon b/std/std.hon index d4697ad..0da939b 100644 --- a/std/std.hon +++ b/std/std.hon @@ -1,5 +1,7 @@ import "io" +import "enums" import "arraylist" Io :: alias io.Io ArrayList :: alias arraylist.ArrayList +EnumMap :: alias enums.EnumMap diff --git a/std/testing/testing.hon b/std/testing/testing.hon index 3de655a..918e4d8 100644 --- a/std/testing/testing.hon +++ b/std/testing/testing.hon @@ -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 + } + } } }