ona/src/core/io.zig

266 lines
8.1 KiB
Zig
Raw Normal View History

const meta = @import("./meta.zig");
const stack = @import("./stack.zig");
const std = @import("std");
2022-10-24 01:02:07 +01:00
///
/// Closure for allocating, reallocating, and deallocating dynamic memory resources through itself.
///
pub const Allocator = meta.BiFunction(56, ?[]u8, usize, AllocationError![]u8);
2022-10-17 10:34:04 +01:00
///
/// 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 {
2022-10-17 10:34:04 +01:00
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 {
2022-10-17 10:34:04 +01:00
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 {
2022-10-17 10:34:04 +01:00
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 {
2022-10-17 10:34:04 +01:00
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 {
2022-10-17 10:34:04 +01:00
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 {
2022-10-17 10:34:04 +01:00
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 {
2022-10-17 10:34:04 +01:00
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;
}
};
}
2022-10-17 14:04:24 +01:00
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);
///
2022-10-23 18:11:02 +01:00
/// Returns `true` if `this` is the same length and contains the same data as `that`, otherwise
/// `false`.
///
2022-10-24 01:02:07 +01:00
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;
2022-10-24 01:02:07 +01:00
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);
}
2022-10-02 23:57:41 +01:00
2022-10-17 14:00:52 +01:00
test "Null writing" {
2022-10-02 23:57:41 +01:00
const testing = std.testing;
{
const sequence = "foo";
try testing.expectEqual(nullWriter().apply(sequence), sequence.len);
2022-10-02 23:57:41 +01:00
}
}
2022-10-02 23:57:41 +01:00
///
/// Applies the singular `byte` to `writer`, returning `true` if it was successfully written,
/// otherwise `false`.
///
pub fn writeByte(writer: *Writer, byte: u8) bool {
return (writer.apply(std.mem.asBytes(&byte)) != 0);
2022-10-02 23:57:41 +01:00
}