/// 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; }