Skip to content

Instantly share code, notes, and snippets.

@matklad
Last active June 21, 2024 10:34
Show Gist options
  • Save matklad/cc25c31417e73c69f11d174dc2df71c6 to your computer and use it in GitHub Desktop.
Save matklad/cc25c31417e73c69f11d174dc2df71c6 to your computer and use it in GitHub Desktop.
const Self = @This();
const mem = std.mem;
const eof = error.EndOfStream;
const std = @import("../std.zig");
const Reader = struct {
context: *const anyopaque,
readFn: *const fn (context: *const anyopaque, buffer: []u8) anyerror!usize,
pub const Error = anyerror;
pub fn read(self: Self, buffer: []u8) anyerror!usize {
return self.readFn(self.context, buffer);
}
pub fn readAll(self: Self, buffer: []u8) anyerror!usize {}
pub fn readAtLeast(self: Self, buffer: []u8, len: usize) anyerror!usize {}
pub fn readNoEof(self: Self, buf: []u8) anyerror!void {}
pub fn readAllArrayList(self: Self, ...) anyerror!void {}
pub fn readAllArrayListAligned(self: Self, ...) anyerror!void {}
pub fn readAllAlloc(self: Self, ...) anyerror![]u8 {}
pub const readByte = @compileError("use Reader.Buffered.readByte");
pub const Buffered = struct {
context: *const anyopaque,
readFn: *const fn (context: *const anyopaque, buffer: []u8) anyerror!usize,
fillFn: *const fn (context: *const anyopaque) anyerror![]const usize,
drainFn: *const fn (context: *const anyopaque, amount: usize) void,
/// Same as `Reader.read`
// NB: While `read` can be expressed in terms of `fill` and `drain`, for cases where passed in
// destination `buffer` is larger than the reader's interior buffer, we want to read directly
// into the destination. That's why we still require a dedicated `readFn` in a VTable.
pub fn read(self: Buffered, buffer: []u8) anyerror!usize {
return self.readFn(self.context, buffer);
}
/// Fills the internal buffer using at most one call to `readFn`.
pub fn fill(self: Buffered) anyerror![]const usize {
return self.fillFn(self.context);
}
/// Drains `amount` bytes from the start of the internal buffer.
pub fn drain(self: Buffered, amount: usize) void {
self.drainFn(self.context, amount);
}
pub fn reader(self: Buffered) Reader {
return .{ .context = self.context, .readFn = self.readFn };
}
// Benefit 1: we no longer issue a syscall to read a single byte, though we do need two virtual
// calls.
pub fn readByte(self: Buffered) anyerror!u8 {
const buffer = try self.fill();
if (buffer.len == 0) return eof;
defer self.drain(1);
return buffer[0];
}
pub fn streamUntilDelimiter(
self: Self,
writer: anytype,
delimiter: u8,
) anyerror!void {
while (true) {
const buffer = try self.fill();
if (buffer.len == 0) return eof;
// Benefit 2: for cases like "find next `\n`", we now:
// - amortize the amount of virtual calls to two per interior buffer,
// - get to use SIMD to search in the buffer.
const len = mem.indexOfScalar(u8, buffer, delimiter) orelse buffer.len;
try writer.writeByte(buffer[0..len]);
self.drain(len);
}
}
};
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment