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.
This commit is contained in:
Travis Staloch 2024-04-20 23:14:39 -07:00 committed by Andrew Kelley
parent fefdbca6e6
commit 8af59d1f98
25 changed files with 608 additions and 387 deletions

View File

@ -222,7 +222,7 @@ set(ZIG_STAGE2_SOURCES
"${CMAKE_SOURCE_DIR}/lib/std/c/linux.zig" "${CMAKE_SOURCE_DIR}/lib/std/c/linux.zig"
"${CMAKE_SOURCE_DIR}/lib/std/child_process.zig" "${CMAKE_SOURCE_DIR}/lib/std/child_process.zig"
"${CMAKE_SOURCE_DIR}/lib/std/coff.zig" "${CMAKE_SOURCE_DIR}/lib/std/coff.zig"
"${CMAKE_SOURCE_DIR}/lib/std/comptime_string_map.zig" "${CMAKE_SOURCE_DIR}/lib/std/static_string_map.zig"
"${CMAKE_SOURCE_DIR}/lib/std/crypto.zig" "${CMAKE_SOURCE_DIR}/lib/std/crypto.zig"
"${CMAKE_SOURCE_DIR}/lib/std/crypto/blake3.zig" "${CMAKE_SOURCE_DIR}/lib/std/crypto/blake3.zig"
"${CMAKE_SOURCE_DIR}/lib/std/crypto/siphash.zig" "${CMAKE_SOURCE_DIR}/lib/std/crypto/siphash.zig"

View File

@ -47,7 +47,7 @@ pub const Standard = enum {
/// Working Draft for ISO C23 with GNU extensions /// Working Draft for ISO C23 with GNU extensions
gnu23, gnu23,
const NameMap = std.ComptimeStringMap(Standard, .{ const NameMap = std.StaticStringMap(Standard).initComptime(.{
.{ "c89", .c89 }, .{ "c90", .c89 }, .{ "iso9899:1990", .c89 }, .{ "c89", .c89 }, .{ "c90", .c89 }, .{ "iso9899:1990", .c89 },
.{ "iso9899:199409", .iso9899 }, .{ "gnu89", .gnu89 }, .{ "gnu90", .gnu89 }, .{ "iso9899:199409", .iso9899 }, .{ "gnu89", .gnu89 }, .{ "gnu90", .gnu89 },
.{ "c99", .c99 }, .{ "iso9899:1999", .c99 }, .{ "c9x", .c99 }, .{ "c99", .c99 }, .{ "iso9899:1999", .c99 }, .{ "c9x", .c99 },

View File

@ -1709,7 +1709,7 @@ fn expandFuncMacro(
} }
if (!pp.comp.langopts.standard.atLeast(.c23)) break :res not_found; if (!pp.comp.langopts.standard.atLeast(.c23)) break :res not_found;
const attrs = std.ComptimeStringMap([]const u8, .{ const attrs = std.StaticStringMap([]const u8).initComptime(.{
.{ "deprecated", "201904L\n" }, .{ "deprecated", "201904L\n" },
.{ "fallthrough", "201904L\n" }, .{ "fallthrough", "201904L\n" },
.{ "maybe_unused", "201904L\n" }, .{ "maybe_unused", "201904L\n" },

View File

@ -872,7 +872,7 @@ pub const Token = struct {
}; };
} }
const all_kws = std.ComptimeStringMap(Id, .{ const all_kws = std.StaticStringMap(Id).initComptime(.{
.{ "auto", auto: { .{ "auto", auto: {
@setEvalBranchQuota(3000); @setEvalBranchQuota(3000);
break :auto .keyword_auto; break :auto .keyword_auto;

View File

@ -240,7 +240,7 @@ pub const ErrorDetails = struct {
// see https://github.com/ziglang/zig/issues/15395 // see https://github.com/ziglang/zig/issues/15395
_: u26 = 0, _: u26 = 0,
pub const strings = std.ComptimeStringMap([]const u8, .{ pub const strings = std.StaticStringMap([]const u8).initComptime(.{
.{ "number", "number" }, .{ "number", "number" },
.{ "number_expression", "number expression" }, .{ "number_expression", "number expression" },
.{ "string_literal", "quoted string literal" }, .{ "string_literal", "quoted string literal" },

View File

@ -47,7 +47,10 @@ pub const Resource = enum {
fontdir_num, fontdir_num,
manifest_num, manifest_num,
const map = std.ComptimeStringMapWithEql(Resource, .{ const map = std.StaticStringMapWithEql(
Resource,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "ACCELERATORS", .accelerators }, .{ "ACCELERATORS", .accelerators },
.{ "BITMAP", .bitmap }, .{ "BITMAP", .bitmap },
.{ "CURSOR", .cursor }, .{ "CURSOR", .cursor },
@ -67,7 +70,7 @@ pub const Resource = enum {
.{ "TOOLBAR", .toolbar }, .{ "TOOLBAR", .toolbar },
.{ "VERSIONINFO", .versioninfo }, .{ "VERSIONINFO", .versioninfo },
.{ "VXD", .vxd }, .{ "VXD", .vxd },
}, std.comptime_string_map.eqlAsciiIgnoreCase); });
pub fn fromString(bytes: SourceBytes) Resource { pub fn fromString(bytes: SourceBytes) Resource {
const maybe_ordinal = res.NameOrOrdinal.maybeOrdinalFromString(bytes); const maybe_ordinal = res.NameOrOrdinal.maybeOrdinalFromString(bytes);
@ -157,20 +160,26 @@ pub const OptionalStatements = enum {
menu, menu,
style, style,
pub const map = std.ComptimeStringMapWithEql(OptionalStatements, .{ pub const map = std.StaticStringMapWithEql(
OptionalStatements,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "CHARACTERISTICS", .characteristics }, .{ "CHARACTERISTICS", .characteristics },
.{ "LANGUAGE", .language }, .{ "LANGUAGE", .language },
.{ "VERSION", .version }, .{ "VERSION", .version },
}, std.comptime_string_map.eqlAsciiIgnoreCase); });
pub const dialog_map = std.ComptimeStringMapWithEql(OptionalStatements, .{ pub const dialog_map = std.StaticStringMapWithEql(
OptionalStatements,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "CAPTION", .caption }, .{ "CAPTION", .caption },
.{ "CLASS", .class }, .{ "CLASS", .class },
.{ "EXSTYLE", .exstyle }, .{ "EXSTYLE", .exstyle },
.{ "FONT", .font }, .{ "FONT", .font },
.{ "MENU", .menu }, .{ "MENU", .menu },
.{ "STYLE", .style }, .{ "STYLE", .style },
}, std.comptime_string_map.eqlAsciiIgnoreCase); });
}; };
pub const Control = enum { pub const Control = enum {
@ -197,7 +206,10 @@ pub const Control = enum {
state3, state3,
userbutton, userbutton,
pub const map = std.ComptimeStringMapWithEql(Control, .{ pub const map = std.StaticStringMapWithEql(
Control,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "AUTO3STATE", .auto3state }, .{ "AUTO3STATE", .auto3state },
.{ "AUTOCHECKBOX", .autocheckbox }, .{ "AUTOCHECKBOX", .autocheckbox },
.{ "AUTORADIOBUTTON", .autoradiobutton }, .{ "AUTORADIOBUTTON", .autoradiobutton },
@ -220,7 +232,7 @@ pub const Control = enum {
.{ "SCROLLBAR", .scrollbar }, .{ "SCROLLBAR", .scrollbar },
.{ "STATE3", .state3 }, .{ "STATE3", .state3 },
.{ "USERBUTTON", .userbutton }, .{ "USERBUTTON", .userbutton },
}, std.comptime_string_map.eqlAsciiIgnoreCase); });
pub fn hasTextParam(control: Control) bool { pub fn hasTextParam(control: Control) bool {
switch (control) { switch (control) {
@ -231,14 +243,17 @@ pub const Control = enum {
}; };
pub const ControlClass = struct { pub const ControlClass = struct {
pub const map = std.ComptimeStringMapWithEql(res.ControlClass, .{ pub const map = std.StaticStringMapWithEql(
res.ControlClass,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "BUTTON", .button }, .{ "BUTTON", .button },
.{ "EDIT", .edit }, .{ "EDIT", .edit },
.{ "STATIC", .static }, .{ "STATIC", .static },
.{ "LISTBOX", .listbox }, .{ "LISTBOX", .listbox },
.{ "SCROLLBAR", .scrollbar }, .{ "SCROLLBAR", .scrollbar },
.{ "COMBOBOX", .combobox }, .{ "COMBOBOX", .combobox },
}, std.comptime_string_map.eqlAsciiIgnoreCase); });
/// Like `map.get` but works on WTF16 strings, for use with parsed /// Like `map.get` but works on WTF16 strings, for use with parsed
/// string literals ("BUTTON", or even "\x42UTTON") /// string literals ("BUTTON", or even "\x42UTTON")
@ -280,10 +295,13 @@ pub const MenuItem = enum {
menuitem, menuitem,
popup, popup,
pub const map = std.ComptimeStringMapWithEql(MenuItem, .{ pub const map = std.StaticStringMapWithEql(
MenuItem,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "MENUITEM", .menuitem }, .{ "MENUITEM", .menuitem },
.{ "POPUP", .popup }, .{ "POPUP", .popup },
}, std.comptime_string_map.eqlAsciiIgnoreCase); });
pub fn isSeparator(bytes: []const u8) bool { pub fn isSeparator(bytes: []const u8) bool {
return std.ascii.eqlIgnoreCase(bytes, "SEPARATOR"); return std.ascii.eqlIgnoreCase(bytes, "SEPARATOR");
@ -297,14 +315,17 @@ pub const MenuItem = enum {
menubarbreak, menubarbreak,
menubreak, menubreak,
pub const map = std.ComptimeStringMapWithEql(Option, .{ pub const map = std.StaticStringMapWithEql(
Option,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "CHECKED", .checked }, .{ "CHECKED", .checked },
.{ "GRAYED", .grayed }, .{ "GRAYED", .grayed },
.{ "HELP", .help }, .{ "HELP", .help },
.{ "INACTIVE", .inactive }, .{ "INACTIVE", .inactive },
.{ "MENUBARBREAK", .menubarbreak }, .{ "MENUBARBREAK", .menubarbreak },
.{ "MENUBREAK", .menubreak }, .{ "MENUBREAK", .menubreak },
}, std.comptime_string_map.eqlAsciiIgnoreCase); });
}; };
}; };
@ -312,10 +333,13 @@ pub const ToolbarButton = enum {
button, button,
separator, separator,
pub const map = std.ComptimeStringMapWithEql(ToolbarButton, .{ pub const map = std.StaticStringMapWithEql(
ToolbarButton,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "BUTTON", .button }, .{ "BUTTON", .button },
.{ "SEPARATOR", .separator }, .{ "SEPARATOR", .separator },
}, std.comptime_string_map.eqlAsciiIgnoreCase); });
}; };
pub const VersionInfo = enum { pub const VersionInfo = enum {
@ -327,7 +351,10 @@ pub const VersionInfo = enum {
file_type, file_type,
file_subtype, file_subtype,
pub const map = std.ComptimeStringMapWithEql(VersionInfo, .{ pub const map = std.StaticStringMapWithEql(
VersionInfo,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "FILEVERSION", .file_version }, .{ "FILEVERSION", .file_version },
.{ "PRODUCTVERSION", .product_version }, .{ "PRODUCTVERSION", .product_version },
.{ "FILEFLAGSMASK", .file_flags_mask }, .{ "FILEFLAGSMASK", .file_flags_mask },
@ -335,17 +362,20 @@ pub const VersionInfo = enum {
.{ "FILEOS", .file_os }, .{ "FILEOS", .file_os },
.{ "FILETYPE", .file_type }, .{ "FILETYPE", .file_type },
.{ "FILESUBTYPE", .file_subtype }, .{ "FILESUBTYPE", .file_subtype },
}, std.comptime_string_map.eqlAsciiIgnoreCase); });
}; };
pub const VersionBlock = enum { pub const VersionBlock = enum {
block, block,
value, value,
pub const map = std.ComptimeStringMapWithEql(VersionBlock, .{ pub const map = std.StaticStringMapWithEql(
VersionBlock,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "BLOCK", .block }, .{ "BLOCK", .block },
.{ "VALUE", .value }, .{ "VALUE", .value },
}, std.comptime_string_map.eqlAsciiIgnoreCase); });
}; };
/// Keywords that are be the first token in a statement and (if so) dictate how the rest /// 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 {
characteristics, characteristics,
stringtable, stringtable,
pub const map = std.ComptimeStringMapWithEql(TopLevelKeywords, .{ pub const map = std.StaticStringMapWithEql(
TopLevelKeywords,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "LANGUAGE", .language }, .{ "LANGUAGE", .language },
.{ "VERSION", .version }, .{ "VERSION", .version },
.{ "CHARACTERISTICS", .characteristics }, .{ "CHARACTERISTICS", .characteristics },
.{ "STRINGTABLE", .stringtable }, .{ "STRINGTABLE", .stringtable },
}, std.comptime_string_map.eqlAsciiIgnoreCase); });
}; };
pub const CommonResourceAttributes = enum { pub const CommonResourceAttributes = enum {
@ -375,7 +408,10 @@ pub const CommonResourceAttributes = enum {
shared, shared,
nonshared, nonshared,
pub const map = std.ComptimeStringMapWithEql(CommonResourceAttributes, .{ pub const map = std.StaticStringMapWithEql(
CommonResourceAttributes,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "PRELOAD", .preload }, .{ "PRELOAD", .preload },
.{ "LOADONCALL", .loadoncall }, .{ "LOADONCALL", .loadoncall },
.{ "FIXED", .fixed }, .{ "FIXED", .fixed },
@ -385,7 +421,7 @@ pub const CommonResourceAttributes = enum {
.{ "IMPURE", .impure }, .{ "IMPURE", .impure },
.{ "SHARED", .shared }, .{ "SHARED", .shared },
.{ "NONSHARED", .nonshared }, .{ "NONSHARED", .nonshared },
}, std.comptime_string_map.eqlAsciiIgnoreCase); });
}; };
pub const AcceleratorTypeAndOptions = enum { pub const AcceleratorTypeAndOptions = enum {
@ -396,12 +432,15 @@ pub const AcceleratorTypeAndOptions = enum {
shift, shift,
control, control,
pub const map = std.ComptimeStringMapWithEql(AcceleratorTypeAndOptions, .{ pub const map = std.StaticStringMapWithEql(
AcceleratorTypeAndOptions,
std.static_string_map.eqlAsciiIgnoreCase,
).initComptime(.{
.{ "VIRTKEY", .virtkey }, .{ "VIRTKEY", .virtkey },
.{ "ASCII", .ascii }, .{ "ASCII", .ascii },
.{ "NOINVERT", .noinvert }, .{ "NOINVERT", .noinvert },
.{ "ALT", .alt }, .{ "ALT", .alt },
.{ "SHIFT", .shift }, .{ "SHIFT", .shift },
.{ "CONTROL", .control }, .{ "CONTROL", .control },
}, std.comptime_string_map.eqlAsciiIgnoreCase); });
}; };

View File

@ -1,320 +0,0 @@
const std = @import("std.zig");
const mem = std.mem;
/// Comptime string map optimized for small sets of disparate string keys.
/// Works by separating the keys by length at comptime and only checking strings of
/// equal length at runtime.
///
/// `kvs_list` expects a list of `struct { []const u8, V }` (key-value pair) tuples.
/// You can pass `struct { []const u8 }` (only keys) tuples if `V` is `void`.
pub fn ComptimeStringMap(
comptime V: type,
comptime kvs_list: anytype,
) type {
return ComptimeStringMapWithEql(V, kvs_list, defaultEql);
}
/// Like `std.mem.eql`, but takes advantage of the fact that the lengths
/// of `a` and `b` are known to be equal.
pub fn defaultEql(a: []const u8, b: []const u8) bool {
if (a.ptr == b.ptr) return true;
for (a, b) |a_elem, b_elem| {
if (a_elem != b_elem) return false;
}
return true;
}
/// Like `std.ascii.eqlIgnoreCase` but takes advantage of the fact that
/// the lengths of `a` and `b` are known to be equal.
pub fn eqlAsciiIgnoreCase(a: []const u8, b: []const u8) bool {
if (a.ptr == b.ptr) return true;
for (a, b) |a_c, b_c| {
if (std.ascii.toLower(a_c) != std.ascii.toLower(b_c)) return false;
}
return true;
}
/// ComptimeStringMap, but accepts an equality function (`eql`).
/// The `eql` function is only called to determine the equality
/// of equal length strings. Any strings that are not equal length
/// are never compared using the `eql` function.
pub fn ComptimeStringMapWithEql(
comptime V: type,
comptime kvs_list: anytype,
comptime eql: fn (a: []const u8, b: []const u8) bool,
) type {
const empty_list = kvs_list.len == 0;
const precomputed = blk: {
@setEvalBranchQuota(1500);
const KV = struct {
key: []const u8,
value: V,
};
if (empty_list)
break :blk .{};
var sorted_kvs: [kvs_list.len]KV = undefined;
for (kvs_list, 0..) |kv, i| {
if (V != void) {
sorted_kvs[i] = .{ .key = kv.@"0", .value = kv.@"1" };
} else {
sorted_kvs[i] = .{ .key = kv.@"0", .value = {} };
}
}
const SortContext = struct {
kvs: []KV,
pub fn lessThan(ctx: @This(), a: usize, b: usize) bool {
return ctx.kvs[a].key.len < ctx.kvs[b].key.len;
}
pub fn swap(ctx: @This(), a: usize, b: usize) void {
return std.mem.swap(KV, &ctx.kvs[a], &ctx.kvs[b]);
}
};
mem.sortUnstableContext(0, sorted_kvs.len, SortContext{ .kvs = &sorted_kvs });
const min_len = sorted_kvs[0].key.len;
const max_len = sorted_kvs[sorted_kvs.len - 1].key.len;
var len_indexes: [max_len + 1]usize = undefined;
var len: usize = 0;
var i: usize = 0;
while (len <= max_len) : (len += 1) {
// find the first keyword len == len
while (len > sorted_kvs[i].key.len) {
i += 1;
}
len_indexes[len] = i;
}
break :blk .{
.min_len = min_len,
.max_len = max_len,
.sorted_kvs = sorted_kvs,
.len_indexes = len_indexes,
};
};
return struct {
/// Array of `struct { key: []const u8, value: V }` where `value` is `void{}` if `V` is `void`.
/// Sorted by `key` length.
pub const kvs = precomputed.sorted_kvs;
/// Checks if the map has a value for the key.
pub fn has(str: []const u8) bool {
return get(str) != null;
}
/// Returns the value for the key if any, else null.
pub fn get(str: []const u8) ?V {
if (empty_list)
return null;
return precomputed.sorted_kvs[getIndex(str) orelse return null].value;
}
pub fn getIndex(str: []const u8) ?usize {
if (empty_list)
return null;
if (str.len < precomputed.min_len or str.len > precomputed.max_len)
return null;
var i = precomputed.len_indexes[str.len];
while (true) {
const kv = precomputed.sorted_kvs[i];
if (kv.key.len != str.len)
return null;
if (eql(kv.key, str))
return i;
i += 1;
if (i >= precomputed.sorted_kvs.len)
return null;
}
}
};
}
const TestEnum = enum {
A,
B,
C,
D,
E,
};
test "list literal of list literals" {
const map = ComptimeStringMap(TestEnum, .{
.{ "these", .D },
.{ "have", .A },
.{ "nothing", .B },
.{ "incommon", .C },
.{ "samelen", .E },
});
try testMap(map);
// Default comparison is case sensitive
try std.testing.expect(null == map.get("NOTHING"));
}
test "array of structs" {
const KV = struct { []const u8, TestEnum };
const map = ComptimeStringMap(TestEnum, [_]KV{
.{ "these", .D },
.{ "have", .A },
.{ "nothing", .B },
.{ "incommon", .C },
.{ "samelen", .E },
});
try testMap(map);
}
test "slice of structs" {
const KV = struct { []const u8, TestEnum };
const slice: []const KV = &[_]KV{
.{ "these", .D },
.{ "have", .A },
.{ "nothing", .B },
.{ "incommon", .C },
.{ "samelen", .E },
};
const map = ComptimeStringMap(TestEnum, slice);
try testMap(map);
}
fn testMap(comptime map: anytype) !void {
try std.testing.expectEqual(TestEnum.A, map.get("have").?);
try std.testing.expectEqual(TestEnum.B, map.get("nothing").?);
try std.testing.expect(null == map.get("missing"));
try std.testing.expectEqual(TestEnum.D, map.get("these").?);
try std.testing.expectEqual(TestEnum.E, map.get("samelen").?);
try std.testing.expect(!map.has("missing"));
try std.testing.expect(map.has("these"));
try std.testing.expect(null == map.get(""));
try std.testing.expect(null == map.get("averylongstringthathasnomatches"));
}
test "void value type, slice of structs" {
const KV = struct { []const u8 };
const slice: []const KV = &[_]KV{
.{"these"},
.{"have"},
.{"nothing"},
.{"incommon"},
.{"samelen"},
};
const map = ComptimeStringMap(void, slice);
try testSet(map);
// Default comparison is case sensitive
try std.testing.expect(null == map.get("NOTHING"));
}
test "void value type, list literal of list literals" {
const map = ComptimeStringMap(void, .{
.{"these"},
.{"have"},
.{"nothing"},
.{"incommon"},
.{"samelen"},
});
try testSet(map);
}
fn testSet(comptime map: anytype) !void {
try std.testing.expectEqual({}, map.get("have").?);
try std.testing.expectEqual({}, map.get("nothing").?);
try std.testing.expect(null == map.get("missing"));
try std.testing.expectEqual({}, map.get("these").?);
try std.testing.expectEqual({}, map.get("samelen").?);
try std.testing.expect(!map.has("missing"));
try std.testing.expect(map.has("these"));
try std.testing.expect(null == map.get(""));
try std.testing.expect(null == map.get("averylongstringthathasnomatches"));
}
test "ComptimeStringMapWithEql" {
const map = ComptimeStringMapWithEql(TestEnum, .{
.{ "these", .D },
.{ "have", .A },
.{ "nothing", .B },
.{ "incommon", .C },
.{ "samelen", .E },
}, eqlAsciiIgnoreCase);
try testMap(map);
try std.testing.expectEqual(TestEnum.A, map.get("HAVE").?);
try std.testing.expectEqual(TestEnum.E, map.get("SameLen").?);
try std.testing.expect(null == map.get("SameLength"));
try std.testing.expect(map.has("ThESe"));
}
test "empty" {
const m1 = ComptimeStringMap(usize, .{});
try std.testing.expect(null == m1.get("anything"));
const m2 = ComptimeStringMapWithEql(usize, .{}, eqlAsciiIgnoreCase);
try std.testing.expect(null == m2.get("anything"));
}
test "redundant entries" {
const map = ComptimeStringMap(TestEnum, .{
.{ "redundant", .D },
.{ "theNeedle", .A },
.{ "redundant", .B },
.{ "re" ++ "dundant", .C },
.{ "redun" ++ "dant", .E },
});
// No promises about which one you get:
try std.testing.expect(null != map.get("redundant"));
// Default map is not case sensitive:
try std.testing.expect(null == map.get("REDUNDANT"));
try std.testing.expectEqual(TestEnum.A, map.get("theNeedle").?);
}
test "redundant insensitive" {
const map = ComptimeStringMapWithEql(TestEnum, .{
.{ "redundant", .D },
.{ "theNeedle", .A },
.{ "redundanT", .B },
.{ "RE" ++ "dundant", .C },
.{ "redun" ++ "DANT", .E },
}, eqlAsciiIgnoreCase);
// No promises about which result you'll get ...
try std.testing.expect(null != map.get("REDUNDANT"));
try std.testing.expect(null != map.get("ReDuNdAnT"));
try std.testing.expectEqual(TestEnum.A, map.get("theNeedle").?);
}
test "comptime-only value" {
const map = std.ComptimeStringMap(type, .{
.{ "a", struct {
pub const foo = 1;
} },
.{ "b", struct {
pub const foo = 2;
} },
.{ "c", struct {
pub const foo = 3;
} },
});
try std.testing.expect(map.get("a").?.foo == 1);
try std.testing.expect(map.get("b").?.foo == 2);
try std.testing.expect(map.get("c").?.foo == 3);
try std.testing.expect(map.get("d") == null);
}

View File

@ -19,7 +19,7 @@ pub const Algorithm = enum {
md5WithRSAEncryption, md5WithRSAEncryption,
curveEd25519, curveEd25519,
pub const map = std.ComptimeStringMap(Algorithm, .{ pub const map = std.StaticStringMap(Algorithm).initComptime(.{
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05 }, .sha1WithRSAEncryption }, .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05 }, .sha1WithRSAEncryption },
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B }, .sha256WithRSAEncryption }, .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B }, .sha256WithRSAEncryption },
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0C }, .sha384WithRSAEncryption }, .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0C }, .sha384WithRSAEncryption },
@ -52,7 +52,7 @@ pub const AlgorithmCategory = enum {
X9_62_id_ecPublicKey, X9_62_id_ecPublicKey,
curveEd25519, curveEd25519,
pub const map = std.ComptimeStringMap(AlgorithmCategory, .{ pub const map = std.StaticStringMap(AlgorithmCategory).initComptime(.{
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 }, .rsaEncryption }, .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 }, .rsaEncryption },
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01 }, .X9_62_id_ecPublicKey }, .{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01 }, .X9_62_id_ecPublicKey },
.{ &[_]u8{ 0x2B, 0x65, 0x70 }, .curveEd25519 }, .{ &[_]u8{ 0x2B, 0x65, 0x70 }, .curveEd25519 },
@ -73,7 +73,7 @@ pub const Attribute = enum {
pkcs9_emailAddress, pkcs9_emailAddress,
domainComponent, domainComponent,
pub const map = std.ComptimeStringMap(Attribute, .{ pub const map = std.StaticStringMap(Attribute).initComptime(.{
.{ &[_]u8{ 0x55, 0x04, 0x03 }, .commonName }, .{ &[_]u8{ 0x55, 0x04, 0x03 }, .commonName },
.{ &[_]u8{ 0x55, 0x04, 0x05 }, .serialNumber }, .{ &[_]u8{ 0x55, 0x04, 0x05 }, .serialNumber },
.{ &[_]u8{ 0x55, 0x04, 0x06 }, .countryName }, .{ &[_]u8{ 0x55, 0x04, 0x06 }, .countryName },
@ -94,7 +94,7 @@ pub const NamedCurve = enum {
secp521r1, secp521r1,
X9_62_prime256v1, X9_62_prime256v1,
pub const map = std.ComptimeStringMap(NamedCurve, .{ pub const map = std.StaticStringMap(NamedCurve).initComptime(.{
.{ &[_]u8{ 0x2B, 0x81, 0x04, 0x00, 0x22 }, .secp384r1 }, .{ &[_]u8{ 0x2B, 0x81, 0x04, 0x00, 0x22 }, .secp384r1 },
.{ &[_]u8{ 0x2B, 0x81, 0x04, 0x00, 0x23 }, .secp521r1 }, .{ &[_]u8{ 0x2B, 0x81, 0x04, 0x00, 0x23 }, .secp521r1 },
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07 }, .X9_62_prime256v1 }, .{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07 }, .X9_62_prime256v1 },
@ -130,7 +130,7 @@ pub const ExtensionId = enum {
netscape_cert_type, netscape_cert_type,
netscape_comment, netscape_comment,
pub const map = std.ComptimeStringMap(ExtensionId, .{ pub const map = std.StaticStringMap(ExtensionId).initComptime(.{
.{ &[_]u8{ 0x55, 0x04, 0x03 }, .commonName }, .{ &[_]u8{ 0x55, 0x04, 0x03 }, .commonName },
.{ &[_]u8{ 0x55, 0x1D, 0x01 }, .authority_key_identifier }, .{ &[_]u8{ 0x55, 0x1D, 0x01 }, .authority_key_identifier },
.{ &[_]u8{ 0x55, 0x1D, 0x07 }, .subject_alt_name }, .{ &[_]u8{ 0x55, 0x1D, 0x07 }, .subject_alt_name },

View File

@ -1641,7 +1641,7 @@ test "walker" {
// iteration order of walker is undefined, so need lookup maps to check against // iteration order of walker is undefined, so need lookup maps to check against
const expected_paths = std.ComptimeStringMap(void, .{ const expected_paths = std.StaticStringMap(void).initComptime(.{
.{"dir1"}, .{"dir1"},
.{"dir2"}, .{"dir2"},
.{"dir3"}, .{"dir3"},
@ -1651,7 +1651,7 @@ test "walker" {
.{"dir3" ++ fs.path.sep_str ++ "sub2" ++ fs.path.sep_str ++ "subsub1"}, .{"dir3" ++ fs.path.sep_str ++ "sub2" ++ fs.path.sep_str ++ "subsub1"},
}); });
const expected_basenames = std.ComptimeStringMap(void, .{ const expected_basenames = std.StaticStringMap(void).initComptime(.{
.{"dir1"}, .{"dir1"},
.{"dir2"}, .{"dir2"},
.{"dir3"}, .{"dir3"},
@ -1661,8 +1661,8 @@ test "walker" {
.{"subsub1"}, .{"subsub1"},
}); });
for (expected_paths.kvs) |kv| { for (expected_paths.keys()) |key| {
try tmp.dir.makePath(kv.key); try tmp.dir.makePath(key);
} }
var walker = try tmp.dir.walk(testing.allocator); var walker = try tmp.dir.walk(testing.allocator);

View File

@ -1570,7 +1570,7 @@ pub const RequestOptions = struct {
}; };
fn validateUri(uri: Uri, arena: Allocator) !struct { Connection.Protocol, Uri } { fn validateUri(uri: Uri, arena: Allocator) !struct { Connection.Protocol, Uri } {
const protocol_map = std.ComptimeStringMap(Connection.Protocol, .{ const protocol_map = std.StaticStringMap(Connection.Protocol).initComptime(.{
.{ "http", .plain }, .{ "http", .plain },
.{ "ws", .plain }, .{ "ws", .plain },
.{ "https", .tls }, .{ "https", .tls },

View File

@ -19,7 +19,7 @@ pub const isTag = @compileError("deprecated; use 'tagged_value == @field(E, tag_
/// Returns the variant of an enum type, `T`, which is named `str`, or `null` if no such variant exists. /// Returns the variant of an enum type, `T`, which is named `str`, or `null` if no such variant exists.
pub fn stringToEnum(comptime T: type, str: []const u8) ?T { pub fn stringToEnum(comptime T: type, str: []const u8) ?T {
// Using ComptimeStringMap here is more performant, but it will start to take too // Using StaticStringMap here is more performant, but it will start to take too
// long to compile if the enum is large enough, due to the current limits of comptime // long to compile if the enum is large enough, due to the current limits of comptime
// performance when doing things like constructing lookup maps at comptime. // performance when doing things like constructing lookup maps at comptime.
// TODO The '100' here is arbitrary and should be increased when possible: // 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 {
} }
break :build_kvs kvs_array[0..]; break :build_kvs kvs_array[0..];
}; };
const map = std.ComptimeStringMap(T, kvs); const map = std.StaticStringMap(T).initComptime(kvs);
return map.get(str); return map.get(str);
} else { } else {
inline for (@typeInfo(T).Enum.fields) |enumField| { inline for (@typeInfo(T).Enum.fields) |enumField| {

View File

@ -0,0 +1,502 @@
const std = @import("std.zig");
const mem = std.mem;
/// Static string map optimized for small sets of disparate string keys.
/// Works by separating the keys by length at initialization and only checking
/// strings of equal length at runtime.
pub fn StaticStringMap(comptime V: type) type {
return StaticStringMapWithEql(V, defaultEql);
}
/// Like `std.mem.eql`, but takes advantage of the fact that the lengths
/// of `a` and `b` are known to be equal.
pub fn defaultEql(a: []const u8, b: []const u8) bool {
if (a.ptr == b.ptr) return true;
for (a, b) |a_elem, b_elem| {
if (a_elem != b_elem) return false;
}
return true;
}
/// Like `std.ascii.eqlIgnoreCase` but takes advantage of the fact that
/// the lengths of `a` and `b` are known to be equal.
pub fn eqlAsciiIgnoreCase(a: []const u8, b: []const u8) bool {
if (a.ptr == b.ptr) return true;
for (a, b) |a_c, b_c| {
if (std.ascii.toLower(a_c) != std.ascii.toLower(b_c)) return false;
}
return true;
}
/// StaticStringMap, but accepts an equality function (`eql`).
/// The `eql` function is only called to determine the equality
/// of equal length strings. Any strings that are not equal length
/// are never compared using the `eql` function.
pub fn StaticStringMapWithEql(
comptime V: type,
comptime eql: fn (a: []const u8, b: []const u8) bool,
) type {
return struct {
kvs: *const KVs = &empty_kvs,
len_indexes: [*]const u32 = &empty_len_indexes,
len_indexes_len: u32 = 0,
min_len: u32 = std.math.maxInt(u32),
max_len: u32 = 0,
pub const KV = struct {
key: []const u8,
value: V,
};
const Self = @This();
const KVs = struct {
keys: [*]const []const u8,
values: [*]const V,
len: u32,
};
const empty_kvs = KVs{
.keys = &empty_keys,
.values = &empty_vals,
.len = 0,
};
const empty_len_indexes = [0]u32{};
const empty_keys = [0][]const u8{};
const empty_vals = [0]V{};
/// Returns a map backed by static, comptime allocated memory.
///
/// `kvs_list` must be either a list of `struct { []const u8, V }`
/// (key-value pair) tuples, or a list of `struct { []const u8 }`
/// (only keys) tuples if `V` is `void`.
pub inline fn initComptime(comptime kvs_list: anytype) Self {
comptime {
@setEvalBranchQuota(30 * kvs_list.len);
var self = Self{};
if (kvs_list.len == 0)
return self;
var sorted_keys: [kvs_list.len][]const u8 = undefined;
var sorted_vals: [kvs_list.len]V = undefined;
self.initSortedKVs(kvs_list, &sorted_keys, &sorted_vals);
const final_keys = sorted_keys;
const final_vals = sorted_vals;
self.kvs = &.{
.keys = &final_keys,
.values = &final_vals,
.len = @intCast(kvs_list.len),
};
var len_indexes: [self.max_len + 1]u32 = undefined;
self.initLenIndexes(&len_indexes);
const final_len_indexes = len_indexes;
self.len_indexes = &final_len_indexes;
self.len_indexes_len = @intCast(len_indexes.len);
return self;
}
}
/// Returns a map backed by memory allocated with `allocator`.
///
/// Handles `kvs_list` the same way as `initComptime()`.
pub fn init(kvs_list: anytype, allocator: mem.Allocator) !Self {
var self = Self{};
if (kvs_list.len == 0)
return self;
const sorted_keys = try allocator.alloc([]const u8, kvs_list.len);
errdefer allocator.free(sorted_keys);
const sorted_vals = try allocator.alloc(V, kvs_list.len);
errdefer allocator.free(sorted_vals);
const kvs = try allocator.create(KVs);
errdefer allocator.destroy(kvs);
self.initSortedKVs(kvs_list, sorted_keys, sorted_vals);
kvs.* = .{
.keys = sorted_keys.ptr,
.values = sorted_vals.ptr,
.len = kvs_list.len,
};
self.kvs = kvs;
const len_indexes = try allocator.alloc(u32, self.max_len + 1);
self.initLenIndexes(len_indexes);
self.len_indexes = len_indexes.ptr;
self.len_indexes_len = @intCast(len_indexes.len);
return self;
}
/// this method should only be used with init() and not with initComptime().
pub fn deinit(self: Self, allocator: mem.Allocator) void {
allocator.free(self.len_indexes[0..self.len_indexes_len]);
allocator.free(self.kvs.keys[0..self.kvs.len]);
allocator.free(self.kvs.values[0..self.kvs.len]);
allocator.destroy(self.kvs);
}
const SortContext = struct {
keys: [][]const u8,
vals: []V,
pub fn lessThan(ctx: @This(), a: usize, b: usize) bool {
return ctx.keys[a].len < ctx.keys[b].len;
}
pub fn swap(ctx: @This(), a: usize, b: usize) void {
std.mem.swap([]const u8, &ctx.keys[a], &ctx.keys[b]);
std.mem.swap(V, &ctx.vals[a], &ctx.vals[b]);
}
};
fn initSortedKVs(
self: *Self,
kvs_list: anytype,
sorted_keys: [][]const u8,
sorted_vals: []V,
) void {
for (kvs_list, 0..) |kv, i| {
sorted_keys[i] = kv.@"0";
sorted_vals[i] = if (V == void) {} else kv.@"1";
self.min_len = @intCast(@min(self.min_len, kv.@"0".len));
self.max_len = @intCast(@max(self.max_len, kv.@"0".len));
}
mem.sortUnstableContext(0, sorted_keys.len, SortContext{
.keys = sorted_keys,
.vals = sorted_vals,
});
}
fn initLenIndexes(self: Self, len_indexes: []u32) void {
var len: usize = 0;
var i: u32 = 0;
while (len <= self.max_len) : (len += 1) {
// find the first keyword len == len
while (len > self.kvs.keys[i].len) {
i += 1;
}
len_indexes[len] = i;
}
}
/// Checks if the map has a value for the key.
pub fn has(self: Self, str: []const u8) bool {
return self.get(str) != null;
}
/// Returns the value for the key if any, else null.
pub fn get(self: Self, str: []const u8) ?V {
if (self.kvs.len == 0)
return null;
return self.kvs.values[self.getIndex(str) orelse return null];
}
pub fn getIndex(self: Self, str: []const u8) ?usize {
const kvs = self.kvs.*;
if (kvs.len == 0)
return null;
if (str.len < self.min_len or str.len > self.max_len)
return null;
var i = self.len_indexes[str.len];
while (true) {
const key = kvs.keys[i];
if (key.len != str.len)
return null;
if (eql(key, str))
return i;
i += 1;
if (i >= kvs.len)
return null;
}
}
/// Returns the longest key, value pair where key is a prefix of `str`
/// else null.
pub fn getLongestPrefix(self: Self, str: []const u8) ?KV {
if (self.kvs.len == 0)
return null;
const i = self.getLongestPrefixIndex(str) orelse return null;
const kvs = self.kvs.*;
return .{
.key = kvs.keys[i],
.value = kvs.values[i],
};
}
pub fn getLongestPrefixIndex(self: Self, str: []const u8) ?usize {
if (self.kvs.len == 0)
return null;
if (str.len < self.min_len)
return null;
var len = @min(self.max_len, str.len);
while (len >= self.min_len) : (len -= 1) {
if (self.getIndex(str[0..len])) |i|
return i;
}
return null;
}
pub fn keys(self: Self) []const []const u8 {
const kvs = self.kvs.*;
return kvs.keys[0..kvs.len];
}
pub fn values(self: Self) []const V {
const kvs = self.kvs.*;
return kvs.values[0..kvs.len];
}
};
}
const TestEnum = enum { A, B, C, D, E };
const TestMap = StaticStringMap(TestEnum);
const TestKV = struct { []const u8, TestEnum };
const TestMapVoid = StaticStringMap(void);
const TestKVVoid = struct { []const u8 };
const TestMapWithEql = StaticStringMapWithEql(TestEnum, eqlAsciiIgnoreCase);
const testing = std.testing;
const test_alloc = testing.allocator;
test "list literal of list literals" {
const slice = [_]TestKV{
.{ "these", .D },
.{ "have", .A },
.{ "nothing", .B },
.{ "incommon", .C },
.{ "samelen", .E },
};
const map = TestMap.initComptime(slice);
try testMap(map);
// Default comparison is case sensitive
try testing.expect(null == map.get("NOTHING"));
// runtime init(), deinit()
const map_rt = try TestMap.init(slice, test_alloc);
defer map_rt.deinit(test_alloc);
try testMap(map_rt);
// Default comparison is case sensitive
try testing.expect(null == map_rt.get("NOTHING"));
}
test "array of structs" {
const slice = [_]TestKV{
.{ "these", .D },
.{ "have", .A },
.{ "nothing", .B },
.{ "incommon", .C },
.{ "samelen", .E },
};
try testMap(TestMap.initComptime(slice));
}
test "slice of structs" {
const slice = [_]TestKV{
.{ "these", .D },
.{ "have", .A },
.{ "nothing", .B },
.{ "incommon", .C },
.{ "samelen", .E },
};
try testMap(TestMap.initComptime(slice));
}
fn testMap(map: anytype) !void {
try testing.expectEqual(TestEnum.A, map.get("have").?);
try testing.expectEqual(TestEnum.B, map.get("nothing").?);
try testing.expect(null == map.get("missing"));
try testing.expectEqual(TestEnum.D, map.get("these").?);
try testing.expectEqual(TestEnum.E, map.get("samelen").?);
try testing.expect(!map.has("missing"));
try testing.expect(map.has("these"));
try testing.expect(null == map.get(""));
try testing.expect(null == map.get("averylongstringthathasnomatches"));
}
test "void value type, slice of structs" {
const slice = [_]TestKVVoid{
.{"these"},
.{"have"},
.{"nothing"},
.{"incommon"},
.{"samelen"},
};
const map = TestMapVoid.initComptime(slice);
try testSet(map);
// Default comparison is case sensitive
try testing.expect(null == map.get("NOTHING"));
}
test "void value type, list literal of list literals" {
const slice = [_]TestKVVoid{
.{"these"},
.{"have"},
.{"nothing"},
.{"incommon"},
.{"samelen"},
};
try testSet(TestMapVoid.initComptime(slice));
}
fn testSet(map: TestMapVoid) !void {
try testing.expectEqual({}, map.get("have").?);
try testing.expectEqual({}, map.get("nothing").?);
try testing.expect(null == map.get("missing"));
try testing.expectEqual({}, map.get("these").?);
try testing.expectEqual({}, map.get("samelen").?);
try testing.expect(!map.has("missing"));
try testing.expect(map.has("these"));
try testing.expect(null == map.get(""));
try testing.expect(null == map.get("averylongstringthathasnomatches"));
}
fn testStaticStringMapWithEql(map: TestMapWithEql) !void {
try testMap(map);
try testing.expectEqual(TestEnum.A, map.get("HAVE").?);
try testing.expectEqual(TestEnum.E, map.get("SameLen").?);
try testing.expect(null == map.get("SameLength"));
try testing.expect(map.has("ThESe"));
}
test "StaticStringMapWithEql" {
const slice = [_]TestKV{
.{ "these", .D },
.{ "have", .A },
.{ "nothing", .B },
.{ "incommon", .C },
.{ "samelen", .E },
};
try testStaticStringMapWithEql(TestMapWithEql.initComptime(slice));
}
test "empty" {
const m1 = StaticStringMap(usize).initComptime(.{});
try testing.expect(null == m1.get("anything"));
const m2 = StaticStringMapWithEql(usize, eqlAsciiIgnoreCase).initComptime(.{});
try testing.expect(null == m2.get("anything"));
const m3 = try StaticStringMap(usize).init(.{}, test_alloc);
try testing.expect(null == m3.get("anything"));
const m4 = try StaticStringMapWithEql(usize, eqlAsciiIgnoreCase).init(.{}, test_alloc);
try testing.expect(null == m4.get("anything"));
}
test "redundant entries" {
const slice = [_]TestKV{
.{ "redundant", .D },
.{ "theNeedle", .A },
.{ "redundant", .B },
.{ "re" ++ "dundant", .C },
.{ "redun" ++ "dant", .E },
};
const map = TestMap.initComptime(slice);
// No promises about which one you get:
try testing.expect(null != map.get("redundant"));
// Default map is not case sensitive:
try testing.expect(null == map.get("REDUNDANT"));
try testing.expectEqual(TestEnum.A, map.get("theNeedle").?);
}
test "redundant insensitive" {
const slice = [_]TestKV{
.{ "redundant", .D },
.{ "theNeedle", .A },
.{ "redundanT", .B },
.{ "RE" ++ "dundant", .C },
.{ "redun" ++ "DANT", .E },
};
const map = TestMapWithEql.initComptime(slice);
// No promises about which result you'll get ...
try testing.expect(null != map.get("REDUNDANT"));
try testing.expect(null != map.get("ReDuNdAnT"));
try testing.expectEqual(TestEnum.A, map.get("theNeedle").?);
}
test "comptime-only value" {
const map = StaticStringMap(type).initComptime(.{
.{ "a", struct {
pub const foo = 1;
} },
.{ "b", struct {
pub const foo = 2;
} },
.{ "c", struct {
pub const foo = 3;
} },
});
try testing.expect(map.get("a").?.foo == 1);
try testing.expect(map.get("b").?.foo == 2);
try testing.expect(map.get("c").?.foo == 3);
try testing.expect(map.get("d") == null);
}
test "getLongestPrefix" {
const slice = [_]TestKV{
.{ "a", .A },
.{ "aa", .B },
.{ "aaa", .C },
.{ "aaaa", .D },
};
const map = TestMap.initComptime(slice);
try testing.expectEqual(null, map.getLongestPrefix(""));
try testing.expectEqual(null, map.getLongestPrefix("bar"));
try testing.expectEqualStrings("aaaa", map.getLongestPrefix("aaaabar").?.key);
try testing.expectEqualStrings("aaa", map.getLongestPrefix("aaabar").?.key);
}
test "getLongestPrefix2" {
const slice = [_]struct { []const u8, u8 }{
.{ "one", 1 },
.{ "two", 2 },
.{ "three", 3 },
.{ "four", 4 },
.{ "five", 5 },
.{ "six", 6 },
.{ "seven", 7 },
.{ "eight", 8 },
.{ "nine", 9 },
};
const map = StaticStringMap(u8).initComptime(slice);
try testing.expectEqual(1, map.get("one"));
try testing.expectEqual(null, map.get("o"));
try testing.expectEqual(null, map.get("onexxx"));
try testing.expectEqual(9, map.get("nine"));
try testing.expectEqual(null, map.get("n"));
try testing.expectEqual(null, map.get("ninexxx"));
try testing.expectEqual(null, map.get("xxx"));
try testing.expectEqual(1, map.getLongestPrefix("one").?.value);
try testing.expectEqual(1, map.getLongestPrefix("onexxx").?.value);
try testing.expectEqual(null, map.getLongestPrefix("o"));
try testing.expectEqual(null, map.getLongestPrefix("on"));
try testing.expectEqual(9, map.getLongestPrefix("nine").?.value);
try testing.expectEqual(9, map.getLongestPrefix("ninexxx").?.value);
try testing.expectEqual(null, map.getLongestPrefix("n"));
try testing.expectEqual(null, map.getLongestPrefix("xxx"));
}
test "long kvs_list doesn't exceed @setEvalBranchQuota" {
_ = TestMapVoid.initComptime([1]TestKVVoid{.{"x"}} ** 1_000);
}

View File

@ -16,8 +16,8 @@ pub const BufMap = @import("buf_map.zig").BufMap;
pub const BufSet = @import("buf_set.zig").BufSet; pub const BufSet = @import("buf_set.zig").BufSet;
/// Deprecated: use `process.Child`. /// Deprecated: use `process.Child`.
pub const ChildProcess = @import("child_process.zig").ChildProcess; pub const ChildProcess = @import("child_process.zig").ChildProcess;
pub const ComptimeStringMap = comptime_string_map.ComptimeStringMap; pub const StaticStringMap = static_string_map.StaticStringMap;
pub const ComptimeStringMapWithEql = comptime_string_map.ComptimeStringMapWithEql; pub const StaticStringMapWithEql = static_string_map.StaticStringMapWithEql;
pub const DoublyLinkedList = @import("linked_list.zig").DoublyLinkedList; pub const DoublyLinkedList = @import("linked_list.zig").DoublyLinkedList;
pub const DynLib = @import("dynamic_library.zig").DynLib; pub const DynLib = @import("dynamic_library.zig").DynLib;
pub const DynamicBitSet = bit_set.DynamicBitSet; pub const DynamicBitSet = bit_set.DynamicBitSet;
@ -62,7 +62,7 @@ pub const builtin = @import("builtin.zig");
pub const c = @import("c.zig"); pub const c = @import("c.zig");
pub const coff = @import("coff.zig"); pub const coff = @import("coff.zig");
pub const compress = @import("compress.zig"); pub const compress = @import("compress.zig");
pub const comptime_string_map = @import("comptime_string_map.zig"); pub const static_string_map = @import("static_string_map.zig");
pub const crypto = @import("crypto.zig"); pub const crypto = @import("crypto.zig");
pub const debug = @import("debug.zig"); pub const debug = @import("debug.zig");
pub const dwarf = @import("dwarf.zig"); pub const dwarf = @import("dwarf.zig");

View File

@ -10125,7 +10125,7 @@ fn calleeExpr(
} }
} }
const primitive_instrs = std.ComptimeStringMap(Zir.Inst.Ref, .{ const primitive_instrs = std.StaticStringMap(Zir.Inst.Ref).initComptime(.{
.{ "anyerror", .anyerror_type }, .{ "anyerror", .anyerror_type },
.{ "anyframe", .anyframe_type }, .{ "anyframe", .anyframe_type },
.{ "anyopaque", .anyopaque_type }, .{ "anyopaque", .anyopaque_type },
@ -10173,14 +10173,14 @@ const primitive_instrs = std.ComptimeStringMap(Zir.Inst.Ref, .{
comptime { comptime {
// These checks ensure that std.zig.primitives stays in sync with the primitive->Zir map. // These checks ensure that std.zig.primitives stays in sync with the primitive->Zir map.
const primitives = std.zig.primitives; const primitives = std.zig.primitives;
for (primitive_instrs.kvs) |kv| { for (primitive_instrs.keys(), primitive_instrs.values()) |key, value| {
if (!primitives.isPrimitive(kv.key)) { if (!primitives.isPrimitive(key)) {
@compileError("std.zig.isPrimitive() is not aware of Zir instr '" ++ @tagName(kv.value) ++ "'"); @compileError("std.zig.isPrimitive() is not aware of Zir instr '" ++ @tagName(value) ++ "'");
} }
} }
for (primitives.names.kvs) |kv| { for (primitives.names.keys()) |key| {
if (primitive_instrs.get(kv.key) == null) { if (primitive_instrs.get(key) == null) {
@compileError("std.zig.primitives entry '" ++ kv.key ++ "' does not have a corresponding Zir instr"); @compileError("std.zig.primitives entry '" ++ key ++ "' does not have a corresponding Zir instr");
} }
} }
} }

View File

@ -160,7 +160,7 @@ param_count: ?u8,
pub const list = list: { pub const list = list: {
@setEvalBranchQuota(3000); @setEvalBranchQuota(3000);
break :list std.ComptimeStringMap(@This(), .{ break :list std.StaticStringMap(@This()).initComptime(.{
.{ .{
"@addWithOverflow", "@addWithOverflow",
.{ .{

View File

@ -2,7 +2,7 @@ const std = @import("std");
/// Set of primitive type and value names. /// Set of primitive type and value names.
/// Does not include `_` or integer type names. /// Does not include `_` or integer type names.
pub const names = std.ComptimeStringMap(void, .{ pub const names = std.StaticStringMap(void).initComptime(.{
.{"anyerror"}, .{"anyerror"},
.{"anyframe"}, .{"anyframe"},
.{"anyopaque"}, .{"anyopaque"},

View File

@ -2886,11 +2886,11 @@ fn renderIdentifier(r: *Render, token_index: Ast.TokenIndex, space: Space, quote
// If we read the whole thing, we have to do further checks. // If we read the whole thing, we have to do further checks.
const longest_keyword_or_primitive_len = comptime blk: { const longest_keyword_or_primitive_len = comptime blk: {
var longest = 0; var longest = 0;
for (primitives.names.kvs) |kv| { for (primitives.names.keys()) |key| {
if (kv.key.len > longest) longest = kv.key.len; if (key.len > longest) longest = key.len;
} }
for (std.zig.Token.keywords.kvs) |kv| { for (std.zig.Token.keywords.keys()) |key| {
if (kv.key.len > longest) longest = kv.key.len; if (key.len > longest) longest = key.len;
} }
break :blk longest; break :blk longest;
}; };

View File

@ -9,7 +9,7 @@ pub const Token = struct {
end: usize, end: usize,
}; };
pub const keywords = std.ComptimeStringMap(Tag, .{ pub const keywords = std.StaticStringMap(Tag).initComptime(.{
.{ "addrspace", .keyword_addrspace }, .{ "addrspace", .keyword_addrspace },
.{ "align", .keyword_align }, .{ "align", .keyword_align },
.{ "allowzero", .keyword_allowzero }, .{ "allowzero", .keyword_allowzero },

View File

@ -265,7 +265,7 @@ pub const CRTFile = struct {
/// Supported languages for "zig clang -x <lang>". /// Supported languages for "zig clang -x <lang>".
/// Loosely based on llvm-project/clang/include/clang/Driver/Types.def /// Loosely based on llvm-project/clang/include/clang/Driver/Types.def
pub const LangToExt = std.ComptimeStringMap(FileExt, .{ pub const LangToExt = std.StaticStringMap(FileExt).initComptime(.{
.{ "c", .c }, .{ "c", .c },
.{ "c-header", .h }, .{ "c-header", .h },
.{ "c++", .cpp }, .{ "c++", .cpp },

View File

@ -116,7 +116,7 @@ const ValueRenderLocation = enum {
const BuiltinInfo = enum { none, bits }; const BuiltinInfo = enum { none, bits };
const reserved_idents = std.ComptimeStringMap(void, .{ const reserved_idents = std.StaticStringMap(void).initComptime(.{
// C language // C language
.{ "alignas", { .{ "alignas", {
@setEvalBranchQuota(4000); @setEvalBranchQuota(4000);

View File

@ -244,7 +244,7 @@ pub const Feature = struct {
} }
}; };
pub const known_features = std.ComptimeStringMap(Feature.Tag, .{ pub const known_features = std.StaticStringMap(Feature.Tag).initComptime(.{
.{ "atomics", .atomics }, .{ "atomics", .atomics },
.{ "bulk-memory", .bulk_memory }, .{ "bulk-memory", .bulk_memory },
.{ "exception-handling", .exception_handling }, .{ "exception-handling", .exception_handling },

View File

@ -671,7 +671,7 @@ fn visitVarDecl(c: *Context, var_decl: *const clang.VarDecl, mangled_name: ?[]co
return addTopLevelDecl(c, var_name, node); return addTopLevelDecl(c, var_name, node);
} }
const builtin_typedef_map = std.ComptimeStringMap([]const u8, .{ const builtin_typedef_map = std.StaticStringMap([]const u8).initComptime(.{
.{ "uint8_t", "u8" }, .{ "uint8_t", "u8" },
.{ "int8_t", "i8" }, .{ "int8_t", "i8" },
.{ "uint16_t", "u16" }, .{ "uint16_t", "u16" },

View File

@ -993,7 +993,7 @@ const TestManifest = struct {
config_map: std.StringHashMap([]const u8), config_map: std.StringHashMap([]const u8),
trailing_bytes: []const u8 = "", trailing_bytes: []const u8 = "",
const valid_keys = std.ComptimeStringMap(void, .{ const valid_keys = std.StaticStringMap(void).initComptime(.{
.{ "is_test", {} }, .{ "is_test", {} },
.{ "output_mode", {} }, .{ "output_mode", {} },
.{ "target", {} }, .{ "target", {} },

View File

@ -45,7 +45,7 @@ const OperandKindMap = std.ArrayHashMap(StringPair, OperandKind, StringPairConte
/// Khronos made it so that these names are not defined explicitly, so /// Khronos made it so that these names are not defined explicitly, so
/// we need to hardcode it (like they did). /// we need to hardcode it (like they did).
/// See https://github.com/KhronosGroup/SPIRV-Registry/ /// See https://github.com/KhronosGroup/SPIRV-Registry/
const set_names = std.ComptimeStringMap([]const u8, .{ const set_names = std.StaticStringMap([]const u8).initComptime(.{
.{ "opencl.std.100", "OpenCL.std" }, .{ "opencl.std.100", "OpenCL.std" },
.{ "glsl.std.450", "GLSL.std.450" }, .{ "glsl.std.450", "GLSL.std.450" },
.{ "opencl.debuginfo.100", "OpenCL.DebugInfo.100" }, .{ "opencl.debuginfo.100", "OpenCL.DebugInfo.100" },

View File

@ -9,7 +9,7 @@ const fmt = std.fmt;
const zig = std.zig; const zig = std.zig;
const fs = std.fs; const fs = std.fs;
const stdlib_renames = std.ComptimeStringMap([]const u8, .{ const stdlib_renames = std.StaticStringMap([]const u8).initComptime(.{
// Most 64-bit archs. // Most 64-bit archs.
.{ "newfstatat", "fstatat64" }, .{ "newfstatat", "fstatat64" },
// POWER. // POWER.