109 lines
3.3 KiB
Zig
109 lines
3.3 KiB
Zig
/// credits to Andrew Kelley
|
|
/// strings.zig
|
|
const std = @import("std");
|
|
const mem = std.mem;
|
|
const assert = std.debug.assert;
|
|
|
|
const Allocator = std.mem.Allocator;
|
|
const Self = @This();
|
|
|
|
const max_load_percent = std.hash_map.default_max_load_percentage;
|
|
|
|
string_bytes: std.ArrayListUnmanaged(u8) = .empty,
|
|
string_table: StringIndex.Table = .empty,
|
|
|
|
pub const empty = Self{
|
|
.string_bytes = .empty,
|
|
.string_table = .empty,
|
|
};
|
|
|
|
pub fn deinit(self: *Self, allocator: Allocator) void {
|
|
self.string_bytes.deinit(allocator);
|
|
self.string_table.deinit(allocator);
|
|
}
|
|
|
|
pub const StringIndex = enum(u32) {
|
|
_,
|
|
|
|
pub const Table = std.HashMapUnmanaged(StringIndex, void, TableContext, max_load_percent);
|
|
|
|
pub const TableContext = struct {
|
|
bytes: []const u8,
|
|
|
|
pub fn eql(_: @This(), a: StringIndex, b: StringIndex) bool {
|
|
return a == b;
|
|
}
|
|
|
|
pub fn hash(ctx: @This(), key: StringIndex) u64 {
|
|
return std.hash_map.hashString(mem.sliceTo(ctx.bytes[@intFromEnum(key)..], 0));
|
|
}
|
|
};
|
|
|
|
pub const TableIndexAdapter = struct {
|
|
bytes: []const u8,
|
|
|
|
pub fn eql(ctx: @This(), a: []const u8, b: StringIndex) bool {
|
|
return mem.eql(u8, a, mem.sliceTo(ctx.bytes[@intFromEnum(b)..], 0));
|
|
}
|
|
|
|
pub fn hash(_: @This(), adapted_key: []const u8) u64 {
|
|
assert(mem.indexOfScalar(u8, adapted_key, 0) == null);
|
|
return std.hash_map.hashString(adapted_key);
|
|
}
|
|
};
|
|
|
|
pub fn slice(index: StringIndex, state: *const Self) [:0]const u8 {
|
|
const start_slice = state.string_bytes.items[@intFromEnum(index)..];
|
|
return start_slice[0..mem.indexOfScalar(u8, start_slice, 0).? :0];
|
|
}
|
|
|
|
pub fn iterator(start: StringIndex, bytes: []const u8) Iterator {
|
|
return .{
|
|
.bytes = bytes,
|
|
.pos = @intFromEnum(start),
|
|
};
|
|
}
|
|
|
|
pub const Iterator = struct {
|
|
bytes: []const u8,
|
|
pos: usize = 0,
|
|
|
|
pub fn next(self: *Iterator) ?[:0]const u8 {
|
|
if (self.pos >= self.bytes.len) return null;
|
|
|
|
// Find the next null terminator starting from current position
|
|
const end_pos = mem.indexOfScalarPos(u8, self.bytes, self.pos, 0) orelse {
|
|
// No null found: return remaining bytes (invalid, but handle gracefully)
|
|
const s = self.bytes[self.pos..];
|
|
self.pos = self.bytes.len;
|
|
return s;
|
|
};
|
|
|
|
const s = self.bytes[self.pos..end_pos :0];
|
|
self.pos = end_pos + 1; // Skip the null terminator
|
|
return s;
|
|
}
|
|
};
|
|
};
|
|
|
|
pub fn add(state: *Self, allocator: Allocator, bytes: []const u8) !StringIndex {
|
|
try state.string_bytes.ensureUnusedCapacity(allocator, bytes.len + 1);
|
|
|
|
const gop = try state.string_table.getOrPutContextAdapted(
|
|
allocator,
|
|
bytes,
|
|
StringIndex.TableIndexAdapter{ .bytes = state.string_bytes.items },
|
|
StringIndex.TableContext{ .bytes = state.string_bytes.items },
|
|
);
|
|
if (gop.found_existing) return gop.key_ptr.*;
|
|
|
|
const new_off: StringIndex = @enumFromInt(state.string_bytes.items.len);
|
|
|
|
state.string_bytes.appendSliceAssumeCapacity(bytes);
|
|
state.string_bytes.appendAssumeCapacity(0);
|
|
|
|
gop.key_ptr.* = new_off;
|
|
|
|
return new_off;
|
|
}
|