authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-04-19 21:53:52-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-01 16:35:27-07:00
log24441b184f989dc889c33b6422ec5ddd8f385c5e
tree597e52884b28e688e417e397074ce19d0d275a7c
parent9fe0ce377c8d74bdfe19b426922a57083de7a94a

std.io.poll: update for BufferedReader

only posix is updated so far also implement `BufferedReader.readVec`

4 files changed, 277 insertions(+), 100 deletions(-)

lib/compiler/std-docs.zig+55-58
...@@ -320,69 +320,19 @@ fn buildWasmBinary(...@@ -320,69 +320,19 @@ fn buildWasmBinary(
320 try sendMessage(child.stdin.?, .update);320 try sendMessage(child.stdin.?, .update);
321 try sendMessage(child.stdin.?, .exit);321 try sendMessage(child.stdin.?, .exit);
322322
323 const Header = std.zig.Server.Message.Header;
324 var result: ?Cache.Path = null;323 var result: ?Cache.Path = null;
325 var result_error_bundle = std.zig.ErrorBundle.empty;324 var result_error_bundle = std.zig.ErrorBundle.empty;
326325
327 const stdout = poller.fifo(.stdout);326 while (true) {
328327 receiveWasmMessage(arena, context, poller.reader(.stdout), &result, &result_error_bundle) catch |err| switch (err) {
329 poll: while (true) {328 error.EndOfStream => break,
330 while (stdout.readableLength() < @sizeOf(Header)) {329 error.ReadFailed => if (!(try poller.poll())) break,
331 if (!(try poller.poll())) break :poll;330 else => |e| return e,
332 }331 };
333 const header = stdout.reader().readStruct(Header) catch unreachable;
334 while (stdout.readableLength() < header.bytes_len) {
335 if (!(try poller.poll())) break :poll;
336 }
337 const body = stdout.readableSliceOfLen(header.bytes_len);
338
339 switch (header.tag) {
340 .zig_version => {
341 if (!std.mem.eql(u8, builtin.zig_version_string, body)) {
342 return error.ZigProtocolVersionMismatch;
343 }
344 },
345 .error_bundle => {
346 const EbHdr = std.zig.Server.Message.ErrorBundle;
347 const eb_hdr = @as(*align(1) const EbHdr, @ptrCast(body));
348 const extra_bytes =
349 body[@sizeOf(EbHdr)..][0 .. @sizeOf(u32) * eb_hdr.extra_len];
350 const string_bytes =
351 body[@sizeOf(EbHdr) + extra_bytes.len ..][0..eb_hdr.string_bytes_len];
352 // TODO: use @ptrCast when the compiler supports it
353 const unaligned_extra = std.mem.bytesAsSlice(u32, extra_bytes);
354 const extra_array = try arena.alloc(u32, unaligned_extra.len);
355 @memcpy(extra_array, unaligned_extra);
356 result_error_bundle = .{
357 .string_bytes = try arena.dupe(u8, string_bytes),
358 .extra = extra_array,
359 };
360 },
361 .emit_digest => {
362 const EmitDigest = std.zig.Server.Message.EmitDigest;
363 const emit_digest = @as(*align(1) const EmitDigest, @ptrCast(body));
364 if (!emit_digest.flags.cache_hit) {
365 std.log.info("source changes detected; rebuilt wasm component", .{});
366 }
367 const digest = body[@sizeOf(EmitDigest)..][0..Cache.bin_digest_len];
368 result = .{
369 .root_dir = Cache.Directory.cwd(),
370 .sub_path = try std.fs.path.join(arena, &.{
371 context.global_cache_path, "o" ++ std.fs.path.sep_str ++ Cache.binToHex(digest.*),
372 }),
373 };
374 },
375 else => {}, // ignore other messages
376 }
377
378 stdout.discard(body.len);
379 }332 }
380333
381 const stderr = poller.fifo(.stderr);334 if (poller.reader(.stderr).buffer.len > 0) {
382 if (stderr.readableLength() > 0) {335 std.debug.print("{s}", .{poller.reader(.stderr).bufferContents()});
383 const owned_stderr = try stderr.toOwnedSlice();
384 defer gpa.free(owned_stderr);
385 std.debug.print("{s}", .{owned_stderr});
386 }336 }
387337
388 // Send EOF to stdin.338 // Send EOF to stdin.
...@@ -426,6 +376,53 @@ fn buildWasmBinary(...@@ -426,6 +376,53 @@ fn buildWasmBinary(
426 };376 };
427}377}
428378
379fn receiveWasmMessage(
380 arena: Allocator,
381 context: *Context,
382 br: *std.io.BufferedReader,
383 result: *?Cache.Path,
384 result_error_bundle: *std.zig.ErrorBundle,
385) !void {
386 // Ensure that we will be able to read the entire message without blocking.
387 const header = try br.peekStructEndian(std.zig.Server.Message.Header, .little);
388 try br.fill(@sizeOf(std.zig.Server.Message.Header) + header.bytes_len);
389 br.toss(@sizeOf(std.zig.Server.Message.Header));
390 switch (header.tag) {
391 .zig_version => {
392 const body = try br.take(header.bytes_len);
393 if (!std.mem.eql(u8, builtin.zig_version_string, body)) {
394 return error.ZigProtocolVersionMismatch;
395 }
396 },
397 .error_bundle => {
398 const eb_hdr = try br.takeStructEndian(std.zig.Server.Message.ErrorBundle, .little);
399 const extra_array = try br.readArrayEndianAlloc(arena, u32, eb_hdr.extra_len, .little);
400 const string_bytes = try br.readAlloc(arena, eb_hdr.string_bytes_len);
401 result_error_bundle.* = .{
402 .string_bytes = string_bytes,
403 .extra = extra_array,
404 };
405 },
406 .emit_digest => {
407 const emit_digest = try br.takeStructEndian(std.zig.Server.Message.EmitDigest, .little);
408 if (!emit_digest.flags.cache_hit) {
409 std.log.info("source changes detected; rebuilt wasm component", .{});
410 }
411 const digest = try br.takeArray(Cache.bin_digest_len);
412 result.* = .{
413 .root_dir = Cache.Directory.cwd(),
414 .sub_path = try std.fs.path.join(arena, &.{
415 context.global_cache_path, "o" ++ std.fs.path.sep_str ++ Cache.binToHex(digest.*),
416 }),
417 };
418 },
419 else => {
420 // Ignore other messages.
421 try br.discard(header.bytes_len);
422 },
423 }
424}
425
429fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void {426fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void {
430 const header: std.zig.Client.Message.Header = .{427 const header: std.zig.Client.Message.Header = .{
431 .tag = tag,428 .tag = tag,
lib/std/fs/File.zig+29
...@@ -1341,14 +1341,43 @@ pub const Writer = struct {...@@ -1341,14 +1341,43 @@ pub const Writer = struct {
1341 }1341 }
1342};1342};
13431343
1344/// Defaults to positional reading; falls back to streaming.
1345///
1346/// Positional is more threadsafe, since the global seek position is not
1347/// affected.
1344pub fn reader(file: File) Reader {1348pub fn reader(file: File) Reader {
1345 return .{ .file = file };1349 return .{ .file = file };
1346}1350}
13471351
1352/// Positional is more threadsafe, since the global seek position is not
1353/// affected, but when such syscalls are not available, preemptively choosing
1354/// `Reader.Mode.streaming` will skip a failed syscall.
1355pub fn readerStreaming(file: File) Reader {
1356 return .{
1357 .file = file,
1358 .mode = .streaming,
1359 .seek_err = error.Unseekable,
1360 };
1361}
1362
1363/// Defaults to positional reading; falls back to streaming.
1364///
1365/// Positional is more threadsafe, since the global seek position is not
1366/// affected.
1348pub fn writer(file: File) Writer {1367pub fn writer(file: File) Writer {
1349 return .{ .file = file };1368 return .{ .file = file };
1350}1369}
13511370
1371/// Positional is more threadsafe, since the global seek position is not
1372/// affected, but when such syscalls are not available, preemptively choosing
1373/// `Writer.Mode.streaming` will skip a failed syscall.
1374pub fn writerStreaming(file: File) Writer {
1375 return .{
1376 .file = file,
1377 .mode = .streaming,
1378 };
1379}
1380
1352const range_off: windows.LARGE_INTEGER = 0;1381const range_off: windows.LARGE_INTEGER = 0;
1353const range_len: windows.LARGE_INTEGER = 1;1382const range_len: windows.LARGE_INTEGER = 1;
13541383
lib/std/io.zig+42-37
...@@ -46,54 +46,57 @@ pub const BufferedAtomicFile = @import("io/buffered_atomic_file.zig").BufferedAt...@@ -46,54 +46,57 @@ pub const BufferedAtomicFile = @import("io/buffered_atomic_file.zig").BufferedAt
46pub const tty = @import("io/tty.zig");46pub const tty = @import("io/tty.zig");
4747
48pub fn poll(48pub fn poll(
49 allocator: Allocator,49 gpa: Allocator,
50 comptime StreamEnum: type,50 comptime StreamEnum: type,
51 files: PollFiles(StreamEnum),51 files: PollFiles(StreamEnum),
52) Poller(StreamEnum) {52) Poller(StreamEnum) {
53 const enum_fields = @typeInfo(StreamEnum).@"enum".fields;53 const enum_fields = @typeInfo(StreamEnum).@"enum".fields;
54 var result: Poller(StreamEnum) = undefined;54 var result: Poller(StreamEnum) = .{
5555 .gpa = gpa,
56 if (is_windows) result.windows = .{56 .readers = undefined,
57 .first_read_done = false,57 .poll_fds = undefined,
58 .overlapped = [1]windows.OVERLAPPED{58 .windows = if (is_windows) .{
59 mem.zeroes(windows.OVERLAPPED),59 .first_read_done = false,
60 } ** enum_fields.len,60 .overlapped = [1]windows.OVERLAPPED{
61 .small_bufs = undefined,61 mem.zeroes(windows.OVERLAPPED),
62 .active = .{62 } ** enum_fields.len,
63 .count = 0,63 .small_bufs = undefined,
64 .handles_buf = undefined,64 .active = .{
65 .stream_map = undefined,65 .count = 0,
66 },66 .handles_buf = undefined,
67 .stream_map = undefined,
68 },
69 } else {},
67 };70 };
6871
69 inline for (0..enum_fields.len) |i| {72 inline for (enum_fields, 0..) |field, i| {
70 result.fifos[i] = .{73 result.readers[i] = .{
71 .allocator = allocator,74 .unbuffered_reader = .failing,
72 .buf = &.{},75 .buffer = &.{},
73 .head = 0,76 .end = 0,
74 .count = 0,77 .seek = 0,
75 };78 };
76 if (is_windows) {79 if (is_windows) {
77 result.windows.active.handles_buf[i] = @field(files, enum_fields[i].name).handle;80 result.windows.active.handles_buf[i] = @field(files, field.name).handle;
78 } else {81 } else {
79 result.poll_fds[i] = .{82 result.poll_fds[i] = .{
80 .fd = @field(files, enum_fields[i].name).handle,83 .fd = @field(files, field.name).handle,
81 .events = posix.POLL.IN,84 .events = posix.POLL.IN,
82 .revents = undefined,85 .revents = undefined,
83 };86 };
84 }87 }
85 }88 }
89
86 return result;90 return result;
87}91}
8892
89pub const PollFifo = std.fifo.LinearFifo(u8, .Dynamic);
90
91pub fn Poller(comptime StreamEnum: type) type {93pub fn Poller(comptime StreamEnum: type) type {
92 return struct {94 return struct {
93 const enum_fields = @typeInfo(StreamEnum).@"enum".fields;95 const enum_fields = @typeInfo(StreamEnum).@"enum".fields;
94 const PollFd = if (is_windows) void else posix.pollfd;96 const PollFd = if (is_windows) void else posix.pollfd;
9597
96 fifos: [enum_fields.len]PollFifo,98 gpa: Allocator,
99 readers: [enum_fields.len]BufferedReader,
97 poll_fds: [enum_fields.len]PollFd,100 poll_fds: [enum_fields.len]PollFd,
98 windows: if (is_windows) struct {101 windows: if (is_windows) struct {
99 first_read_done: bool,102 first_read_done: bool,
...@@ -105,7 +108,7 @@ pub fn Poller(comptime StreamEnum: type) type {...@@ -105,7 +108,7 @@ pub fn Poller(comptime StreamEnum: type) type {
105 stream_map: [enum_fields.len]StreamEnum,108 stream_map: [enum_fields.len]StreamEnum,
106109
107 pub fn removeAt(self: *@This(), index: u32) void {110 pub fn removeAt(self: *@This(), index: u32) void {
108 std.debug.assert(index < self.count);111 assert(index < self.count);
109 for (index + 1..self.count) |i| {112 for (index + 1..self.count) |i| {
110 self.handles_buf[i - 1] = self.handles_buf[i];113 self.handles_buf[i - 1] = self.handles_buf[i];
111 self.stream_map[i - 1] = self.stream_map[i];114 self.stream_map[i - 1] = self.stream_map[i];
...@@ -118,13 +121,14 @@ pub fn Poller(comptime StreamEnum: type) type {...@@ -118,13 +121,14 @@ pub fn Poller(comptime StreamEnum: type) type {
118 const Self = @This();121 const Self = @This();
119122
120 pub fn deinit(self: *Self) void {123 pub fn deinit(self: *Self) void {
124 const gpa = self.gpa;
121 if (is_windows) {125 if (is_windows) {
122 // cancel any pending IO to prevent clobbering OVERLAPPED value126 // cancel any pending IO to prevent clobbering OVERLAPPED value
123 for (self.windows.active.handles_buf[0..self.windows.active.count]) |h| {127 for (self.windows.active.handles_buf[0..self.windows.active.count]) |h| {
124 _ = windows.kernel32.CancelIo(h);128 _ = windows.kernel32.CancelIo(h);
125 }129 }
126 }130 }
127 inline for (&self.fifos) |*q| q.deinit();131 inline for (&self.readers) |*br| gpa.free(br.buffer);
128 self.* = undefined;132 self.* = undefined;
129 }133 }
130134
...@@ -144,8 +148,8 @@ pub fn Poller(comptime StreamEnum: type) type {...@@ -144,8 +148,8 @@ pub fn Poller(comptime StreamEnum: type) type {
144 }148 }
145 }149 }
146150
147 pub inline fn fifo(self: *Self, comptime which: StreamEnum) *PollFifo {151 pub inline fn reader(self: *Self, comptime which: StreamEnum) *BufferedReader {
148 return &self.fifos[@intFromEnum(which)];152 return &self.readers[@intFromEnum(which)];
149 }153 }
150154
151 fn pollWindows(self: *Self, nanoseconds: ?u64) !bool {155 fn pollWindows(self: *Self, nanoseconds: ?u64) !bool {
...@@ -236,6 +240,7 @@ pub fn Poller(comptime StreamEnum: type) type {...@@ -236,6 +240,7 @@ pub fn Poller(comptime StreamEnum: type) type {
236 }240 }
237241
238 fn pollPosix(self: *Self, nanoseconds: ?u64) !bool {242 fn pollPosix(self: *Self, nanoseconds: ?u64) !bool {
243 const gpa = self.gpa;
239 // We ask for ensureUnusedCapacity with this much extra space. This244 // We ask for ensureUnusedCapacity with this much extra space. This
240 // has more of an effect on small reads because once the reads245 // has more of an effect on small reads because once the reads
241 // start to get larger the amount of space an ArrayList will246 // start to get larger the amount of space an ArrayList will
...@@ -255,18 +260,18 @@ pub fn Poller(comptime StreamEnum: type) type {...@@ -255,18 +260,18 @@ pub fn Poller(comptime StreamEnum: type) type {
255 }260 }
256261
257 var keep_polling = false;262 var keep_polling = false;
258 inline for (&self.poll_fds, &self.fifos) |*poll_fd, *q| {263 inline for (&self.poll_fds, &self.readers) |*poll_fd, *br| {
259 // Try reading whatever is available before checking the error264 // Try reading whatever is available before checking the error
260 // conditions.265 // conditions.
261 // It's still possible to read after a POLL.HUP is received,266 // It's still possible to read after a POLL.HUP is received,
262 // always check if there's some data waiting to be read first.267 // always check if there's some data waiting to be read first.
263 if (poll_fd.revents & posix.POLL.IN != 0) {268 if (poll_fd.revents & posix.POLL.IN != 0) {
264 const buf = try q.writableWithSize(bump_amt);269 const buf = try br.writableSliceGreedyAlloc(gpa, bump_amt);
265 const amt = posix.read(poll_fd.fd, buf) catch |err| switch (err) {270 const amt = posix.read(poll_fd.fd, buf) catch |err| switch (err) {
266 error.BrokenPipe => 0, // Handle the same as EOF.271 error.BrokenPipe => 0, // Handle the same as EOF.
267 else => |e| return e,272 else => |e| return e,
268 };273 };
269 q.update(amt);274 br.advanceBufferEnd(amt);
270 if (amt == 0) {275 if (amt == 0) {
271 // Remove the fd when the EOF condition is met.276 // Remove the fd when the EOF condition is met.
272 poll_fd.fd = -1;277 poll_fd.fd = -1;
...@@ -297,14 +302,14 @@ var win_dummy_bytes_read: u32 = undefined;...@@ -297,14 +302,14 @@ var win_dummy_bytes_read: u32 = undefined;
297fn windowsAsyncReadToFifoAndQueueSmallRead(302fn windowsAsyncReadToFifoAndQueueSmallRead(
298 handle: windows.HANDLE,303 handle: windows.HANDLE,
299 overlapped: *windows.OVERLAPPED,304 overlapped: *windows.OVERLAPPED,
300 fifo: *PollFifo,305 br: *BufferedReader,
301 small_buf: *[128]u8,306 small_buf: *[128]u8,
302 bump_amt: usize,307 bump_amt: usize,
303) !enum { empty, populated, closed_populated, closed } {308) !enum { empty, populated, closed_populated, closed } {
304 var read_any_data = false;309 var read_any_data = false;
305 while (true) {310 while (true) {
306 const fifo_read_pending = while (true) {311 const fifo_read_pending = while (true) {
307 const buf = try fifo.writableWithSize(bump_amt);312 const buf = try br.writableWithSize(bump_amt);
308 const buf_len = math.cast(u32, buf.len) orelse math.maxInt(u32);313 const buf_len = math.cast(u32, buf.len) orelse math.maxInt(u32);
309314
310 if (0 == windows.kernel32.ReadFile(315 if (0 == windows.kernel32.ReadFile(
...@@ -326,7 +331,7 @@ fn windowsAsyncReadToFifoAndQueueSmallRead(...@@ -326,7 +331,7 @@ fn windowsAsyncReadToFifoAndQueueSmallRead(
326 };331 };
327332
328 read_any_data = true;333 read_any_data = true;
329 fifo.update(num_bytes_read);334 br.update(num_bytes_read);
330335
331 if (num_bytes_read == buf_len) {336 if (num_bytes_read == buf_len) {
332 // We filled the buffer, so there's probably more data available.337 // We filled the buffer, so there's probably more data available.
...@@ -356,7 +361,7 @@ fn windowsAsyncReadToFifoAndQueueSmallRead(...@@ -356,7 +361,7 @@ fn windowsAsyncReadToFifoAndQueueSmallRead(
356 .aborted => break :cancel_read,361 .aborted => break :cancel_read,
357 };362 };
358 read_any_data = true;363 read_any_data = true;
359 fifo.update(num_bytes_read);364 br.update(num_bytes_read);
360 }365 }
361366
362 // Try to queue the 1-byte read.367 // Try to queue the 1-byte read.
...@@ -381,7 +386,7 @@ fn windowsAsyncReadToFifoAndQueueSmallRead(...@@ -381,7 +386,7 @@ fn windowsAsyncReadToFifoAndQueueSmallRead(
381 .closed => return if (read_any_data) .closed_populated else .closed,386 .closed => return if (read_any_data) .closed_populated else .closed,
382 .aborted => unreachable,387 .aborted => unreachable,
383 };388 };
384 try fifo.write(small_buf[0..num_bytes_read]);389 try br.write(small_buf[0..num_bytes_read]);
385 read_any_data = true;390 read_any_data = true;
386 }391 }
387}392}
lib/std/io/BufferedReader.zig+151-5
...@@ -6,6 +6,7 @@ const assert = std.debug.assert;...@@ -6,6 +6,7 @@ const assert = std.debug.assert;
6const testing = std.testing;6const testing = std.testing;
7const BufferedWriter = std.io.BufferedWriter;7const BufferedWriter = std.io.BufferedWriter;
8const Reader = std.io.Reader;8const Reader = std.io.Reader;
9const Allocator = std.mem.Allocator;
910
10const BufferedReader = @This();11const BufferedReader = @This();
1112
...@@ -46,13 +47,17 @@ pub fn reader(br: *BufferedReader) Reader {...@@ -46,13 +47,17 @@ pub fn reader(br: *BufferedReader) Reader {
46 };47 };
47}48}
4849
50pub fn readVec(br: *BufferedReader, data: []const []u8) Reader.Error!usize {
51 return passthruReadVec(br, data);
52}
53
49fn passthruRead(ctx: ?*anyopaque, bw: *BufferedWriter, limit: Reader.Limit) Reader.RwError!usize {54fn passthruRead(ctx: ?*anyopaque, bw: *BufferedWriter, limit: Reader.Limit) Reader.RwError!usize {
50 const br: *BufferedReader = @alignCast(@ptrCast(ctx));55 const br: *BufferedReader = @alignCast(@ptrCast(ctx));
51 const buffer = br.buffer[0..br.end];56 const buffer = br.buffer[0..br.end];
52 const buffered = buffer[br.seek..];57 const buffered = buffer[br.seek..];
53 const limited = buffered[0..limit.min(buffered.len)];58 const limited = buffered[0..limit.min(buffered.len)];
54 if (limited.len > 0) {59 if (limited.len > 0) {
55 const n = try bw.writeSplat(limited, 1);60 const n = try bw.write(limited);
56 br.seek += n;61 br.seek += n;
57 return n;62 return n;
58 }63 }
...@@ -61,9 +66,44 @@ fn passthruRead(ctx: ?*anyopaque, bw: *BufferedWriter, limit: Reader.Limit) Read...@@ -61,9 +66,44 @@ fn passthruRead(ctx: ?*anyopaque, bw: *BufferedWriter, limit: Reader.Limit) Read
6166
62fn passthruReadVec(ctx: ?*anyopaque, data: []const []u8) Reader.Error!usize {67fn passthruReadVec(ctx: ?*anyopaque, data: []const []u8) Reader.Error!usize {
63 const br: *BufferedReader = @alignCast(@ptrCast(ctx));68 const br: *BufferedReader = @alignCast(@ptrCast(ctx));
64 _ = br;69 var total: usize = 0;
65 _ = data;70 for (data, 0..) |buf, i| {
66 @panic("TODO");71 const buffered = br.buffer[br.seek..br.end];
72 const copy_len = @min(buffered.len, buf.len);
73 @memcpy(buf[0..copy_len], buffered[0..copy_len]);
74 total += copy_len;
75 br.seek += copy_len;
76 if (copy_len < buf.len) {
77 br.seek = 0;
78 br.end = 0;
79 var vecs: [8][]u8 = undefined; // Arbitrarily chosen value.
80 vecs[0] = buf[copy_len..];
81 const vecs_len: usize = @min(vecs.len, data.len - i);
82 var vec_data_len: usize = vecs[0].len;
83 for (&vecs[1..vecs_len], data[i + 1 ..][0 .. vecs_len - 1]) |*v, d| {
84 vec_data_len += d.len;
85 v.* = d;
86 }
87 if (vecs_len < vecs.len) {
88 vecs[vecs_len] = br.buffer;
89 const n = try br.unbuffered_reader.readVec(vecs[0 .. vecs_len + 1]);
90 total += @min(n, vec_data_len);
91 br.end = n -| vec_data_len;
92 return total;
93 }
94 if (vecs[vecs.len - 1].len >= br.buffer.len) {
95 total += try br.unbuffered_reader.readVec(&vecs);
96 return total;
97 }
98 vec_data_len -= vecs[vecs.len - 1].len;
99 vecs[vecs.len - 1] = br.buffer;
100 const n = try br.unbuffered_reader.readVec(&vecs);
101 total += @min(n, vec_data_len);
102 br.end = n -| vec_data_len;
103 return total;
104 }
105 }
106 return total;
67}107}
68108
69pub fn seekBy(br: *BufferedReader, seek_by: i64) !void {109pub fn seekBy(br: *BufferedReader, seek_by: i64) !void {
...@@ -147,7 +187,7 @@ pub fn take(br: *BufferedReader, n: usize) Reader.Error![]u8 {...@@ -147,7 +187,7 @@ pub fn take(br: *BufferedReader, n: usize) Reader.Error![]u8 {
147}187}
148188
149/// Returns the next `n` bytes from `unbuffered_reader` as an array, filling189/// Returns the next `n` bytes from `unbuffered_reader` as an array, filling
150/// the buffer as necessary.190/// the buffer as necessary and advancing the seek position `n` bytes.
151///191///
152/// Asserts that the `BufferedReader` was initialized with a buffer capacity at192/// Asserts that the `BufferedReader` was initialized with a buffer capacity at
153/// least as big as `n`.193/// least as big as `n`.
...@@ -161,6 +201,22 @@ pub fn takeArray(br: *BufferedReader, comptime n: usize) Reader.Error!*[n]u8 {...@@ -161,6 +201,22 @@ pub fn takeArray(br: *BufferedReader, comptime n: usize) Reader.Error!*[n]u8 {
161 return (try br.take(n))[0..n];201 return (try br.take(n))[0..n];
162}202}
163203
204/// Returns the next `n` bytes from `unbuffered_reader` as an array, filling
205/// the buffer as necessary, without advancing the seek position.
206///
207/// Asserts that the `BufferedReader` was initialized with a buffer capacity at
208/// least as big as `n`.
209///
210/// If there are fewer than `n` bytes left in the stream, `error.EndOfStream`
211/// is returned instead.
212///
213/// See also:
214/// * `peek`
215/// * `takeArray`
216pub fn peekArray(br: *BufferedReader, comptime n: usize) Reader.Error!*[n]u8 {
217 return (try br.peek(n))[0..n];
218}
219
164/// Skips the next `n` bytes from the stream, advancing the seek position.220/// Skips the next `n` bytes from the stream, advancing the seek position.
165///221///
166/// Unlike `toss` which is infallible, in this function `n` can be any amount.222/// Unlike `toss` which is infallible, in this function `n` can be any amount.
...@@ -255,6 +311,31 @@ pub fn readShort(br: *BufferedReader, buffer: []u8) Reader.ShortError!usize {...@@ -255,6 +311,31 @@ pub fn readShort(br: *BufferedReader, buffer: []u8) Reader.ShortError!usize {
255 @panic("TODO");311 @panic("TODO");
256}312}
257313
314/// The function is inline to avoid the dead code in case `endian` is
315/// comptime-known and matches host endianness.
316pub inline fn readArrayEndianAlloc(
317 br: *BufferedReader,
318 allocator: Allocator,
319 Elem: type,
320 len: usize,
321 endian: std.builtin.Endian,
322) ReadAllocError![]Elem {
323 const dest = try allocator.alloc(Elem, len);
324 errdefer allocator.free(dest);
325 try read(br, @ptrCast(dest));
326 if (native_endian != endian) std.mem.byteSwapAllFields(Elem, dest);
327 return dest;
328}
329
330pub const ReadAllocError = Reader.Error || Allocator.Error;
331
332pub fn readAlloc(br: *BufferedReader, allocator: Allocator, len: usize) ReadAllocError![]u8 {
333 const dest = try allocator.alloc(u8, len);
334 errdefer allocator.free(dest);
335 try read(br, dest);
336 return dest;
337}
338
258pub const DelimiterInclusiveError = error{339pub const DelimiterInclusiveError = error{
259 /// See the `Reader` implementation for detailed diagnostics.340 /// See the `Reader` implementation for detailed diagnostics.
260 ReadFailed,341 ReadFailed,
...@@ -498,12 +579,29 @@ pub fn takeVarInt(br: *BufferedReader, comptime Int: type, endian: std.builtin.E...@@ -498,12 +579,29 @@ pub fn takeVarInt(br: *BufferedReader, comptime Int: type, endian: std.builtin.E
498}579}
499580
500/// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`.581/// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`.
582///
583/// Advances the seek position.
584///
585/// See also:
586/// * `peekStruct`
501pub fn takeStruct(br: *BufferedReader, comptime T: type) Reader.Error!*align(1) T {587pub fn takeStruct(br: *BufferedReader, comptime T: type) Reader.Error!*align(1) T {
502 // Only extern and packed structs have defined in-memory layout.588 // Only extern and packed structs have defined in-memory layout.
503 comptime assert(@typeInfo(T).@"struct".layout != .auto);589 comptime assert(@typeInfo(T).@"struct".layout != .auto);
504 return @ptrCast(try br.takeArray(@sizeOf(T)));590 return @ptrCast(try br.takeArray(@sizeOf(T)));
505}591}
506592
593/// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`.
594///
595/// Does not advance the seek position.
596///
597/// See also:
598/// * `takeStruct`
599pub fn peekStruct(br: *BufferedReader, comptime T: type) Reader.Error!*align(1) T {
600 // Only extern and packed structs have defined in-memory layout.
601 comptime assert(@typeInfo(T).@"struct".layout != .auto);
602 return @ptrCast(try br.peekArray(@sizeOf(T)));
603}
604
507/// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`.605/// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`.
508///606///
509/// This function is inline to avoid referencing `std.mem.byteSwapAllFields`607/// This function is inline to avoid referencing `std.mem.byteSwapAllFields`
...@@ -514,6 +612,16 @@ pub inline fn takeStructEndian(br: *BufferedReader, comptime T: type, endian: st...@@ -514,6 +612,16 @@ pub inline fn takeStructEndian(br: *BufferedReader, comptime T: type, endian: st
514 return res;612 return res;
515}613}
516614
615/// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`.
616///
617/// This function is inline to avoid referencing `std.mem.byteSwapAllFields`
618/// when `endian` is comptime-known and matches the host endianness.
619pub inline fn peekStructEndian(br: *BufferedReader, comptime T: type, endian: std.builtin.Endian) Reader.Error!T {
620 var res = (try br.peekStruct(T)).*;
621 if (native_endian != endian) std.mem.byteSwapAllFields(T, &res);
622 return res;
623}
624
517/// Reads an integer with the same size as the given enum's tag type. If the625/// Reads an integer with the same size as the given enum's tag type. If the
518/// integer matches an enum tag, casts the integer to the enum tag and returns626/// integer matches an enum tag, casts the integer to the enum tag and returns
519/// it. Otherwise, returns `error.InvalidEnumTag`.627/// it. Otherwise, returns `error.InvalidEnumTag`.
...@@ -536,6 +644,28 @@ pub fn takeLeb128(br: *BufferedReader, comptime Result: type) TakeLeb128Error!Re...@@ -536,6 +644,28 @@ pub fn takeLeb128(br: *BufferedReader, comptime Result: type) TakeLeb128Error!Re
536 } }))) orelse error.Overflow;644 } }))) orelse error.Overflow;
537}645}
538646
647/// Returns a slice into the unused capacity of `buffer` with at least
648/// `min_len` bytes, extending `buffer` by resizing it with `gpa` as necessary.
649///
650/// After calling this function, typically the caller will follow up with a
651/// call to `advanceBufferEnd` to report the actual number of bytes buffered.
652pub fn writableSliceGreedyAlloc(
653 br: *BufferedReader,
654 allocator: Allocator,
655 min_len: usize,
656) error{OutOfMemory}![]u8 {
657 _ = br;
658 _ = allocator;
659 _ = min_len;
660 @panic("TODO");
661}
662
663/// After writing directly into the unused capacity of `buffer`, this function
664/// updates `end` so that users of `BufferedReader` can receive the data.
665pub fn advanceBufferEnd(br: *BufferedReader, n: usize) void {
666 br.end += n;
667}
668
539fn takeMultipleOf7Leb128(br: *BufferedReader, comptime Result: type) TakeLeb128Error!Result {669fn takeMultipleOf7Leb128(br: *BufferedReader, comptime Result: type) TakeLeb128Error!Result {
540 const result_info = @typeInfo(Result).int;670 const result_info = @typeInfo(Result).int;
541 comptime assert(result_info.bits % 7 == 0);671 comptime assert(result_info.bits % 7 == 0);
...@@ -599,6 +729,10 @@ test takeArray {...@@ -599,6 +729,10 @@ test takeArray {
599 return error.Unimplemented;729 return error.Unimplemented;
600}730}
601731
732test peekArray {
733 return error.Unimplemented;
734}
735
602test discard {736test discard {
603 var br: BufferedReader = undefined;737 var br: BufferedReader = undefined;
604 br.initFixed("foobar");738 br.initFixed("foobar");
...@@ -684,10 +818,18 @@ test takeStruct {...@@ -684,10 +818,18 @@ test takeStruct {
684 return error.Unimplemented;818 return error.Unimplemented;
685}819}
686820
821test peekStruct {
822 return error.Unimplemented;
823}
824
687test takeStructEndian {825test takeStructEndian {
688 return error.Unimplemented;826 return error.Unimplemented;
689}827}
690828
829test peekStructEndian {
830 return error.Unimplemented;
831}
832
691test takeEnum {833test takeEnum {
692 return error.Unimplemented;834 return error.Unimplemented;
693}835}
...@@ -699,3 +841,7 @@ test takeLeb128 {...@@ -699,3 +841,7 @@ test takeLeb128 {
699test readShort {841test readShort {
700 return error.Unimplemented;842 return error.Unimplemented;
701}843}
844
845test readVec {
846 return error.Unimplemented;
847}