const debug = @import("./debug.zig"); const io = @import("./io.zig"); const list = @import("./list.zig"); const math = @import("./math.zig"); pub fn Slab(comptime Value: type) type { return struct { next_index: usize, entries: EntryList, const EntryList = list.Stack(union (enum) { value: Value, next_index: usize, }); const Self = @This(); pub fn lookup(self: Self, key: usize) ?Value { if (key == 0 or key > self.entries.values.len) { return null; } return switch (self.entries.values[key - 1]) { .value => |value| value, .next_index => null, }; } pub fn free(self: *Self) void { self.entries.free(); self.next_index = 0; } pub fn insert(self: *Self, value: Value) io.AllocationError!usize { if (self.next_index < self.entries.values.len) { const index = self.next_index; const entry = &self.entries.values[index]; debug.assert(entry.* == .next_index); self.next_index = entry.next_index; entry.* = .{.value = value}; return index + 1; } else { try self.entries.push_one(.{.value = value}); self.next_index += 1; return self.next_index; } } pub fn insert_at(self: *Self, key: usize, value: Value) bool { if (self.entries.values.len < key) { return false; } const entry = &self.entries.values[key - 1]; if (entry.* == .value) { return false; } self.next_index = entry.next_index; entry.* = .{.value = value}; return true; } pub fn make(allocator: io.Allocator) Self { return .{ .next_index = 0, .entries = EntryList.make(allocator), }; } pub fn remove(self: *Self, key: usize) ?Value { if (key == 0 or key > self.entries.values.len) { return null; } const index = key - 1; const entry = &self.entries.values[index]; return switch (entry.*) { .next_index => null, .value => get_value: { const value = entry.value; entry.* = .{.next_index = self.next_index}; self.next_index = index; break: get_value value; }, }; } }; } pub fn Table(comptime Key: type, comptime Value: type, comptime traits: TableTraits(Key)) type { const load_max = 0.75; return struct { allocator: io.Allocator, count: usize, entries: []?Entry, pub const Entry = struct { key: Key, value: Value, fn write_into(self: Entry, entry_table: []?Entry) bool { const hash_max = math.min(math.max_int(@typeInfo(usize).Int), entry_table.len); var hashed_key = math.wrap(traits.hash(self.key), math.min_int(@typeInfo(usize).Int), hash_max); var iterations = @as(usize, 0); while (true) : (iterations += 1) { debug.assert(iterations < entry_table.len); const table_entry = &(entry_table[hashed_key] orelse { entry_table[hashed_key] = .{ .key = self.key, .value = self.value, }; return true; }); if (traits.match(table_entry.key, self.key)) { return false; } hashed_key = (hashed_key +% 1) % hash_max; } } }; pub const Iterable = struct { table: *Self, iterations: usize = 0, pub fn next(self: *Iterable) ?Entry { while (self.iterations < self.table.entries.len) { defer self.iterations += 1; if (self.table.entries[self.iterations]) |entry| { return entry; } } return null; } }; const Self = @This(); pub fn remove(self: *Self, key: Key) ?Entry { const hash_max = math.min(math.max_int(@typeInfo(usize).Int), self.entries.len); var hashed_key = math.wrap(traits.hash(key), math.min_int(@typeInfo(usize).Int), hash_max); while (true) { const entry = &(self.entries[hashed_key] orelse continue); if (traits.match(entry.key, key)) { const original_entry = entry.*; self.entries[hashed_key] = null; return original_entry; } hashed_key = (hashed_key +% 1) % hash_max; } } pub fn replace(self: *Self, key: Key, value: Value) io.AllocationError!?Entry { try self.rehash(load_max); debug.assert(self.entries.len > self.count); { const hash_max = math.min(math.max_int(@typeInfo(usize).Int), self.entries.len); var hashed_key = math.wrap(traits.hash(key), math.min_int(@typeInfo(usize).Int), hash_max); while (true) { const entry = &(self.entries[hashed_key] orelse { self.entries[hashed_key] = .{ .key = key, .value = value, }; self.count += 1; return null; }); if (traits.match(entry.key, key)) { const original_entry = entry.*; entry.* = .{ .key = key, .value = value, }; return original_entry; } hashed_key = (hashed_key +% 1) % hash_max; } } } pub fn calculate_load_factor(self: Self) f32 { return if (self.entries.len == 0) 1 else @as(f32, @floatFromInt(self.count)) / @as(f32, @floatFromInt(self.entries.len)); } pub fn clear(self: *Self) void { for (self.entries) |*entry| { entry.* = null; } self.count = 0; } pub fn free(self: *Self) void { if (self.entries.len == 0) { return; } self.allocator.deallocate(self.entries); self.entries = &.{}; self.count = 0; } pub fn insert(self: *Self, key: Key, value: Value) io.AllocationError!bool { try self.rehash(load_max); debug.assert(self.entries.len > self.count); defer self.count += 1; const entry = Entry{ .key = key, .value = value, }; return entry.write_into(self.entries); } pub fn lookup(self: Self, key: Key) ?Value { if (self.count == 0) { return null; } const hash_max = math.min(math.max_int(@typeInfo(usize).Int), self.entries.len); var hashed_key = math.wrap(traits.hash(key), math.min_int(@typeInfo(usize).Int), hash_max); var iterations = @as(usize, 0); while (iterations < self.count) : (iterations += 1) { const entry = &(self.entries[hashed_key] orelse return null); if (traits.match(entry.key, key)) { return entry.value; } hashed_key = (hashed_key +% 1) % hash_max; } return null; } pub fn make(allocator: io.Allocator) Self { return .{ .allocator = allocator, .count = 0, .entries = &.{}, }; } pub fn rehash(self: *Self, max_load: f32) io.AllocationError!void { if (self.calculate_load_factor() <= max_load) { return; } const min_count = math.max(1, self.count); const table_size = min_count * 2; const allocation = @as([*]?Entry, @ptrCast(@alignCast(try self.allocator.reallocate(null, @sizeOf(?Entry) * table_size))))[0 .. table_size]; errdefer self.allocator.deallocate(allocation); self.entries = replace_table: { for (allocation) |*entry| { entry.* = null; } if (self.entries.len != 0) { for (self.entries) |maybe_entry| { if (maybe_entry) |entry| { debug.assert(entry.write_into(allocation)); } } self.allocator.deallocate(self.entries); } break: replace_table allocation; }; } }; } pub fn TableTraits(comptime Key: type) type { return struct { hash: fn (key: Key) usize, match: fn (key: Key, key: Key) bool, }; } fn hash_string(key: []const io.Byte) usize { var hash_code = @as(usize, 5381); for (key) |byte| { hash_code = ((hash_code << 5) +% hash_code) +% byte; } return hash_code; } pub const string_table_traits = TableTraits([]const io.Byte){ .hash = hash_string, .match = io.equals, };