mirror of
https://github.com/ziglang/zig.git
synced 2024-11-26 15:12:31 +00:00
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:
parent
fefdbca6e6
commit
8af59d1f98
@ -222,7 +222,7 @@ set(ZIG_STAGE2_SOURCES
|
||||
"${CMAKE_SOURCE_DIR}/lib/std/c/linux.zig"
|
||||
"${CMAKE_SOURCE_DIR}/lib/std/child_process.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/blake3.zig"
|
||||
"${CMAKE_SOURCE_DIR}/lib/std/crypto/siphash.zig"
|
||||
|
2
lib/compiler/aro/aro/LangOpts.zig
vendored
2
lib/compiler/aro/aro/LangOpts.zig
vendored
@ -47,7 +47,7 @@ pub const Standard = enum {
|
||||
/// Working Draft for ISO C23 with GNU extensions
|
||||
gnu23,
|
||||
|
||||
const NameMap = std.ComptimeStringMap(Standard, .{
|
||||
const NameMap = std.StaticStringMap(Standard).initComptime(.{
|
||||
.{ "c89", .c89 }, .{ "c90", .c89 }, .{ "iso9899:1990", .c89 },
|
||||
.{ "iso9899:199409", .iso9899 }, .{ "gnu89", .gnu89 }, .{ "gnu90", .gnu89 },
|
||||
.{ "c99", .c99 }, .{ "iso9899:1999", .c99 }, .{ "c9x", .c99 },
|
||||
|
2
lib/compiler/aro/aro/Preprocessor.zig
vendored
2
lib/compiler/aro/aro/Preprocessor.zig
vendored
@ -1709,7 +1709,7 @@ fn expandFuncMacro(
|
||||
}
|
||||
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" },
|
||||
.{ "fallthrough", "201904L\n" },
|
||||
.{ "maybe_unused", "201904L\n" },
|
||||
|
2
lib/compiler/aro/aro/Tokenizer.zig
vendored
2
lib/compiler/aro/aro/Tokenizer.zig
vendored
@ -872,7 +872,7 @@ pub const Token = struct {
|
||||
};
|
||||
}
|
||||
|
||||
const all_kws = std.ComptimeStringMap(Id, .{
|
||||
const all_kws = std.StaticStringMap(Id).initComptime(.{
|
||||
.{ "auto", auto: {
|
||||
@setEvalBranchQuota(3000);
|
||||
break :auto .keyword_auto;
|
||||
|
@ -240,7 +240,7 @@ pub const ErrorDetails = struct {
|
||||
// see https://github.com/ziglang/zig/issues/15395
|
||||
_: u26 = 0,
|
||||
|
||||
pub const strings = std.ComptimeStringMap([]const u8, .{
|
||||
pub const strings = std.StaticStringMap([]const u8).initComptime(.{
|
||||
.{ "number", "number" },
|
||||
.{ "number_expression", "number expression" },
|
||||
.{ "string_literal", "quoted string literal" },
|
||||
|
@ -47,7 +47,10 @@ pub const Resource = enum {
|
||||
fontdir_num,
|
||||
manifest_num,
|
||||
|
||||
const map = std.ComptimeStringMapWithEql(Resource, .{
|
||||
const map = std.StaticStringMapWithEql(
|
||||
Resource,
|
||||
std.static_string_map.eqlAsciiIgnoreCase,
|
||||
).initComptime(.{
|
||||
.{ "ACCELERATORS", .accelerators },
|
||||
.{ "BITMAP", .bitmap },
|
||||
.{ "CURSOR", .cursor },
|
||||
@ -67,7 +70,7 @@ pub const Resource = enum {
|
||||
.{ "TOOLBAR", .toolbar },
|
||||
.{ "VERSIONINFO", .versioninfo },
|
||||
.{ "VXD", .vxd },
|
||||
}, std.comptime_string_map.eqlAsciiIgnoreCase);
|
||||
});
|
||||
|
||||
pub fn fromString(bytes: SourceBytes) Resource {
|
||||
const maybe_ordinal = res.NameOrOrdinal.maybeOrdinalFromString(bytes);
|
||||
@ -157,20 +160,26 @@ pub const OptionalStatements = enum {
|
||||
menu,
|
||||
style,
|
||||
|
||||
pub const map = std.ComptimeStringMapWithEql(OptionalStatements, .{
|
||||
pub const map = std.StaticStringMapWithEql(
|
||||
OptionalStatements,
|
||||
std.static_string_map.eqlAsciiIgnoreCase,
|
||||
).initComptime(.{
|
||||
.{ "CHARACTERISTICS", .characteristics },
|
||||
.{ "LANGUAGE", .language },
|
||||
.{ "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 },
|
||||
.{ "CLASS", .class },
|
||||
.{ "EXSTYLE", .exstyle },
|
||||
.{ "FONT", .font },
|
||||
.{ "MENU", .menu },
|
||||
.{ "STYLE", .style },
|
||||
}, std.comptime_string_map.eqlAsciiIgnoreCase);
|
||||
});
|
||||
};
|
||||
|
||||
pub const Control = enum {
|
||||
@ -197,7 +206,10 @@ pub const Control = enum {
|
||||
state3,
|
||||
userbutton,
|
||||
|
||||
pub const map = std.ComptimeStringMapWithEql(Control, .{
|
||||
pub const map = std.StaticStringMapWithEql(
|
||||
Control,
|
||||
std.static_string_map.eqlAsciiIgnoreCase,
|
||||
).initComptime(.{
|
||||
.{ "AUTO3STATE", .auto3state },
|
||||
.{ "AUTOCHECKBOX", .autocheckbox },
|
||||
.{ "AUTORADIOBUTTON", .autoradiobutton },
|
||||
@ -220,7 +232,7 @@ pub const Control = enum {
|
||||
.{ "SCROLLBAR", .scrollbar },
|
||||
.{ "STATE3", .state3 },
|
||||
.{ "USERBUTTON", .userbutton },
|
||||
}, std.comptime_string_map.eqlAsciiIgnoreCase);
|
||||
});
|
||||
|
||||
pub fn hasTextParam(control: Control) bool {
|
||||
switch (control) {
|
||||
@ -231,14 +243,17 @@ pub const Control = enum {
|
||||
};
|
||||
|
||||
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 },
|
||||
.{ "EDIT", .edit },
|
||||
.{ "STATIC", .static },
|
||||
.{ "LISTBOX", .listbox },
|
||||
.{ "SCROLLBAR", .scrollbar },
|
||||
.{ "COMBOBOX", .combobox },
|
||||
}, std.comptime_string_map.eqlAsciiIgnoreCase);
|
||||
});
|
||||
|
||||
/// Like `map.get` but works on WTF16 strings, for use with parsed
|
||||
/// string literals ("BUTTON", or even "\x42UTTON")
|
||||
@ -280,10 +295,13 @@ pub const MenuItem = enum {
|
||||
menuitem,
|
||||
popup,
|
||||
|
||||
pub const map = std.ComptimeStringMapWithEql(MenuItem, .{
|
||||
pub const map = std.StaticStringMapWithEql(
|
||||
MenuItem,
|
||||
std.static_string_map.eqlAsciiIgnoreCase,
|
||||
).initComptime(.{
|
||||
.{ "MENUITEM", .menuitem },
|
||||
.{ "POPUP", .popup },
|
||||
}, std.comptime_string_map.eqlAsciiIgnoreCase);
|
||||
});
|
||||
|
||||
pub fn isSeparator(bytes: []const u8) bool {
|
||||
return std.ascii.eqlIgnoreCase(bytes, "SEPARATOR");
|
||||
@ -297,14 +315,17 @@ pub const MenuItem = enum {
|
||||
menubarbreak,
|
||||
menubreak,
|
||||
|
||||
pub const map = std.ComptimeStringMapWithEql(Option, .{
|
||||
pub const map = std.StaticStringMapWithEql(
|
||||
Option,
|
||||
std.static_string_map.eqlAsciiIgnoreCase,
|
||||
).initComptime(.{
|
||||
.{ "CHECKED", .checked },
|
||||
.{ "GRAYED", .grayed },
|
||||
.{ "HELP", .help },
|
||||
.{ "INACTIVE", .inactive },
|
||||
.{ "MENUBARBREAK", .menubarbreak },
|
||||
.{ "MENUBREAK", .menubreak },
|
||||
}, std.comptime_string_map.eqlAsciiIgnoreCase);
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
@ -312,10 +333,13 @@ pub const ToolbarButton = enum {
|
||||
button,
|
||||
separator,
|
||||
|
||||
pub const map = std.ComptimeStringMapWithEql(ToolbarButton, .{
|
||||
pub const map = std.StaticStringMapWithEql(
|
||||
ToolbarButton,
|
||||
std.static_string_map.eqlAsciiIgnoreCase,
|
||||
).initComptime(.{
|
||||
.{ "BUTTON", .button },
|
||||
.{ "SEPARATOR", .separator },
|
||||
}, std.comptime_string_map.eqlAsciiIgnoreCase);
|
||||
});
|
||||
};
|
||||
|
||||
pub const VersionInfo = enum {
|
||||
@ -327,7 +351,10 @@ pub const VersionInfo = enum {
|
||||
file_type,
|
||||
file_subtype,
|
||||
|
||||
pub const map = std.ComptimeStringMapWithEql(VersionInfo, .{
|
||||
pub const map = std.StaticStringMapWithEql(
|
||||
VersionInfo,
|
||||
std.static_string_map.eqlAsciiIgnoreCase,
|
||||
).initComptime(.{
|
||||
.{ "FILEVERSION", .file_version },
|
||||
.{ "PRODUCTVERSION", .product_version },
|
||||
.{ "FILEFLAGSMASK", .file_flags_mask },
|
||||
@ -335,17 +362,20 @@ pub const VersionInfo = enum {
|
||||
.{ "FILEOS", .file_os },
|
||||
.{ "FILETYPE", .file_type },
|
||||
.{ "FILESUBTYPE", .file_subtype },
|
||||
}, std.comptime_string_map.eqlAsciiIgnoreCase);
|
||||
});
|
||||
};
|
||||
|
||||
pub const VersionBlock = enum {
|
||||
block,
|
||||
value,
|
||||
|
||||
pub const map = std.ComptimeStringMapWithEql(VersionBlock, .{
|
||||
pub const map = std.StaticStringMapWithEql(
|
||||
VersionBlock,
|
||||
std.static_string_map.eqlAsciiIgnoreCase,
|
||||
).initComptime(.{
|
||||
.{ "BLOCK", .block },
|
||||
.{ "VALUE", .value },
|
||||
}, std.comptime_string_map.eqlAsciiIgnoreCase);
|
||||
});
|
||||
};
|
||||
|
||||
/// 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,
|
||||
stringtable,
|
||||
|
||||
pub const map = std.ComptimeStringMapWithEql(TopLevelKeywords, .{
|
||||
pub const map = std.StaticStringMapWithEql(
|
||||
TopLevelKeywords,
|
||||
std.static_string_map.eqlAsciiIgnoreCase,
|
||||
).initComptime(.{
|
||||
.{ "LANGUAGE", .language },
|
||||
.{ "VERSION", .version },
|
||||
.{ "CHARACTERISTICS", .characteristics },
|
||||
.{ "STRINGTABLE", .stringtable },
|
||||
}, std.comptime_string_map.eqlAsciiIgnoreCase);
|
||||
});
|
||||
};
|
||||
|
||||
pub const CommonResourceAttributes = enum {
|
||||
@ -375,7 +408,10 @@ pub const CommonResourceAttributes = enum {
|
||||
shared,
|
||||
nonshared,
|
||||
|
||||
pub const map = std.ComptimeStringMapWithEql(CommonResourceAttributes, .{
|
||||
pub const map = std.StaticStringMapWithEql(
|
||||
CommonResourceAttributes,
|
||||
std.static_string_map.eqlAsciiIgnoreCase,
|
||||
).initComptime(.{
|
||||
.{ "PRELOAD", .preload },
|
||||
.{ "LOADONCALL", .loadoncall },
|
||||
.{ "FIXED", .fixed },
|
||||
@ -385,7 +421,7 @@ pub const CommonResourceAttributes = enum {
|
||||
.{ "IMPURE", .impure },
|
||||
.{ "SHARED", .shared },
|
||||
.{ "NONSHARED", .nonshared },
|
||||
}, std.comptime_string_map.eqlAsciiIgnoreCase);
|
||||
});
|
||||
};
|
||||
|
||||
pub const AcceleratorTypeAndOptions = enum {
|
||||
@ -396,12 +432,15 @@ pub const AcceleratorTypeAndOptions = enum {
|
||||
shift,
|
||||
control,
|
||||
|
||||
pub const map = std.ComptimeStringMapWithEql(AcceleratorTypeAndOptions, .{
|
||||
pub const map = std.StaticStringMapWithEql(
|
||||
AcceleratorTypeAndOptions,
|
||||
std.static_string_map.eqlAsciiIgnoreCase,
|
||||
).initComptime(.{
|
||||
.{ "VIRTKEY", .virtkey },
|
||||
.{ "ASCII", .ascii },
|
||||
.{ "NOINVERT", .noinvert },
|
||||
.{ "ALT", .alt },
|
||||
.{ "SHIFT", .shift },
|
||||
.{ "CONTROL", .control },
|
||||
}, std.comptime_string_map.eqlAsciiIgnoreCase);
|
||||
});
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
@ -19,7 +19,7 @@ pub const Algorithm = enum {
|
||||
md5WithRSAEncryption,
|
||||
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, 0x0B }, .sha256WithRSAEncryption },
|
||||
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0C }, .sha384WithRSAEncryption },
|
||||
@ -52,7 +52,7 @@ pub const AlgorithmCategory = enum {
|
||||
X9_62_id_ecPublicKey,
|
||||
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, 0xCE, 0x3D, 0x02, 0x01 }, .X9_62_id_ecPublicKey },
|
||||
.{ &[_]u8{ 0x2B, 0x65, 0x70 }, .curveEd25519 },
|
||||
@ -73,7 +73,7 @@ pub const Attribute = enum {
|
||||
pkcs9_emailAddress,
|
||||
domainComponent,
|
||||
|
||||
pub const map = std.ComptimeStringMap(Attribute, .{
|
||||
pub const map = std.StaticStringMap(Attribute).initComptime(.{
|
||||
.{ &[_]u8{ 0x55, 0x04, 0x03 }, .commonName },
|
||||
.{ &[_]u8{ 0x55, 0x04, 0x05 }, .serialNumber },
|
||||
.{ &[_]u8{ 0x55, 0x04, 0x06 }, .countryName },
|
||||
@ -94,7 +94,7 @@ pub const NamedCurve = enum {
|
||||
secp521r1,
|
||||
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, 0x23 }, .secp521r1 },
|
||||
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07 }, .X9_62_prime256v1 },
|
||||
@ -130,7 +130,7 @@ pub const ExtensionId = enum {
|
||||
netscape_cert_type,
|
||||
netscape_comment,
|
||||
|
||||
pub const map = std.ComptimeStringMap(ExtensionId, .{
|
||||
pub const map = std.StaticStringMap(ExtensionId).initComptime(.{
|
||||
.{ &[_]u8{ 0x55, 0x04, 0x03 }, .commonName },
|
||||
.{ &[_]u8{ 0x55, 0x1D, 0x01 }, .authority_key_identifier },
|
||||
.{ &[_]u8{ 0x55, 0x1D, 0x07 }, .subject_alt_name },
|
||||
|
@ -1641,7 +1641,7 @@ test "walker" {
|
||||
|
||||
// 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"},
|
||||
.{"dir2"},
|
||||
.{"dir3"},
|
||||
@ -1651,7 +1651,7 @@ test "walker" {
|
||||
.{"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"},
|
||||
.{"dir2"},
|
||||
.{"dir3"},
|
||||
@ -1661,8 +1661,8 @@ test "walker" {
|
||||
.{"subsub1"},
|
||||
});
|
||||
|
||||
for (expected_paths.kvs) |kv| {
|
||||
try tmp.dir.makePath(kv.key);
|
||||
for (expected_paths.keys()) |key| {
|
||||
try tmp.dir.makePath(key);
|
||||
}
|
||||
|
||||
var walker = try tmp.dir.walk(testing.allocator);
|
||||
|
@ -1570,7 +1570,7 @@ pub const RequestOptions = struct {
|
||||
};
|
||||
|
||||
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 },
|
||||
.{ "ws", .plain },
|
||||
.{ "https", .tls },
|
||||
|
@ -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.
|
||||
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
|
||||
// performance when doing things like constructing lookup maps at comptime.
|
||||
// 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..];
|
||||
};
|
||||
const map = std.ComptimeStringMap(T, kvs);
|
||||
const map = std.StaticStringMap(T).initComptime(kvs);
|
||||
return map.get(str);
|
||||
} else {
|
||||
inline for (@typeInfo(T).Enum.fields) |enumField| {
|
||||
|
502
lib/std/static_string_map.zig
Normal file
502
lib/std/static_string_map.zig
Normal 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);
|
||||
}
|
@ -16,8 +16,8 @@ pub const BufMap = @import("buf_map.zig").BufMap;
|
||||
pub const BufSet = @import("buf_set.zig").BufSet;
|
||||
/// Deprecated: use `process.Child`.
|
||||
pub const ChildProcess = @import("child_process.zig").ChildProcess;
|
||||
pub const ComptimeStringMap = comptime_string_map.ComptimeStringMap;
|
||||
pub const ComptimeStringMapWithEql = comptime_string_map.ComptimeStringMapWithEql;
|
||||
pub const StaticStringMap = static_string_map.StaticStringMap;
|
||||
pub const StaticStringMapWithEql = static_string_map.StaticStringMapWithEql;
|
||||
pub const DoublyLinkedList = @import("linked_list.zig").DoublyLinkedList;
|
||||
pub const DynLib = @import("dynamic_library.zig").DynLib;
|
||||
pub const DynamicBitSet = bit_set.DynamicBitSet;
|
||||
@ -62,7 +62,7 @@ pub const builtin = @import("builtin.zig");
|
||||
pub const c = @import("c.zig");
|
||||
pub const coff = @import("coff.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 debug = @import("debug.zig");
|
||||
pub const dwarf = @import("dwarf.zig");
|
||||
|
@ -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 },
|
||||
.{ "anyframe", .anyframe_type },
|
||||
.{ "anyopaque", .anyopaque_type },
|
||||
@ -10173,14 +10173,14 @@ const primitive_instrs = std.ComptimeStringMap(Zir.Inst.Ref, .{
|
||||
comptime {
|
||||
// These checks ensure that std.zig.primitives stays in sync with the primitive->Zir map.
|
||||
const primitives = std.zig.primitives;
|
||||
for (primitive_instrs.kvs) |kv| {
|
||||
if (!primitives.isPrimitive(kv.key)) {
|
||||
@compileError("std.zig.isPrimitive() is not aware of Zir instr '" ++ @tagName(kv.value) ++ "'");
|
||||
for (primitive_instrs.keys(), primitive_instrs.values()) |key, value| {
|
||||
if (!primitives.isPrimitive(key)) {
|
||||
@compileError("std.zig.isPrimitive() is not aware of Zir instr '" ++ @tagName(value) ++ "'");
|
||||
}
|
||||
}
|
||||
for (primitives.names.kvs) |kv| {
|
||||
if (primitive_instrs.get(kv.key) == null) {
|
||||
@compileError("std.zig.primitives entry '" ++ kv.key ++ "' does not have a corresponding Zir instr");
|
||||
for (primitives.names.keys()) |key| {
|
||||
if (primitive_instrs.get(key) == null) {
|
||||
@compileError("std.zig.primitives entry '" ++ key ++ "' does not have a corresponding Zir instr");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -160,7 +160,7 @@ param_count: ?u8,
|
||||
|
||||
pub const list = list: {
|
||||
@setEvalBranchQuota(3000);
|
||||
break :list std.ComptimeStringMap(@This(), .{
|
||||
break :list std.StaticStringMap(@This()).initComptime(.{
|
||||
.{
|
||||
"@addWithOverflow",
|
||||
.{
|
||||
|
@ -2,7 +2,7 @@ const std = @import("std");
|
||||
|
||||
/// Set of primitive type and value names.
|
||||
/// Does not include `_` or integer type names.
|
||||
pub const names = std.ComptimeStringMap(void, .{
|
||||
pub const names = std.StaticStringMap(void).initComptime(.{
|
||||
.{"anyerror"},
|
||||
.{"anyframe"},
|
||||
.{"anyopaque"},
|
||||
|
@ -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.
|
||||
const longest_keyword_or_primitive_len = comptime blk: {
|
||||
var longest = 0;
|
||||
for (primitives.names.kvs) |kv| {
|
||||
if (kv.key.len > longest) longest = kv.key.len;
|
||||
for (primitives.names.keys()) |key| {
|
||||
if (key.len > longest) longest = key.len;
|
||||
}
|
||||
for (std.zig.Token.keywords.kvs) |kv| {
|
||||
if (kv.key.len > longest) longest = kv.key.len;
|
||||
for (std.zig.Token.keywords.keys()) |key| {
|
||||
if (key.len > longest) longest = key.len;
|
||||
}
|
||||
break :blk longest;
|
||||
};
|
||||
|
@ -9,7 +9,7 @@ pub const Token = struct {
|
||||
end: usize,
|
||||
};
|
||||
|
||||
pub const keywords = std.ComptimeStringMap(Tag, .{
|
||||
pub const keywords = std.StaticStringMap(Tag).initComptime(.{
|
||||
.{ "addrspace", .keyword_addrspace },
|
||||
.{ "align", .keyword_align },
|
||||
.{ "allowzero", .keyword_allowzero },
|
||||
|
@ -265,7 +265,7 @@ pub const CRTFile = struct {
|
||||
|
||||
/// Supported languages for "zig clang -x <lang>".
|
||||
/// 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-header", .h },
|
||||
.{ "c++", .cpp },
|
||||
|
@ -116,7 +116,7 @@ const ValueRenderLocation = enum {
|
||||
|
||||
const BuiltinInfo = enum { none, bits };
|
||||
|
||||
const reserved_idents = std.ComptimeStringMap(void, .{
|
||||
const reserved_idents = std.StaticStringMap(void).initComptime(.{
|
||||
// C language
|
||||
.{ "alignas", {
|
||||
@setEvalBranchQuota(4000);
|
||||
|
@ -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 },
|
||||
.{ "bulk-memory", .bulk_memory },
|
||||
.{ "exception-handling", .exception_handling },
|
||||
|
@ -671,7 +671,7 @@ fn visitVarDecl(c: *Context, var_decl: *const clang.VarDecl, mangled_name: ?[]co
|
||||
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" },
|
||||
.{ "int8_t", "i8" },
|
||||
.{ "uint16_t", "u16" },
|
||||
|
@ -993,7 +993,7 @@ const TestManifest = struct {
|
||||
config_map: std.StringHashMap([]const u8),
|
||||
trailing_bytes: []const u8 = "",
|
||||
|
||||
const valid_keys = std.ComptimeStringMap(void, .{
|
||||
const valid_keys = std.StaticStringMap(void).initComptime(.{
|
||||
.{ "is_test", {} },
|
||||
.{ "output_mode", {} },
|
||||
.{ "target", {} },
|
||||
|
@ -45,7 +45,7 @@ const OperandKindMap = std.ArrayHashMap(StringPair, OperandKind, StringPairConte
|
||||
/// Khronos made it so that these names are not defined explicitly, so
|
||||
/// we need to hardcode it (like they did).
|
||||
/// 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" },
|
||||
.{ "glsl.std.450", "GLSL.std.450" },
|
||||
.{ "opencl.debuginfo.100", "OpenCL.DebugInfo.100" },
|
||||
|
@ -9,7 +9,7 @@ const fmt = std.fmt;
|
||||
const zig = std.zig;
|
||||
const fs = std.fs;
|
||||
|
||||
const stdlib_renames = std.ComptimeStringMap([]const u8, .{
|
||||
const stdlib_renames = std.StaticStringMap([]const u8).initComptime(.{
|
||||
// Most 64-bit archs.
|
||||
.{ "newfstatat", "fstatat64" },
|
||||
// POWER.
|
||||
|
Loading…
Reference in New Issue
Block a user