diff --git a/src/core/io.zig b/src/core/io.zig index 770e77f..4418c63 100644 --- a/src/core/io.zig +++ b/src/core/io.zig @@ -2,6 +2,11 @@ const meta = @import("./meta.zig"); const stack = @import("./stack.zig"); const std = @import("std"); +/// +/// Closure for allocating, reallocating, and deallocating dynamic memory resources through itself. +/// +pub const Allocator = meta.BiFunction(56, ?[]u8, usize, AllocationError![]u8); + /// /// File-system agnostic abstraction for manipulating a file. /// @@ -186,16 +191,24 @@ pub const Writer = meta.Function(@sizeOf(usize), []const u8, usize); /// Returns `true` if `this` is the same length and contains the same data as `that`, otherwise /// `false`. /// -pub fn equalsBytes(this: []const u8, that: []const u8) bool { - return std.mem.eql(u8, this, that); +pub fn equals(comptime Element: type, this: []const Element, that: []const Element) bool { + if (this.len != that.len) return false; + + { + var i = std.mem.zeroes(usize); + + while (i < this.len) : (i += 1) if (this[i] != that[i]) return false; + } + + return true; } test "Equivalence of bytes" { const bytes_sequence = &.{69, 42, 0}; const testing = std.testing; - try testing.expect(equalsBytes(bytes_sequence, bytes_sequence)); - try testing.expect(!equalsBytes(bytes_sequence, &.{69, 42})); + try testing.expect(equals(u8, bytes_sequence, bytes_sequence)); + try testing.expect(!equals(u8, bytes_sequence, &.{69, 42})); } /// diff --git a/src/core/meta.zig b/src/core/meta.zig index 884c4a4..582176b 100644 --- a/src/core/meta.zig +++ b/src/core/meta.zig @@ -11,6 +11,63 @@ pub fn FnReturn(comptime Fn: type) type { return type_info.Fn.return_type orelse void; } +/// +/// Returns a double-input single-output closure type where `A` represents the first input type, `B` +/// represents the second, and `Out` represents the output type, and `captures_size` represents the +/// size of the closure context. +/// +pub fn BiFunction(comptime captures_size: usize, comptime A: type, + comptime B: type, comptime Out: type) type { + + return struct { + applyErased: fn (*anyopaque, A, B) Out, + context: [captures_size]u8, + + /// + /// Function type. + /// + const Self = @This(); + + /// + /// Applies `a` and `b` to `self`, producing a result according to the current context data. + /// + pub fn apply(self: *Self, a: A, b: B) Out { + return self.applyErased(&self.context, a, b); + } + + /// + /// Creates a new [Self] by capturing the `captures` value as the context and `call` as the + /// as the behavior executed when [apply] or [applyErased] is called. + /// + /// The newly created [Self] is returned. + /// + pub fn capture(captures: anytype, comptime call: fn (@TypeOf(captures), A, B) Out) Self { + const Captures = @TypeOf(captures); + + if (@sizeOf(Captures) > captures_size) + @compileError("`captures` must be smaller than or equal to " ++ + std.fmt.comptimePrint("{d}", .{captures_size}) ++ " bytes"); + + const captures_align = @alignOf(Captures); + + var function = Self{ + .context = undefined, + + .applyErased = struct { + fn do(erased: *anyopaque, a: A, b: B) Out { + return call(@ptrCast(*Captures, @alignCast( + captures_align, erased)).*, a, b); + } + }.do, + }; + + @ptrCast(*Captures, @alignCast(captures_align, &function.context)).* = captures; + + return function; + } + }; +} + /// /// Returns a single-input single-output closure type where `In` represents the input type, `Out` /// represents the output type, and `captures_size` represents the size of the closure context.