Last active
June 21, 2024 10:34
-
-
Save matklad/cc25c31417e73c69f11d174dc2df71c6 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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