Skip to content

Instantly share code, notes, and snippets.

@squeek502
Created May 26, 2020 22:49
Show Gist options
  • Save squeek502/7f7db10c520bacff4c24b22c9102db8d to your computer and use it in GitHub Desktop.
Save squeek502/7f7db10c520bacff4c24b22c9102db8d to your computer and use it in GitHub Desktop.
An attempt at making https://github.com/ziglang/zig/pull/5442 reusable
const std = @import("std");
const mem = std.mem;
pub fn stringLookup(comptime V: type, comptime values: var) type {
const precomputed = comptime blk: {
@setEvalBranchQuota(2000);
const KV = struct {
key: []const u8,
value: V,
};
var sorted_kvs: [values.len]KV = undefined;
const lenAsc = (struct {
fn lenAsc(a: KV, b: KV) bool {
return a.key.len < b.key.len;
}
}).lenAsc;
var init_i = 0;
for (values) |kv| {
sorted_kvs[init_i] = .{.key = kv.@"0", .value = kv.@"1"};
init_i += 1;
}
std.sort.sort(KV, &sorted_kvs, lenAsc);
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 {
fn lookup(str: []const u8) ?V {
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 (mem.eql(u8, kv.key, str))
return kv.value;
i += 1;
if (i >= precomputed.sorted_kvs.len)
return null;
}
}
};
}
test "stringLookup" {
const E1 = enum {
A,
B,
C,
D,
};
const lookup = stringLookup(E1, .{
.{"these", .D},
.{"have", .A},
.{"nothing", .B},
.{"incommon", .C},
});
std.testing.expectEqual(E1.A, lookup.lookup("have").?);
std.testing.expectEqual(E1.B, lookup.lookup("nothing").?);
std.testing.expect(null == lookup.lookup("missing"));
std.testing.expectEqual(E1.D, lookup.lookup("these").?);
}
pub fn stringToEnum(comptime T: type, str: []const u8) ?T {
const kvs = comptime build_kvs: {
// This is kind of bizarre. In order to generate an array of structs
// that play nice with anonymous list literals, we need to give them
// "0" and "1" field names.
const EnumKV = struct {
@"0": []const u8,
@"1": T,
};
var kvs_array: [@typeInfo(T).Enum.fields.len]EnumKV = undefined;
var i: usize = 0;
inline for (@typeInfo(T).Enum.fields) |enumField| {
kvs_array[i] = .{.@"0" = enumField.name, .@"1" = @field(T, enumField.name)};
i += 1;
}
break :build_kvs kvs_array[0..];
};
const lookup = comptime stringLookup(T, kvs);
return lookup.lookup(str);
}
test "std.meta.stringToEnum" {
const E1 = enum {
A,
B,
D,
E,
F,
G,
H,
I,
J,
};
std.testing.expect(E1.A == stringToEnum(E1, "A").?);
std.testing.expect(E1.B == stringToEnum(E1, "B").?);
std.testing.expect(null == stringToEnum(E1, "C"));
std.testing.expect(E1.I == stringToEnum(E1, "I").?);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment