| ... | @@ -43,12 +43,15 @@ pub fn ComptimeStringMapWithEql( | ... | @@ -43,12 +43,15 @@ pub fn ComptimeStringMapWithEql( |
| 43 | comptime kvs_list: anytype, | 43 | comptime kvs_list: anytype, |
| 44 | comptime eql: fn (a: []const u8, b: []const u8) bool, | 44 | comptime eql: fn (a: []const u8, b: []const u8) bool, |
| 45 | ) type { | 45 | ) type { |
| 46 | const precomputed = comptime blk: { | 46 | const empty_list = kvs_list.len == 0; |
| | 47 | const precomputed = blk: { |
| 47 | @setEvalBranchQuota(1500); | 48 | @setEvalBranchQuota(1500); |
| 48 | const KV = struct { | 49 | const KV = struct { |
| 49 | key: []const u8, | 50 | key: []const u8, |
| 50 | value: V, | 51 | value: V, |
| 51 | }; | 52 | }; |
| | 53 | if (empty_list) |
| | 54 | break :blk .{}; |
| 52 | var sorted_kvs: [kvs_list.len]KV = undefined; | 55 | var sorted_kvs: [kvs_list.len]KV = undefined; |
| 53 | for (kvs_list, 0..) |kv, i| { | 56 | for (kvs_list, 0..) |kv, i| { |
| 54 | if (V != void) { | 57 | if (V != void) { |
| ... | @@ -103,10 +106,16 @@ pub fn ComptimeStringMapWithEql( | ... | @@ -103,10 +106,16 @@ pub fn ComptimeStringMapWithEql( |
| 103 | | 106 | |
| 104 | /// Returns the value for the key if any, else null. | 107 | /// Returns the value for the key if any, else null. |
| 105 | pub fn get(str: []const u8) ?V { | 108 | pub fn get(str: []const u8) ?V { |
| | 109 | if (empty_list) |
| | 110 | return null; |
| | 111 | |
| 106 | return precomputed.sorted_kvs[getIndex(str) orelse return null].value; | 112 | return precomputed.sorted_kvs[getIndex(str) orelse return null].value; |
| 107 | } | 113 | } |
| 108 | | 114 | |
| 109 | pub fn getIndex(str: []const u8) ?usize { | 115 | pub fn getIndex(str: []const u8) ?usize { |
| | 116 | if (empty_list) |
| | 117 | return null; |
| | 118 | |
| 110 | if (str.len < precomputed.min_len or str.len > precomputed.max_len) | 119 | if (str.len < precomputed.min_len or str.len > precomputed.max_len) |
| 111 | return null; | 120 | return null; |
| 112 | | 121 | |
| ... | @@ -143,6 +152,9 @@ test "ComptimeStringMap list literal of list literals" { | ... | @@ -143,6 +152,9 @@ test "ComptimeStringMap list literal of list literals" { |
| 143 | }); | 152 | }); |
| 144 | | 153 | |
| 145 | try testMap(map); | 154 | try testMap(map); |
| | 155 | |
| | 156 | // Default comparison is case sensitive |
| | 157 | try std.testing.expect(null == map.get("NOTHING")); |
| 146 | } | 158 | } |
| 147 | | 159 | |
| 148 | test "ComptimeStringMap array of structs" { | 160 | test "ComptimeStringMap array of structs" { |
| ... | @@ -181,6 +193,9 @@ fn testMap(comptime map: anytype) !void { | ... | @@ -181,6 +193,9 @@ fn testMap(comptime map: anytype) !void { |
| 181 | | 193 | |
| 182 | try std.testing.expect(!map.has("missing")); | 194 | try std.testing.expect(!map.has("missing")); |
| 183 | try std.testing.expect(map.has("these")); | 195 | try std.testing.expect(map.has("these")); |
| | 196 | |
| | 197 | try std.testing.expect(null == map.get("")); |
| | 198 | try std.testing.expect(null == map.get("averylongstringthathasnomatches")); |
| 184 | } | 199 | } |
| 185 | | 200 | |
| 186 | test "ComptimeStringMap void value type, slice of structs" { | 201 | test "ComptimeStringMap void value type, slice of structs" { |
| ... | @@ -195,6 +210,9 @@ test "ComptimeStringMap void value type, slice of structs" { | ... | @@ -195,6 +210,9 @@ test "ComptimeStringMap void value type, slice of structs" { |
| 195 | const map = ComptimeStringMap(void, slice); | 210 | const map = ComptimeStringMap(void, slice); |
| 196 | | 211 | |
| 197 | try testSet(map); | 212 | try testSet(map); |
| | 213 | |
| | 214 | // Default comparison is case sensitive |
| | 215 | try std.testing.expect(null == map.get("NOTHING")); |
| 198 | } | 216 | } |
| 199 | | 217 | |
| 200 | test "ComptimeStringMap void value type, list literal of list literals" { | 218 | test "ComptimeStringMap void value type, list literal of list literals" { |
| ... | @@ -218,6 +236,9 @@ fn testSet(comptime map: anytype) !void { | ... | @@ -218,6 +236,9 @@ fn testSet(comptime map: anytype) !void { |
| 218 | | 236 | |
| 219 | try std.testing.expect(!map.has("missing")); | 237 | try std.testing.expect(!map.has("missing")); |
| 220 | try std.testing.expect(map.has("these")); | 238 | try std.testing.expect(map.has("these")); |
| | 239 | |
| | 240 | try std.testing.expect(null == map.get("")); |
| | 241 | try std.testing.expect(null == map.get("averylongstringthathasnomatches")); |
| 221 | } | 242 | } |
| 222 | | 243 | |
| 223 | test "ComptimeStringMapWithEql" { | 244 | test "ComptimeStringMapWithEql" { |
| ... | @@ -236,3 +257,45 @@ test "ComptimeStringMapWithEql" { | ... | @@ -236,3 +257,45 @@ test "ComptimeStringMapWithEql" { |
| 236 | | 257 | |
| 237 | try std.testing.expect(map.has("ThESe")); | 258 | try std.testing.expect(map.has("ThESe")); |
| 238 | } | 259 | } |
| | 260 | |
| | 261 | test "ComptimeStringMap empty" { |
| | 262 | const m1 = ComptimeStringMap(usize, .{}); |
| | 263 | try std.testing.expect(null == m1.get("anything")); |
| | 264 | |
| | 265 | const m2 = ComptimeStringMapWithEql(usize, .{}, eqlAsciiIgnoreCase); |
| | 266 | try std.testing.expect(null == m2.get("anything")); |
| | 267 | } |
| | 268 | |
| | 269 | test "ComptimeStringMap redundant entries" { |
| | 270 | const map = ComptimeStringMap(TestEnum, .{ |
| | 271 | .{ "redundant", .D }, |
| | 272 | .{ "theNeedle", .A }, |
| | 273 | .{ "redundant", .B }, |
| | 274 | .{ "re" ++ "dundant", .C }, |
| | 275 | .{ "redun" ++ "dant", .E }, |
| | 276 | }); |
| | 277 | |
| | 278 | // No promises about which one you get: |
| | 279 | try std.testing.expect(null != map.get("redundant")); |
| | 280 | |
| | 281 | // Default map is not case sensitive: |
| | 282 | try std.testing.expect(null == map.get("REDUNDANT")); |
| | 283 | |
| | 284 | try std.testing.expectEqual(TestEnum.A, map.get("theNeedle").?); |
| | 285 | } |
| | 286 | |
| | 287 | test "ComptimeStringMap redundant insensitive" { |
| | 288 | const map = ComptimeStringMapWithEql(TestEnum, .{ |
| | 289 | .{ "redundant", .D }, |
| | 290 | .{ "theNeedle", .A }, |
| | 291 | .{ "redundanT", .B }, |
| | 292 | .{ "RE" ++ "dundant", .C }, |
| | 293 | .{ "redun" ++ "DANT", .E }, |
| | 294 | }, eqlAsciiIgnoreCase); |
| | 295 | |
| | 296 | // No promises about which result you'll get ... |
| | 297 | try std.testing.expect(null != map.get("REDUNDANT")); |
| | 298 | try std.testing.expect(null != map.get("ReDuNdAnT")); |
| | 299 | |
| | 300 | try std.testing.expectEqual(TestEnum.A, map.get("theNeedle").?); |
| | 301 | } |