Skip to content

Commit 7f43808

Browse files
committed
- remove custom equal function abstraction (it either was going to be too complicated, or was going to have limitations that made it equivalent to a runtime staticStringMap)
- remove staticArrayMap (and withEql) - YAGNI - keep only StaticStringMap and StaticStringMapIgnoreCase
1 parent 5a39c53 commit 7f43808

File tree

3 files changed

+31
-119
lines changed

3 files changed

+31
-119
lines changed

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -479,7 +479,7 @@ set(ZIG_STAGE2_SOURCES
479479
lib/std/process/Child.zig
480480
lib/std/sort.zig
481481
lib/std/start.zig
482-
lib/std/static_array_map.zig
482+
lib/std/static_string_map.zig
483483
lib/std/std.zig
484484
lib/std/time.zig
485485
lib/std/treap.zig

lib/std/static_array_map.zig renamed to lib/std/static_string_map.zig

Lines changed: 27 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -3,50 +3,34 @@ const assert = std.debug.assert;
33

44
/// 'comptime' optimized mapping between string keys and associated `V` values.
55
pub fn StaticStringMap(comptime V: type) type {
6-
return StaticArrayMapWithEql(V, u8, defaultEql);
6+
return StaticStringMapWithEql(V, defaultEql);
77
}
88

99
/// Same as StaticStringMap, except keys are compared case-insensitively.
1010
pub fn StaticStringMapIgnoreCase(comptime V: type) type {
11-
return StaticArrayMapWithEql(V, u8, ignoreCaseEql);
11+
return StaticStringMapWithEql(V, ignoreCaseEql);
1212
}
1313

14-
/// Same as StaticStringMap, but allows you to provide the `eql` function yourself.
15-
pub fn StaticStringMapWithEql(
16-
comptime V: type,
17-
comptime eql: fn (comptime anytype, anytype) bool,
18-
) type {
19-
return StaticArrayMapWithEql(V, u8, eql);
20-
}
21-
22-
/// 'comptime' optimized mapping between `[]const T` keys and associated `V` values.
23-
pub fn StaticArrayMap(comptime T: type, comptime V: type) type {
24-
return StaticArrayMapWithEql(V, T, defaultEql);
25-
}
26-
27-
pub fn defaultEql(comptime expected: anytype, actual: anytype) bool {
28-
const Array = @TypeOf(expected, actual);
29-
const T = @typeInfo(Array).array.child;
30-
const child_bits = @sizeOf(T) * std.mem.byte_size_in_bits;
14+
fn defaultEql(comptime expected: anytype, actual: anytype) bool {
15+
comptime assert(expected.len == actual.len);
16+
const child_bits = expected.len * std.mem.byte_size_in_bits;
3117

32-
// Directly comparing sections of memory as integer types
33-
// does not work for padded array fields, nor does it work for arrays
34-
// which exceed 65535 bits (there are no integer types that large).
35-
const unique = std.meta.hasUniqueRepresentation(Array);
18+
// Integers are limited to 65535 bits; default to a for loop
19+
// if the strings to compare are too long
3620
const too_many_bits = child_bits > 65535;
3721

3822
// TODO: riscv64 backend can't airBitCast [7]u8 to u56
3923
const limited_backend = @import("builtin").zig_backend == .stage2_riscv64;
4024

41-
if (!unique or too_many_bits or limited_backend) {
25+
if (too_many_bits or limited_backend) {
4226
var match: bool = true;
4327
for (expected, actual) |a, b| {
4428
match = match and a == b;
4529
}
4630
return match;
4731
}
4832

49-
const Compare = std.meta.Int(.unsigned, @bitSizeOf(Array));
33+
const Compare = std.meta.Int(.unsigned, child_bits);
5034
const a: Compare = @bitCast(expected);
5135
const b: Compare = @bitCast(actual);
5236
return a == b;
@@ -67,6 +51,7 @@ fn ignoreCaseEql(comptime expected: anytype, actual: anytype) bool {
6751
return defaultEql(lower_expected, lower_actual);
6852
}
6953

54+
// For backends that cannot handle integer vectors
7055
fn toLowerSimple(comptime len: usize, input: anytype) [len]u8 {
7156
var output: [len]u8 = undefined;
7257
for (input, &output) |in_byte, *out_byte| {
@@ -93,25 +78,23 @@ fn toLowerSimd(input: anytype) [@typeInfo(@TypeOf(input)).array.len]u8 {
9378

9479
/// Static string map constructed at compile time for additional optimizations.
9580
/// First branches on the key length, then compares the possible matching keys.
96-
pub fn StaticArrayMapWithEql(
81+
fn StaticStringMapWithEql(
9782
/// The type of the value
9883
comptime V: type,
99-
/// The type of the element in the array, eg. []const T - would be u8 for a string
100-
comptime T: type,
10184
/// The equal function that is used to compare the keys
10285
comptime eql: fn (comptime anytype, anytype) bool,
10386
) type {
10487
return struct {
10588
const Self = @This();
10689

10790
pub const Kv = struct {
108-
key: []const T,
91+
key: []const u8,
10992
value: V,
11093
};
11194

11295
kvs: []const Kv,
11396

114-
/// Initializes the map at comptime
97+
/// Initializes the map at comptime with a list of key / value pairs
11598
pub inline fn initComptime(comptime kvs_list: anytype) Self {
11699
const list = comptime blk: {
117100
var list: [kvs_list.len]Kv = undefined;
@@ -126,23 +109,23 @@ pub fn StaticArrayMapWithEql(
126109
return comptime .{ .kvs = &list };
127110
}
128111

129-
/// Checks if the map has a value for the key.
130-
pub fn has(comptime self: Self, key: []const T) bool {
112+
/// Checks if the map contains the key.
113+
pub fn has(comptime self: Self, key: []const u8) bool {
131114
return self.get(key) != null;
132115
}
133116

134-
/// Returns the value for the key if any, else null.
135-
pub fn get(comptime self: Self, key: []const T) ?V {
117+
/// Returns the value for the key if any.
118+
pub fn get(comptime self: Self, key: []const u8) ?V {
136119
switch (self.kvs.len) {
137120
0 => return null,
138121
else => return self.filterLength(key),
139122
}
140123
}
141124

142-
/// The list of all the keys in the map.
143-
pub fn keys(comptime self: Self) []const []const T {
125+
/// Returns the list of all the keys in the map.
126+
pub fn keys(comptime self: Self) []const []const u8 {
144127
const list = comptime blk: {
145-
var list: [self.kvs.len][]const T = undefined;
128+
var list: [self.kvs.len][]const u8 = undefined;
146129
for (&list, self.kvs) |*key, kv| {
147130
key.* = kv.key;
148131
}
@@ -151,7 +134,7 @@ pub fn StaticArrayMapWithEql(
151134
return &list;
152135
}
153136

154-
/// The list of all the values in the map.
137+
/// Returns the list of all the values in the map.
155138
pub fn values(comptime self: Self) []const V {
156139
const list = comptime blk: {
157140
var list: [self.kvs.len]V = undefined;
@@ -165,7 +148,7 @@ pub fn StaticArrayMapWithEql(
165148

166149
/// Filters the input key by length, then compares it to the possible matches.
167150
/// Because we know the length at comptime, we can compare the strings faster.
168-
fn filterLength(comptime self: Self, key: []const T) ?V {
151+
fn filterLength(comptime self: Self, key: []const u8) ?V {
169152
// separateLength is hungry - provide 2000 branches per key/value pair
170153
@setEvalBranchQuota(2000 * self.kvs.len);
171154
const kvs_by_lengths = comptime self.separateLength();
@@ -179,14 +162,7 @@ pub fn StaticArrayMapWithEql(
179162
@setEvalBranchQuota(10 * len * idx);
180163
comptime for (kvs_by_len.kvs[0..idx]) |prev_kv| {
181164
if (eql(kv.key[0..len].*, prev_kv.key[0..len].*)) {
182-
if (T == u8 and std.unicode.utf8ValidateSlice(kv.key)) {
183-
@compileError("duplicate key \"" ++ kv.key ++ "\"");
184-
} else {
185-
@compileError(std.fmt.comptimePrint(
186-
"duplicate key: {any}",
187-
.{kv.key},
188-
));
189-
}
165+
@compileError("redundant key \"" ++ kv.key ++ "\"");
190166
}
191167
};
192168

@@ -429,73 +405,12 @@ test "sorting kvs doesn't exceed eval branch quota" {
429405
try testing.expectEqual(1, TypeToByteSizeLUT.get("t1"));
430406
}
431407

432-
test "static array map" {
433-
const map = StaticArrayMap(u16, u4).initComptime(.{
434-
.{ &[_]u16{ 0, 1, 2, 3 }, 0 },
435-
.{ &[_]u16{ 4, 5, 6, 7, 8 }, 1 },
436-
.{ &[_]u16{ 9, 10, 1 << 13, 4 }, 2 },
437-
.{ &[_]u16{0}, 3 },
438-
.{ &[_]u16{}, 4 },
439-
});
440-
441-
try testing.expectEqual(0, map.get(&[_]u16{ 0, 1, 2, 3 }).?);
442-
try testing.expectEqual(2, map.get(&[_]u16{ 9, 10, 1 << 13, 4 }).?);
443-
try testing.expectEqual(4, map.get(&[_]u16{}).?);
444-
445-
try testing.expectEqual(null, map.get(&[_]u16{ 7, 7, 7 }));
446-
try testing.expectEqual(null, map.get(&[_]u16{ 0, 1 }));
447-
448-
try testing.expectEqual(true, map.has(&[_]u16{ 4, 5, 6, 7, 8 }));
449-
try testing.expectEqual(true, map.has(&[_]u16{0}));
450-
451-
try testing.expectEqual(false, map.has(&[_]u16{5}));
452-
try testing.expectEqual(false, map.has(&[_]u16{ 0, 0 }));
453-
}
454-
455-
test "array elements that are padded" {
456-
const map = StaticArrayMap(u7, u4).initComptime(&.{
457-
.{ &.{ 0, 1, 2, 3, 4, 5 }, 0 },
458-
.{ &.{ 0, 1, 127, 3, 4, 5 }, 1 },
459-
.{ &.{ 0, 1, 2, 126, 4, 5 }, 2 },
460-
});
461-
462-
try testing.expectEqual(null, map.get(&.{0}));
463-
try testing.expectEqual(0, map.get(&.{ 0, 1, 2, 3, 4, 5 }));
464-
try testing.expectEqual(1, map.get(&.{ 0, 1, 127, 3, 4, 5 }));
465-
try testing.expectEqual(2, map.get(&.{ 0, 1, 2, 126, 4, 5 }));
466-
}
467-
468-
fn lastElementEql(comptime expected: anytype, actual: anytype) bool {
469-
if (expected.len == 0) {
470-
return false;
471-
} else {
472-
const last_idx = expected.len - 1;
473-
return expected[last_idx] == actual[last_idx];
474-
}
475-
}
476-
477-
test "custom equal function" {
478-
const map = StaticStringMapWithEql(u2, lastElementEql).initComptime(.{
479-
.{ "last byte is a t", 0 },
480-
.{ "last byte is a b", 1 },
481-
.{ "last byte is a s", 2 },
482-
.{ "last byte is a c", 3 },
483-
});
484-
485-
// limitation: eql functions only are called on same-length inputs
486-
try testing.expectEqual(false, map.has("t"));
487-
488-
try testing.expectEqual(1, map.get("my magic byte: b"));
489-
try testing.expectEqual(0, map.get("my magic byte: t"));
490-
try testing.expectEqual(2, map.get("my magic byte: s"));
491-
}
492-
493408
test "single string StaticStringMap" {
494-
const map = StaticStringMap(void).initComptime(.{.{"o kama pona"}});
409+
const map = StaticStringMap(void).initComptime(.{.{"Hello, World!"}});
495410

496-
try testing.expectEqual(true, map.has("o kama pona"));
497-
try testing.expectEqual(false, map.has("o kama ike"));
498-
try testing.expectEqual(false, map.has("o kama pona ala"));
411+
try testing.expectEqual(true, map.has("Hello, World!"));
412+
try testing.expectEqual(false, map.has("Same len str!"));
413+
try testing.expectEqual(false, map.has("Hello, World! (not the same)"));
499414
}
500415

501416
test "empty StaticStringMap" {

lib/std/std.zig

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,8 @@ pub const BoundedArrayAligned = @import("bounded_array.zig").BoundedArrayAligned
1414
pub const Build = @import("Build.zig");
1515
pub const BufMap = @import("buf_map.zig").BufMap;
1616
pub const BufSet = @import("buf_set.zig").BufSet;
17-
pub const StaticArrayMap = static_array_map.StaticArrayMap;
18-
pub const StaticArrayMapWithEql = static_array_map.StaticArrayMapWithEql;
19-
pub const StaticStringMap = static_array_map.StaticStringMap;
20-
pub const StaticStringMapIgnoreCase = static_array_map.StaticStringMapIgnoreCase;
21-
pub const StaticStringMapWithEql = static_array_map.StaticStringMapWithEql;
17+
pub const StaticStringMap = static_string_map.StaticStringMap;
18+
pub const StaticStringMapIgnoreCase = static_string_map.StaticStringMapIgnoreCase;
2219
pub const DoublyLinkedList = @import("linked_list.zig").DoublyLinkedList;
2320
pub const DynLib = @import("dynamic_library.zig").DynLib;
2421
pub const DynamicBitSet = bit_set.DynamicBitSet;
@@ -56,7 +53,7 @@ pub const builtin = @import("builtin.zig");
5653
pub const c = @import("c.zig");
5754
pub const coff = @import("coff.zig");
5855
pub const compress = @import("compress.zig");
59-
pub const static_array_map = @import("static_array_map.zig");
56+
pub const static_string_map = @import("static_string_map.zig");
6057
pub const crypto = @import("crypto.zig");
6158
pub const debug = @import("debug.zig");
6259
pub const dwarf = @import("dwarf.zig");

0 commit comments

Comments
 (0)