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(
320320 try sendMessage(child.stdin.?, .update);
321321 try sendMessage(child.stdin.?, .exit);
322322
323 const Header = std.zig.Server.Message.Header;
324323 var result: ?Cache.Path = null;
325324 var result_error_bundle = std.zig.ErrorBundle.empty;
326325
327 const stdout = poller.fifo(.stdout);
328
329 poll: while (true) {
330 while (stdout.readableLength() < @sizeOf(Header)) {
331 if (!(try poller.poll())) break :poll;
332 }
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);
326 while (true) {
327 receiveWasmMessage(arena, context, poller.reader(.stdout), &result, &result_error_bundle) catch |err| switch (err) {
328 error.EndOfStream => break,
329 error.ReadFailed => if (!(try poller.poll())) break,
330 else => |e| return e,
331 };
379332 }
380333
381 const stderr = poller.fifo(.stderr);
382 if (stderr.readableLength() > 0) {
383 const owned_stderr = try stderr.toOwnedSlice();
384 defer gpa.free(owned_stderr);
385 std.debug.print("{s}", .{owned_stderr});
334 if (poller.reader(.stderr).buffer.len > 0) {
335 std.debug.print("{s}", .{poller.reader(.stderr).bufferContents()});
386336 }
387337
388338 // Send EOF to stdin.
......@@ -426,6 +376,53 @@ fn buildWasmBinary(
426376 };
427377}
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
429426fn sendMessage(file: std.fs.File, tag: std.zig.Client.Message.Tag) !void {
430427 const header: std.zig.Client.Message.Header = .{
431428 .tag = tag,
lib/std/fs/File.zig+29
......@@ -1341,14 +1341,43 @@ pub const Writer = struct {
13411341 }
13421342};
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.
13441348pub fn reader(file: File) Reader {
13451349 return .{ .file = file };
13461350}
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.
13481367pub fn writer(file: File) Writer {
13491368 return .{ .file = file };
13501369}
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
13521381const range_off: windows.LARGE_INTEGER = 0;
13531382const 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
4646pub const tty = @import("io/tty.zig");
4747
4848pub fn poll(
49 allocator: Allocator,
49 gpa: Allocator,
5050 comptime StreamEnum: type,
5151 files: PollFiles(StreamEnum),
5252) Poller(StreamEnum) {
5353 const enum_fields = @typeInfo(StreamEnum).@"enum".fields;
54 var result: Poller(StreamEnum) = undefined;
55
56 if (is_windows) result.windows = .{
57 .first_read_done = false,
58 .overlapped = [1]windows.OVERLAPPED{
59 mem.zeroes(windows.OVERLAPPED),
60 } ** enum_fields.len,
61 .small_bufs = undefined,
62 .active = .{
63 .count = 0,
64 .handles_buf = undefined,
65 .stream_map = undefined,
66 },
54 var result: Poller(StreamEnum) = .{
55 .gpa = gpa,
56 .readers = undefined,
57 .poll_fds = undefined,
58 .windows = if (is_windows) .{
59 .first_read_done = false,
60 .overlapped = [1]windows.OVERLAPPED{
61 mem.zeroes(windows.OVERLAPPED),
62 } ** enum_fields.len,
63 .small_bufs = undefined,
64 .active = .{
65 .count = 0,
66 .handles_buf = undefined,
67 .stream_map = undefined,
68 },
69 } else {},
6770 };
6871
69 inline for (0..enum_fields.len) |i| {
70 result.fifos[i] = .{
71 .allocator = allocator,
72 .buf = &.{},
73 .head = 0,
74 .count = 0,
72 inline for (enum_fields, 0..) |field, i| {
73 result.readers[i] = .{
74 .unbuffered_reader = .failing,
75 .buffer = &.{},
76 .end = 0,
77 .seek = 0,
7578 };
7679 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;
7881 } else {
7982 result.poll_fds[i] = .{
80 .fd = @field(files, enum_fields[i].name).handle,
83 .fd = @field(files, field.name).handle,
8184 .events = posix.POLL.IN,
8285 .revents = undefined,
8386 };
8487 }
8588 }
89
8690 return result;
8791}
8892
89pub const PollFifo = std.fifo.LinearFifo(u8, .Dynamic);
90
9193pub fn Poller(comptime StreamEnum: type) type {
9294 return struct {
9395 const enum_fields = @typeInfo(StreamEnum).@"enum".fields;
9496 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,
97100 poll_fds: [enum_fields.len]PollFd,
98101 windows: if (is_windows) struct {
99102 first_read_done: bool,
......@@ -105,7 +108,7 @@ pub fn Poller(comptime StreamEnum: type) type {
105108 stream_map: [enum_fields.len]StreamEnum,
106109
107110 pub fn removeAt(self: *@This(), index: u32) void {
108 std.debug.assert(index < self.count);
111 assert(index < self.count);
109112 for (index + 1..self.count) |i| {
110113 self.handles_buf[i - 1] = self.handles_buf[i];
111114 self.stream_map[i - 1] = self.stream_map[i];
......@@ -118,13 +121,14 @@ pub fn Poller(comptime StreamEnum: type) type {
118121 const Self = @This();
119122
120123 pub fn deinit(self: *Self) void {
124 const gpa = self.gpa;
121125 if (is_windows) {
122126 // cancel any pending IO to prevent clobbering OVERLAPPED value
123127 for (self.windows.active.handles_buf[0..self.windows.active.count]) |h| {
124128 _ = windows.kernel32.CancelIo(h);
125129 }
126130 }
127 inline for (&self.fifos) |*q| q.deinit();
131 inline for (&self.readers) |*br| gpa.free(br.buffer);
128132 self.* = undefined;
129133 }
130134
......@@ -144,8 +148,8 @@ pub fn Poller(comptime StreamEnum: type) type {
144148 }
145149 }
146150
147 pub inline fn fifo(self: *Self, comptime which: StreamEnum) *PollFifo {
148 return &self.fifos[@intFromEnum(which)];
151 pub inline fn reader(self: *Self, comptime which: StreamEnum) *BufferedReader {
152 return &self.readers[@intFromEnum(which)];
149153 }
150154
151155 fn pollWindows(self: *Self, nanoseconds: ?u64) !bool {
......@@ -236,6 +240,7 @@ pub fn Poller(comptime StreamEnum: type) type {
236240 }
237241
238242 fn pollPosix(self: *Self, nanoseconds: ?u64) !bool {
243 const gpa = self.gpa;
239244 // We ask for ensureUnusedCapacity with this much extra space. This
240245 // has more of an effect on small reads because once the reads
241246 // start to get larger the amount of space an ArrayList will
......@@ -255,18 +260,18 @@ pub fn Poller(comptime StreamEnum: type) type {
255260 }
256261
257262 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| {
259264 // Try reading whatever is available before checking the error
260265 // conditions.
261266 // It's still possible to read after a POLL.HUP is received,
262267 // always check if there's some data waiting to be read first.
263268 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);
265270 const amt = posix.read(poll_fd.fd, buf) catch |err| switch (err) {
266271 error.BrokenPipe => 0, // Handle the same as EOF.
267272 else => |e| return e,
268273 };
269 q.update(amt);
274 br.advanceBufferEnd(amt);
270275 if (amt == 0) {
271276 // Remove the fd when the EOF condition is met.
272277 poll_fd.fd = -1;
......@@ -297,14 +302,14 @@ var win_dummy_bytes_read: u32 = undefined;
297302fn windowsAsyncReadToFifoAndQueueSmallRead(
298303 handle: windows.HANDLE,
299304 overlapped: *windows.OVERLAPPED,
300 fifo: *PollFifo,
305 br: *BufferedReader,
301306 small_buf: *[128]u8,
302307 bump_amt: usize,
303308) !enum { empty, populated, closed_populated, closed } {
304309 var read_any_data = false;
305310 while (true) {
306311 const fifo_read_pending = while (true) {
307 const buf = try fifo.writableWithSize(bump_amt);
312 const buf = try br.writableWithSize(bump_amt);
308313 const buf_len = math.cast(u32, buf.len) orelse math.maxInt(u32);
309314
310315 if (0 == windows.kernel32.ReadFile(
......@@ -326,7 +331,7 @@ fn windowsAsyncReadToFifoAndQueueSmallRead(
326331 };
327332
328333 read_any_data = true;
329 fifo.update(num_bytes_read);
334 br.update(num_bytes_read);
330335
331336 if (num_bytes_read == buf_len) {
332337 // We filled the buffer, so there's probably more data available.
......@@ -356,7 +361,7 @@ fn windowsAsyncReadToFifoAndQueueSmallRead(
356361 .aborted => break :cancel_read,
357362 };
358363 read_any_data = true;
359 fifo.update(num_bytes_read);
364 br.update(num_bytes_read);
360365 }
361366
362367 // Try to queue the 1-byte read.
......@@ -381,7 +386,7 @@ fn windowsAsyncReadToFifoAndQueueSmallRead(
381386 .closed => return if (read_any_data) .closed_populated else .closed,
382387 .aborted => unreachable,
383388 };
384 try fifo.write(small_buf[0..num_bytes_read]);
389 try br.write(small_buf[0..num_bytes_read]);
385390 read_any_data = true;
386391 }
387392}
lib/std/io/BufferedReader.zig+151-5
......@@ -6,6 +6,7 @@ const assert = std.debug.assert;
66const testing = std.testing;
77const BufferedWriter = std.io.BufferedWriter;
88const Reader = std.io.Reader;
9const Allocator = std.mem.Allocator;
910
1011const BufferedReader = @This();
1112
......@@ -46,13 +47,17 @@ pub fn reader(br: *BufferedReader) Reader {
4647 };
4748}
4849
50pub fn readVec(br: *BufferedReader, data: []const []u8) Reader.Error!usize {
51 return passthruReadVec(br, data);
52}
53
4954fn passthruRead(ctx: ?*anyopaque, bw: *BufferedWriter, limit: Reader.Limit) Reader.RwError!usize {
5055 const br: *BufferedReader = @alignCast(@ptrCast(ctx));
5156 const buffer = br.buffer[0..br.end];
5257 const buffered = buffer[br.seek..];
5358 const limited = buffered[0..limit.min(buffered.len)];
5459 if (limited.len > 0) {
55 const n = try bw.writeSplat(limited, 1);
60 const n = try bw.write(limited);
5661 br.seek += n;
5762 return n;
5863 }
......@@ -61,9 +66,44 @@ fn passthruRead(ctx: ?*anyopaque, bw: *BufferedWriter, limit: Reader.Limit) Read
6166
6267fn passthruReadVec(ctx: ?*anyopaque, data: []const []u8) Reader.Error!usize {
6368 const br: *BufferedReader = @alignCast(@ptrCast(ctx));
64 _ = br;
65 _ = data;
66 @panic("TODO");
69 var total: usize = 0;
70 for (data, 0..) |buf, i| {
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;
67107}
68108
69109pub fn seekBy(br: *BufferedReader, seek_by: i64) !void {
......@@ -147,7 +187,7 @@ pub fn take(br: *BufferedReader, n: usize) Reader.Error![]u8 {
147187}
148188
149189/// 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.
151191///
152192/// Asserts that the `BufferedReader` was initialized with a buffer capacity at
153193/// least as big as `n`.
......@@ -161,6 +201,22 @@ pub fn takeArray(br: *BufferedReader, comptime n: usize) Reader.Error!*[n]u8 {
161201 return (try br.take(n))[0..n];
162202}
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
164220/// Skips the next `n` bytes from the stream, advancing the seek position.
165221///
166222/// 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 {
255311 @panic("TODO");
256312}
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
258339pub const DelimiterInclusiveError = error{
259340 /// See the `Reader` implementation for detailed diagnostics.
260341 ReadFailed,
......@@ -498,12 +579,29 @@ pub fn takeVarInt(br: *BufferedReader, comptime Int: type, endian: std.builtin.E
498579}
499580
500581/// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`.
582///
583/// Advances the seek position.
584///
585/// See also:
586/// * `peekStruct`
501587pub fn takeStruct(br: *BufferedReader, comptime T: type) Reader.Error!*align(1) T {
502588 // Only extern and packed structs have defined in-memory layout.
503589 comptime assert(@typeInfo(T).@"struct".layout != .auto);
504590 return @ptrCast(try br.takeArray(@sizeOf(T)));
505591}
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
507605/// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`.
508606///
509607/// 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
514612 return res;
515613}
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
517625/// Reads an integer with the same size as the given enum's tag type. If the
518626/// integer matches an enum tag, casts the integer to the enum tag and returns
519627/// it. Otherwise, returns `error.InvalidEnumTag`.
......@@ -536,6 +644,28 @@ pub fn takeLeb128(br: *BufferedReader, comptime Result: type) TakeLeb128Error!Re
536644 } }))) orelse error.Overflow;
537645}
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
539669fn takeMultipleOf7Leb128(br: *BufferedReader, comptime Result: type) TakeLeb128Error!Result {
540670 const result_info = @typeInfo(Result).int;
541671 comptime assert(result_info.bits % 7 == 0);
......@@ -599,6 +729,10 @@ test takeArray {
599729 return error.Unimplemented;
600730}
601731
732test peekArray {
733 return error.Unimplemented;
734}
735
602736test discard {
603737 var br: BufferedReader = undefined;
604738 br.initFixed("foobar");
......@@ -684,10 +818,18 @@ test takeStruct {
684818 return error.Unimplemented;
685819}
686820
821test peekStruct {
822 return error.Unimplemented;
823}
824
687825test takeStructEndian {
688826 return error.Unimplemented;
689827}
690828
829test peekStructEndian {
830 return error.Unimplemented;
831}
832
691833test takeEnum {
692834 return error.Unimplemented;
693835}
......@@ -699,3 +841,7 @@ test takeLeb128 {
699841test readShort {
700842 return error.Unimplemented;
701843}
844
845test readVec {
846 return error.Unimplemented;
847}