const debug = @import("./debug.zig"); const math = @import("./math.zig"); const std = @import("std"); pub const AllocationError = error { OutOfMemory, }; pub const Allocator = struct { context: *anyopaque, actions: *const struct { deallocate: *const fn (context: *anyopaque, allocation: []Byte) void, reallocate: *const fn (context: *anyopaque, return_address: usize, existing_allocation: ?[]Byte, size: usize) AllocationError![]Byte, }, pub fn Actions(comptime State: type) type { return struct { deallocate: fn (state: *State, allocation: []Byte) void, reallocate: fn (state: *State, return_address: usize, existing_allocation: ?[]Byte, size: usize) AllocationError![]Byte, }; } pub fn bind(comptime State: type, state: *State, comptime actions: Actions(State)) Allocator { const is_zero_aligned = @alignOf(State) == 0; const ErasedActions = struct { fn deallocate(context: *anyopaque, allocation: []Byte) void { if (is_zero_aligned) { return actions.deallocate(@ptrCast(context), allocation); } return actions.deallocate(@ptrCast(@alignCast(context)), allocation); } fn reallocate(context: *anyopaque, return_address: usize, existing_allocation: ?[]Byte, size: usize) AllocationError![]Byte { if (is_zero_aligned) { return actions.reallocate(@ptrCast(context), return_address, existing_allocation, size); } return actions.reallocate(@ptrCast(@alignCast(context)), return_address, existing_allocation, size); } }; return .{ .context = if (is_zero_aligned) state else @ptrCast(state), .actions = &.{ .deallocate = ErasedActions.deallocate, .reallocate = ErasedActions.reallocate, } }; } pub fn deallocate(self: Allocator, allocation: anytype) void { switch (@typeInfo(@TypeOf(allocation))) { .Pointer => |pointer| { self.actions.deallocate(self.context, switch (pointer.size) { .One => @as([*]Byte, @ptrCast(allocation))[0 .. @sizeOf(pointer.child)], .Slice => @as([*]Byte, @ptrCast(allocation.ptr))[0 .. (@sizeOf(pointer.child) * allocation.len)], .Many, .C => @compileError("length of allocation must be known to deallocate"), }); }, else => @compileError("cannot deallocate " ++ allocation), } } pub fn reallocate(self: Allocator, allocation: ?[]Byte, allocation_size: usize) AllocationError![]Byte { return self.actions.reallocate(self.context, @returnAddress(), allocation, allocation_size); } }; pub const Byte = u8; pub const FixedBuffer = struct { bytes: []Byte, pub fn as_writer(self: *FixedBuffer) Writer { return Writer.bind(FixedBuffer, self, struct { fn write(writable_memory: *FixedBuffer, data: []const Byte) ?usize { return writable_memory.write(data); } }.write); } pub fn put(self: *FixedBuffer, byte: Byte) bool { if (self.bytes.len == 0) { return false; } self.bytes[0] = byte; self.bytes = self.bytes[1 ..]; return true; } pub fn write(self: *FixedBuffer, bytes: []const Byte) usize { const writable = @min(self.bytes.len, bytes.len); copy(self.bytes, bytes); self.bytes = self.bytes[writable ..]; return writable; } }; pub fn Functor(comptime Output: type, comptime Input: type) type { return struct { context: *const anyopaque, invoker: *const fn (capture: *const anyopaque, input: Input) Output, const Self = @This(); pub fn bind(comptime State: type, state: *const State, comptime invoker: fn (capture: *const State, input: Input) Output) Self { const is_zero_aligned = @alignOf(State) == 0; const Invoker = struct { fn invoke(context: *const anyopaque, input: Input) Output { if (is_zero_aligned) { return invoker(@ptrCast(context), input); } return invoker(@ptrCast(@alignCast(context)), input); } }; return .{ .context = if (is_zero_aligned) state else @ptrCast(state), .invoker = Invoker.invoke, }; } pub fn from(comptime invoker: fn (input: Input) Output) Self { const Invoker = struct { fn invoke(_: *const anyopaque, input: Input) Output { return invoker(input); } }; return .{ .context = &.{}, .invoker = Invoker.invoke, }; } pub fn invoke(self: Self, input: Input) Output { return self.invoker(self.context, input); } }; } pub fn Generator(comptime Output: type, comptime Input: type) type { return struct { context: *anyopaque, invoker: *const fn (capture: *anyopaque, input: Input) Output, const Self = @This(); pub fn bind(comptime State: type, state: *State, comptime invoker: fn (capture: *State, input: Input) Output) Self { const is_zero_aligned = @alignOf(State) == 0; return .{ .context = if (is_zero_aligned) state else @ptrCast(state), .invoker = struct { fn invoke(context: *anyopaque, input: Input) Output { if (is_zero_aligned) { return invoker(@ptrCast(context), input); } return invoker(@ptrCast(@alignCast(context)), input); } }.invoke, }; } pub fn from_fn(comptime invoker: fn (input: Input) Output) Self { const Invoker = struct { fn invoke(_: *const anyopaque, input: Input) Output { return invoker(input); } }; return .{ .context = &.{}, .invoker = Invoker.invoke, }; } pub fn invoke(self: Self, input: Input) Output { return self.invoker(self.context, input); } }; } pub fn Tag(comptime Element: type) type { return switch (@typeInfo(Element)) { .Enum => |info| info.tag_type, .Union => |info| info.tag_type orelse @compileError(@typeName(Element) ++ " has no tag type"), else => @compileError("expected enum or union type, found '" ++ @typeName(Element) ++ "'"), }; } pub const Writer = Generator(?usize, []const Byte); pub fn all_equals(target: []const Byte, match: Byte) bool { for (0 .. target.len) |index| { if (target[index] != match) { return false; } } return true; } pub fn allocate_copy(comptime Element: type, allocator: Allocator, source: []const Element) AllocationError![]Element { const allocation = try allocator.actions.reallocate(allocator.context, @returnAddress(), null, @sizeOf(Element) * source.len); copy(allocation, bytes_of(source)); return @as([*]Element, @ptrCast(@alignCast(allocation.ptr)))[0 .. source.len]; } pub fn allocate_many(allocator: Allocator, count: usize, value: anytype) AllocationError![]@TypeOf(value) { const Type = @TypeOf(value); const typeSize = @sizeOf(Type); if (typeSize == 0) { @compileError("Cannot allocate memory for 0-byte sized type " ++ @typeName(Type)); } const allocations = @as([*]Type, @ptrCast(@alignCast(try allocator.actions.reallocate( allocator.context, @returnAddress(), null, typeSize * count))))[0 .. count]; for (allocations) |*allocation| { allocation.* = value; } return allocations; } pub fn allocate_one(allocator: Allocator, value: anytype) AllocationError!*@TypeOf(value) { const Type = @TypeOf(value); const typeSize = @sizeOf(Type); if (typeSize == 0) { @compileError("Cannot allocate memory for 0-byte sized type " ++ @typeName(Type)); } const allocation = @as(*Type, @ptrCast(@alignCast(try allocator.actions.reallocate( allocator.context, @returnAddress(), null, typeSize)))); allocation.* = value; return allocation; } pub fn allocate_string(allocator: Allocator, source: []const Byte) AllocationError![:0]Byte { const allocation = try allocator.actions.reallocate(allocator.context, @returnAddress(), null, source.len + 1); copy(allocation[0 .. source.len], source); allocation[source.len] = 0; return @ptrCast(allocation); } pub fn are_equal(target: []const Byte, match: []const Byte) bool { if (target.len != match.len) { return false; } for (0 .. target.len) |index| { if (target[index] != match[index]) { return false; } } return true; } pub fn bytes_of(value: anytype) []const Byte { const pointer_info = @typeInfo(@TypeOf(value)).Pointer; return switch (pointer_info.size) { .One => @as([*]const Byte, @ptrCast(value))[0 .. @sizeOf(pointer_info.child)], .Slice => @as([*]const Byte, @ptrCast(value.ptr))[0 .. @sizeOf(pointer_info.child) * value.len], else => @compileError("`value` must be single-element pointer or slice type"), }; } pub fn copy(target: []Byte, source: []const Byte) void { var index: usize = 0; while (index < source.len) : (index += 1) { target[index] = source[index]; } } pub fn compare(this: []const Byte, that: []const Byte) isize { const range = @min(this.len, that.len); var index: usize = 0; while (index < range) : (index += 1) { const difference = @as(isize, @intCast(this[index])) - @as(isize, @intCast(that[index])); if (difference != 0) { return difference; } } return @as(isize, @intCast(this.len)) - @as(isize, @intCast(that.len)); } pub fn djb2_hash(comptime int: std.builtin.Type.Int, target: []const Byte) math.Int(int) { var hash_code = @as(math.Int(int), 5381); for (target) |byte| { hash_code = ((hash_code << 5) +% hash_code) +% byte; } return hash_code; } pub fn find_first(haystack: []const Byte, needle: Byte) ?usize { for (0 .. haystack.len) |i| { if (haystack[i] == needle) { return i; } } return null; } pub fn jenkins_hash(comptime int: std.builtin.Type.Int, bytes: []const Byte) math.Int(int) { var hash = @as(math.Int(int), 0); for (bytes) |byte| { hash +%= byte; hash +%= (hash << 10); hash ^= (hash >> 6); } hash +%= (hash << 3); hash ^= (hash >> 11); hash +%= (hash << 15); return hash; } pub const null_writer = Writer.from_fn(write_null); pub fn slice_sentineled(comptime sen: anytype, ptr: [*:sen]const @TypeOf(sen)) [:sen]const @TypeOf(sen) { var len = @as(usize, 0); while (ptr[len] != sen) { len += 1; } return ptr[0 .. len:sen]; } pub fn tag_of(comptime value: anytype) Tag(@TypeOf(value)) { return @as(Tag(@TypeOf(value)), value); } fn write_null(buffer: []const u8) ?usize { return buffer.len; } pub fn zero(target: []Byte) void { for (target) |*t| t.* = 0; }