const debug = @import("./debug.zig"); const math = @import("./math.zig"); 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; return .{ .context = if (is_zero_aligned) state else @ptrCast(state), .actions = &.{ .deallocate = struct { fn deallocate(context: *anyopaque, allocation: []Byte) void { if (is_zero_aligned) { return actions.deallocator(@ptrCast(context), allocation); } return actions.deallocate(@ptrCast(@alignCast(context)), allocation); } }.deallocate, .reallocate = struct { fn reallocate(context: *anyopaque, return_address: usize, existing_allocation: ?[]Byte, size: usize) AllocationError![]Byte { if (is_zero_aligned) { return actions.reallocator(@ptrCast(context), return_address, existing_allocation, size); } return actions.reallocate(@ptrCast(@alignCast(context)), return_address, existing_allocation, size); } }.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 = math.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; return .{ .context = if (is_zero_aligned) state else @ptrCast(state), .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); } }.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 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 allocate_copy(allocator: Allocator, source: []const Byte) AllocationError![]Byte { const allocation = try allocator.actions.reallocate(allocator.context, @returnAddress(), null, source.len); copy(allocation, source); return allocation; } 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 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 ends_with(target: []const Byte, match: []const Byte) bool { if (target.len < match.len) { return false; } { var index = @as(usize, 0); while (index < match.len) : (index += 1) { if (target[target.len - (1 + index)] != match[match.len - (1 + index)]) { return false; } } } return true; } pub fn equals(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; } var null_context = @as(usize, 0); pub const null_writer = Writer.bind(usize, &null_context, struct { fn write(context: *usize, buffer: []const u8) ?usize { debug.assert(context.* == 0); return buffer.len; } }.write); 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); } pub fn zero(target: []Byte) void { for (target) |*t| t.* = 0; }