const meta = @import("./meta.zig"); const stack = @import("./stack.zig"); const std = @import("std"); /// /// /// pub const Allocator = std.mem.Allocator; /// /// File-system agnostic abstraction for manipulating a file. /// pub const FileAccess = struct { context: *anyopaque, implementation: *const Implementation, /// /// Provides a set of implementation-specific behaviors to a [FileAccess] instance. /// pub const Implementation = struct { close: fn (*anyopaque) void, queryCursor: fn (*anyopaque) Error!u64, queryLength: fn (*anyopaque) Error!u64, read: fn (*anyopaque, []u8) Error!usize, seek: fn (*anyopaque, u64) Error!void, seekToEnd: fn (*anyopaque) Error!void, skip: fn (*anyopaque, i64) Error!void, }; /// /// [Error.FileInaccessible] is a generic catch-all for a [FileAccess] reference no longer /// pointing to a file or the file becomming invalid for whatever reason. /// pub const Error = error { FileInaccessible, }; /// /// Close the file referenced by `file_access` on the main thread, invalidating the reference to /// it and releasing any associated resources. /// /// Freeing an invalid `file_access` has no effect on the file and logs a warning over the /// wasted effort. /// pub fn close(file_access: FileAccess) void { return file_access.implementation.close(file_access.context); } /// /// Attempts to query the current cursor position for the file referenced by `file_access`. /// /// Returns the number of bytes into the file that the cursor is relative to its beginning or a /// [Error] on failure. /// pub fn queryCursor(file_access: FileAccess) Error!u64 { return file_access.implementation.queryCursor(file_access.context); } /// /// Attempts to query the current length for the file referenced by `file_access`. /// /// Returns the current length of the file at the time of the operation or a [Error] if the file /// failed to be queried. /// pub fn queryLength(file_access: FileAccess) Error!u64 { return file_access.implementation.queryLength(file_access.context); } /// /// Attempts to read `file_access` from the its current position into `buffer`. /// /// Returns the number of bytes that were available to be read, otherwise an [Error] on failure. /// pub fn read(file_access: FileAccess, buffer: []u8) Error!usize { return file_access.implementation.read(file_access.context, buffer); } /// /// Attempts to seek `file_access` from the beginning of the file to `cursor` bytes. /// /// Returns [Error] on failure. /// pub fn seek(file_access: FileAccess, cursor: u64) Error!void { return file_access.implementation.seek(file_access.context, cursor); } /// /// Attempts to seek `file_access` to the end of the file. /// /// Returns [Error] on failure. /// pub fn seekToEnd(file_access: FileAccess) Error!void { return file_access.implementation.seekToEnd(file_access.context); } /// /// Attempts to seek `file_access` by `offset` from the current file position. /// /// Returns [Error] on failure; /// pub fn skip(file_access: FileAccess, offset: i64) Error!void { return file_access.implementation.skip(file_access.context, offset); } }; /// /// Closure that captures a reference to readable resources like block devices, memory buffers, /// network sockets, and more. /// pub const Reader = meta.Function(@sizeOf(usize), []u8, usize); /// /// Returns a state machine for lazily computing all `Element` components of a given source input /// that match a delimiting pattern. /// pub fn Spliterator(comptime Element: type) type { return struct { source: []const Element, delimiter: []const Element, const Self = @This(); /// /// Returns `true` if there is more data to be processed, otherwise `false`. /// pub fn hasNext(self: Self) bool { return (self.source.len != 0); } /// /// Iterates on `self` and returns the next view of [Spliterator.source] that matches /// [Spliterator.delimiter], or `null` if there is no more data to be processed. /// pub fn next(self: *Self) ?[]const Element { if (!self.hasNext()) return null; if (std.mem.indexOfPos(Element, self.source, 0, self.delimiter)) |index| { defer self.source = self.source[(index + self.delimiter.len) .. self.source.len]; return self.source[0 .. index]; } defer self.source = self.source[self.source.len .. self.source.len]; return self.source; } }; } test "Spliterating text" { const testing = std.testing; // Single-character delimiter. { var spliterator = Spliterator(u8){ .source = "single.character.separated.hello.world", .delimiter = ".", }; const components = [_][]const u8{"single", "character", "separated", "hello", "world"}; var index = @as(usize, 0); while (spliterator.next()) |split| : (index += 1) { try testing.expect(std.mem.eql(u8, split, components[index])); } } // Multi-character delimiter. { var spliterator = Spliterator(u8){ .source = "finding a needle in a needle stack", .delimiter = "needle", }; const components = [_][]const u8{"finding a ", " in a ", " stack"}; var index = @as(usize, 0); while (spliterator.next()) |split| : (index += 1) { try testing.expect(std.mem.eql(u8, split, components[index])); } } } /// /// Closure that captures a reference to writable resources like block devices, memory buffers, /// network sockets, and more. /// 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 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 "Memory buffers equal" { const bytes_sequence = &.{69, 42, 0}; const testing = std.testing; try testing.expect(equals(u8, bytes_sequence, bytes_sequence)); try testing.expect(!equals(u8, bytes_sequence, &.{69, 42})); } /// /// Returns a deterministic hash code compiled from each byte in `bytes`. /// /// **Note** that this operation has `O(n)` time complexity. /// pub fn hashBytes(bytes: []const u8) usize { var hash = @as(usize, 5381); for (bytes) |byte| hash = ((hash << 5) + hash) + byte; return hash; } test "Hashing bytes" { const bytes_sequence = &.{69, 42, 0}; const testing = std.testing; try testing.expect(hashBytes(bytes_sequence) == hashBytes(bytes_sequence)); try testing.expect(hashBytes(bytes_sequence) != hashBytes(&.{69, 42})); } /// /// Returns a [Writer] that silently consumes all given data without failure and throws it away. /// /// This is commonly used for testing or redirected otherwise unwanted output data that has to be /// sent somewhere for whatever reason. /// pub fn nullWriter() Writer { return Writer.capture(std.mem.zeroes(usize), struct { fn write(_: usize, buffer: []const u8) usize { return buffer.len; } }.write); } test "Null writing" { const testing = std.testing; { const sequence = "foo"; try testing.expectEqual(nullWriter().apply(sequence), sequence.len); } }