authorgravatar for twostepted@gmail.comTravis Staloch <twostepted@gmail.com> 2024-04-20 23:14:39-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-04-22 15:31:41-07:00
log8af59d1f98266bd70b3afb44d196bbd151cedf22
tree64b0c48f2b2d222629acbd5698c1f5310fdc708f
parentfefdbca6e62145a20777789961262f15c2bf6cbe

ComptimeStringMap: return a regular struct and optimize

this patch renames ComptimeStringMap to StaticStringMap, makes it accept only a single type parameter, and return a known struct type instead of an anonymous struct. initial motivation for these changes was to reduce the 'very long type names' issue described here https://github.com/ziglang/zig/pull/19682. this breaks the previous API. users will now need to write: `const map = std.StaticStringMap(T).initComptime(kvs_list);` * move `kvs_list` param from type param to an `initComptime()` param * new public methods * `keys()`, `values()` helpers * `init(allocator)`, `deinit(allocator)` for runtime data * `getLongestPrefix(str)`, `getLongestPrefixIndex(str)` - i'm not sure these belong but have left in for now incase they are deemed useful * performance notes: * i posted some benchmarking results here: https://github.com/travisstaloch/comptime-string-map-revised/issues/1 * i noticed a speedup reducing the size of the struct from 48 to 32 bytes and thus use u32s instead of usize for all length fields * i noticed speedup storing KVs as a struct of arrays * latest benchmark shows these wall_time improvements for debug/safe/small/fast builds: -6.6% / -10.2% / -19.1% / -8.9%. full output in link above.

25 files changed, 608 insertions(+), 387 deletions(-)

