1const std = @import("../std.zig");
2const io = std.io;
3const assert = std.debug.assert;
4const testing = std.testing;
5
6pub fn BufferedReader(comptime buffer_size: usize, comptime ReaderType: type) type {
7    return struct {
8        unbuffered_reader: ReaderType,
9        fifo: FifoType = FifoType.init(),
10
11        pub const Error = ReaderType.Error;
12        pub const Reader = io.Reader(*Self, Error, read);
13
14        const Self = @This();
15        const FifoType = std.fifo.LinearFifo(u8, std.fifo.LinearFifoBufferType{ .Static = buffer_size });
16
17        pub fn read(self: *Self, dest: []u8) Error!usize {
18            var dest_index: usize = 0;
19            while (dest_index < dest.len) {
20                const written = self.fifo.read(dest[dest_index..]);
21                if (written == 0) {
22                    // fifo empty, fill it
23                    const writable = self.fifo.writableSlice(0);
24                    assert(writable.len > 0);
25                    const n = try self.unbuffered_reader.read(writable);
26                    if (n == 0) {
27                        // reading from the unbuffered stream returned nothing
28                        // so we have nothing left to read.
29                        return dest_index;
30                    }
31                    self.fifo.update(n);
32                }
33                dest_index += written;
34            }
35            return dest.len;
36        }
37
38        pub fn reader(self: *Self) Reader {
39            return .{ .context = self };
40        }
41    };
42}
43
44pub fn bufferedReader(underlying_stream: anytype) BufferedReader(4096, @TypeOf(underlying_stream)) {
45    return .{ .unbuffered_reader = underlying_stream };
46}
47
48test "io.BufferedReader" {
49    const OneByteReadReader = struct {
50        str: []const u8,
51        curr: usize,
52
53        const Error = error{NoError};
54        const Self = @This();
55        const Reader = io.Reader(*Self, Error, read);
56
57        fn init(str: []const u8) Self {
58            return Self{
59                .str = str,
60                .curr = 0,
61            };
62        }
63
64        fn read(self: *Self, dest: []u8) Error!usize {
65            if (self.str.len <= self.curr or dest.len == 0)
66                return 0;
67
68            dest[0] = self.str[self.curr];
69            self.curr += 1;
70            return 1;
71        }
72
73        fn reader(self: *Self) Reader {
74            return .{ .context = self };
75        }
76    };
77
78    const str = "This is a test";
79    var one_byte_stream = OneByteReadReader.init(str);
80    var buf_reader = bufferedReader(one_byte_stream.reader());
81    const stream = buf_reader.reader();
82
83    const res = try stream.readAllAlloc(testing.allocator, str.len + 1);
84    defer testing.allocator.free(res);
85    try testing.expectEqualSlices(u8, str, res);
86}
87