const debug = @import("./debug.zig"); const math = @import("./math.zig"); pub const AllocationError = error { OutOfMemory, }; pub const AllocationOptions = struct { return_address: usize, allocation: ?[]u8 = null, size: usize, }; pub const Allocator = Generator(?[]u8, AllocationOptions); /// /// Function pointer coupled with an immutable state context for providing dynamic dispatch over a given `Input` and /// `Output`. /// 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 alignment = @alignOf(State); const is_zero_aligned = alignment == 0; return .{ .context = if (is_zero_aligned) state else @ptrCast(*const anyopaque, state), .invoker = struct { fn invoke_opaque(context: *const anyopaque, input: Input) Output { if (is_zero_aligned) { return invoker(@ptrCast(*const State, context), input); } return invoker(@ptrCast(*const State, @alignCast(alignment, context)), input); } }.invoke_opaque, }; } pub fn invoke(self: Self, input: Input) Output { return self.invoker(self.context, input); } }; } /// /// Function pointer coupled with a mutable state context for providing dynamic dispatch over a given `Input` and /// `Output`. /// 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 alignment = @alignOf(State); const is_zero_aligned = alignment == 0; return .{ .context = if (is_zero_aligned) state else @ptrCast(*anyopaque, state), .invoker = struct { fn invoke_opaque(context: *anyopaque, input: Input) Output { if (is_zero_aligned) { return invoker(@ptrCast(*State, context), input); } return invoker(@ptrCast(*State, @alignCast(alignment, context)), input); } }.invoke_opaque, }; } pub fn invoke(self: Self, input: Input) Output { return self.invoker(self.context, input); } }; } pub const Reader = Generator(?usize, []u8); pub const StreamError = error { ReadFailure, WriteFailure, }; 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 FixedBuffer = struct { slice: []u8, pub fn as_writer(self: *FixedBuffer) Writer { return Writer.bind(FixedBuffer, self, struct { fn write(writable_memory: *FixedBuffer, data: []const u8) ?usize { return writable_memory.write(data); } }.write); } pub fn put(self: *FixedBuffer, byte: u8) bool { if (self.slice.len == 0) { return false; } self.slice[0] = byte; self.slice = self.slice[1 ..]; return true; } pub fn write(self: *FixedBuffer, bytes: []const u8) usize { const writable = math.min(self.slice.len, bytes.len); copy(self.slice, bytes); self.slice = self.slice[writable ..]; return writable; } }; pub const Writer = Generator(?usize, []const u8); pub fn allocate_many(allocator: Allocator, amount: usize, comptime Type: type) AllocationError![]Type { if (@sizeOf(Type) == 0) { @compileError("Cannot allocate memory for 0-byte type " ++ @typeName(Type)); } return @ptrCast([*]Type, @alignCast(@alignOf(Type), allocator.invoke(.{ .size = @sizeOf(Type) * amount, .return_address = @returnAddress(), }) orelse return error.OutOfMemory))[0 .. amount]; } pub fn allocate_one(allocator: Allocator, value: anytype) AllocationError!*@TypeOf(value) { const Type = @TypeOf(value); if (@sizeOf(Type) == 0) { @compileError("Cannot allocate memory for 0-byte type " ++ @typeName(Type)); } const allocation = @ptrCast(*Type, @alignCast(@alignOf(Type), allocator.invoke(.{ .size = @sizeOf(Type), .return_address = @returnAddress(), }) orelse return error.OutOfMemory)); allocation.* = value; return allocation; } pub fn bytes_of(value: anytype) []const u8 { const pointer_info = @typeInfo(@TypeOf(value)).Pointer; debug.assert(pointer_info.size == .One); return @ptrCast([*]const u8, value)[0 .. @sizeOf(pointer_info.child)]; } pub fn compare(this: []const u8, that: []const u8) isize { const range = math.min(this.len, that.len); var index: usize = 0; while (index < range) : (index += 1) { const difference = @intCast(isize, this[index]) - @intCast(isize, that[index]); if (difference != 0) { return difference; } } return @intCast(isize, this.len) - @intCast(isize, that.len); } pub fn copy(target: []u8, source: []const u8) void { var index: usize = 0; while (index < source.len) : (index += 1) target[index] = source[index]; } pub fn deallocate(allocator: Allocator, allocation: anytype) void { switch (@typeInfo(@TypeOf(allocation))) { .Pointer => |pointer| { _ = allocator.invoke(.{ .allocation = switch (pointer.size) { .One => @ptrCast([*]u8, allocation)[0 .. @sizeOf(pointer.child)], .Slice => @ptrCast([*]u8, allocation.ptr)[0 .. (@sizeOf(pointer.child) * allocation.len)], .Many, .C => @compileError("length of allocation must be known to deallocate"), }, .return_address = @returnAddress(), .size = 0, }); }, else => @compileError("cannot deallocate " ++ allocation), } } pub fn ends_with(target: []const u8, match: []const u8) 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(this: []const u8, that: []const u8) bool { if (this.len != that.len) return false; { var index: usize = 0; while (index < this.len) : (index += 1) if (this[index] != that[index]) return false; } return true; } var null_context = @as(usize, 0); pub const null_allocator = Allocator.bind(&null_context, struct { fn reallocate(context: *usize, options: AllocationOptions) ?[]u8 { debug.assert(context.* == 0); debug.assert(options.allocation == null); return null; } }); pub const null_writer = Writer.bind(&null_context, struct { fn write(context: *usize, buffer: []const u8) usize { debug.assert(context.* == 0); return buffer.len; } }.write); pub fn reallocate(allocator: Allocator, allocation: anytype, amount: usize) AllocationError![]@typeInfo(@TypeOf(allocation)).Pointer.child { const pointer_info = @typeInfo(@TypeOf(allocation)).Pointer; const Element = pointer_info.child; return @ptrCast([*]Element, @alignCast(@alignOf(Element), (allocator.invoke(switch (pointer_info.size) { .Slice => .{ .allocation = @ptrCast([*]u8, allocation.ptr)[0 .. (@sizeOf(Element) * allocation.len)], .size = @sizeOf(Element) * amount, }, .Many, .C, .One => @compileError("allocation must be a slice to reallocate"), }) orelse return error.OutOfMemory).ptr))[0 .. amount]; } pub fn sentinel_index(comptime element: type, comptime sentinel: element, sequence: [*:sentinel]const element) usize { var index: usize = 0; while (sequence[index] != sentinel) : (index += 1) {} return index; } pub fn stream(output: Writer, input: Reader, buffer: []u8) StreamError!u64 { var total_written: u64 = 0; var read = input.invoke(buffer) orelse return error.ReadFailure; while (read != 0) { total_written += output.invoke(buffer[0..read]) orelse return error.WriteFailure; read = input.invoke(buffer) orelse return error.ReadFailure; } return total_written; } pub fn swap(comptime Element: type, this: *Element, that: *Element) void { const temp = this.*; this.* = that.*; that.* = temp; } pub fn tag_of(comptime value: anytype) Tag(@TypeOf(value)) { return @as(Tag(@TypeOf(value)), value); } pub fn zero(target: []u8) void { for (target) |*t| t.* = 0; }