aether/strings.zig
2025-06-03 02:08:52 -05:00

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