authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-04-10 23:03:20-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-01 16:35:26-07:00
log4ee25345668ff93f1a4a857436b14fc305f8cee7
tree5764951bf7555cdf2d84f84eea10403c65f4e9bc
parent60854795b85d075ffc5418f9009c832f6a06a84c

std.io.Reader: extract PositionalReader to separate interface


5 files changed, 112 insertions(+), 122 deletions(-)

lib/std/io.zig+3
...@@ -17,6 +17,8 @@ const Alignment = std.mem.Alignment;...@@ -17,6 +17,8 @@ const Alignment = std.mem.Alignment;
17pub const Reader = @import("io/Reader.zig");17pub const Reader = @import("io/Reader.zig");
18pub const Writer = @import("io/Writer.zig");18pub const Writer = @import("io/Writer.zig");
1919
20pub const PositionalReader = @import("io/PositionalReader.zig");
21
20pub const BufferedReader = @import("io/BufferedReader.zig");22pub const BufferedReader = @import("io/BufferedReader.zig");
21pub const BufferedWriter = @import("io/BufferedWriter.zig");23pub const BufferedWriter = @import("io/BufferedWriter.zig");
22pub const AllocatingWriter = @import("io/AllocatingWriter.zig");24pub const AllocatingWriter = @import("io/AllocatingWriter.zig");
...@@ -451,6 +453,7 @@ test {...@@ -451,6 +453,7 @@ test {
451 _ = BufferedReader;453 _ = BufferedReader;
452 _ = Reader;454 _ = Reader;
453 _ = Writer;455 _ = Writer;
456 _ = PositionalReader;
454 _ = AllocatingWriter;457 _ = AllocatingWriter;
455 _ = @import("io/bit_reader.zig");458 _ = @import("io/bit_reader.zig");
456 _ = @import("io/bit_writer.zig");459 _ = @import("io/bit_writer.zig");
lib/std/io/BufferedReader.zig+14-51
...@@ -28,10 +28,8 @@ const eof_writer: std.io.Writer.VTable = .{...@@ -28,10 +28,8 @@ const eof_writer: std.io.Writer.VTable = .{
28 .writeFile = eof_writeFile,28 .writeFile = eof_writeFile,
29};29};
30const eof_reader: std.io.Reader.VTable = .{30const eof_reader: std.io.Reader.VTable = .{
31 .posRead = eof_posRead,31 .read = eof_read,
32 .posReadVec = eof_posReadVec,32 .readv = eof_readv,
33 .streamRead = eof_streamRead,
34 .streamReadVec = eof_streamReadVec,
35};33};
3634
37fn eof_writeSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) anyerror!usize {35fn eof_writeSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) anyerror!usize {
...@@ -58,29 +56,14 @@ fn eof_writeFile(...@@ -58,29 +56,14 @@ fn eof_writeFile(
58 return error.NoSpaceLeft;56 return error.NoSpaceLeft;
59}57}
6058
61fn eof_posRead(ctx: ?*anyopaque, bw: *std.io.BufferedWriter, limit: Reader.Limit, offset: u64) anyerror!Reader.Status {59fn eof_read(ctx: ?*anyopaque, bw: *std.io.BufferedWriter, limit: Reader.Limit) anyerror!Reader.Status {
62 _ = ctx;
63 _ = bw;
64 _ = limit;
65 _ = offset;
66 return error.EndOfStream;
67}
68
69fn eof_posReadVec(ctx: ?*anyopaque, data: []const []u8, offset: u64) anyerror!Reader.Status {
70 _ = ctx;
71 _ = data;
72 _ = offset;
73 return error.EndOfStream;
74}
75
76fn eof_streamRead(ctx: ?*anyopaque, bw: *std.io.BufferedWriter, limit: Reader.Limit) anyerror!Reader.Status {
77 _ = ctx;60 _ = ctx;
78 _ = bw;61 _ = bw;
79 _ = limit;62 _ = limit;
80 return error.EndOfStream;63 return error.EndOfStream;
81}64}
8265
83fn eof_streamReadVec(ctx: ?*anyopaque, data: []const []u8) anyerror!Reader.Status {66fn eof_readv(ctx: ?*anyopaque, data: []const []u8) anyerror!Reader.Status {
84 _ = ctx;67 _ = ctx;
85 _ = data;68 _ = data;
86 return error.EndOfStream;69 return error.EndOfStream;
...@@ -117,15 +100,13 @@ pub fn reader(br: *BufferedReader) Reader {...@@ -117,15 +100,13 @@ pub fn reader(br: *BufferedReader) Reader {
117 return .{100 return .{
118 .context = br,101 .context = br,
119 .vtable = &.{102 .vtable = &.{
120 .streamRead = passthru_streamRead,103 .read = passthru_read,
121 .streamReadVec = passthru_streamReadVec,104 .readv = passthru_readv,
122 .posRead = passthru_posRead,
123 .posReadVec = passthru_posReadVec,
124 },105 },
125 };106 };
126}107}
127108
128fn passthru_streamRead(ctx: ?*anyopaque, bw: *BufferedWriter, limit: Reader.Limit) anyerror!Reader.RwResult {109fn passthru_read(ctx: ?*anyopaque, bw: *BufferedWriter, limit: Reader.Limit) anyerror!Reader.RwResult {
129 const br: *BufferedReader = @alignCast(@ptrCast(ctx));110 const br: *BufferedReader = @alignCast(@ptrCast(ctx));
130 const buffer = br.storage.buffer.items;111 const buffer = br.storage.buffer.items;
131 const buffered = buffer[br.seek..];112 const buffered = buffer[br.seek..];
...@@ -139,31 +120,13 @@ fn passthru_streamRead(ctx: ?*anyopaque, bw: *BufferedWriter, limit: Reader.Limi...@@ -139,31 +120,13 @@ fn passthru_streamRead(ctx: ?*anyopaque, bw: *BufferedWriter, limit: Reader.Limi
139 .write_end = result.end,120 .write_end = result.end,
140 };121 };
141 }122 }
142 return br.unbuffered_reader.streamRead(bw, limit);123 return br.unbuffered_reader.read(bw, limit);
143}
144
145fn passthru_streamReadVec(ctx: ?*anyopaque, data: []const []u8) anyerror!Reader.Status {
146 const br: *BufferedReader = @alignCast(@ptrCast(ctx));
147 _ = br;
148 _ = data;
149 @panic("TODO");
150}
151
152fn passthru_posRead(ctx: ?*anyopaque, bw: *BufferedWriter, limit: Reader.Limit, off: u64) anyerror!Reader.Status {
153 const br: *BufferedReader = @alignCast(@ptrCast(ctx));
154 const buffer = br.storage.buffer.items;
155 if (off < buffer.len) {
156 const send = buffer[off..limit.min(buffer.len)];
157 return bw.writeSplat(send, 1);
158 }
159 return br.unbuffered_reader.posRead(bw, limit, off - buffer.len);
160}124}
161125
162fn passthru_posReadVec(ctx: ?*anyopaque, data: []const []u8, off: u64) anyerror!Reader.Status {126fn passthru_readv(ctx: ?*anyopaque, data: []const []u8) anyerror!Reader.Status {
163 const br: *BufferedReader = @alignCast(@ptrCast(ctx));127 const br: *BufferedReader = @alignCast(@ptrCast(ctx));
164 _ = br;128 _ = br;
165 _ = data;129 _ = data;
166 _ = off;
167 @panic("TODO");130 @panic("TODO");
168}131}
169132
...@@ -293,7 +256,7 @@ pub fn discardUpTo(br: *BufferedReader, n: usize) anyerror!usize {...@@ -293,7 +256,7 @@ pub fn discardUpTo(br: *BufferedReader, n: usize) anyerror!usize {
293 remaining -= (list.items.len - br.seek);256 remaining -= (list.items.len - br.seek);
294 list.items.len = 0;257 list.items.len = 0;
295 br.seek = 0;258 br.seek = 0;
296 const result = try br.unbuffered_reader.streamRead(&br.storage, .none);259 const result = try br.unbuffered_reader.read(&br.storage, .none);
297 result.write_err catch unreachable;260 result.write_err catch unreachable;
298 try result.read_err;261 try result.read_err;
299 assert(result.len == list.items.len);262 assert(result.len == list.items.len);
...@@ -337,7 +300,7 @@ pub fn read(br: *BufferedReader, buffer: []u8) anyerror!void {...@@ -337,7 +300,7 @@ pub fn read(br: *BufferedReader, buffer: []u8) anyerror!void {
337 br.seek = 0;300 br.seek = 0;
338 var i: usize = in_buffer.len;301 var i: usize = in_buffer.len;
339 while (true) {302 while (true) {
340 const status = try br.unbuffered_reader.streamRead(&br.storage, .none);303 const status = try br.unbuffered_reader.read(&br.storage, .none);
341 const next_i = i + list.items.len;304 const next_i = i + list.items.len;
342 if (next_i >= buffer.len) {305 if (next_i >= buffer.len) {
343 const remaining = buffer[i..];306 const remaining = buffer[i..];
...@@ -397,7 +360,7 @@ pub fn peekDelimiterInclusive(br: *BufferedReader, delimiter: u8) anyerror![]u8...@@ -397,7 +360,7 @@ pub fn peekDelimiterInclusive(br: *BufferedReader, delimiter: u8) anyerror![]u8
397 list.items.len = i;360 list.items.len = i;
398 br.seek = 0;361 br.seek = 0;
399 while (i < list.capacity) {362 while (i < list.capacity) {
400 const status = try br.unbuffered_reader.streamRead(&br.storage, .none);363 const status = try br.unbuffered_reader.read(&br.storage, .none);
401 if (std.mem.indexOfScalarPos(u8, list.items, i, delimiter)) |end| {364 if (std.mem.indexOfScalarPos(u8, list.items, i, delimiter)) |end| {
402 return list.items[0 .. end + 1];365 return list.items[0 .. end + 1];
403 }366 }
...@@ -442,7 +405,7 @@ pub fn peekDelimiterConclusive(br: *BufferedReader, delimiter: u8) anyerror![]u8...@@ -442,7 +405,7 @@ pub fn peekDelimiterConclusive(br: *BufferedReader, delimiter: u8) anyerror![]u8
442 list.items.len = i;405 list.items.len = i;
443 br.seek = 0;406 br.seek = 0;
444 while (i < list.capacity) {407 while (i < list.capacity) {
445 const status = try br.unbuffered_reader.streamRead(&br.storage, .none);408 const status = try br.unbuffered_reader.read(&br.storage, .none);
446 if (std.mem.indexOfScalarPos(u8, list.items, i, delimiter)) |end| {409 if (std.mem.indexOfScalarPos(u8, list.items, i, delimiter)) |end| {
447 return list.items[0 .. end + 1];410 return list.items[0 .. end + 1];
448 }411 }
...@@ -540,7 +503,7 @@ pub fn fill(br: *BufferedReader, n: usize) anyerror!void {...@@ -540,7 +503,7 @@ pub fn fill(br: *BufferedReader, n: usize) anyerror!void {
540 list.items.len = remainder.len;503 list.items.len = remainder.len;
541 br.seek = 0;504 br.seek = 0;
542 while (true) {505 while (true) {
543 const status = try br.unbuffered_reader.streamRead(&br.storage, .none);506 const status = try br.unbuffered_reader.read(&br.storage, .none);
544 if (n <= list.items.len) return;507 if (n <= list.items.len) return;
545 if (status.end) return error.EndOfStream;508 if (status.end) return error.EndOfStream;
546 }509 }
lib/std/io/PositionalReader.zig created+64
...@@ -0,0 +1,64 @@
1const std = @import("../std.zig");
2const PositionalReader = @This();
3const assert = std.debug.assert;
4
5context: ?*anyopaque,
6vtable: *const VTable,
7
8pub const VTable = struct {
9 /// Writes bytes starting from `offset` to `bw`.
10 ///
11 /// Returns the number of bytes written, which will be at minimum `0` and
12 /// at most `limit`. The number of bytes written, including zero, does not
13 /// indicate end of stream.
14 ///
15 /// If the resource represented by the reader has an internal seek
16 /// position, it is not mutated.
17 ///
18 /// The implementation should do a maximum of one underlying read call.
19 ///
20 /// If `error.Unseekable` is returned, the resource cannot be used via a
21 /// positional reading interface.
22 read: *const fn (ctx: ?*anyopaque, bw: *std.io.BufferedWriter, limit: Limit, offset: u64) anyerror!Status,
23
24 /// Writes bytes starting from `offset` to `data`.
25 ///
26 /// Returns the number of bytes written, which will be at minimum `0` and
27 /// at most `limit`. The number of bytes written, including zero, does not
28 /// indicate end of stream.
29 ///
30 /// If the resource represented by the reader has an internal seek
31 /// position, it is not mutated.
32 ///
33 /// The implementation should do a maximum of one underlying read call.
34 ///
35 /// If `error.Unseekable` is returned, the resource cannot be used via a
36 /// positional reading interface.
37 readv: *const fn (ctx: ?*anyopaque, data: []const []u8, offset: u64) anyerror!Status,
38};
39
40pub const Len = std.io.Reader.Len;
41pub const Status = std.io.Reader.Status;
42pub const Limit = std.io.Reader.Limit;
43
44pub fn read(pr: PositionalReader, bw: *std.io.BufferedWriter, limit: Limit, offset: u64) anyerror!Status {
45 return pr.vtable.read(pr.context, bw, limit, offset);
46}
47
48pub fn readv(pr: PositionalReader, data: []const []u8, offset: u64) anyerror!Status {
49 return pr.vtable.read(pr.context, data, offset);
50}
51
52/// Returns total number of bytes written to `w`.
53///
54/// May return `error.Unseekable`, indicating this function cannot be used to
55/// read from the reader.
56pub fn readAll(pr: PositionalReader, w: *std.io.BufferedWriter, start_offset: u64) anyerror!usize {
57 const readFn = pr.vtable.read;
58 var offset: u64 = start_offset;
59 while (true) {
60 const status = try readFn(pr.context, w, .none, offset);
61 offset += status.len;
62 if (status.end) return @intCast(offset - start_offset);
63 }
64}
lib/std/io/Reader.zig+30-70
...@@ -6,39 +6,35 @@ context: ?*anyopaque,...@@ -6,39 +6,35 @@ context: ?*anyopaque,
6vtable: *const VTable,6vtable: *const VTable,
77
8pub const VTable = struct {8pub const VTable = struct {
9 /// Writes bytes starting from `offset` to `bw`, or returns9 /// Writes bytes from the internally tracked stream position to `bw`.
10 /// `error.Unseekable`, indicating `streamRead` should be used instead.
11 ///10 ///
12 /// Returns the number of bytes written, which will be at minimum `0` and at11 /// Returns the number of bytes written, which will be at minimum `0` and at
13 /// most `limit`. The number of bytes read, including zero, does not12 /// most `limit`. The number of bytes read, including zero, does not
14 /// indicate end of stream.13 /// indicate end of stream.
15 ///14 ///
16 /// If the reader has an internal seek position, it is not mutated.15 /// If the reader has an internal seek position, it moves forward in
16 /// accordance with the number of bytes return from this function.
17 ///17 ///
18 /// The implementation should do a maximum of one underlying read call.18 /// The implementation should do a maximum of one underlying read call.
19 ///19 ///
20 /// If this is `null` it is equivalent to always returning20 /// If `error.Unstreamable` is returned, the resource cannot be used via a
21 /// `error.Unseekable`.21 /// streaming reading interface.
22 posRead: ?*const fn (ctx: ?*anyopaque, bw: *std.io.BufferedWriter, limit: Limit, offset: u64) anyerror!Status,22 read: *const fn (ctx: ?*anyopaque, bw: *std.io.BufferedWriter, limit: Limit) anyerror!Status,
23 posReadVec: ?*const fn (ctx: ?*anyopaque, data: []const []u8, offset: u64) anyerror!Status,23
2424 /// Writes bytes from the internally tracked stream position to `data`.
25 /// Writes bytes from the internally tracked stream position to `bw`, or
26 /// returns `error.Unstreamable`, indicating `posRead` should be used
27 /// instead.
28 ///25 ///
29 /// Returns the number of bytes written, which will be at minimum `0` and at26 /// Returns the number of bytes written, which will be at minimum `0` and at
30 /// most `limit`. The number of bytes read, including zero, does not27 /// most `limit`. The number of bytes read, including zero, does not
31 /// indicate end of stream.28 /// indicate end of stream.
32 ///29 ///
33 /// If the reader has an internal seek position, it moves forward in accordance30 /// If the reader has an internal seek position, it moves forward in
34 /// with the number of bytes return from this function.31 /// accordance with the number of bytes return from this function.
35 ///32 ///
36 /// The implementation should do a maximum of one underlying read call.33 /// The implementation should do a maximum of one underlying read call.
37 ///34 ///
38 /// If this is `null` it is equivalent to always returning35 /// If `error.Unstreamable` is returned, the resource cannot be used via a
39 /// `error.Unstreamable`.36 /// streaming reading interface.
40 streamRead: ?*const fn (ctx: ?*anyopaque, bw: *std.io.BufferedWriter, limit: Limit) anyerror!Status,37 readv: *const fn (ctx: ?*anyopaque, data: []const []u8) anyerror!Status,
41 streamReadVec: ?*const fn (ctx: ?*anyopaque, data: []const []u8) anyerror!Status,
42};38};
4339
44pub const Len = @Type(.{ .int = .{ .signedness = .unsigned, .bits = @bitSizeOf(usize) - 1 } });40pub const Len = @Type(.{ .int = .{ .signedness = .unsigned, .bits = @bitSizeOf(usize) - 1 } });
...@@ -60,42 +56,20 @@ pub const Limit = enum(usize) {...@@ -60,42 +56,20 @@ pub const Limit = enum(usize) {
60 }56 }
61};57};
6258
63/// Returns total number of bytes written to `w`.59pub fn read(r: Reader, w: *std.io.BufferedWriter, limit: Limit) anyerror!Status {
64pub fn readAll(r: Reader, w: *std.io.BufferedWriter) anyerror!usize {60 return r.vtable.read(r.context, w, limit);
65 if (r.vtable.pread != null) {
66 return posReadAll(r, w) catch |err| switch (err) {
67 error.Unseekable => {},
68 else => return err,
69 };
70 }
71 return streamReadAll(r, w);
72}61}
7362
74/// Returns total number of bytes written to `w`.63pub fn readv(r: Reader, data: []const []u8) anyerror!Status {
75///64 return r.vtable.readv(r.context, data);
76/// May return `error.Unseekable`, indicating this function cannot be used to
77/// read from the reader.
78pub fn posReadAll(r: Reader, w: *std.io.BufferedWriter, start_offset: u64) anyerror!usize {
79 const vtable_posRead = r.vtable.posRead.?;
80 var offset: u64 = start_offset;
81 while (true) {
82 const status = try vtable_posRead(r.context, w, .none, offset);
83 offset += status.len;
84 if (status.end) return @intCast(offset - start_offset);
85 }
86}65}
8766
88/// Returns total number of bytes written to `w`.67/// Returns total number of bytes written to `w`.
89pub fn streamRead(r: Reader, w: *std.io.BufferedWriter, limit: Limit) anyerror!Status {68pub fn readAll(r: Reader, w: *std.io.BufferedWriter) anyerror!usize {
90 return r.vtable.streamRead.?(r.context, w, limit);69 const readFn = r.vtable.read;
91}
92
93/// Returns total number of bytes written to `w`.
94pub fn streamReadAll(r: Reader, w: *std.io.BufferedWriter) anyerror!usize {
95 const vtable_streamRead = r.vtable.streamRead.?;
96 var offset: usize = 0;70 var offset: usize = 0;
97 while (true) {71 while (true) {
98 const status = try vtable_streamRead(r.context, w, .none);72 const status = try readFn(r.context, w, .none);
99 offset += status.len;73 offset += status.len;
100 if (status.end) return offset;74 if (status.end) return offset;
101 }75 }
...@@ -107,42 +81,28 @@ pub fn streamReadAll(r: Reader, w: *std.io.BufferedWriter) anyerror!usize {...@@ -107,42 +81,28 @@ pub fn streamReadAll(r: Reader, w: *std.io.BufferedWriter) anyerror!usize {
107/// Caller owns returned memory.81/// Caller owns returned memory.
108///82///
109/// If this function returns an error, the contents from the stream read so far are lost.83/// If this function returns an error, the contents from the stream read so far are lost.
110pub fn streamReadAlloc(r: Reader, gpa: std.mem.Allocator, max_size: usize) anyerror![]u8 {84pub fn readAlloc(r: Reader, gpa: std.mem.Allocator, max_size: usize) anyerror![]u8 {
111 const vtable_streamRead = r.vtable.streamRead.?;85 const readFn = r.vtable.read;
11286 var aw: std.io.AllocatingWriter = undefined;
113 var bw: std.io.BufferedWriter = .{87 errdefer aw.deinit();
114 .buffer = .empty,88 const bw = aw.init(gpa);
115 .mode = .{ .allocator = gpa },
116 };
117 const list = &bw.buffer;
118 defer list.deinit(gpa);
119
120 var remaining = max_size;89 var remaining = max_size;
121 while (remaining > 0) {90 while (remaining > 0) {
122 const status = try vtable_streamRead(r.context, &bw, .init(remaining));91 const status = try readFn(r.context, bw, .init(remaining));
123 if (status.end) return list.toOwnedSlice(gpa);92 if (status.end) break;
124 remaining -= status.len;93 remaining -= status.len;
125 }94 }
95 return aw.toOwnedSlice(gpa);
126}96}
12797
128/// Reads the stream until the end, ignoring all the data.98/// Reads the stream until the end, ignoring all the data.
129/// Returns the number of bytes discarded.99/// Returns the number of bytes discarded.
130pub fn discardUntilEnd(r: Reader) anyerror!usize {100pub fn discardUntilEnd(r: Reader) anyerror!usize {
131 var bw = std.io.null_writer.unbuffered();101 var bw = std.io.null_writer.unbuffered();
132 return streamReadAll(r, &bw);102 return readAll(r, &bw);
133}
134
135pub fn allocating(r: Reader, gpa: std.mem.Allocator) std.io.BufferedReader {
136 return .{
137 .reader = r,
138 .buffered_writer = .{
139 .buffer = .empty,
140 .mode = .{ .allocator = gpa },
141 },
142 };
143}103}
144104
145test "when the backing reader provides one byte at a time" {105test "readAlloc when the backing reader provides one byte at a time" {
146 const OneByteReader = struct {106 const OneByteReader = struct {
147 str: []const u8,107 str: []const u8,
148 curr: usize,108 curr: usize,
lib/std/zon/stringify.zig+1-1
...@@ -434,7 +434,7 @@ pub const SerializeContainerOptions = struct {...@@ -434,7 +434,7 @@ pub const SerializeContainerOptions = struct {
434/// * `beginStruct`434/// * `beginStruct`
435/// * `beginTuple`435/// * `beginTuple`
436pub const Serializer = struct {436pub const Serializer = struct {
437 options: Options,437 options: Options = .{},
438 indent_level: u8 = 0,438 indent_level: u8 = 0,
439 writer: *std.io.BufferedWriter,439 writer: *std.io.BufferedWriter,
440440