Skip to content

Instantly share code, notes, and snippets.

@notcancername
Created June 10, 2023 10:55
Show Gist options
  • Save notcancername/6bf003f1d8b226da7d6a04c5d911944b to your computer and use it in GitHub Desktop.
Save notcancername/6bf003f1d8b226da7d6a04c5d911944b to your computer and use it in GitHub Desktop.
const std = @import("std");
const io = std.io;
const math = std.math;
// TODO: this is a massive hack that relies on implementation details
// and **WILL** break when BufferedReader or SeekableStream change,
// however, I do not see any other way, so this is fine for now.
/// An amalgam of `std.io.BufferedReader` and `std.io.SeekableStream`, such that they work together.
///
/// Get the reader with `reader` and the seekable stream with `seekableStream`.
pub fn BufferedSeekableReader(
comptime buffer_size: usize,
comptime Reader: type,
comptime SeekableStream: type,
) type {
const SeekError = SeekableStream.SeekError;
const GetSeekPosError = SeekableStream.GetSeekPosError;
const BufferedReader = io.BufferedReader(buffer_size, Reader);
return struct {
unseekable_buffered_reader: BufferedReader,
seekable: SeekableStream,
const Self = @This();
pub fn seekableStream(self: *Self) io.SeekableStream(
*Self,
SeekableStream.SeekError,
SeekableStream.GetSeekPosError,
seekTo,
seekBy,
getPos,
getEndPos,
) {
return .{.context = self};
}
pub fn reader(self: *Self) BufferedReader.Reader {
return self.unseekable_buffered_reader.reader();
}
fn emptyBuffer(self: *Self) void {
self.unseekable_buffered_reader.start = 0;
self.unseekable_buffered_reader.end = 0;
self.unseekable_buffered_reader.buf = undefined;
}
fn bytesInBuf(self: *Self) usize {
const ubr = self.unseekable_buffered_reader;
return ubr.end - ubr.start;
}
fn seekTo(self: *Self, pos: u64) SeekError!void {
const cur_pos = self.getPos() catch return self.seekToFar(pos);
const diff = @as(i65, pos) - @as(i65, cur_pos);
if(diff > 0 and math.absCast(diff) <= math.maxInt(i64)) {
return self.seekBy(@intCast(i64, math.absCast(diff)));
}
return self.seekToFar(pos);
}
fn seekBy(self: *Self, amt: i64) SeekError!void {
if(amt > 0 and amt < self.bytesInBuf() and amt <= std.math.maxInt(usize)) {
self.unseekable_buffered_reader.start += @intCast(usize, amt);
return;
}
return self.seekByFar(amt);
}
fn getEndPos(self: *Self) GetSeekPosError!u64 {
return self.seekable.getEndPos();
}
fn getPos(self: *Self) GetSeekPosError!u64 {
return (try self.seekable.getPos()) - (self.unseekable_buffered_reader.end - self.unseekable_buffered_reader.start);
}
fn seekToFar(self: *Self, pos: u64) SeekError!void {
self.emptyBuffer();
return self.seekable.seekTo(pos);
}
fn seekByFar(self: *Self, amt: i64) SeekError!void {
self.emptyBuffer();
return self.seekable.seekBy(amt);
}
};
}
pub fn bufferedSeekableReaderSize(
comptime buffer_size: usize,
reader: anytype,
seekable_stream: anytype
) BufferedSeekableReader(buffer_size, @TypeOf(reader), @TypeOf(seekable_stream)) {
return BufferedSeekableReader(buffer_size, @TypeOf(reader), @TypeOf(seekable_stream)){
.unseekable_buffered_reader = io.bufferedReaderSize(buffer_size, reader),
.seekable = seekable_stream,
};
}
pub fn bufferedSeekableReader(
reader: anytype,
seekable_stream: anytype
) BufferedSeekableReader(4096, @TypeOf(reader), @TypeOf(seekable_stream)) {
return bufferedSeekableReaderSize(4096, reader, seekable_stream);
}
test "intra-buffer seek" {
const s1 = "The sun dipped below the horizon, casting a warm golden glow over the tranquil meadow.";
const s2 = "The air was filled with the sweet scent of wildflowers, their vibrant colors dotting the landscape like a painter's masterpiece.";
const s3 = "Birds chirped their evening melodies, while a gentle breeze rustled the tall grass, creating a soothing symphony of nature.";
const s4 = "In this moment of serenity, all worries and troubles seemed to fade away, replaced by a profound sense of peace and harmony.";
const s = s1 ++ " " ++ s2 ++ " " ++ s3 ++ " " ++ s4;
var fbs = io.fixedBufferStream(s);
var bsr = bufferedSeekableReaderSize(512, fbs.reader(), fbs.seekableStream());
const reader = bsr.reader();
const seekable_stream = bsr.seekableStream();
try std.testing.expectEqualSlices(u8, s1, &(try reader.readBytesNoEof(s1.len)));
try seekable_stream.seekTo(0);
try std.testing.expectEqualSlices(u8, s1, &(try reader.readBytesNoEof(s1.len)));
try seekable_stream.seekTo(0);
_ = try reader.readByte();
try seekable_stream.seekBy(2);
std.debug.print("{!}\n", .{bsr.getPos()});
try std.testing.expectEqualSlices(u8, s1[3..][0..3], &(try reader.readBytesNoEof(3)));
try seekable_stream.seekTo(8);
try std.testing.expectEqualSlices(u8, s1[8..][0..3], &(try reader.readBytesNoEof(3)));
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment