2023-07-10 02:10:56 +02:00
|
|
|
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;
|
|
|
|
|
2023-07-10 23:25:21 +02:00
|
|
|
const ErasedActions = 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);
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2023-07-10 02:10:56 +02:00
|
|
|
return .{
|
|
|
|
.context = if (is_zero_aligned) state else @ptrCast(state),
|
|
|
|
|
|
|
|
.actions = &.{
|
2023-07-10 23:25:21 +02:00
|
|
|
.deallocate = ErasedActions.deallocate,
|
|
|
|
.reallocate = ErasedActions.reallocate,
|
2023-07-10 02:10:56 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|