CMakeLists.txt+1-1
...@@ -222,7 +222,7 @@ set(ZIG_STAGE2_SOURCES...@@ -222,7 +222,7 @@ set(ZIG_STAGE2_SOURCES
222 "${CMAKE_SOURCE_DIR}/lib/std/c/linux.zig"222 "${CMAKE_SOURCE_DIR}/lib/std/c/linux.zig"
223 "${CMAKE_SOURCE_DIR}/lib/std/child_process.zig"223 "${CMAKE_SOURCE_DIR}/lib/std/child_process.zig"
224 "${CMAKE_SOURCE_DIR}/lib/std/coff.zig"224 "${CMAKE_SOURCE_DIR}/lib/std/coff.zig"
225 "${CMAKE_SOURCE_DIR}/lib/std/comptime_string_map.zig"225 "${CMAKE_SOURCE_DIR}/lib/std/static_string_map.zig"
226 "${CMAKE_SOURCE_DIR}/lib/std/crypto.zig"226 "${CMAKE_SOURCE_DIR}/lib/std/crypto.zig"
227 "${CMAKE_SOURCE_DIR}/lib/std/crypto/blake3.zig"227 "${CMAKE_SOURCE_DIR}/lib/std/crypto/blake3.zig"
228 "${CMAKE_SOURCE_DIR}/lib/std/crypto/siphash.zig"228 "${CMAKE_SOURCE_DIR}/lib/std/crypto/siphash.zig"
lib/compiler/aro/aro/LangOpts.zig+1-1
...@@ -47,7 +47,7 @@ pub const Standard = enum {...@@ -47,7 +47,7 @@ pub const Standard = enum {
47 /// Working Draft for ISO C23 with GNU extensions47 /// Working Draft for ISO C23 with GNU extensions
48 gnu23,48 gnu23,
4949
50 const NameMap = std.ComptimeStringMap(Standard, .{50 const NameMap = std.StaticStringMap(Standard).initComptime(.{
51 .{ "c89", .c89 }, .{ "c90", .c89 }, .{ "iso9899:1990", .c89 },51 .{ "c89", .c89 }, .{ "c90", .c89 }, .{ "iso9899:1990", .c89 },
52 .{ "iso9899:199409", .iso9899 }, .{ "gnu89", .gnu89 }, .{ "gnu90", .gnu89 },52 .{ "iso9899:199409", .iso9899 }, .{ "gnu89", .gnu89 }, .{ "gnu90", .gnu89 },
53 .{ "c99", .c99 }, .{ "iso9899:1999", .c99 }, .{ "c9x", .c99 },53 .{ "c99", .c99 }, .{ "iso9899:1999", .c99 }, .{ "c9x", .c99 },
lib/compiler/aro/aro/Preprocessor.zig+1-1
...@@ -1709,7 +1709,7 @@ fn expandFuncMacro(...@@ -1709,7 +1709,7 @@ fn expandFuncMacro(
1709 }1709 }
1710 if (!pp.comp.langopts.standard.atLeast(.c23)) break :res not_found;1710 if (!pp.comp.langopts.standard.atLeast(.c23)) break :res not_found;
17111711
1712 const attrs = std.ComptimeStringMap([]const u8, .{1712 const attrs = std.StaticStringMap([]const u8).initComptime(.{
1713 .{ "deprecated", "201904L\n" },1713 .{ "deprecated", "201904L\n" },
1714 .{ "fallthrough", "201904L\n" },1714 .{ "fallthrough", "201904L\n" },
1715 .{ "maybe_unused", "201904L\n" },1715 .{ "maybe_unused", "201904L\n" },
lib/compiler/aro/aro/Tokenizer.zig+1-1
...@@ -872,7 +872,7 @@ pub const Token = struct {...@@ -872,7 +872,7 @@ pub const Token = struct {
872 };872 };
873 }873 }
874874
875 const all_kws = std.ComptimeStringMap(Id, .{875 const all_kws = std.StaticStringMap(Id).initComptime(.{
876 .{ "auto", auto: {876 .{ "auto", auto: {
877 @setEvalBranchQuota(3000);877 @setEvalBranchQuota(3000);
878 break :auto .keyword_auto;878 break :auto .keyword_auto;
lib/compiler/resinator/errors.zig+1-1
...@@ -240,7 +240,7 @@ pub const ErrorDetails = struct {...@@ -240,7 +240,7 @@ pub const ErrorDetails = struct {
240 // see https://github.com/ziglang/zig/issues/15395240 // see https://github.com/ziglang/zig/issues/15395
241 _: u26 = 0,241 _: u26 = 0,
242242
243 pub const strings = std.ComptimeStringMap([]const u8, .{243 pub const strings = std.StaticStringMap([]const u8).initComptime(.{
244 .{ "number", "number" },244 .{ "number", "number" },
245 .{ "number_expression", "number expression" },245 .{ "number_expression", "number expression" },
246 .{ "string_literal", "quoted string literal" },246 .{ "string_literal", "quoted string literal" },
lib/compiler/resinator/rc.zig+65-26
...@@ -47,7 +47,10 @@ pub const Resource = enum {...@@ -47,7 +47,10 @@ pub const Resource = enum {
47 fontdir_num,47 fontdir_num,
48 manifest_num,48 manifest_num,
4949
50 const map = std.ComptimeStringMapWithEql(Resource, .{50 const map = std.StaticStringMapWithEql(
51 Resource,
52 std.static_string_map.eqlAsciiIgnoreCase,
53 ).initComptime(.{
51 .{ "ACCELERATORS", .accelerators },54 .{ "ACCELERATORS", .accelerators },
52 .{ "BITMAP", .bitmap },55 .{ "BITMAP", .bitmap },
53 .{ "CURSOR", .cursor },56 .{ "CURSOR", .cursor },
...@@ -67,7 +70,7 @@ pub const Resource = enum {...@@ -67,7 +70,7 @@ pub const Resource = enum {
67 .{ "TOOLBAR", .toolbar },70 .{ "TOOLBAR", .toolbar },
68 .{ "VERSIONINFO", .versioninfo },71 .{ "VERSIONINFO", .versioninfo },
69 .{ "VXD", .vxd },72 .{ "VXD", .vxd },
70 }, std.comptime_string_map.eqlAsciiIgnoreCase);73 });
7174
72 pub fn fromString(bytes: SourceBytes) Resource {75 pub fn fromString(bytes: SourceBytes) Resource {
73 const maybe_ordinal = res.NameOrOrdinal.maybeOrdinalFromString(bytes);76 const maybe_ordinal = res.NameOrOrdinal.maybeOrdinalFromString(bytes);
...@@ -157,20 +160,26 @@ pub const OptionalStatements = enum {...@@ -157,20 +160,26 @@ pub const OptionalStatements = enum {
157 menu,160 menu,
158 style,161 style,
159162
160 pub const map = std.ComptimeStringMapWithEql(OptionalStatements, .{163 pub const map = std.StaticStringMapWithEql(
164 OptionalStatements,
165 std.static_string_map.eqlAsciiIgnoreCase,
166 ).initComptime(.{
161 .{ "CHARACTERISTICS", .characteristics },167 .{ "CHARACTERISTICS", .characteristics },
162 .{ "LANGUAGE", .language },168 .{ "LANGUAGE", .language },
163 .{ "VERSION", .version },169 .{ "VERSION", .version },
164 }, std.comptime_string_map.eqlAsciiIgnoreCase);170 });
165171
166 pub const dialog_map = std.ComptimeStringMapWithEql(OptionalStatements, .{172 pub const dialog_map = std.StaticStringMapWithEql(
173 OptionalStatements,
174 std.static_string_map.eqlAsciiIgnoreCase,
175 ).initComptime(.{
167 .{ "CAPTION", .caption },176 .{ "CAPTION", .caption },
168 .{ "CLASS", .class },177 .{ "CLASS", .class },
169 .{ "EXSTYLE", .exstyle },178 .{ "EXSTYLE", .exstyle },
170 .{ "FONT", .font },179 .{ "FONT", .font },
171 .{ "MENU", .menu },180 .{ "MENU", .menu },
172 .{ "STYLE", .style },181 .{ "STYLE", .style },
173 }, std.comptime_string_map.eqlAsciiIgnoreCase);182 });
174};183};
175184
176pub const Control = enum {185pub const Control = enum {
...@@ -197,7 +206,10 @@ pub const Control = enum {...@@ -197,7 +206,10 @@ pub const Control = enum {
197 state3,206 state3,
198 userbutton,207 userbutton,
199208
200 pub const map = std.ComptimeStringMapWithEql(Control, .{209 pub const map = std.StaticStringMapWithEql(
210 Control,
211 std.static_string_map.eqlAsciiIgnoreCase,
212 ).initComptime(.{
201 .{ "AUTO3STATE", .auto3state },213 .{ "AUTO3STATE", .auto3state },
202 .{ "AUTOCHECKBOX", .autocheckbox },214 .{ "AUTOCHECKBOX", .autocheckbox },
203 .{ "AUTORADIOBUTTON", .autoradiobutton },215 .{ "AUTORADIOBUTTON", .autoradiobutton },
...@@ -220,7 +232,7 @@ pub const Control = enum {...@@ -220,7 +232,7 @@ pub const Control = enum {
220 .{ "SCROLLBAR", .scrollbar },232 .{ "SCROLLBAR", .scrollbar },
221 .{ "STATE3", .state3 },233 .{ "STATE3", .state3 },
222 .{ "USERBUTTON", .userbutton },234 .{ "USERBUTTON", .userbutton },
223 }, std.comptime_string_map.eqlAsciiIgnoreCase);235 });
224236
225 pub fn hasTextParam(control: Control) bool {237 pub fn hasTextParam(control: Control) bool {
226 switch (control) {238 switch (control) {
...@@ -231,14 +243,17 @@ pub const Control = enum {...@@ -231,14 +243,17 @@ pub const Control = enum {
231};243};
232244
233pub const ControlClass = struct {245pub const ControlClass = struct {
234 pub const map = std.ComptimeStringMapWithEql(res.ControlClass, .{246 pub const map = std.StaticStringMapWithEql(
247 res.ControlClass,
248 std.static_string_map.eqlAsciiIgnoreCase,
249 ).initComptime(.{
235 .{ "BUTTON", .button },250 .{ "BUTTON", .button },
236 .{ "EDIT", .edit },251 .{ "EDIT", .edit },
237 .{ "STATIC", .static },252 .{ "STATIC", .static },
238 .{ "LISTBOX", .listbox },253 .{ "LISTBOX", .listbox },
239 .{ "SCROLLBAR", .scrollbar },254 .{ "SCROLLBAR", .scrollbar },
240 .{ "COMBOBOX", .combobox },255 .{ "COMBOBOX", .combobox },
241 }, std.comptime_string_map.eqlAsciiIgnoreCase);256 });
242257
243 /// Like `map.get` but works on WTF16 strings, for use with parsed258 /// Like `map.get` but works on WTF16 strings, for use with parsed
244 /// string literals ("BUTTON", or even "\x42UTTON")259 /// string literals ("BUTTON", or even "\x42UTTON")
...@@ -280,10 +295,13 @@ pub const MenuItem = enum {...@@ -280,10 +295,13 @@ pub const MenuItem = enum {
280 menuitem,295 menuitem,
281 popup,296 popup,
282297
283 pub const map = std.ComptimeStringMapWithEql(MenuItem, .{298 pub const map = std.StaticStringMapWithEql(
299 MenuItem,
300 std.static_string_map.eqlAsciiIgnoreCase,
301 ).initComptime(.{
284 .{ "MENUITEM", .menuitem },302 .{ "MENUITEM", .menuitem },
285 .{ "POPUP", .popup },303 .{ "POPUP", .popup },
286 }, std.comptime_string_map.eqlAsciiIgnoreCase);304 });
287305
288 pub fn isSeparator(bytes: []const u8) bool {306 pub fn isSeparator(bytes: []const u8) bool {
289 return std.ascii.eqlIgnoreCase(bytes, "SEPARATOR");307 return std.ascii.eqlIgnoreCase(bytes, "SEPARATOR");
...@@ -297,14 +315,17 @@ pub const MenuItem = enum {...@@ -297,14 +315,17 @@ pub const MenuItem = enum {
297 menubarbreak,315 menubarbreak,
298 menubreak,316 menubreak,
299317
300 pub const map = std.ComptimeStringMapWithEql(Option, .{318 pub const map = std.StaticStringMapWithEql(
319 Option,
320 std.static_string_map.eqlAsciiIgnoreCase,
321 ).initComptime(.{
301 .{ "CHECKED", .checked },322 .{ "CHECKED", .checked },
302 .{ "GRAYED", .grayed },323 .{ "GRAYED", .grayed },
303 .{ "HELP", .help },324 .{ "HELP", .help },
304 .{ "INACTIVE", .inactive },325 .{ "INACTIVE", .inactive },
305 .{ "MENUBARBREAK", .menubarbreak },326 .{ "MENUBARBREAK", .menubarbreak },
306 .{ "MENUBREAK", .menubreak },327 .{ "MENUBREAK", .menubreak },
307 }, std.comptime_string_map.eqlAsciiIgnoreCase);328 });
308 };329 };
309};330};
310331
...@@ -312,10 +333,13 @@ pub const ToolbarButton = enum {...@@ -312,10 +333,13 @@ pub const ToolbarButton = enum {
312 button,333 button,
313 separator,334 separator,
314335
315 pub const map = std.ComptimeStringMapWithEql(ToolbarButton, .{336 pub const map = std.StaticStringMapWithEql(
337 ToolbarButton,
338 std.static_string_map.eqlAsciiIgnoreCase,
339 ).initComptime(.{
316 .{ "BUTTON", .button },340 .{ "BUTTON", .button },
317 .{ "SEPARATOR", .separator },341 .{ "SEPARATOR", .separator },
318 }, std.comptime_string_map.eqlAsciiIgnoreCase);342 });
319};343};
320344
321pub const VersionInfo = enum {345pub const VersionInfo = enum {
...@@ -327,7 +351,10 @@ pub const VersionInfo = enum {...@@ -327,7 +351,10 @@ pub const VersionInfo = enum {
327 file_type,351 file_type,
328 file_subtype,352 file_subtype,
329353
330 pub const map = std.ComptimeStringMapWithEql(VersionInfo, .{354 pub const map = std.StaticStringMapWithEql(
355 VersionInfo,
356 std.static_string_map.eqlAsciiIgnoreCase,
357 ).initComptime(.{
331 .{ "FILEVERSION", .file_version },358 .{ "FILEVERSION", .file_version },
332 .{ "PRODUCTVERSION", .product_version },359 .{ "PRODUCTVERSION", .product_version },
333 .{ "FILEFLAGSMASK", .file_flags_mask },360 .{ "FILEFLAGSMASK", .file_flags_mask },
...@@ -335,17 +362,20 @@ pub const VersionInfo = enum {...@@ -335,17 +362,20 @@ pub const VersionInfo = enum {
335 .{ "FILEOS", .file_os },362 .{ "FILEOS", .file_os },
336 .{ "FILETYPE", .file_type },363 .{ "FILETYPE", .file_type },
337 .{ "FILESUBTYPE", .file_subtype },364 .{ "FILESUBTYPE", .file_subtype },
338 }, std.comptime_string_map.eqlAsciiIgnoreCase);365 });
339};366};
340367
341pub const VersionBlock = enum {368pub const VersionBlock = enum {
342 block,369 block,
343 value,370 value,
344371
345 pub const map = std.ComptimeStringMapWithEql(VersionBlock, .{372 pub const map = std.StaticStringMapWithEql(
373 VersionBlock,
374 std.static_string_map.eqlAsciiIgnoreCase,
375 ).initComptime(.{
346 .{ "BLOCK", .block },376 .{ "BLOCK", .block },
347 .{ "VALUE", .value },377 .{ "VALUE", .value },
348 }, std.comptime_string_map.eqlAsciiIgnoreCase);378 });
349};379};
350380
351/// Keywords that are be the first token in a statement and (if so) dictate how the rest381/// Keywords that are be the first token in a statement and (if so) dictate how the rest
...@@ -356,12 +386,15 @@ pub const TopLevelKeywords = enum {...@@ -356,12 +386,15 @@ pub const TopLevelKeywords = enum {
356 characteristics,386 characteristics,
357 stringtable,387 stringtable,
358388
359 pub const map = std.ComptimeStringMapWithEql(TopLevelKeywords, .{389 pub const map = std.StaticStringMapWithEql(
390 TopLevelKeywords,
391 std.static_string_map.eqlAsciiIgnoreCase,
392 ).initComptime(.{
360 .{ "LANGUAGE", .language },393 .{ "LANGUAGE", .language },
361 .{ "VERSION", .version },394 .{ "VERSION", .version },
362 .{ "CHARACTERISTICS", .characteristics },395 .{ "CHARACTERISTICS", .characteristics },
363 .{ "STRINGTABLE", .stringtable },396 .{ "STRINGTABLE", .stringtable },
364 }, std.comptime_string_map.eqlAsciiIgnoreCase);397 });
365};398};
366399
367pub const CommonResourceAttributes = enum {400pub const CommonResourceAttributes = enum {
...@@ -375,7 +408,10 @@ pub const CommonResourceAttributes = enum {...@@ -375,7 +408,10 @@ pub const CommonResourceAttributes = enum {
375 shared,408 shared,
376 nonshared,409 nonshared,
377410
378 pub const map = std.ComptimeStringMapWithEql(CommonResourceAttributes, .{411 pub const map = std.StaticStringMapWithEql(
412 CommonResourceAttributes,
413 std.static_string_map.eqlAsciiIgnoreCase,
414 ).initComptime(.{
379 .{ "PRELOAD", .preload },415 .{ "PRELOAD", .preload },
380 .{ "LOADONCALL", .loadoncall },416 .{ "LOADONCALL", .loadoncall },
381 .{ "FIXED", .fixed },417 .{ "FIXED", .fixed },
...@@ -385,7 +421,7 @@ pub const CommonResourceAttributes = enum {...@@ -385,7 +421,7 @@ pub const CommonResourceAttributes = enum {
385 .{ "IMPURE", .impure },421 .{ "IMPURE", .impure },
386 .{ "SHARED", .shared },422 .{ "SHARED", .shared },
387 .{ "NONSHARED", .nonshared },423 .{ "NONSHARED", .nonshared },
388 }, std.comptime_string_map.eqlAsciiIgnoreCase);424 });
389};425};
390426
391pub const AcceleratorTypeAndOptions = enum {427pub const AcceleratorTypeAndOptions = enum {
...@@ -396,12 +432,15 @@ pub const AcceleratorTypeAndOptions = enum {...@@ -396,12 +432,15 @@ pub const AcceleratorTypeAndOptions = enum {
396 shift,432 shift,
397 control,433 control,
398434
399 pub const map = std.ComptimeStringMapWithEql(AcceleratorTypeAndOptions, .{435 pub const map = std.StaticStringMapWithEql(
436 AcceleratorTypeAndOptions,
437 std.static_string_map.eqlAsciiIgnoreCase,
438 ).initComptime(.{
400 .{ "VIRTKEY", .virtkey },439 .{ "VIRTKEY", .virtkey },
401 .{ "ASCII", .ascii },440 .{ "ASCII", .ascii },
402 .{ "NOINVERT", .noinvert },441 .{ "NOINVERT", .noinvert },
403 .{ "ALT", .alt },442 .{ "ALT", .alt },
404 .{ "SHIFT", .shift },443 .{ "SHIFT", .shift },
405 .{ "CONTROL", .control },444 .{ "CONTROL", .control },
406 }, std.comptime_string_map.eqlAsciiIgnoreCase);445 });
407};446};
lib/std/comptime_string_map.zig deleted-320
...@@ -1,320 +0,0 @@
1const std = @import("std.zig");
2const mem = std.mem;
3
4/// Comptime string map optimized for small sets of disparate string keys.
5/// Works by separating the keys by length at comptime and only checking strings of
6/// equal length at runtime.
7///
8/// `kvs_list` expects a list of `struct { []const u8, V }` (key-value pair) tuples.
9/// You can pass `struct { []const u8 }` (only keys) tuples if `V` is `void`.
10pub fn ComptimeStringMap(
11 comptime V: type,
12 comptime kvs_list: anytype,
13) type {
14 return ComptimeStringMapWithEql(V, kvs_list, defaultEql);
15}
16
17/// Like `std.mem.eql`, but takes advantage of the fact that the lengths
18/// of `a` and `b` are known to be equal.
19pub fn defaultEql(a: []const u8, b: []const u8) bool {
20 if (a.ptr == b.ptr) return true;
21 for (a, b) |a_elem, b_elem| {
22 if (a_elem != b_elem) return false;
23 }
24 return true;
25}
26
27/// Like `std.ascii.eqlIgnoreCase` but takes advantage of the fact that
28/// the lengths of `a` and `b` are known to be equal.
29pub fn eqlAsciiIgnoreCase(a: []const u8, b: []const u8) bool {
30 if (a.ptr == b.ptr) return true;
31 for (a, b) |a_c, b_c| {
32 if (std.ascii.toLower(a_c) != std.ascii.toLower(b_c)) return false;
33 }
34 return true;
35}
36
37/// ComptimeStringMap, but accepts an equality function (`eql`).
38/// The `eql` function is only called to determine the equality
39/// of equal length strings. Any strings that are not equal length
40/// are never compared using the `eql` function.
41pub fn ComptimeStringMapWithEql(
42 comptime V: type,
43 comptime kvs_list: anytype,
44 comptime eql: fn (a: []const u8, b: []const u8) bool,
45) type {
46 const empty_list = kvs_list.len == 0;
47 const precomputed = blk: {
48 @setEvalBranchQuota(1500);
49 const KV = struct {
50 key: []const u8,
51 value: V,
52 };
53 if (empty_list)
54 break :blk .{};
55 var sorted_kvs: [kvs_list.len]KV = undefined;
56 for (kvs_list, 0..) |kv, i| {
57 if (V != void) {
58 sorted_kvs[i] = .{ .key = kv.@"0", .value = kv.@"1" };
59 } else {
60 sorted_kvs[i] = .{ .key = kv.@"0", .value = {} };
61 }
62 }
63
64 const SortContext = struct {
65 kvs: []KV,
66
67 pub fn lessThan(ctx: @This(), a: usize, b: usize) bool {
68 return ctx.kvs[a].key.len < ctx.kvs[b].key.len;
69 }
70
71 pub fn swap(ctx: @This(), a: usize, b: usize) void {
72 return std.mem.swap(KV, &ctx.kvs[a], &ctx.kvs[b]);
73 }
74 };
75 mem.sortUnstableContext(0, sorted_kvs.len, SortContext{ .kvs = &sorted_kvs });
76
77 const min_len = sorted_kvs[0].key.len;
78 const max_len = sorted_kvs[sorted_kvs.len - 1].key.len;
79 var len_indexes: [max_len + 1]usize = undefined;
80 var len: usize = 0;
81 var i: usize = 0;
82 while (len <= max_len) : (len += 1) {
83 // find the first keyword len == len
84 while (len > sorted_kvs[i].key.len) {
85 i += 1;
86 }
87 len_indexes[len] = i;
88 }
89 break :blk .{
90 .min_len = min_len,
91 .max_len = max_len,
92 .sorted_kvs = sorted_kvs,
93 .len_indexes = len_indexes,
94 };
95 };
96
97 return struct {
98 /// Array of `struct { key: []const u8, value: V }` where `value` is `void{}` if `V` is `void`.
99 /// Sorted by `key` length.
100 pub const kvs = precomputed.sorted_kvs;
101
102 /// Checks if the map has a value for the key.
103 pub fn has(str: []const u8) bool {
104 return get(str) != null;
105 }
106
107 /// Returns the value for the key if any, else null.
108 pub fn get(str: []const u8) ?V {
109 if (empty_list)
110 return null;
111
112 return precomputed.sorted_kvs[getIndex(str) orelse return null].value;
113 }
114
115 pub fn getIndex(str: []const u8) ?usize {
116 if (empty_list)
117 return null;
118
119 if (str.len < precomputed.min_len or str.len > precomputed.max_len)
120 return null;
121
122 var i = precomputed.len_indexes[str.len];
123 while (true) {
124 const kv = precomputed.sorted_kvs[i];
125 if (kv.key.len != str.len)
126 return null;
127 if (eql(kv.key, str))
128 return i;
129 i += 1;
130 if (i >= precomputed.sorted_kvs.len)
131 return null;
132 }
133 }
134 };
135}
136
137const TestEnum = enum {
138 A,
139 B,
140 C,
141 D,
142 E,
143};
144
145test "list literal of list literals" {
146 const map = ComptimeStringMap(TestEnum, .{
147 .{ "these", .D },
148 .{ "have", .A },
149 .{ "nothing", .B },
150 .{ "incommon", .C },
151 .{ "samelen", .E },
152 });
153
154 try testMap(map);
155
156 // Default comparison is case sensitive
157 try std.testing.expect(null == map.get("NOTHING"));
158}
159
160test "array of structs" {
161 const KV = struct { []const u8, TestEnum };
162 const map = ComptimeStringMap(TestEnum, [_]KV{
163 .{ "these", .D },
164 .{ "have", .A },
165 .{ "nothing", .B },
166 .{ "incommon", .C },
167 .{ "samelen", .E },
168 });
169
170 try testMap(map);
171}
172
173test "slice of structs" {
174 const KV = struct { []const u8, TestEnum };
175 const slice: []const KV = &[_]KV{
176 .{ "these", .D },
177 .{ "have", .A },
178 .{ "nothing", .B },
179 .{ "incommon", .C },
180 .{ "samelen", .E },
181 };
182 const map = ComptimeStringMap(TestEnum, slice);
183
184 try testMap(map);
185}
186
187fn testMap(comptime map: anytype) !void {
188 try std.testing.expectEqual(TestEnum.A, map.get("have").?);
189 try std.testing.expectEqual(TestEnum.B, map.get("nothing").?);
190 try std.testing.expect(null == map.get("missing"));
191 try std.testing.expectEqual(TestEnum.D, map.get("these").?);
192 try std.testing.expectEqual(TestEnum.E, map.get("samelen").?);
193
194 try std.testing.expect(!map.has("missing"));
195 try std.testing.expect(map.has("these"));
196
197 try std.testing.expect(null == map.get(""));
198 try std.testing.expect(null == map.get("averylongstringthathasnomatches"));
199}
200
201test "void value type, slice of structs" {
202 const KV = struct { []const u8 };
203 const slice: []const KV = &[_]KV{
204 .{"these"},
205 .{"have"},
206 .{"nothing"},
207 .{"incommon"},
208 .{"samelen"},
209 };
210 const map = ComptimeStringMap(void, slice);
211
212 try testSet(map);
213
214 // Default comparison is case sensitive
215 try std.testing.expect(null == map.get("NOTHING"));
216}
217
218test "void value type, list literal of list literals" {
219 const map = ComptimeStringMap(void, .{
220 .{"these"},
221 .{"have"},
222 .{"nothing"},
223 .{"incommon"},
224 .{"samelen"},
225 });
226
227 try testSet(map);
228}
229
230fn testSet(comptime map: anytype) !void {
231 try std.testing.expectEqual({}, map.get("have").?);
232 try std.testing.expectEqual({}, map.get("nothing").?);
233 try std.testing.expect(null == map.get("missing"));
234 try std.testing.expectEqual({}, map.get("these").?);
235 try std.testing.expectEqual({}, map.get("samelen").?);
236
237 try std.testing.expect(!map.has("missing"));
238 try std.testing.expect(map.has("these"));
239
240 try std.testing.expect(null == map.get(""));
241 try std.testing.expect(null == map.get("averylongstringthathasnomatches"));
242}
243
244test "ComptimeStringMapWithEql" {
245 const map = ComptimeStringMapWithEql(TestEnum, .{
246 .{ "these", .D },
247 .{ "have", .A },
248 .{ "nothing", .B },
249 .{ "incommon", .C },
250 .{ "samelen", .E },
251 }, eqlAsciiIgnoreCase);
252
253 try testMap(map);
254 try std.testing.expectEqual(TestEnum.A, map.get("HAVE").?);
255 try std.testing.expectEqual(TestEnum.E, map.get("SameLen").?);
256 try std.testing.expect(null == map.get("SameLength"));
257
258 try std.testing.expect(map.has("ThESe"));
259}
260
261test "empty" {
262 const m1 = ComptimeStringMap(usize, .{});
263 try std.testing.expect(null == m1.get("anything"));
264
265 const m2 = ComptimeStringMapWithEql(usize, .{}, eqlAsciiIgnoreCase);
266 try std.testing.expect(null == m2.get("anything"));
267}
268
269test "redundant entries" {
270 const map = ComptimeStringMap(TestEnum, .{
271 .{ "redundant", .D },
272 .{ "theNeedle", .A },
273 .{ "redundant", .B },
274 .{ "re" ++ "dundant", .C },
275 .{ "redun" ++ "dant", .E },
276 });
277
278 // No promises about which one you get:
279 try std.testing.expect(null != map.get("redundant"));
280
281 // Default map is not case sensitive:
282 try std.testing.expect(null == map.get("REDUNDANT"));
283
284 try std.testing.expectEqual(TestEnum.A, map.get("theNeedle").?);
285}
286
287test "redundant insensitive" {
288 const map = ComptimeStringMapWithEql(TestEnum, .{
289 .{ "redundant", .D },
290 .{ "theNeedle", .A },
291 .{ "redundanT", .B },
292 .{ "RE" ++ "dundant", .C },
293 .{ "redun" ++ "DANT", .E },
294 }, eqlAsciiIgnoreCase);
295
296 // No promises about which result you'll get ...
297 try std.testing.expect(null != map.get("REDUNDANT"));
298 try std.testing.expect(null != map.get("ReDuNdAnT"));
299
300 try std.testing.expectEqual(TestEnum.A, map.get("theNeedle").?);
301}
302
303test "comptime-only value" {
304 const map = std.ComptimeStringMap(type, .{
305 .{ "a", struct {
306 pub const foo = 1;
307 } },
308 .{ "b", struct {
309 pub const foo = 2;
310 } },
311 .{ "c", struct {
312 pub const foo = 3;
313 } },
314 });
315
316 try std.testing.expect(map.get("a").?.foo == 1);
317 try std.testing.expect(map.get("b").?.foo == 2);
318 try std.testing.expect(map.get("c").?.foo == 3);
319 try std.testing.expect(map.get("d") == null);
320}
lib/std/crypto/Certificate.zig+5-5
...@@ -19,7 +19,7 @@ pub const Algorithm = enum {...@@ -19,7 +19,7 @@ pub const Algorithm = enum {
19 md5WithRSAEncryption,19 md5WithRSAEncryption,
20 curveEd25519,20 curveEd25519,
2121
22 pub const map = std.ComptimeStringMap(Algorithm, .{22 pub const map = std.StaticStringMap(Algorithm).initComptime(.{
23 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05 }, .sha1WithRSAEncryption },23 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05 }, .sha1WithRSAEncryption },
24 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B }, .sha256WithRSAEncryption },24 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B }, .sha256WithRSAEncryption },
25 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0C }, .sha384WithRSAEncryption },25 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0C }, .sha384WithRSAEncryption },
...@@ -52,7 +52,7 @@ pub const AlgorithmCategory = enum {...@@ -52,7 +52,7 @@ pub const AlgorithmCategory = enum {
52 X9_62_id_ecPublicKey,52 X9_62_id_ecPublicKey,
53 curveEd25519,53 curveEd25519,
5454
55 pub const map = std.ComptimeStringMap(AlgorithmCategory, .{55 pub const map = std.StaticStringMap(AlgorithmCategory).initComptime(.{
56 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 }, .rsaEncryption },56 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 }, .rsaEncryption },
57 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01 }, .X9_62_id_ecPublicKey },57 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01 }, .X9_62_id_ecPublicKey },
58 .{ &[_]u8{ 0x2B, 0x65, 0x70 }, .curveEd25519 },58 .{ &[_]u8{ 0x2B, 0x65, 0x70 }, .curveEd25519 },
...@@ -73,7 +73,7 @@ pub const Attribute = enum {...@@ -73,7 +73,7 @@ pub const Attribute = enum {
73 pkcs9_emailAddress,73 pkcs9_emailAddress,
74 domainComponent,74 domainComponent,
7575
76 pub const map = std.ComptimeStringMap(Attribute, .{76 pub const map = std.StaticStringMap(Attribute).initComptime(.{
77 .{ &[_]u8{ 0x55, 0x04, 0x03 }, .commonName },77 .{ &[_]u8{ 0x55, 0x04, 0x03 }, .commonName },
78 .{ &[_]u8{ 0x55, 0x04, 0x05 }, .serialNumber },78 .{ &[_]u8{ 0x55, 0x04, 0x05 }, .serialNumber },
79 .{ &[_]u8{ 0x55, 0x04, 0x06 }, .countryName },79 .{ &[_]u8{ 0x55, 0x04, 0x06 }, .countryName },
...@@ -94,7 +94,7 @@ pub const NamedCurve = enum {...@@ -94,7 +94,7 @@ pub const NamedCurve = enum {
94 secp521r1,94 secp521r1,
95 X9_62_prime256v1,95 X9_62_prime256v1,
9696
97 pub const map = std.ComptimeStringMap(NamedCurve, .{97 pub const map = std.StaticStringMap(NamedCurve).initComptime(.{
98 .{ &[_]u8{ 0x2B, 0x81, 0x04, 0x00, 0x22 }, .secp384r1 },98 .{ &[_]u8{ 0x2B, 0x81, 0x04, 0x00, 0x22 }, .secp384r1 },
99 .{ &[_]u8{ 0x2B, 0x81, 0x04, 0x00, 0x23 }, .secp521r1 },99 .{ &[_]u8{ 0x2B, 0x81, 0x04, 0x00, 0x23 }, .secp521r1 },
100 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07 }, .X9_62_prime256v1 },100 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07 }, .X9_62_prime256v1 },
...@@ -130,7 +130,7 @@ pub const ExtensionId = enum {...@@ -130,7 +130,7 @@ pub const ExtensionId = enum {
130 netscape_cert_type,130 netscape_cert_type,
131 netscape_comment,131 netscape_comment,
132132
133 pub const map = std.ComptimeStringMap(ExtensionId, .{133 pub const map = std.StaticStringMap(ExtensionId).initComptime(.{
134 .{ &[_]u8{ 0x55, 0x04, 0x03 }, .commonName },134 .{ &[_]u8{ 0x55, 0x04, 0x03 }, .commonName },
135 .{ &[_]u8{ 0x55, 0x1D, 0x01 }, .authority_key_identifier },135 .{ &[_]u8{ 0x55, 0x1D, 0x01 }, .authority_key_identifier },
136 .{ &[_]u8{ 0x55, 0x1D, 0x07 }, .subject_alt_name },136 .{ &[_]u8{ 0x55, 0x1D, 0x07 }, .subject_alt_name },
lib/std/fs/test.zig+4-4
...@@ -1641,7 +1641,7 @@ test "walker" {...@@ -1641,7 +1641,7 @@ test "walker" {
16411641
1642 // iteration order of walker is undefined, so need lookup maps to check against1642 // iteration order of walker is undefined, so need lookup maps to check against
16431643
1644 const expected_paths = std.ComptimeStringMap(void, .{1644 const expected_paths = std.StaticStringMap(void).initComptime(.{
1645 .{"dir1"},1645 .{"dir1"},
1646 .{"dir2"},1646 .{"dir2"},
1647 .{"dir3"},1647 .{"dir3"},
...@@ -1651,7 +1651,7 @@ test "walker" {...@@ -1651,7 +1651,7 @@ test "walker" {
1651 .{"dir3" ++ fs.path.sep_str ++ "sub2" ++ fs.path.sep_str ++ "subsub1"},1651 .{"dir3" ++ fs.path.sep_str ++ "sub2" ++ fs.path.sep_str ++ "subsub1"},
1652 });1652 });
16531653
1654 const expected_basenames = std.ComptimeStringMap(void, .{1654 const expected_basenames = std.StaticStringMap(void).initComptime(.{
1655 .{"dir1"},1655 .{"dir1"},
1656 .{"dir2"},1656 .{"dir2"},
1657 .{"dir3"},1657 .{"dir3"},
...@@ -1661,8 +1661,8 @@ test "walker" {...@@ -1661,8 +1661,8 @@ test "walker" {
1661 .{"subsub1"},1661 .{"subsub1"},
1662 });1662 });
16631663
1664 for (expected_paths.kvs) |kv| {1664 for (expected_paths.keys()) |key| {
1665 try tmp.dir.makePath(kv.key);1665 try tmp.dir.makePath(key);
1666 }1666 }
16671667
1668 var walker = try tmp.dir.walk(testing.allocator);1668 var walker = try tmp.dir.walk(testing.allocator);
lib/std/http/Client.zig+1-1
...@@ -1570,7 +1570,7 @@ pub const RequestOptions = struct {...@@ -1570,7 +1570,7 @@ pub const RequestOptions = struct {
1570};1570};
15711571
1572fn validateUri(uri: Uri, arena: Allocator) !struct { Connection.Protocol, Uri } {1572fn validateUri(uri: Uri, arena: Allocator) !struct { Connection.Protocol, Uri } {
1573 const protocol_map = std.ComptimeStringMap(Connection.Protocol, .{1573 const protocol_map = std.StaticStringMap(Connection.Protocol).initComptime(.{
1574 .{ "http", .plain },1574 .{ "http", .plain },
1575 .{ "ws", .plain },1575 .{ "ws", .plain },
1576 .{ "https", .tls },1576 .{ "https", .tls },
lib/std/meta.zig+2-2
...@@ -19,7 +19,7 @@ pub const isTag = @compileError("deprecated; use 'tagged_value == @field(E, tag_...@@ -19,7 +19,7 @@ pub const isTag = @compileError("deprecated; use 'tagged_value == @field(E, tag_
1919
20/// Returns the variant of an enum type, `T`, which is named `str`, or `null` if no such variant exists.20/// Returns the variant of an enum type, `T`, which is named `str`, or `null` if no such variant exists.
21pub fn stringToEnum(comptime T: type, str: []const u8) ?T {21pub fn stringToEnum(comptime T: type, str: []const u8) ?T {
22 // Using ComptimeStringMap here is more performant, but it will start to take too22 // Using StaticStringMap here is more performant, but it will start to take too
23 // long to compile if the enum is large enough, due to the current limits of comptime23 // long to compile if the enum is large enough, due to the current limits of comptime
24 // performance when doing things like constructing lookup maps at comptime.24 // performance when doing things like constructing lookup maps at comptime.
25 // TODO The '100' here is arbitrary and should be increased when possible:25 // TODO The '100' here is arbitrary and should be increased when possible:
...@@ -34,7 +34,7 @@ pub fn stringToEnum(comptime T: type, str: []const u8) ?T {...@@ -34,7 +34,7 @@ pub fn stringToEnum(comptime T: type, str: []const u8) ?T {
34 }34 }
35 break :build_kvs kvs_array[0..];35 break :build_kvs kvs_array[0..];
36 };36 };
37 const map = std.ComptimeStringMap(T, kvs);37 const map = std.StaticStringMap(T).initComptime(kvs);
38 return map.get(str);38 return map.get(str);
39 } else {39 } else {
40 inline for (@typeInfo(T).Enum.fields) |enumField| {40 inline for (@typeInfo(T).Enum.fields) |enumField| {
lib/std/static_string_map.zig created+502
...@@ -0,0 +1,502 @@
1const std = @import("std.zig");
2const mem = std.mem;
3
4/// Static string map optimized for small sets of disparate string keys.
5/// Works by separating the keys by length at initialization and only checking
6/// strings of equal length at runtime.
7pub fn StaticStringMap(comptime V: type) type {
8 return StaticStringMapWithEql(V, defaultEql);
9}
10
11/// Like `std.mem.eql`, but takes advantage of the fact that the lengths
12/// of `a` and `b` are known to be equal.
13pub fn defaultEql(a: []const u8, b: []const u8) bool {
14 if (a.ptr == b.ptr) return true;
15 for (a, b) |a_elem, b_elem| {
16 if (a_elem != b_elem) return false;
17 }
18 return true;
19}
20
21/// Like `std.ascii.eqlIgnoreCase` but takes advantage of the fact that
22/// the lengths of `a` and `b` are known to be equal.
23pub fn eqlAsciiIgnoreCase(a: []const u8, b: []const u8) bool {
24 if (a.ptr == b.ptr) return true;
25 for (a, b) |a_c, b_c| {
26 if (std.ascii.toLower(a_c) != std.ascii.toLower(b_c)) return false;
27 }
28 return true;
29}
30
31/// StaticStringMap, but accepts an equality function (`eql`).
32/// The `eql` function is only called to determine the equality
33/// of equal length strings. Any strings that are not equal length
34/// are never compared using the `eql` function.
35pub fn StaticStringMapWithEql(
36 comptime V: type,
37 comptime eql: fn (a: []const u8, b: []const u8) bool,
38) type {
39 return struct {
40 kvs: *const KVs = &empty_kvs,
41 len_indexes: [*]const u32 = &empty_len_indexes,
42 len_indexes_len: u32 = 0,
43 min_len: u32 = std.math.maxInt(u32),
44 max_len: u32 = 0,
45
46 pub const KV = struct {
47 key: []const u8,
48 value: V,
49 };
50
51 const Self = @This();
52 const KVs = struct {
53 keys: [*]const []const u8,
54 values: [*]const V,
55 len: u32,
56 };
57 const empty_kvs = KVs{
58 .keys = &empty_keys,
59 .values = &empty_vals,
60 .len = 0,
61 };
62 const empty_len_indexes = [0]u32{};
63 const empty_keys = [0][]const u8{};
64 const empty_vals = [0]V{};
65
66 /// Returns a map backed by static, comptime allocated memory.
67 ///
68 /// `kvs_list` must be either a list of `struct { []const u8, V }`
69 /// (key-value pair) tuples, or a list of `struct { []const u8 }`
70 /// (only keys) tuples if `V` is `void`.
71 pub inline fn initComptime(comptime kvs_list: anytype) Self {
72 comptime {
73 @setEvalBranchQuota(30 * kvs_list.len);
74 var self = Self{};
75 if (kvs_list.len == 0)
76 return self;
77
78 var sorted_keys: [kvs_list.len][]const u8 = undefined;
79 var sorted_vals: [kvs_list.len]V = undefined;
80
81 self.initSortedKVs(kvs_list, &sorted_keys, &sorted_vals);
82 const final_keys = sorted_keys;
83 const final_vals = sorted_vals;
84 self.kvs = &.{
85 .keys = &final_keys,
86 .values = &final_vals,
87 .len = @intCast(kvs_list.len),
88 };
89
90 var len_indexes: [self.max_len + 1]u32 = undefined;
91 self.initLenIndexes(&len_indexes);
92 const final_len_indexes = len_indexes;
93 self.len_indexes = &final_len_indexes;
94 self.len_indexes_len = @intCast(len_indexes.len);
95 return self;
96 }
97 }
98
99 /// Returns a map backed by memory allocated with `allocator`.
100 ///
101 /// Handles `kvs_list` the same way as `initComptime()`.
102 pub fn init(kvs_list: anytype, allocator: mem.Allocator) !Self {
103 var self = Self{};
104 if (kvs_list.len == 0)
105 return self;
106
107 const sorted_keys = try allocator.alloc([]const u8, kvs_list.len);
108 errdefer allocator.free(sorted_keys);
109 const sorted_vals = try allocator.alloc(V, kvs_list.len);
110 errdefer allocator.free(sorted_vals);
111 const kvs = try allocator.create(KVs);
112 errdefer allocator.destroy(kvs);
113
114 self.initSortedKVs(kvs_list, sorted_keys, sorted_vals);
115 kvs.* = .{
116 .keys = sorted_keys.ptr,
117 .values = sorted_vals.ptr,
118 .len = kvs_list.len,
119 };
120 self.kvs = kvs;
121
122 const len_indexes = try allocator.alloc(u32, self.max_len + 1);
123 self.initLenIndexes(len_indexes);
124 self.len_indexes = len_indexes.ptr;
125 self.len_indexes_len = @intCast(len_indexes.len);
126 return self;
127 }
128
129 /// this method should only be used with init() and not with initComptime().
130 pub fn deinit(self: Self, allocator: mem.Allocator) void {
131 allocator.free(self.len_indexes[0..self.len_indexes_len]);
132 allocator.free(self.kvs.keys[0..self.kvs.len]);
133 allocator.free(self.kvs.values[0..self.kvs.len]);
134 allocator.destroy(self.kvs);
135 }
136
137 const SortContext = struct {
138 keys: [][]const u8,
139 vals: []V,
140
141 pub fn lessThan(ctx: @This(), a: usize, b: usize) bool {
142 return ctx.keys[a].len < ctx.keys[b].len;
143 }
144
145 pub fn swap(ctx: @This(), a: usize, b: usize) void {
146 std.mem.swap([]const u8, &ctx.keys[a], &ctx.keys[b]);
147 std.mem.swap(V, &ctx.vals[a], &ctx.vals[b]);
148 }
149 };
150
151 fn initSortedKVs(
152 self: *Self,
153 kvs_list: anytype,
154 sorted_keys: [][]const u8,
155 sorted_vals: []V,
156 ) void {
157 for (kvs_list, 0..) |kv, i| {
158 sorted_keys[i] = kv.@"0";
159 sorted_vals[i] = if (V == void) {} else kv.@"1";
160 self.min_len = @intCast(@min(self.min_len, kv.@"0".len));
161 self.max_len = @intCast(@max(self.max_len, kv.@"0".len));
162 }
163 mem.sortUnstableContext(0, sorted_keys.len, SortContext{
164 .keys = sorted_keys,
165 .vals = sorted_vals,
166 });
167 }
168
169 fn initLenIndexes(self: Self, len_indexes: []u32) void {
170 var len: usize = 0;
171 var i: u32 = 0;
172 while (len <= self.max_len) : (len += 1) {
173 // find the first keyword len == len
174 while (len > self.kvs.keys[i].len) {
175 i += 1;
176 }
177 len_indexes[len] = i;
178 }
179 }
180
181 /// Checks if the map has a value for the key.
182 pub fn has(self: Self, str: []const u8) bool {
183 return self.get(str) != null;
184 }
185
186 /// Returns the value for the key if any, else null.
187 pub fn get(self: Self, str: []const u8) ?V {
188 if (self.kvs.len == 0)
189 return null;
190
191 return self.kvs.values[self.getIndex(str) orelse return null];
192 }
193
194 pub fn getIndex(self: Self, str: []const u8) ?usize {
195 const kvs = self.kvs.*;
196 if (kvs.len == 0)
197 return null;
198
199 if (str.len < self.min_len or str.len > self.max_len)
200 return null;
201
202 var i = self.len_indexes[str.len];
203 while (true) {
204 const key = kvs.keys[i];
205 if (key.len != str.len)
206 return null;
207 if (eql(key, str))
208 return i;
209 i += 1;
210 if (i >= kvs.len)
211 return null;
212 }
213 }
214
215 /// Returns the longest key, value pair where key is a prefix of `str`
216 /// else null.
217 pub fn getLongestPrefix(self: Self, str: []const u8) ?KV {
218 if (self.kvs.len == 0)
219 return null;
220 const i = self.getLongestPrefixIndex(str) orelse return null;
221 const kvs = self.kvs.*;
222 return .{
223 .key = kvs.keys[i],
224 .value = kvs.values[i],
225 };
226 }
227
228 pub fn getLongestPrefixIndex(self: Self, str: []const u8) ?usize {
229 if (self.kvs.len == 0)
230 return null;
231
232 if (str.len < self.min_len)
233 return null;
234
235 var len = @min(self.max_len, str.len);
236 while (len >= self.min_len) : (len -= 1) {
237 if (self.getIndex(str[0..len])) |i|
238 return i;
239 }
240 return null;
241 }
242
243 pub fn keys(self: Self) []const []const u8 {
244 const kvs = self.kvs.*;
245 return kvs.keys[0..kvs.len];
246 }
247
248 pub fn values(self: Self) []const V {
249 const kvs = self.kvs.*;
250 return kvs.values[0..kvs.len];
251 }
252 };
253}
254
255const TestEnum = enum { A, B, C, D, E };
256const TestMap = StaticStringMap(TestEnum);
257const TestKV = struct { []const u8, TestEnum };
258const TestMapVoid = StaticStringMap(void);
259const TestKVVoid = struct { []const u8 };
260const TestMapWithEql = StaticStringMapWithEql(TestEnum, eqlAsciiIgnoreCase);
261const testing = std.testing;
262const test_alloc = testing.allocator;
263
264test "list literal of list literals" {
265 const slice = [_]TestKV{
266 .{ "these", .D },
267 .{ "have", .A },
268 .{ "nothing", .B },
269 .{ "incommon", .C },
270 .{ "samelen", .E },
271 };
272 const map = TestMap.initComptime(slice);
273 try testMap(map);
274 // Default comparison is case sensitive
275 try testing.expect(null == map.get("NOTHING"));
276
277 // runtime init(), deinit()
278 const map_rt = try TestMap.init(slice, test_alloc);
279 defer map_rt.deinit(test_alloc);
280 try testMap(map_rt);
281 // Default comparison is case sensitive
282 try testing.expect(null == map_rt.get("NOTHING"));
283}
284
285test "array of structs" {
286 const slice = [_]TestKV{
287 .{ "these", .D },
288 .{ "have", .A },
289 .{ "nothing", .B },
290 .{ "incommon", .C },
291 .{ "samelen", .E },
292 };
293
294 try testMap(TestMap.initComptime(slice));
295}
296
297test "slice of structs" {
298 const slice = [_]TestKV{
299 .{ "these", .D },
300 .{ "have", .A },
301 .{ "nothing", .B },
302 .{ "incommon", .C },
303 .{ "samelen", .E },
304 };
305
306 try testMap(TestMap.initComptime(slice));
307}
308
309fn testMap(map: anytype) !void {
310 try testing.expectEqual(TestEnum.A, map.get("have").?);
311 try testing.expectEqual(TestEnum.B, map.get("nothing").?);
312 try testing.expect(null == map.get("missing"));
313 try testing.expectEqual(TestEnum.D, map.get("these").?);
314 try testing.expectEqual(TestEnum.E, map.get("samelen").?);
315
316 try testing.expect(!map.has("missing"));
317 try testing.expect(map.has("these"));
318
319 try testing.expect(null == map.get(""));
320 try testing.expect(null == map.get("averylongstringthathasnomatches"));
321}
322
323test "void value type, slice of structs" {
324 const slice = [_]TestKVVoid{
325 .{"these"},
326 .{"have"},
327 .{"nothing"},
328 .{"incommon"},
329 .{"samelen"},
330 };
331 const map = TestMapVoid.initComptime(slice);
332 try testSet(map);
333 // Default comparison is case sensitive
334 try testing.expect(null == map.get("NOTHING"));
335}
336
337test "void value type, list literal of list literals" {
338 const slice = [_]TestKVVoid{
339 .{"these"},
340 .{"have"},
341 .{"nothing"},
342 .{"incommon"},
343 .{"samelen"},
344 };
345
346 try testSet(TestMapVoid.initComptime(slice));
347}
348
349fn testSet(map: TestMapVoid) !void {
350 try testing.expectEqual({}, map.get("have").?);
351 try testing.expectEqual({}, map.get("nothing").?);
352 try testing.expect(null == map.get("missing"));
353 try testing.expectEqual({}, map.get("these").?);
354 try testing.expectEqual({}, map.get("samelen").?);
355
356 try testing.expect(!map.has("missing"));
357 try testing.expect(map.has("these"));
358
359 try testing.expect(null == map.get(""));
360 try testing.expect(null == map.get("averylongstringthathasnomatches"));
361}
362
363fn testStaticStringMapWithEql(map: TestMapWithEql) !void {
364 try testMap(map);
365 try testing.expectEqual(TestEnum.A, map.get("HAVE").?);
366 try testing.expectEqual(TestEnum.E, map.get("SameLen").?);
367 try testing.expect(null == map.get("SameLength"));
368 try testing.expect(map.has("ThESe"));
369}
370
371test "StaticStringMapWithEql" {
372 const slice = [_]TestKV{
373 .{ "these", .D },
374 .{ "have", .A },
375 .{ "nothing", .B },
376 .{ "incommon", .C },
377 .{ "samelen", .E },
378 };
379
380 try testStaticStringMapWithEql(TestMapWithEql.initComptime(slice));
381}
382
383test "empty" {
384 const m1 = StaticStringMap(usize).initComptime(.{});
385 try testing.expect(null == m1.get("anything"));
386
387 const m2 = StaticStringMapWithEql(usize, eqlAsciiIgnoreCase).initComptime(.{});
388 try testing.expect(null == m2.get("anything"));
389
390 const m3 = try StaticStringMap(usize).init(.{}, test_alloc);
391 try testing.expect(null == m3.get("anything"));
392
393 const m4 = try StaticStringMapWithEql(usize, eqlAsciiIgnoreCase).init(.{}, test_alloc);
394 try testing.expect(null == m4.get("anything"));
395}
396
397test "redundant entries" {
398 const slice = [_]TestKV{
399 .{ "redundant", .D },
400 .{ "theNeedle", .A },
401 .{ "redundant", .B },
402 .{ "re" ++ "dundant", .C },
403 .{ "redun" ++ "dant", .E },
404 };
405 const map = TestMap.initComptime(slice);
406
407 // No promises about which one you get:
408 try testing.expect(null != map.get("redundant"));
409
410 // Default map is not case sensitive:
411 try testing.expect(null == map.get("REDUNDANT"));
412
413 try testing.expectEqual(TestEnum.A, map.get("theNeedle").?);
414}
415
416test "redundant insensitive" {
417 const slice = [_]TestKV{
418 .{ "redundant", .D },
419 .{ "theNeedle", .A },
420 .{ "redundanT", .B },
421 .{ "RE" ++ "dundant", .C },
422 .{ "redun" ++ "DANT", .E },
423 };
424
425 const map = TestMapWithEql.initComptime(slice);
426
427 // No promises about which result you'll get ...
428 try testing.expect(null != map.get("REDUNDANT"));
429 try testing.expect(null != map.get("ReDuNdAnT"));
430 try testing.expectEqual(TestEnum.A, map.get("theNeedle").?);
431}
432
433test "comptime-only value" {
434 const map = StaticStringMap(type).initComptime(.{
435 .{ "a", struct {
436 pub const foo = 1;
437 } },
438 .{ "b", struct {
439 pub const foo = 2;
440 } },
441 .{ "c", struct {
442 pub const foo = 3;
443 } },
444 });
445
446 try testing.expect(map.get("a").?.foo == 1);
447 try testing.expect(map.get("b").?.foo == 2);
448 try testing.expect(map.get("c").?.foo == 3);
449 try testing.expect(map.get("d") == null);
450}
451
452test "getLongestPrefix" {
453 const slice = [_]TestKV{
454 .{ "a", .A },
455 .{ "aa", .B },
456 .{ "aaa", .C },
457 .{ "aaaa", .D },
458 };
459
460 const map = TestMap.initComptime(slice);
461
462 try testing.expectEqual(null, map.getLongestPrefix(""));
463 try testing.expectEqual(null, map.getLongestPrefix("bar"));
464 try testing.expectEqualStrings("aaaa", map.getLongestPrefix("aaaabar").?.key);
465 try testing.expectEqualStrings("aaa", map.getLongestPrefix("aaabar").?.key);
466}
467
468test "getLongestPrefix2" {
469 const slice = [_]struct { []const u8, u8 }{
470 .{ "one", 1 },
471 .{ "two", 2 },
472 .{ "three", 3 },
473 .{ "four", 4 },
474 .{ "five", 5 },
475 .{ "six", 6 },
476 .{ "seven", 7 },
477 .{ "eight", 8 },
478 .{ "nine", 9 },
479 };
480 const map = StaticStringMap(u8).initComptime(slice);
481
482 try testing.expectEqual(1, map.get("one"));
483 try testing.expectEqual(null, map.get("o"));
484 try testing.expectEqual(null, map.get("onexxx"));
485 try testing.expectEqual(9, map.get("nine"));
486 try testing.expectEqual(null, map.get("n"));
487 try testing.expectEqual(null, map.get("ninexxx"));
488 try testing.expectEqual(null, map.get("xxx"));
489
490 try testing.expectEqual(1, map.getLongestPrefix("one").?.value);
491 try testing.expectEqual(1, map.getLongestPrefix("onexxx").?.value);
492 try testing.expectEqual(null, map.getLongestPrefix("o"));
493 try testing.expectEqual(null, map.getLongestPrefix("on"));
494 try testing.expectEqual(9, map.getLongestPrefix("nine").?.value);
495 try testing.expectEqual(9, map.getLongestPrefix("ninexxx").?.value);
496 try testing.expectEqual(null, map.getLongestPrefix("n"));
497 try testing.expectEqual(null, map.getLongestPrefix("xxx"));
498}
499
500test "long kvs_list doesn't exceed @setEvalBranchQuota" {
501 _ = TestMapVoid.initComptime([1]TestKVVoid{.{"x"}} ** 1_000);
502}
lib/std/std.zig+3-3
...@@ -16,8 +16,8 @@ pub const BufMap = @import("buf_map.zig").BufMap;...@@ -16,8 +16,8 @@ pub const BufMap = @import("buf_map.zig").BufMap;
16pub const BufSet = @import("buf_set.zig").BufSet;16pub const BufSet = @import("buf_set.zig").BufSet;
17/// Deprecated: use `process.Child`.17/// Deprecated: use `process.Child`.
18pub const ChildProcess = @import("child_process.zig").ChildProcess;18pub const ChildProcess = @import("child_process.zig").ChildProcess;
19pub const ComptimeStringMap = comptime_string_map.ComptimeStringMap;19pub const StaticStringMap = static_string_map.StaticStringMap;
20pub const ComptimeStringMapWithEql = comptime_string_map.ComptimeStringMapWithEql;20pub const StaticStringMapWithEql = static_string_map.StaticStringMapWithEql;
21pub const DoublyLinkedList = @import("linked_list.zig").DoublyLinkedList;21pub const DoublyLinkedList = @import("linked_list.zig").DoublyLinkedList;
22pub const DynLib = @import("dynamic_library.zig").DynLib;22pub const DynLib = @import("dynamic_library.zig").DynLib;
23pub const DynamicBitSet = bit_set.DynamicBitSet;23pub const DynamicBitSet = bit_set.DynamicBitSet;
...@@ -62,7 +62,7 @@ pub const builtin = @import("builtin.zig");...@@ -62,7 +62,7 @@ pub const builtin = @import("builtin.zig");
62pub const c = @import("c.zig");62pub const c = @import("c.zig");
63pub const coff = @import("coff.zig");63pub const coff = @import("coff.zig");
64pub const compress = @import("compress.zig");64pub const compress = @import("compress.zig");
65pub const comptime_string_map = @import("comptime_string_map.zig");65pub const static_string_map = @import("static_string_map.zig");
66pub const crypto = @import("crypto.zig");66pub const crypto = @import("crypto.zig");
67pub const debug = @import("debug.zig");67pub const debug = @import("debug.zig");
68pub const dwarf = @import("dwarf.zig");68pub const dwarf = @import("dwarf.zig");
lib/std/zig/AstGen.zig+7-7
...@@ -10125,7 +10125,7 @@ fn calleeExpr(...@@ -10125,7 +10125,7 @@ fn calleeExpr(
10125 }10125 }
10126}10126}
1012710127
10128const primitive_instrs = std.ComptimeStringMap(Zir.Inst.Ref, .{10128const primitive_instrs = std.StaticStringMap(Zir.Inst.Ref).initComptime(.{
10129 .{ "anyerror", .anyerror_type },10129 .{ "anyerror", .anyerror_type },
10130 .{ "anyframe", .anyframe_type },10130 .{ "anyframe", .anyframe_type },
10131 .{ "anyopaque", .anyopaque_type },10131 .{ "anyopaque", .anyopaque_type },
...@@ -10173,14 +10173,14 @@ const primitive_instrs = std.ComptimeStringMap(Zir.Inst.Ref, .{...@@ -10173,14 +10173,14 @@ const primitive_instrs = std.ComptimeStringMap(Zir.Inst.Ref, .{
10173comptime {10173comptime {
10174 // These checks ensure that std.zig.primitives stays in sync with the primitive->Zir map.10174 // These checks ensure that std.zig.primitives stays in sync with the primitive->Zir map.
10175 const primitives = std.zig.primitives;10175 const primitives = std.zig.primitives;
10176 for (primitive_instrs.kvs) |kv| {10176 for (primitive_instrs.keys(), primitive_instrs.values()) |key, value| {
10177 if (!primitives.isPrimitive(kv.key)) {10177 if (!primitives.isPrimitive(key)) {
10178 @compileError("std.zig.isPrimitive() is not aware of Zir instr '" ++ @tagName(kv.value) ++ "'");10178 @compileError("std.zig.isPrimitive() is not aware of Zir instr '" ++ @tagName(value) ++ "'");
10179 }10179 }
10180 }10180 }
10181 for (primitives.names.kvs) |kv| {10181 for (primitives.names.keys()) |key| {
10182 if (primitive_instrs.get(kv.key) == null) {10182 if (primitive_instrs.get(key) == null) {
10183 @compileError("std.zig.primitives entry '" ++ kv.key ++ "' does not have a corresponding Zir instr");10183 @compileError("std.zig.primitives entry '" ++ key ++ "' does not have a corresponding Zir instr");
10184 }10184 }
10185 }10185 }
10186}10186}
lib/std/zig/BuiltinFn.zig+1-1
...@@ -160,7 +160,7 @@ param_count: ?u8,...@@ -160,7 +160,7 @@ param_count: ?u8,
160160
161pub const list = list: {161pub const list = list: {
162 @setEvalBranchQuota(3000);162 @setEvalBranchQuota(3000);
163 break :list std.ComptimeStringMap(@This(), .{163 break :list std.StaticStringMap(@This()).initComptime(.{
164 .{164 .{
165 "@addWithOverflow",165 "@addWithOverflow",
166 .{166 .{
lib/std/zig/primitives.zig+1-1
...@@ -2,7 +2,7 @@ const std = @import("std");...@@ -2,7 +2,7 @@ const std = @import("std");
22
3/// Set of primitive type and value names.3/// Set of primitive type and value names.
4/// Does not include `_` or integer type names.4/// Does not include `_` or integer type names.
5pub const names = std.ComptimeStringMap(void, .{5pub const names = std.StaticStringMap(void).initComptime(.{
6 .{"anyerror"},6 .{"anyerror"},
7 .{"anyframe"},7 .{"anyframe"},
8 .{"anyopaque"},8 .{"anyopaque"},
lib/std/zig/render.zig+4-4
...@@ -2886,11 +2886,11 @@ fn renderIdentifier(r: *Render, token_index: Ast.TokenIndex, space: Space, quote...@@ -2886,11 +2886,11 @@ fn renderIdentifier(r: *Render, token_index: Ast.TokenIndex, space: Space, quote
2886 // If we read the whole thing, we have to do further checks.2886 // If we read the whole thing, we have to do further checks.
2887 const longest_keyword_or_primitive_len = comptime blk: {2887 const longest_keyword_or_primitive_len = comptime blk: {
2888 var longest = 0;2888 var longest = 0;
2889 for (primitives.names.kvs) |kv| {2889 for (primitives.names.keys()) |key| {
2890 if (kv.key.len > longest) longest = kv.key.len;2890 if (key.len > longest) longest = key.len;
2891 }2891 }
2892 for (std.zig.Token.keywords.kvs) |kv| {2892 for (std.zig.Token.keywords.keys()) |key| {
2893 if (kv.key.len > longest) longest = kv.key.len;2893 if (key.len > longest) longest = key.len;
2894 }2894 }
2895 break :blk longest;2895 break :blk longest;
2896 };2896 };
lib/std/zig/tokenizer.zig+1-1
...@@ -9,7 +9,7 @@ pub const Token = struct {...@@ -9,7 +9,7 @@ pub const Token = struct {
9 end: usize,9 end: usize,
10 };10 };
1111
12 pub const keywords = std.ComptimeStringMap(Tag, .{12 pub const keywords = std.StaticStringMap(Tag).initComptime(.{
13 .{ "addrspace", .keyword_addrspace },13 .{ "addrspace", .keyword_addrspace },
14 .{ "align", .keyword_align },14 .{ "align", .keyword_align },
15 .{ "allowzero", .keyword_allowzero },15 .{ "allowzero", .keyword_allowzero },
src/Compilation.zig+1-1
...@@ -265,7 +265,7 @@ pub const CRTFile = struct {...@@ -265,7 +265,7 @@ pub const CRTFile = struct {
265265
266/// Supported languages for "zig clang -x <lang>".266/// Supported languages for "zig clang -x <lang>".
267/// Loosely based on llvm-project/clang/include/clang/Driver/Types.def267/// Loosely based on llvm-project/clang/include/clang/Driver/Types.def
268pub const LangToExt = std.ComptimeStringMap(FileExt, .{268pub const LangToExt = std.StaticStringMap(FileExt).initComptime(.{
269 .{ "c", .c },269 .{ "c", .c },
270 .{ "c-header", .h },270 .{ "c-header", .h },
271 .{ "c++", .cpp },271 .{ "c++", .cpp },
src/codegen/c.zig+1-1
...@@ -116,7 +116,7 @@ const ValueRenderLocation = enum {...@@ -116,7 +116,7 @@ const ValueRenderLocation = enum {
116116
117const BuiltinInfo = enum { none, bits };117const BuiltinInfo = enum { none, bits };
118118
119const reserved_idents = std.ComptimeStringMap(void, .{119const reserved_idents = std.StaticStringMap(void).initComptime(.{
120 // C language120 // C language
121 .{ "alignas", {121 .{ "alignas", {
122 @setEvalBranchQuota(4000);122 @setEvalBranchQuota(4000);
src/link/Wasm/types.zig+1-1
...@@ -244,7 +244,7 @@ pub const Feature = struct {...@@ -244,7 +244,7 @@ pub const Feature = struct {
244 }244 }
245};245};
246246
247pub const known_features = std.ComptimeStringMap(Feature.Tag, .{247pub const known_features = std.StaticStringMap(Feature.Tag).initComptime(.{
248 .{ "atomics", .atomics },248 .{ "atomics", .atomics },
249 .{ "bulk-memory", .bulk_memory },249 .{ "bulk-memory", .bulk_memory },
250 .{ "exception-handling", .exception_handling },250 .{ "exception-handling", .exception_handling },
src/translate_c.zig+1-1
...@@ -671,7 +671,7 @@ fn visitVarDecl(c: *Context, var_decl: *const clang.VarDecl, mangled_name: ?[]co...@@ -671,7 +671,7 @@ fn visitVarDecl(c: *Context, var_decl: *const clang.VarDecl, mangled_name: ?[]co
671 return addTopLevelDecl(c, var_name, node);671 return addTopLevelDecl(c, var_name, node);
672}672}
673673
674const builtin_typedef_map = std.ComptimeStringMap([]const u8, .{674const builtin_typedef_map = std.StaticStringMap([]const u8).initComptime(.{
675 .{ "uint8_t", "u8" },675 .{ "uint8_t", "u8" },
676 .{ "int8_t", "i8" },676 .{ "int8_t", "i8" },
677 .{ "uint16_t", "u16" },677 .{ "uint16_t", "u16" },
test/src/Cases.zig+1-1
...@@ -993,7 +993,7 @@ const TestManifest = struct {...@@ -993,7 +993,7 @@ const TestManifest = struct {
993 config_map: std.StringHashMap([]const u8),993 config_map: std.StringHashMap([]const u8),
994 trailing_bytes: []const u8 = "",994 trailing_bytes: []const u8 = "",
995995
996 const valid_keys = std.ComptimeStringMap(void, .{996 const valid_keys = std.StaticStringMap(void).initComptime(.{
997 .{ "is_test", {} },997 .{ "is_test", {} },
998 .{ "output_mode", {} },998 .{ "output_mode", {} },
999 .{ "target", {} },999 .{ "target", {} },
tools/gen_spirv_spec.zig+1-1
...@@ -45,7 +45,7 @@ const OperandKindMap = std.ArrayHashMap(StringPair, OperandKind, StringPairConte...@@ -45,7 +45,7 @@ const OperandKindMap = std.ArrayHashMap(StringPair, OperandKind, StringPairConte
45/// Khronos made it so that these names are not defined explicitly, so45/// Khronos made it so that these names are not defined explicitly, so
46/// we need to hardcode it (like they did).46/// we need to hardcode it (like they did).
47/// See https://github.com/KhronosGroup/SPIRV-Registry/47/// See https://github.com/KhronosGroup/SPIRV-Registry/
48const set_names = std.ComptimeStringMap([]const u8, .{48const set_names = std.StaticStringMap([]const u8).initComptime(.{
49 .{ "opencl.std.100", "OpenCL.std" },49 .{ "opencl.std.100", "OpenCL.std" },
50 .{ "glsl.std.450", "GLSL.std.450" },50 .{ "glsl.std.450", "GLSL.std.450" },
51 .{ "opencl.debuginfo.100", "OpenCL.DebugInfo.100" },51 .{ "opencl.debuginfo.100", "OpenCL.DebugInfo.100" },
tools/generate_linux_syscalls.zig+1-1
...@@ -9,7 +9,7 @@ const fmt = std.fmt;...@@ -9,7 +9,7 @@ const fmt = std.fmt;
9const zig = std.zig;9const zig = std.zig;
10const fs = std.fs;10const fs = std.fs;
1111
12const stdlib_renames = std.ComptimeStringMap([]const u8, .{12const stdlib_renames = std.StaticStringMap([]const u8).initComptime(.{
13 // Most 64-bit archs.13 // Most 64-bit archs.
14 .{ "newfstatat", "fstatat64" },14 .{ "newfstatat", "fstatat64" },
15 // POWER.15 // POWER.