Skip to content

Commit

Permalink
Reworked buffererd writer interface.
Browse files Browse the repository at this point in the history
  • Loading branch information
permutationlock committed Dec 10, 2023
1 parent 45d1131 commit 36f082c
Show file tree
Hide file tree
Showing 10 changed files with 154 additions and 82 deletions.
18 changes: 8 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ A dead simple implementation of [static dispatch][2] interfaces in Zig
that emerged from a tiny subset of [ztrait][1]. See [here][3]
for some motivation.

The `zimpl` module is ~30 lines of code and exposes one public
The `zimpl` module is ~20 lines of code and exposes one public
declaration: `Impl`.

```Zig
Expand All @@ -13,21 +13,19 @@ pub fn Impl(comptime Ifc: fn (type) type, comptime T: type) type { ... }

## Arguments

The function `Ifc` must always return a struct type. If `Unwrap(T)`
has a declaration matching the name of a field from
`Ifc(T)` that cannot coerce to the type of the field, then a
compile error will occur.

### Unwrap
The function `Ifc` must always return a struct type.

The internal `Unwrap` function removes all layers of `*`, `?`, or `!`
wrapping a type, e.g. `Unwrap(!?*u32)` is `u32`.
If `T` is a single item pointer type, let `U` be the child type of
the pointer, i.e. `T=*U`. Otherwise let `U=T`.
If `U` has a declaration matching the name of a field from
`Ifc(T)` that cannot coerce to the type of that field, then a
compile error will occur.

## Return value

The type `Impl(Ifc, T)` is a struct type with the same fields
as `Ifc(T)`, but with the default value of each field set equal to
the declaration of `Unwrap(T)` of the same name, if such a declaration
the declaration of `U` of the same name, if such a declaration
exists.

## Example
Expand Down
4 changes: 2 additions & 2 deletions benchmarks/buffered_io.zig
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,10 @@ pub fn main() !void {
io.seekTo(&fbr, .{}, 0) catch unreachable;

while (true) {
// use as unbuffered reader by setting 'getBuffer = null'
// use as unbuffered reader by setting 'readBuffer = null'
io.streamUntilDelimiter(
&fbr,
.{ .getBuffer = null },
.{ .readBuffer = null },
&out_stream,
.{},
'\n',
Expand Down
2 changes: 1 addition & 1 deletion build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ pub fn build(b: *Builder) !void {

inline for (examples) |example| {
const ex_test = b.addTest(.{
.name = "example_" ++ example.name,
.name = example.name,
.root_source_file = .{ .path = example.path },
.target = target,
.optimize = optimize,
Expand Down
86 changes: 59 additions & 27 deletions examples/io.zig
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ pub fn Reader(comptime T: type) type {
return struct {
ReadError: type = anyerror,
read: fn (reader_ctx: T, buffer: []u8) anyerror!usize,
getBuffer: ?fn (reader_ctx: T) []const u8 = null,
readBuffer: ?fn (reader_ctx: T) anyerror![]const u8 = null,
};
}

Expand All @@ -43,6 +43,23 @@ pub inline fn read(
return @errorCast(reader_impl.read(reader_ctx, buffer));
}

pub inline fn isBufferedReader(
comptime ReaderCtx: type,
reader_impl: Impl(Reader, ReaderCtx),
) bool {
return !(reader_impl.readBuffer == null);
}

pub inline fn readBuffer(
reader_ctx: anytype,
reader_impl: Impl(Reader, @TypeOf(reader_ctx)),
) reader_impl.ReadError![]const u8 {
if (reader_impl.readBuffer) |readBufferFn| {
return @errorCast(readBufferFn(reader_ctx));
}
@compileError("called 'readBuffer' on unbuffered reader");
}

pub inline fn readAll(
reader_ctx: anytype,
reader_impl: Impl(Reader, @TypeOf(reader_ctx)),
Expand Down Expand Up @@ -87,9 +104,9 @@ pub inline fn streamUntilDelimiter(
EndOfStream,
StreamTooLong,
})!void {
if (reader_impl.getBuffer) |getBuffer| {
if (isBufferedReader(@TypeOf(reader_ctx), reader_impl)) {
while (true) {
const buffer = getBuffer(reader_ctx);
const buffer = try readBuffer(reader_ctx, reader_impl);
if (buffer.len == 0) {
return error.EndOfStream;
}
Expand All @@ -110,19 +127,20 @@ pub inline fn streamUntilDelimiter(
}
try skipBytes(reader_ctx, reader_impl, len, .{});
}
}
if (optional_max_size) |max_size| {
for (0..max_size) |_| {
const byte: u8 = try readByte(reader_ctx, reader_impl);
if (byte == delimiter) return;
try writeByte(writer_ctx, writer_impl, byte);
}
return error.StreamTooLong;
} else {
while (true) {
const byte: u8 = try readByte(reader_ctx, reader_impl);
if (byte == delimiter) return;
try writeByte(writer_ctx, writer_impl, byte);
if (optional_max_size) |max_size| {
for (0..max_size) |_| {
const byte: u8 = try readByte(reader_ctx, reader_impl);
if (byte == delimiter) return;
try writeByte(writer_ctx, writer_impl, byte);
}
return error.StreamTooLong;
} else {
while (true) {
const byte: u8 = try readByte(reader_ctx, reader_impl);
if (byte == delimiter) return;
try writeByte(writer_ctx, writer_impl, byte);
}
}
}
}
Expand All @@ -132,9 +150,9 @@ pub inline fn skipUntilDelimiterOrEof(
reader_impl: Impl(Reader, @TypeOf(reader_ctx)),
delimiter: u8,
) reader_impl.ReadError!void {
if (reader_impl.getBuffer) |getBuffer| {
if (isBufferedReader(@TypeOf(reader_ctx), reader_impl)) {
while (true) {
const buffer = getBuffer(reader_ctx);
const buffer = try readBuffer(reader_ctx, reader_impl);
if (buffer.len == 0) {
return error.EndOfStream;
}
Expand All @@ -154,13 +172,17 @@ pub inline fn skipUntilDelimiterOrEof(
}
skipBytes(reader_ctx, reader_impl, len, .{}) catch unreachable;
}
}
while (true) {
const byte = readByte(reader_ctx, reader_impl) catch |err| switch (err) {
error.EndOfStream => return,
else => |e| return e,
};
if (byte == delimiter) return;
} else {
while (true) {
const byte = readByte(
reader_ctx,
reader_impl,
) catch |err| switch (err) {
error.EndOfStream => return,
else => |e| return e,
};
if (byte == delimiter) return;
}
}
}

Expand Down Expand Up @@ -245,8 +267,8 @@ pub inline fn isBytes(
var i: usize = 0;
var matches = true;
while (i < slice.len) {
if (reader_impl.getBuffer) |getBuffer| {
const buffer = getBuffer(reader_ctx);
if (isBufferedReader(@TypeOf(reader_ctx), reader_impl)) {
const buffer = try readBuffer(reader_ctx, reader_impl);
const len = @min(buffer.len, slice.len - i);
if (len == 0) {
return error.EndOfStream;
Expand Down Expand Up @@ -316,17 +338,27 @@ pub fn Writer(comptime T: type) type {
return struct {
WriteError: type = anyerror,
write: fn (writer_ctx: T, bytes: []const u8) anyerror!usize,
flush: ?fn (writer_ctx: T) anyerror!void = null,
};
}

pub fn write(
pub inline fn write(
writer_ctx: anytype,
writer_impl: Impl(Writer, @TypeOf(writer_ctx)),
bytes: []const u8,
) writer_impl.WriteError!usize {
return @errorCast(writer_impl.write(writer_ctx, bytes));
}

pub inline fn flush(
writer_ctx: anytype,
writer_impl: Impl(Writer, @TypeOf(writer_ctx)),
) writer_impl.WriteError!void {
if (writer_impl.flush) |flushFn| {
return @errorCast(flushFn(writer_ctx));
}
}

pub fn writeAll(
writer_ctx: anytype,
writer_impl: Impl(Writer, @TypeOf(writer_ctx)),
Expand Down
13 changes: 9 additions & 4 deletions examples/io/FixedBufferReader.zig
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ pub fn read(self: *@This(), out_buffer: []u8) ReadError!usize {
return len;
}

pub fn readBuffer(self: *const @This()) ReadError![]const u8 {
return self.buffer[self.pos..];
}

pub fn seekTo(self: *@This(), pos: u64) error{}!void {
if (std.math.cast(usize, pos)) |usize_pos| {
self.pos = @min(self.buffer.len, usize_pos);
Expand Down Expand Up @@ -47,10 +51,6 @@ pub fn getEndPos(self: *const @This()) error{}!u64 {
return self.buffer.len;
}

pub fn getBuffer(self: *const @This()) []const u8 {
return self.buffer[self.pos..];
}

test "read and seek" {
const buffer: []const u8 = "I really hope that this works!";
var stream = @This(){ .buffer = buffer, .pos = 0 };
Expand All @@ -67,3 +67,8 @@ test "read and seek" {
try testing.expectEqual(buffer.len, len2);
try testing.expectEqualSlices(u8, buffer, &out_buf);
}

test "FixedBufferReader is a buffered io.Reader" {
const impl = @import("zimpl").Impl(io.Reader, *@This()){};
try std.testing.expect(!(impl.readBuffer == null));
}
13 changes: 9 additions & 4 deletions examples/io/FixedBufferStream.zig
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ pub fn read(self: *@This(), out_buffer: []u8) ReadError!usize {
return len;
}

pub fn readBuffer(self: *const @This()) ReadError![]u8 {
return self.buffer[self.pos..];
}

pub fn write(self: *@This(), in_buffer: []const u8) WriteError!usize {
const len = @min(self.buffer[self.pos..].len, in_buffer.len);
if (len == 0) {
Expand Down Expand Up @@ -67,10 +71,6 @@ pub fn getWritten(self: *const @This()) []u8 {
return self.buffer[0..self.pos];
}

pub fn getBuffer(self: *const @This()) []const u8 {
return self.buffer[self.pos..];
}

test "write, seek 0, and read back" {
const in_buf: []const u8 = "I really hope that this works!";

Expand All @@ -87,3 +87,8 @@ test "write, seek 0, and read back" {
try testing.expectEqual(in_buf.len, rlen);
try testing.expectEqualSlices(u8, in_buf, &out_buf);
}

test "FixedBufferStream is a buffered io.Reader" {
const impl = @import("zimpl").Impl(io.Reader, *@This()){};
try std.testing.expect(!(impl.readBuffer == null));
}
24 changes: 15 additions & 9 deletions examples/io/buffered_reader.zig
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,15 @@ pub fn BufferedReader(

pub const ReadError = child_impl.ReadError;

fn fill(self: *@This()) ReadError!void {
self.start = 0;
self.end = try io.read(
self.child_ctx,
child_impl,
self.buffer[0..],
);
}

pub fn read(self: *@This(), dest: []u8) ReadError!usize {
var dest_index: usize = 0;
while (dest_index < dest.len) {
Expand All @@ -30,24 +39,21 @@ pub fn BufferedReader(
self.buffer[self.start..][0..written],
);
if (written == 0) {
const n = try io.read(
self.child_ctx,
child_impl,
self.buffer[0..],
);
if (n == 0) {
try self.fill();
if (self.start == self.end) {
return dest_index;
}
self.start = 0;
self.end = n;
}
self.start += written;
dest_index += written;
}
return dest.len;
}

pub fn getBuffer(self: *const @This()) []const u8 {
pub fn readBuffer(self: *@This()) ReadError![]const u8 {
if (self.start == self.end) {
try self.fill();
}
return self.buffer[self.start..self.end];
}
};
Expand Down
16 changes: 10 additions & 6 deletions examples/io/buffered_writer.zig
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,18 @@ pub fn BufferedWriter(

pub const WriteError = child_impl.WriteError;

pub fn flush(self: *@This()) WriteError!void {
try io.writeAll(
self.child_ctx,
child_impl,
self.buffer[0..self.end],
);
self.end = 0;
}

pub fn write(self: *@This(), bytes: []const u8) WriteError!usize {
if (self.end + bytes.len > self.buffer.len) {
try io.writeAll(
self.child_ctx,
child_impl,
self.buffer[0..self.end],
);
self.end = 0;
try self.flush();
if (bytes.len > self.buffer.len) {
return io.write(self.child_ctx, child_impl, bytes);
}
Expand Down
31 changes: 31 additions & 0 deletions examples/read_file.zig
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,34 @@ test "read file with std.os.fd_t" {
fbs.getWritten(),
);
}

test "read file with buffered std.fs.File" {
const file = try std.fs.cwd().openFile("examples/read_file/test.txt", .{});
var buffered_file = io.bufferedReader(256, file, .{});
var buffer: [32]u8 = undefined;
var fbs: FixedBufferStream = .{ .buffer = &buffer };
try io.streamUntilDelimiter(&buffered_file, .{}, &fbs, .{}, '\n', 32);
try std.testing.expectEqualStrings(
"Hello, I am a file!",
fbs.getWritten(),
);
}

test "read file with buffered std.os.fd_t" {
const fd = try std.os.open(
"examples/read_file/test.txt",
std.os.O.RDONLY,
0,
);
var buffered_fd = io.bufferedReader(256, fd, .{
.read = std.os.read,
.ReadError = std.os.ReadError,
});
var buffer: [32]u8 = undefined;
var fbs: FixedBufferStream = .{ .buffer = &buffer };
try io.streamUntilDelimiter(&buffered_fd, .{}, &fbs, .{}, '\n', 32);
try std.testing.expectEqualStrings(
"Hello, I am a file!",
fbs.getWritten(),
);
}
Loading

0 comments on commit 36f082c

Please sign in to comment.