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;
1717pub const Reader = @import("io/Reader.zig");
1818pub const Writer = @import("io/Writer.zig");
1919
20pub const PositionalReader = @import("io/PositionalReader.zig");
21
2022pub const BufferedReader = @import("io/BufferedReader.zig");
2123pub const BufferedWriter = @import("io/BufferedWriter.zig");
2224pub const AllocatingWriter = @import("io/AllocatingWriter.zig");
......@@ -451,6 +453,7 @@ test {
451453 _ = BufferedReader;
452454 _ = Reader;
453455 _ = Writer;
456 _ = PositionalReader;
454457 _ = AllocatingWriter;
455458 _ = @import("io/bit_reader.zig");
456459 _ = @import("io/bit_writer.zig");
lib/std/io/BufferedReader.zig+14-51
......@@ -28,10 +28,8 @@ const eof_writer: std.io.Writer.VTable = .{
2828 .writeFile = eof_writeFile,
2929};
3030const eof_reader: std.io.Reader.VTable = .{
31 .posRead = eof_posRead,
32 .posReadVec = eof_posReadVec,
33 .streamRead = eof_streamRead,
34 .streamReadVec = eof_streamReadVec,
31 .read = eof_read,
32 .readv = eof_readv,
3533};
3634
3735fn eof_writeSplat(context: ?*anyopaque, data: []const []const u8, splat: usize) anyerror!usize {
......@@ -58,29 +56,14 @@ fn eof_writeFile(
5856 return error.NoSpaceLeft;
5957}
6058
61fn eof_posRead(ctx: ?*anyopaque, bw: *std.io.BufferedWriter, limit: Reader.Limit, offset: u64) 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 {
59fn eof_read(ctx: ?*anyopaque, bw: *std.io.BufferedWriter, limit: Reader.Limit) anyerror!Reader.Status {
7760 _ = ctx;
7861 _ = bw;
7962 _ = limit;
8063 return error.EndOfStream;
8164}
8265
83fn eof_streamReadVec(ctx: ?*anyopaque, data: []const []u8) anyerror!Reader.Status {
66fn eof_readv(ctx: ?*anyopaque, data: []const []u8) anyerror!Reader.Status {
8467 _ = ctx;
8568 _ = data;
8669 return error.EndOfStream;
......@@ -117,15 +100,13 @@ pub fn reader(br: *BufferedReader) Reader {
117100 return .{
118101 .context = br,
119102 .vtable = &.{
120 .streamRead = passthru_streamRead,
121 .streamReadVec = passthru_streamReadVec,
122 .posRead = passthru_posRead,
123 .posReadVec = passthru_posReadVec,
103 .read = passthru_read,
104 .readv = passthru_readv,
124105 },
125106 };
126107}
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 {
129110 const br: *BufferedReader = @alignCast(@ptrCast(ctx));
130111 const buffer = br.storage.buffer.items;
131112 const buffered = buffer[br.seek..];
......@@ -139,31 +120,13 @@ fn passthru_streamRead(ctx: ?*anyopaque, bw: *BufferedWriter, limit: Reader.Limi
139120 .write_end = result.end,
140121 };
141122 }
142 return br.unbuffered_reader.streamRead(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);
123 return br.unbuffered_reader.read(bw, limit);
160124}
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 {
163127 const br: *BufferedReader = @alignCast(@ptrCast(ctx));
164128 _ = br;
165129 _ = data;
166 _ = off;
167130 @panic("TODO");
168131}
169132
......@@ -293,7 +256,7 @@ pub fn discardUpTo(br: *BufferedReader, n: usize) anyerror!usize {
293256 remaining -= (list.items.len - br.seek);
294257 list.items.len = 0;
295258 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);
297260 result.write_err catch unreachable;
298261 try result.read_err;
299262 assert(result.len == list.items.len);
......@@ -337,7 +300,7 @@ pub fn read(br: *BufferedReader, buffer: []u8) anyerror!void {
337300 br.seek = 0;
338301 var i: usize = in_buffer.len;
339302 while (true) {
340 const status = try br.unbuffered_reader.streamRead(&br.storage, .none);
303 const status = try br.unbuffered_reader.read(&br.storage, .none);
341304 const next_i = i + list.items.len;
342305 if (next_i >= buffer.len) {
343306 const remaining = buffer[i..];
......@@ -397,7 +360,7 @@ pub fn peekDelimiterInclusive(br: *BufferedReader, delimiter: u8) anyerror![]u8
397360 list.items.len = i;
398361 br.seek = 0;
399362 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);
401364 if (std.mem.indexOfScalarPos(u8, list.items, i, delimiter)) |end| {
402365 return list.items[0 .. end + 1];
403366 }
......@@ -442,7 +405,7 @@ pub fn peekDelimiterConclusive(br: *BufferedReader, delimiter: u8) anyerror![]u8
442405 list.items.len = i;
443406 br.seek = 0;
444407 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);
446409 if (std.mem.indexOfScalarPos(u8, list.items, i, delimiter)) |end| {
447410 return list.items[0 .. end + 1];
448411 }
......@@ -540,7 +503,7 @@ pub fn fill(br: *BufferedReader, n: usize) anyerror!void {
540503 list.items.len = remainder.len;
541504 br.seek = 0;
542505 while (true) {
543 const status = try br.unbuffered_reader.streamRead(&br.storage, .none);
506 const status = try br.unbuffered_reader.read(&br.storage, .none);
544507 if (n <= list.items.len) return;
545508 if (status.end) return error.EndOfStream;
546509 }
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,
66vtable: *const VTable,
77
88pub const VTable = struct {
9 /// Writes bytes starting from `offset` to `bw`, or returns
10 /// `error.Unseekable`, indicating `streamRead` should be used instead.
9 /// Writes bytes from the internally tracked stream position to `bw`.
1110 ///
1211 /// Returns the number of bytes written, which will be at minimum `0` and at
1312 /// most `limit`. The number of bytes read, including zero, does not
1413 /// indicate end of stream.
1514 ///
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.
1717 ///
1818 /// The implementation should do a maximum of one underlying read call.
1919 ///
20 /// If this is `null` it is equivalent to always returning
21 /// `error.Unseekable`.
22 posRead: ?*const fn (ctx: ?*anyopaque, bw: *std.io.BufferedWriter, limit: Limit, offset: u64) anyerror!Status,
23 posReadVec: ?*const fn (ctx: ?*anyopaque, data: []const []u8, offset: u64) anyerror!Status,
24
25 /// Writes bytes from the internally tracked stream position to `bw`, or
26 /// returns `error.Unstreamable`, indicating `posRead` should be used
27 /// instead.
20 /// If `error.Unstreamable` is returned, the resource cannot be used via a
21 /// streaming reading interface.
22 read: *const fn (ctx: ?*anyopaque, bw: *std.io.BufferedWriter, limit: Limit) anyerror!Status,
23
24 /// Writes bytes from the internally tracked stream position to `data`.
2825 ///
2926 /// Returns the number of bytes written, which will be at minimum `0` and at
3027 /// most `limit`. The number of bytes read, including zero, does not
3128 /// indicate end of stream.
3229 ///
33 /// If the reader has an internal seek position, it moves forward in accordance
34 /// with the number of bytes return from this function.
30 /// If the reader has an internal seek position, it moves forward in
31 /// accordance with the number of bytes return from this function.
3532 ///
3633 /// The implementation should do a maximum of one underlying read call.
3734 ///
38 /// If this is `null` it is equivalent to always returning
39 /// `error.Unstreamable`.
40 streamRead: ?*const fn (ctx: ?*anyopaque, bw: *std.io.BufferedWriter, limit: Limit) anyerror!Status,
41 streamReadVec: ?*const fn (ctx: ?*anyopaque, data: []const []u8) anyerror!Status,
35 /// If `error.Unstreamable` is returned, the resource cannot be used via a
36 /// streaming reading interface.
37 readv: *const fn (ctx: ?*anyopaque, data: []const []u8) anyerror!Status,
4238};
4339
4440pub const Len = @Type(.{ .int = .{ .signedness = .unsigned, .bits = @bitSizeOf(usize) - 1 } });
......@@ -60,42 +56,20 @@ pub const Limit = enum(usize) {
6056 }
6157};
6258
63/// Returns total number of bytes written to `w`.
64pub fn readAll(r: Reader, w: *std.io.BufferedWriter) anyerror!usize {
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);
59pub fn read(r: Reader, w: *std.io.BufferedWriter, limit: Limit) anyerror!Status {
60 return r.vtable.read(r.context, w, limit);
7261}
7362
74/// Returns total number of bytes written to `w`.
75///
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 }
63pub fn readv(r: Reader, data: []const []u8) anyerror!Status {
64 return r.vtable.readv(r.context, data);
8665}
8766
8867/// Returns total number of bytes written to `w`.
89pub fn streamRead(r: Reader, w: *std.io.BufferedWriter, limit: Limit) anyerror!Status {
90 return r.vtable.streamRead.?(r.context, w, limit);
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.?;
68pub fn readAll(r: Reader, w: *std.io.BufferedWriter) anyerror!usize {
69 const readFn = r.vtable.read;
9670 var offset: usize = 0;
9771 while (true) {
98 const status = try vtable_streamRead(r.context, w, .none);
72 const status = try readFn(r.context, w, .none);
9973 offset += status.len;
10074 if (status.end) return offset;
10175 }
......@@ -107,42 +81,28 @@ pub fn streamReadAll(r: Reader, w: *std.io.BufferedWriter) anyerror!usize {
10781/// Caller owns returned memory.
10882///
10983/// 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 {
111 const vtable_streamRead = r.vtable.streamRead.?;
112
113 var bw: std.io.BufferedWriter = .{
114 .buffer = .empty,
115 .mode = .{ .allocator = gpa },
116 };
117 const list = &bw.buffer;
118 defer list.deinit(gpa);
119
84pub fn readAlloc(r: Reader, gpa: std.mem.Allocator, max_size: usize) anyerror![]u8 {
85 const readFn = r.vtable.read;
86 var aw: std.io.AllocatingWriter = undefined;
87 errdefer aw.deinit();
88 const bw = aw.init(gpa);
12089 var remaining = max_size;
12190 while (remaining > 0) {
122 const status = try vtable_streamRead(r.context, &bw, .init(remaining));
123 if (status.end) return list.toOwnedSlice(gpa);
91 const status = try readFn(r.context, bw, .init(remaining));
92 if (status.end) break;
12493 remaining -= status.len;
12594 }
95 return aw.toOwnedSlice(gpa);
12696}
12797
12898/// Reads the stream until the end, ignoring all the data.
12999/// Returns the number of bytes discarded.
130100pub fn discardUntilEnd(r: Reader) anyerror!usize {
131101 var bw = std.io.null_writer.unbuffered();
132 return streamReadAll(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 };
102 return readAll(r, &bw);
143103}
144104
145test "when the backing reader provides one byte at a time" {
105test "readAlloc when the backing reader provides one byte at a time" {
146106 const OneByteReader = struct {
147107 str: []const u8,
148108 curr: usize,
lib/std/zon/stringify.zig+1-1
......@@ -434,7 +434,7 @@ pub const SerializeContainerOptions = struct {
434434/// * `beginStruct`
435435/// * `beginTuple`
436436pub const Serializer = struct {
437 options: Options,
437 options: Options = .{},
438438 indent_level: u8 = 0,
439439 writer: *std.io.BufferedWriter,
440440