224 lines
4.6 KiB
Markdown
224 lines
4.6 KiB
Markdown
# std.StaticStringMap
|
|
|
|
Compile-time optimized string lookup. Perfect hash for small, fixed sets of string keys.
|
|
|
|
## When to Use
|
|
|
|
- Keywords/reserved words lookup
|
|
- Command/option parsing
|
|
- Static configuration keys
|
|
- When string set is known at compile time
|
|
- Very fast O(1) lookups by string length
|
|
|
|
## Basic Usage
|
|
|
|
```zig
|
|
const std = @import("std");
|
|
|
|
const keywords = std.StaticStringMap(enum { @"if", @"else", @"while", @"for" }).initComptime(.{
|
|
.{ "if", .@"if" },
|
|
.{ "else", .@"else" },
|
|
.{ "while", .@"while" },
|
|
.{ "for", .@"for" },
|
|
});
|
|
|
|
// Lookup
|
|
if (keywords.get("while")) |kw| {
|
|
switch (kw) {
|
|
.@"while" => std.debug.print("found while\n", .{}),
|
|
else => {},
|
|
}
|
|
}
|
|
|
|
// Check existence
|
|
if (keywords.has("if")) {
|
|
// it's a keyword
|
|
}
|
|
```
|
|
|
|
## Void Value (Set)
|
|
|
|
```zig
|
|
// When you only need presence check, use void
|
|
const reserved = std.StaticStringMap(void).initComptime(.{
|
|
.{"break"},
|
|
.{"continue"},
|
|
.{"return"},
|
|
.{"defer"},
|
|
});
|
|
|
|
if (reserved.has("break")) {
|
|
std.debug.print("'break' is reserved\n", .{});
|
|
}
|
|
```
|
|
|
|
## Case-Insensitive Lookup
|
|
|
|
```zig
|
|
const commands = std.StaticStringMapWithEql(
|
|
i32,
|
|
std.static_string_map.eqlAsciiIgnoreCase,
|
|
).initComptime(.{
|
|
.{ "help", 1 },
|
|
.{ "quit", 2 },
|
|
.{ "list", 3 },
|
|
});
|
|
|
|
// All find the same entry
|
|
_ = commands.get("HELP"); // 1
|
|
_ = commands.get("Help"); // 1
|
|
_ = commands.get("help"); // 1
|
|
```
|
|
|
|
## Runtime Initialization
|
|
|
|
```zig
|
|
// When data isn't known at comptime
|
|
const pairs = [_]struct { []const u8, i32 }{
|
|
.{ "one", 1 },
|
|
.{ "two", 2 },
|
|
.{ "three", 3 },
|
|
};
|
|
|
|
const map = try std.StaticStringMap(i32).init(&pairs, allocator);
|
|
defer map.deinit(allocator);
|
|
|
|
_ = map.get("two"); // 2
|
|
```
|
|
|
|
## Get Index
|
|
|
|
```zig
|
|
// Get the index in the internal array
|
|
if (keywords.getIndex("if")) |idx| {
|
|
// idx is position in sorted-by-length array
|
|
}
|
|
```
|
|
|
|
## Longest Prefix Match
|
|
|
|
```zig
|
|
const prefixes = std.StaticStringMap(u32).initComptime(.{
|
|
.{ "/api", 1 },
|
|
.{ "/api/v1", 2 },
|
|
.{ "/api/v2", 3 },
|
|
});
|
|
|
|
// Find longest matching prefix
|
|
if (prefixes.getLongestPrefix("/api/v2/users")) |kv| {
|
|
std.debug.print("matched: {s} -> {}\n", .{ kv.key, kv.value });
|
|
// matched: /api/v2 -> 3
|
|
}
|
|
```
|
|
|
|
## Access All Keys/Values
|
|
|
|
```zig
|
|
const all_keys = keywords.keys(); // []const []const u8
|
|
const all_values = keywords.values(); // []const EnumType
|
|
```
|
|
|
|
## Complete Example: HTTP Method Parser
|
|
|
|
```zig
|
|
const std = @import("std");
|
|
|
|
const Method = enum {
|
|
GET,
|
|
POST,
|
|
PUT,
|
|
DELETE,
|
|
PATCH,
|
|
HEAD,
|
|
OPTIONS,
|
|
};
|
|
|
|
const methods = std.StaticStringMap(Method).initComptime(.{
|
|
.{ "GET", .GET },
|
|
.{ "POST", .POST },
|
|
.{ "PUT", .PUT },
|
|
.{ "DELETE", .DELETE },
|
|
.{ "PATCH", .PATCH },
|
|
.{ "HEAD", .HEAD },
|
|
.{ "OPTIONS", .OPTIONS },
|
|
});
|
|
|
|
fn parseMethod(s: []const u8) ?Method {
|
|
return methods.get(s);
|
|
}
|
|
|
|
pub fn main() void {
|
|
if (parseMethod("POST")) |m| {
|
|
std.debug.print("Method: {}\n", .{m}); // Method: POST
|
|
}
|
|
|
|
if (parseMethod("INVALID")) |_| {
|
|
unreachable;
|
|
} else {
|
|
std.debug.print("Invalid method\n", .{});
|
|
}
|
|
}
|
|
```
|
|
|
|
## Complete Example: Config Parser
|
|
|
|
```zig
|
|
const std = @import("std");
|
|
|
|
const ConfigKey = enum {
|
|
host,
|
|
port,
|
|
debug,
|
|
timeout,
|
|
};
|
|
|
|
const config_keys = std.StaticStringMapWithEql(
|
|
ConfigKey,
|
|
std.static_string_map.eqlAsciiIgnoreCase,
|
|
).initComptime(.{
|
|
.{ "host", .host },
|
|
.{ "port", .port },
|
|
.{ "debug", .debug },
|
|
.{ "timeout", .timeout },
|
|
});
|
|
|
|
fn parseConfig(line: []const u8) ?struct { key: ConfigKey, value: []const u8 } {
|
|
const eq_idx = std.mem.indexOf(u8, line, "=") orelse return null;
|
|
const key_str = std.mem.trim(u8, line[0..eq_idx], " ");
|
|
const value = std.mem.trim(u8, line[eq_idx + 1 ..], " ");
|
|
|
|
const key = config_keys.get(key_str) orelse return null;
|
|
return .{ .key = key, .value = value };
|
|
}
|
|
|
|
pub fn main() void {
|
|
const lines = [_][]const u8{
|
|
"HOST = localhost",
|
|
"PORT = 8080",
|
|
"Debug = true",
|
|
};
|
|
|
|
for (lines) |line| {
|
|
if (parseConfig(line)) |cfg| {
|
|
std.debug.print("{}: {s}\n", .{ cfg.key, cfg.value });
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## How It Works
|
|
|
|
1. Strings are sorted by length at compile time
|
|
2. Lookup first checks length to narrow candidates
|
|
3. Only strings of matching length are compared
|
|
4. Very efficient for disparate key lengths
|
|
|
|
## Notes
|
|
|
|
- Comptime version has zero runtime allocation
|
|
- Keys are grouped by length for fast rejection
|
|
- Use `eqlAsciiIgnoreCase` for case-insensitive matching
|
|
- Runtime `init()` requires `deinit()` to free memory
|
|
- Best for small to medium-sized static string sets
|
|
- Not suitable for dynamic key sets (use HashMap instead)
|