authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-10 16:57:41-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-10 16:57:41-07:00
logaa81c2df7c35be629e3dd4e8ea02b42b1cb027ac
tree03e4400e69072f8a8382427563488a74c7a2fc16
parent6e6c68d88909f008afc706949effe38c470a055d
parente25541549852c8dcf4acbcc1a3f3d7ef4bcef9d7

Merge remote-tracking branch 'origin/fixes' into wrangle-writer-buffering


25 files changed, 5701 insertions(+), 5684 deletions(-)

CMakeLists.txt+12-3
...@@ -387,6 +387,18 @@ set(ZIG_STAGE2_SOURCES...@@ -387,6 +387,18 @@ set(ZIG_STAGE2_SOURCES
387 lib/std/Build.zig387 lib/std/Build.zig
388 lib/std/Build/Cache.zig388 lib/std/Build/Cache.zig
389 lib/std/Build/Cache/DepTokenizer.zig389 lib/std/Build/Cache/DepTokenizer.zig
390 lib/std/Io.zig
391 lib/std/Io/Reader.zig
392 lib/std/Io/Writer.zig
393 lib/std/Io/buffered_atomic_file.zig
394 lib/std/Io/buffered_writer.zig
395 lib/std/Io/change_detection_stream.zig
396 lib/std/Io/counting_reader.zig
397 lib/std/Io/counting_writer.zig
398 lib/std/Io/find_byte_writer.zig
399 lib/std/Io/fixed_buffer_stream.zig
400 lib/std/Io/limited_reader.zig
401 lib/std/Io/seekable_stream.zig
390 lib/std/Progress.zig402 lib/std/Progress.zig
391 lib/std/Random.zig403 lib/std/Random.zig
392 lib/std/Target.zig404 lib/std/Target.zig
...@@ -448,9 +460,6 @@ set(ZIG_STAGE2_SOURCES...@@ -448,9 +460,6 @@ set(ZIG_STAGE2_SOURCES
448 lib/std/hash_map.zig460 lib/std/hash_map.zig
449 lib/std/heap.zig461 lib/std/heap.zig
450 lib/std/heap/arena_allocator.zig462 lib/std/heap/arena_allocator.zig
451 lib/std/io.zig
452 lib/std/io/Reader.zig
453 lib/std/io/Writer.zig
454 lib/std/json.zig463 lib/std/json.zig
455 lib/std/leb128.zig464 lib/std/leb128.zig
456 lib/std/log.zig465 lib/std/log.zig
lib/std/Io.zig created+499
...@@ -0,0 +1,499 @@
1const builtin = @import("builtin");
2const is_windows = builtin.os.tag == .windows;
3
4const std = @import("std.zig");
5const windows = std.os.windows;
6const posix = std.posix;
7const math = std.math;
8const assert = std.debug.assert;
9const Allocator = std.mem.Allocator;
10const Alignment = std.mem.Alignment;
11
12pub const Limit = enum(usize) {
13 nothing = 0,
14 unlimited = std.math.maxInt(usize),
15 _,
16
17 /// `std.math.maxInt(usize)` is interpreted to mean `.unlimited`.
18 pub fn limited(n: usize) Limit {
19 return @enumFromInt(n);
20 }
21
22 /// Any value grater than `std.math.maxInt(usize)` is interpreted to mean
23 /// `.unlimited`.
24 pub fn limited64(n: u64) Limit {
25 return @enumFromInt(@min(n, std.math.maxInt(usize)));
26 }
27
28 pub fn countVec(data: []const []const u8) Limit {
29 var total: usize = 0;
30 for (data) |d| total += d.len;
31 return .limited(total);
32 }
33
34 pub fn min(a: Limit, b: Limit) Limit {
35 return @enumFromInt(@min(@intFromEnum(a), @intFromEnum(b)));
36 }
37
38 pub fn minInt(l: Limit, n: usize) usize {
39 return @min(n, @intFromEnum(l));
40 }
41
42 pub fn minInt64(l: Limit, n: u64) usize {
43 return @min(n, @intFromEnum(l));
44 }
45
46 pub fn slice(l: Limit, s: []u8) []u8 {
47 return s[0..l.minInt(s.len)];
48 }
49
50 pub fn sliceConst(l: Limit, s: []const u8) []const u8 {
51 return s[0..l.minInt(s.len)];
52 }
53
54 pub fn toInt(l: Limit) ?usize {
55 return switch (l) {
56 else => @intFromEnum(l),
57 .unlimited => null,
58 };
59 }
60
61 /// Reduces a slice to account for the limit, leaving room for one extra
62 /// byte above the limit, allowing for the use case of differentiating
63 /// between end-of-stream and reaching the limit.
64 pub fn slice1(l: Limit, non_empty_buffer: []u8) []u8 {
65 assert(non_empty_buffer.len >= 1);
66 return non_empty_buffer[0..@min(@intFromEnum(l) +| 1, non_empty_buffer.len)];
67 }
68
69 pub fn nonzero(l: Limit) bool {
70 return @intFromEnum(l) > 0;
71 }
72
73 /// Return a new limit reduced by `amount` or return `null` indicating
74 /// limit would be exceeded.
75 pub fn subtract(l: Limit, amount: usize) ?Limit {
76 if (l == .unlimited) return .unlimited;
77 if (amount > @intFromEnum(l)) return null;
78 return @enumFromInt(@intFromEnum(l) - amount);
79 }
80};
81
82pub const Reader = @import("Io/Reader.zig");
83pub const Writer = @import("Io/Writer.zig");
84
85pub const ChangeDetectionStream = @import("Io/change_detection_stream.zig").ChangeDetectionStream;
86pub const changeDetectionStream = @import("Io/change_detection_stream.zig").changeDetectionStream;
87
88pub const tty = @import("Io/tty.zig");
89
90pub fn poll(
91 gpa: Allocator,
92 comptime StreamEnum: type,
93 files: PollFiles(StreamEnum),
94) Poller(StreamEnum) {
95 const enum_fields = @typeInfo(StreamEnum).@"enum".fields;
96 var result: Poller(StreamEnum) = .{
97 .gpa = gpa,
98 .readers = @splat(.{
99 .unbuffered_reader = .failing,
100 .buffer = &.{},
101 .end = 0,
102 .seek = 0,
103 }),
104 .poll_fds = undefined,
105 .windows = if (is_windows) .{
106 .first_read_done = false,
107 .overlapped = [1]windows.OVERLAPPED{
108 std.mem.zeroes(windows.OVERLAPPED),
109 } ** enum_fields.len,
110 .small_bufs = undefined,
111 .active = .{
112 .count = 0,
113 .handles_buf = undefined,
114 .stream_map = undefined,
115 },
116 } else {},
117 };
118
119 inline for (enum_fields, 0..) |field, i| {
120 if (is_windows) {
121 result.windows.active.handles_buf[i] = @field(files, field.name).handle;
122 } else {
123 result.poll_fds[i] = .{
124 .fd = @field(files, field.name).handle,
125 .events = posix.POLL.IN,
126 .revents = undefined,
127 };
128 }
129 }
130
131 return result;
132}
133
134pub fn Poller(comptime StreamEnum: type) type {
135 return struct {
136 const enum_fields = @typeInfo(StreamEnum).@"enum".fields;
137 const PollFd = if (is_windows) void else posix.pollfd;
138
139 gpa: Allocator,
140 readers: [enum_fields.len]Reader,
141 poll_fds: [enum_fields.len]PollFd,
142 windows: if (is_windows) struct {
143 first_read_done: bool,
144 overlapped: [enum_fields.len]windows.OVERLAPPED,
145 small_bufs: [enum_fields.len][128]u8,
146 active: struct {
147 count: math.IntFittingRange(0, enum_fields.len),
148 handles_buf: [enum_fields.len]windows.HANDLE,
149 stream_map: [enum_fields.len]StreamEnum,
150
151 pub fn removeAt(self: *@This(), index: u32) void {
152 assert(index < self.count);
153 for (index + 1..self.count) |i| {
154 self.handles_buf[i - 1] = self.handles_buf[i];
155 self.stream_map[i - 1] = self.stream_map[i];
156 }
157 self.count -= 1;
158 }
159 },
160 } else void,
161
162 const Self = @This();
163
164 pub fn deinit(self: *Self) void {
165 const gpa = self.gpa;
166 if (is_windows) {
167 // cancel any pending IO to prevent clobbering OVERLAPPED value
168 for (self.windows.active.handles_buf[0..self.windows.active.count]) |h| {
169 _ = windows.kernel32.CancelIo(h);
170 }
171 }
172 inline for (&self.readers) |*r| gpa.free(r.buffer);
173 self.* = undefined;
174 }
175
176 pub fn poll(self: *Self) !bool {
177 if (is_windows) {
178 return pollWindows(self, null);
179 } else {
180 return pollPosix(self, null);
181 }
182 }
183
184 pub fn pollTimeout(self: *Self, nanoseconds: u64) !bool {
185 if (is_windows) {
186 return pollWindows(self, nanoseconds);
187 } else {
188 return pollPosix(self, nanoseconds);
189 }
190 }
191
192 pub inline fn reader(self: *Self, comptime which: StreamEnum) *Reader {
193 return &self.readers[@intFromEnum(which)];
194 }
195
196 fn pollWindows(self: *Self, nanoseconds: ?u64) !bool {
197 const bump_amt = 512;
198
199 if (!self.windows.first_read_done) {
200 var already_read_data = false;
201 for (0..enum_fields.len) |i| {
202 const handle = self.windows.active.handles_buf[i];
203 switch (try windowsAsyncReadToFifoAndQueueSmallRead(
204 handle,
205 &self.windows.overlapped[i],
206 &self.fifos[i],
207 &self.windows.small_bufs[i],
208 bump_amt,
209 )) {
210 .populated, .empty => |state| {
211 if (state == .populated) already_read_data = true;
212 self.windows.active.handles_buf[self.windows.active.count] = handle;
213 self.windows.active.stream_map[self.windows.active.count] = @as(StreamEnum, @enumFromInt(i));
214 self.windows.active.count += 1;
215 },
216 .closed => {}, // don't add to the wait_objects list
217 .closed_populated => {
218 // don't add to the wait_objects list, but we did already get data
219 already_read_data = true;
220 },
221 }
222 }
223 self.windows.first_read_done = true;
224 if (already_read_data) return true;
225 }
226
227 while (true) {
228 if (self.windows.active.count == 0) return false;
229
230 const status = windows.kernel32.WaitForMultipleObjects(
231 self.windows.active.count,
232 &self.windows.active.handles_buf,
233 0,
234 if (nanoseconds) |ns|
235 @min(std.math.cast(u32, ns / std.time.ns_per_ms) orelse (windows.INFINITE - 1), windows.INFINITE - 1)
236 else
237 windows.INFINITE,
238 );
239 if (status == windows.WAIT_FAILED)
240 return windows.unexpectedError(windows.GetLastError());
241 if (status == windows.WAIT_TIMEOUT)
242 return true;
243
244 if (status < windows.WAIT_OBJECT_0 or status > windows.WAIT_OBJECT_0 + enum_fields.len - 1)
245 unreachable;
246
247 const active_idx = status - windows.WAIT_OBJECT_0;
248
249 const stream_idx = @intFromEnum(self.windows.active.stream_map[active_idx]);
250 const handle = self.windows.active.handles_buf[active_idx];
251
252 const overlapped = &self.windows.overlapped[stream_idx];
253 const stream_fifo = &self.fifos[stream_idx];
254 const small_buf = &self.windows.small_bufs[stream_idx];
255
256 const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) {
257 .success => |n| n,
258 .closed => {
259 self.windows.active.removeAt(active_idx);
260 continue;
261 },
262 .aborted => unreachable,
263 };
264 try stream_fifo.write(small_buf[0..num_bytes_read]);
265
266 switch (try windowsAsyncReadToFifoAndQueueSmallRead(
267 handle,
268 overlapped,
269 stream_fifo,
270 small_buf,
271 bump_amt,
272 )) {
273 .empty => {}, // irrelevant, we already got data from the small buffer
274 .populated => {},
275 .closed,
276 .closed_populated, // identical, since we already got data from the small buffer
277 => self.windows.active.removeAt(active_idx),
278 }
279 return true;
280 }
281 }
282
283 fn pollPosix(self: *Self, nanoseconds: ?u64) !bool {
284 const gpa = self.gpa;
285 // We ask for ensureUnusedCapacity with this much extra space. This
286 // has more of an effect on small reads because once the reads
287 // start to get larger the amount of space an ArrayList will
288 // allocate grows exponentially.
289 const bump_amt = 512;
290
291 const err_mask = posix.POLL.ERR | posix.POLL.NVAL | posix.POLL.HUP;
292
293 const events_len = try posix.poll(&self.poll_fds, if (nanoseconds) |ns|
294 std.math.cast(i32, ns / std.time.ns_per_ms) orelse std.math.maxInt(i32)
295 else
296 -1);
297 if (events_len == 0) {
298 for (self.poll_fds) |poll_fd| {
299 if (poll_fd.fd != -1) return true;
300 } else return false;
301 }
302
303 var keep_polling = false;
304 inline for (&self.poll_fds, &self.readers) |*poll_fd, *r| {
305 // Try reading whatever is available before checking the error
306 // conditions.
307 // It's still possible to read after a POLL.HUP is received,
308 // always check if there's some data waiting to be read first.
309 if (poll_fd.revents & posix.POLL.IN != 0) {
310 const buf = try r.writableSliceGreedyAlloc(gpa, bump_amt);
311 const amt = posix.read(poll_fd.fd, buf) catch |err| switch (err) {
312 error.BrokenPipe => 0, // Handle the same as EOF.
313 else => |e| return e,
314 };
315 r.advanceBufferEnd(amt);
316 if (amt == 0) {
317 // Remove the fd when the EOF condition is met.
318 poll_fd.fd = -1;
319 } else {
320 keep_polling = true;
321 }
322 } else if (poll_fd.revents & err_mask != 0) {
323 // Exclude the fds that signaled an error.
324 poll_fd.fd = -1;
325 } else if (poll_fd.fd != -1) {
326 keep_polling = true;
327 }
328 }
329 return keep_polling;
330 }
331 };
332}
333
334/// The `ReadFile` docuementation states that `lpNumberOfBytesRead` does not have a meaningful
335/// result when using overlapped I/O, but also that it cannot be `null` on Windows 7. For
336/// compatibility, we point it to this dummy variables, which we never otherwise access.
337/// See: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile
338var win_dummy_bytes_read: u32 = undefined;
339
340/// Read as much data as possible from `handle` with `overlapped`, and write it to the FIFO. Before
341/// returning, queue a read into `small_buf` so that `WaitForMultipleObjects` returns when more data
342/// is available. `handle` must have no pending asynchronous operation.
343fn windowsAsyncReadToFifoAndQueueSmallRead(
344 handle: windows.HANDLE,
345 overlapped: *windows.OVERLAPPED,
346 r: *Reader,
347 small_buf: *[128]u8,
348 bump_amt: usize,
349) !enum { empty, populated, closed_populated, closed } {
350 var read_any_data = false;
351 while (true) {
352 const fifo_read_pending = while (true) {
353 const buf = try r.writableWithSize(bump_amt);
354 const buf_len = math.cast(u32, buf.len) orelse math.maxInt(u32);
355
356 if (0 == windows.kernel32.ReadFile(
357 handle,
358 buf.ptr,
359 buf_len,
360 &win_dummy_bytes_read,
361 overlapped,
362 )) switch (windows.GetLastError()) {
363 .IO_PENDING => break true,
364 .BROKEN_PIPE => return if (read_any_data) .closed_populated else .closed,
365 else => |err| return windows.unexpectedError(err),
366 };
367
368 const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) {
369 .success => |n| n,
370 .closed => return if (read_any_data) .closed_populated else .closed,
371 .aborted => unreachable,
372 };
373
374 read_any_data = true;
375 r.update(num_bytes_read);
376
377 if (num_bytes_read == buf_len) {
378 // We filled the buffer, so there's probably more data available.
379 continue;
380 } else {
381 // We didn't fill the buffer, so assume we're out of data.
382 // There is no pending read.
383 break false;
384 }
385 };
386
387 if (fifo_read_pending) cancel_read: {
388 // Cancel the pending read into the FIFO.
389 _ = windows.kernel32.CancelIo(handle);
390
391 // We have to wait for the handle to be signalled, i.e. for the cancellation to complete.
392 switch (windows.kernel32.WaitForSingleObject(handle, windows.INFINITE)) {
393 windows.WAIT_OBJECT_0 => {},
394 windows.WAIT_FAILED => return windows.unexpectedError(windows.GetLastError()),
395 else => unreachable,
396 }
397
398 // If it completed before we canceled, make sure to tell the FIFO!
399 const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, true)) {
400 .success => |n| n,
401 .closed => return if (read_any_data) .closed_populated else .closed,
402 .aborted => break :cancel_read,
403 };
404 read_any_data = true;
405 r.update(num_bytes_read);
406 }
407
408 // Try to queue the 1-byte read.
409 if (0 == windows.kernel32.ReadFile(
410 handle,
411 small_buf,
412 small_buf.len,
413 &win_dummy_bytes_read,
414 overlapped,
415 )) switch (windows.GetLastError()) {
416 .IO_PENDING => {
417 // 1-byte read pending as intended
418 return if (read_any_data) .populated else .empty;
419 },
420 .BROKEN_PIPE => return if (read_any_data) .closed_populated else .closed,
421 else => |err| return windows.unexpectedError(err),
422 };
423
424 // We got data back this time. Write it to the FIFO and run the main loop again.
425 const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) {
426 .success => |n| n,
427 .closed => return if (read_any_data) .closed_populated else .closed,
428 .aborted => unreachable,
429 };
430 try r.write(small_buf[0..num_bytes_read]);
431 read_any_data = true;
432 }
433}
434
435/// Simple wrapper around `GetOverlappedResult` to determine the result of a `ReadFile` operation.
436/// If `!allow_aborted`, then `aborted` is never returned (`OPERATION_ABORTED` is considered unexpected).
437///
438/// The `ReadFile` documentation states that the number of bytes read by an overlapped `ReadFile` must be determined using `GetOverlappedResult`, even if the
439/// operation immediately returns data:
440/// "Use NULL for [lpNumberOfBytesRead] if this is an asynchronous operation to avoid potentially
441/// erroneous results."
442/// "If `hFile` was opened with `FILE_FLAG_OVERLAPPED`, the following conditions are in effect: [...]
443/// The lpNumberOfBytesRead parameter should be set to NULL. Use the GetOverlappedResult function to
444/// get the actual number of bytes read."
445/// See: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile
446fn windowsGetReadResult(
447 handle: windows.HANDLE,
448 overlapped: *windows.OVERLAPPED,
449 allow_aborted: bool,
450) !union(enum) {
451 success: u32,
452 closed,
453 aborted,
454} {
455 var num_bytes_read: u32 = undefined;
456 if (0 == windows.kernel32.GetOverlappedResult(
457 handle,
458 overlapped,
459 &num_bytes_read,
460 0,
461 )) switch (windows.GetLastError()) {
462 .BROKEN_PIPE => return .closed,
463 .OPERATION_ABORTED => |err| if (allow_aborted) {
464 return .aborted;
465 } else {
466 return windows.unexpectedError(err);
467 },
468 else => |err| return windows.unexpectedError(err),
469 };
470 return .{ .success = num_bytes_read };
471}
472
473/// Given an enum, returns a struct with fields of that enum, each field
474/// representing an I/O stream for polling.
475pub fn PollFiles(comptime StreamEnum: type) type {
476 const enum_fields = @typeInfo(StreamEnum).@"enum".fields;
477 var struct_fields: [enum_fields.len]std.builtin.Type.StructField = undefined;
478 for (&struct_fields, enum_fields) |*struct_field, enum_field| {
479 struct_field.* = .{
480 .name = enum_field.name,
481 .type = std.fs.File,
482 .default_value_ptr = null,
483 .is_comptime = false,
484 .alignment = @alignOf(std.fs.File),
485 };
486 }
487 return @Type(.{ .@"struct" = .{
488 .layout = .auto,
489 .fields = &struct_fields,
490 .decls = &.{},
491 .is_tuple = false,
492 } });
493}
494
495test {
496 _ = Reader;
497 _ = Writer;
498 _ = @import("Io/test.zig");
499}
lib/std/Io/DeprecatedReader.zig created+386
...@@ -0,0 +1,386 @@
1context: *const anyopaque,
2readFn: *const fn (context: *const anyopaque, buffer: []u8) anyerror!usize,
3
4pub const Error = anyerror;
5
6/// Returns the number of bytes read. It may be less than buffer.len.
7/// If the number of bytes read is 0, it means end of stream.
8/// End of stream is not an error condition.
9pub fn read(self: Self, buffer: []u8) anyerror!usize {
10 return self.readFn(self.context, buffer);
11}
12
13/// Returns the number of bytes read. If the number read is smaller than `buffer.len`, it
14/// means the stream reached the end. Reaching the end of a stream is not an error
15/// condition.
16pub fn readAll(self: Self, buffer: []u8) anyerror!usize {
17 return readAtLeast(self, buffer, buffer.len);
18}
19
20/// Returns the number of bytes read, calling the underlying read
21/// function the minimal number of times until the buffer has at least
22/// `len` bytes filled. If the number read is less than `len` it means
23/// the stream reached the end. Reaching the end of the stream is not
24/// an error condition.
25pub fn readAtLeast(self: Self, buffer: []u8, len: usize) anyerror!usize {
26 assert(len <= buffer.len);
27 var index: usize = 0;
28 while (index < len) {
29 const amt = try self.read(buffer[index..]);
30 if (amt == 0) break;
31 index += amt;
32 }
33 return index;
34}
35
36/// If the number read would be smaller than `buf.len`, `error.EndOfStream` is returned instead.
37pub fn readNoEof(self: Self, buf: []u8) anyerror!void {
38 const amt_read = try self.readAll(buf);
39 if (amt_read < buf.len) return error.EndOfStream;
40}
41
42/// Appends to the `std.ArrayList` contents by reading from the stream
43/// until end of stream is found.
44/// If the number of bytes appended would exceed `max_append_size`,
45/// `error.StreamTooLong` is returned
46/// and the `std.ArrayList` has exactly `max_append_size` bytes appended.
47pub fn readAllArrayList(
48 self: Self,
49 array_list: *std.ArrayList(u8),
50 max_append_size: usize,
51) anyerror!void {
52 return self.readAllArrayListAligned(null, array_list, max_append_size);
53}
54
55pub fn readAllArrayListAligned(
56 self: Self,
57 comptime alignment: ?Alignment,
58 array_list: *std.ArrayListAligned(u8, alignment),
59 max_append_size: usize,
60) anyerror!void {
61 try array_list.ensureTotalCapacity(@min(max_append_size, 4096));
62 const original_len = array_list.items.len;
63 var start_index: usize = original_len;
64 while (true) {
65 array_list.expandToCapacity();
66 const dest_slice = array_list.items[start_index..];
67 const bytes_read = try self.readAll(dest_slice);
68 start_index += bytes_read;
69
70 if (start_index - original_len > max_append_size) {
71 array_list.shrinkAndFree(original_len + max_append_size);
72 return error.StreamTooLong;
73 }
74
75 if (bytes_read != dest_slice.len) {
76 array_list.shrinkAndFree(start_index);
77 return;
78 }
79
80 // This will trigger ArrayList to expand superlinearly at whatever its growth rate is.
81 try array_list.ensureTotalCapacity(start_index + 1);
82 }
83}
84
85/// Allocates enough memory to hold all the contents of the stream. If the allocated
86/// memory would be greater than `max_size`, returns `error.StreamTooLong`.
87/// Caller owns returned memory.
88/// If this function returns an error, the contents from the stream read so far are lost.
89pub fn readAllAlloc(self: Self, allocator: mem.Allocator, max_size: usize) anyerror![]u8 {
90 var array_list = std.ArrayList(u8).init(allocator);
91 defer array_list.deinit();
92 try self.readAllArrayList(&array_list, max_size);
93 return try array_list.toOwnedSlice();
94}
95
96/// Deprecated: use `streamUntilDelimiter` with ArrayList's writer instead.
97/// Replaces the `std.ArrayList` contents by reading from the stream until `delimiter` is found.
98/// Does not include the delimiter in the result.
99/// If the `std.ArrayList` length would exceed `max_size`, `error.StreamTooLong` is returned and the
100/// `std.ArrayList` is populated with `max_size` bytes from the stream.
101pub fn readUntilDelimiterArrayList(
102 self: Self,
103 array_list: *std.ArrayList(u8),
104 delimiter: u8,
105 max_size: usize,
106) anyerror!void {
107 array_list.shrinkRetainingCapacity(0);
108 try self.streamUntilDelimiter(array_list.writer(), delimiter, max_size);
109}
110
111/// Deprecated: use `streamUntilDelimiter` with ArrayList's writer instead.
112/// Allocates enough memory to read until `delimiter`. If the allocated
113/// memory would be greater than `max_size`, returns `error.StreamTooLong`.
114/// Caller owns returned memory.
115/// If this function returns an error, the contents from the stream read so far are lost.
116pub fn readUntilDelimiterAlloc(
117 self: Self,
118 allocator: mem.Allocator,
119 delimiter: u8,
120 max_size: usize,
121) anyerror![]u8 {
122 var array_list = std.ArrayList(u8).init(allocator);
123 defer array_list.deinit();
124 try self.streamUntilDelimiter(array_list.writer(), delimiter, max_size);
125 return try array_list.toOwnedSlice();
126}
127
128/// Deprecated: use `streamUntilDelimiter` with FixedBufferStream's writer instead.
129/// Reads from the stream until specified byte is found. If the buffer is not
130/// large enough to hold the entire contents, `error.StreamTooLong` is returned.
131/// If end-of-stream is found, `error.EndOfStream` is returned.
132/// Returns a slice of the stream data, with ptr equal to `buf.ptr`. The
133/// delimiter byte is written to the output buffer but is not included
134/// in the returned slice.
135pub fn readUntilDelimiter(self: Self, buf: []u8, delimiter: u8) anyerror![]u8 {
136 var fbs = std.io.fixedBufferStream(buf);
137 try self.streamUntilDelimiter(fbs.writer(), delimiter, fbs.buffer.len);
138 const output = fbs.getWritten();
139 buf[output.len] = delimiter; // emulating old behaviour
140 return output;
141}
142
143/// Deprecated: use `streamUntilDelimiter` with ArrayList's (or any other's) writer instead.
144/// Allocates enough memory to read until `delimiter` or end-of-stream.
145/// If the allocated memory would be greater than `max_size`, returns
146/// `error.StreamTooLong`. If end-of-stream is found, returns the rest
147/// of the stream. If this function is called again after that, returns
148/// null.
149/// Caller owns returned memory.
150/// If this function returns an error, the contents from the stream read so far are lost.
151pub fn readUntilDelimiterOrEofAlloc(
152 self: Self,
153 allocator: mem.Allocator,
154 delimiter: u8,
155 max_size: usize,
156) anyerror!?[]u8 {
157 var array_list = std.ArrayList(u8).init(allocator);
158 defer array_list.deinit();
159 self.streamUntilDelimiter(array_list.writer(), delimiter, max_size) catch |err| switch (err) {
160 error.EndOfStream => if (array_list.items.len == 0) {
161 return null;
162 },
163 else => |e| return e,
164 };
165 return try array_list.toOwnedSlice();
166}
167
168/// Deprecated: use `streamUntilDelimiter` with FixedBufferStream's writer instead.
169/// Reads from the stream until specified byte is found. If the buffer is not
170/// large enough to hold the entire contents, `error.StreamTooLong` is returned.
171/// If end-of-stream is found, returns the rest of the stream. If this
172/// function is called again after that, returns null.
173/// Returns a slice of the stream data, with ptr equal to `buf.ptr`. The
174/// delimiter byte is written to the output buffer but is not included
175/// in the returned slice.
176pub fn readUntilDelimiterOrEof(self: Self, buf: []u8, delimiter: u8) anyerror!?[]u8 {
177 var fbs = std.io.fixedBufferStream(buf);
178 self.streamUntilDelimiter(fbs.writer(), delimiter, fbs.buffer.len) catch |err| switch (err) {
179 error.EndOfStream => if (fbs.getWritten().len == 0) {
180 return null;
181 },
182
183 else => |e| return e,
184 };
185 const output = fbs.getWritten();
186 buf[output.len] = delimiter; // emulating old behaviour
187 return output;
188}
189
190/// Appends to the `writer` contents by reading from the stream until `delimiter` is found.
191/// Does not write the delimiter itself.
192/// If `optional_max_size` is not null and amount of written bytes exceeds `optional_max_size`,
193/// returns `error.StreamTooLong` and finishes appending.
194/// If `optional_max_size` is null, appending is unbounded.
195pub fn streamUntilDelimiter(
196 self: Self,
197 writer: anytype,
198 delimiter: u8,
199 optional_max_size: ?usize,
200) anyerror!void {
201 if (optional_max_size) |max_size| {
202 for (0..max_size) |_| {
203 const byte: u8 = try self.readByte();
204 if (byte == delimiter) return;
205 try writer.writeByte(byte);
206 }
207 return error.StreamTooLong;
208 } else {
209 while (true) {
210 const byte: u8 = try self.readByte();
211 if (byte == delimiter) return;
212 try writer.writeByte(byte);
213 }
214 // Can not throw `error.StreamTooLong` since there are no boundary.
215 }
216}
217
218/// Reads from the stream until specified byte is found, discarding all data,
219/// including the delimiter.
220/// If end-of-stream is found, this function succeeds.
221pub fn skipUntilDelimiterOrEof(self: Self, delimiter: u8) anyerror!void {
222 while (true) {
223 const byte = self.readByte() catch |err| switch (err) {
224 error.EndOfStream => return,
225 else => |e| return e,
226 };
227 if (byte == delimiter) return;
228 }
229}
230
231/// Reads 1 byte from the stream or returns `error.EndOfStream`.
232pub fn readByte(self: Self) anyerror!u8 {
233 var result: [1]u8 = undefined;
234 const amt_read = try self.read(result[0..]);
235 if (amt_read < 1) return error.EndOfStream;
236 return result[0];
237}
238
239/// Same as `readByte` except the returned byte is signed.
240pub fn readByteSigned(self: Self) anyerror!i8 {
241 return @as(i8, @bitCast(try self.readByte()));
242}
243
244/// Reads exactly `num_bytes` bytes and returns as an array.
245/// `num_bytes` must be comptime-known
246pub fn readBytesNoEof(self: Self, comptime num_bytes: usize) anyerror![num_bytes]u8 {
247 var bytes: [num_bytes]u8 = undefined;
248 try self.readNoEof(&bytes);
249 return bytes;
250}
251
252/// Reads bytes until `bounded.len` is equal to `num_bytes`,
253/// or the stream ends.
254///
255/// * it is assumed that `num_bytes` will not exceed `bounded.capacity()`
256pub fn readIntoBoundedBytes(
257 self: Self,
258 comptime num_bytes: usize,
259 bounded: *std.BoundedArray(u8, num_bytes),
260) anyerror!void {
261 while (bounded.len < num_bytes) {
262 // get at most the number of bytes free in the bounded array
263 const bytes_read = try self.read(bounded.unusedCapacitySlice());
264 if (bytes_read == 0) return;
265
266 // bytes_read will never be larger than @TypeOf(bounded.len)
267 // due to `self.read` being bounded by `bounded.unusedCapacitySlice()`
268 bounded.len += @as(@TypeOf(bounded.len), @intCast(bytes_read));
269 }
270}
271
272/// Reads at most `num_bytes` and returns as a bounded array.
273pub fn readBoundedBytes(self: Self, comptime num_bytes: usize) anyerror!std.BoundedArray(u8, num_bytes) {
274 var result = std.BoundedArray(u8, num_bytes){};
275 try self.readIntoBoundedBytes(num_bytes, &result);
276 return result;
277}
278
279pub inline fn readInt(self: Self, comptime T: type, endian: std.builtin.Endian) anyerror!T {
280 const bytes = try self.readBytesNoEof(@divExact(@typeInfo(T).int.bits, 8));
281 return mem.readInt(T, &bytes, endian);
282}
283
284pub fn readVarInt(
285 self: Self,
286 comptime ReturnType: type,
287 endian: std.builtin.Endian,
288 size: usize,
289) anyerror!ReturnType {
290 assert(size <= @sizeOf(ReturnType));
291 var bytes_buf: [@sizeOf(ReturnType)]u8 = undefined;
292 const bytes = bytes_buf[0..size];
293 try self.readNoEof(bytes);
294 return mem.readVarInt(ReturnType, bytes, endian);
295}
296
297/// Optional parameters for `skipBytes`
298pub const SkipBytesOptions = struct {
299 buf_size: usize = 512,
300};
301
302// `num_bytes` is a `u64` to match `off_t`
303/// Reads `num_bytes` bytes from the stream and discards them
304pub fn skipBytes(self: Self, num_bytes: u64, comptime options: SkipBytesOptions) anyerror!void {
305 var buf: [options.buf_size]u8 = undefined;
306 var remaining = num_bytes;
307
308 while (remaining > 0) {
309 const amt = @min(remaining, options.buf_size);
310 try self.readNoEof(buf[0..amt]);
311 remaining -= amt;
312 }
313}
314
315/// Reads `slice.len` bytes from the stream and returns if they are the same as the passed slice
316pub fn isBytes(self: Self, slice: []const u8) anyerror!bool {
317 var i: usize = 0;
318 var matches = true;
319 while (i < slice.len) : (i += 1) {
320 if (slice[i] != try self.readByte()) {
321 matches = false;
322 }
323 }
324 return matches;
325}
326
327pub fn readStruct(self: Self, comptime T: type) anyerror!T {
328 // Only extern and packed structs have defined in-memory layout.
329 comptime assert(@typeInfo(T).@"struct".layout != .auto);
330 var res: [1]T = undefined;
331 try self.readNoEof(mem.sliceAsBytes(res[0..]));
332 return res[0];
333}
334
335pub fn readStructEndian(self: Self, comptime T: type, endian: std.builtin.Endian) anyerror!T {
336 var res = try self.readStruct(T);
337 if (native_endian != endian) {
338 mem.byteSwapAllFields(T, &res);
339 }
340 return res;
341}
342
343/// Reads an integer with the same size as the given enum's tag type. If the integer matches
344/// an enum tag, casts the integer to the enum tag and returns it. Otherwise, returns an `error.InvalidValue`.
345/// TODO optimization taking advantage of most fields being in order
346pub fn readEnum(self: Self, comptime Enum: type, endian: std.builtin.Endian) anyerror!Enum {
347 const E = error{
348 /// An integer was read, but it did not match any of the tags in the supplied enum.
349 InvalidValue,
350 };
351 const type_info = @typeInfo(Enum).@"enum";
352 const tag = try self.readInt(type_info.tag_type, endian);
353
354 inline for (std.meta.fields(Enum)) |field| {
355 if (tag == field.value) {
356 return @field(Enum, field.name);
357 }
358 }
359
360 return E.InvalidValue;
361}
362
363/// Reads the stream until the end, ignoring all the data.
364/// Returns the number of bytes discarded.
365pub fn discard(self: Self) anyerror!u64 {
366 var trash: [4096]u8 = undefined;
367 var index: u64 = 0;
368 while (true) {
369 const n = try self.read(&trash);
370 if (n == 0) return index;
371 index += n;
372 }
373}
374
375const std = @import("../std.zig");
376const Self = @This();
377const math = std.math;
378const assert = std.debug.assert;
379const mem = std.mem;
380const testing = std.testing;
381const native_endian = @import("builtin").target.cpu.arch.endian();
382const Alignment = std.mem.Alignment;
383
384test {
385 _ = @import("Reader/test.zig");
386}
lib/std/Io/DeprecatedWriter.zig created+109
...@@ -0,0 +1,109 @@
1const std = @import("../std.zig");
2const assert = std.debug.assert;
3const mem = std.mem;
4const native_endian = @import("builtin").target.cpu.arch.endian();
5
6context: *const anyopaque,
7writeFn: *const fn (context: *const anyopaque, bytes: []const u8) anyerror!usize,
8
9const Self = @This();
10pub const Error = anyerror;
11
12pub fn write(self: Self, bytes: []const u8) anyerror!usize {
13 return self.writeFn(self.context, bytes);
14}
15
16pub fn writeAll(self: Self, bytes: []const u8) anyerror!void {
17 var index: usize = 0;
18 while (index != bytes.len) {
19 index += try self.write(bytes[index..]);
20 }
21}
22
23pub fn print(self: Self, comptime format: []const u8, args: anytype) anyerror!void {
24 return std.fmt.format(self, format, args);
25}
26
27pub fn writeByte(self: Self, byte: u8) anyerror!void {
28 const array = [1]u8{byte};
29 return self.writeAll(&array);
30}
31
32pub fn writeByteNTimes(self: Self, byte: u8, n: usize) anyerror!void {
33 var bytes: [256]u8 = undefined;
34 @memset(bytes[0..], byte);
35
36 var remaining: usize = n;
37 while (remaining > 0) {
38 const to_write = @min(remaining, bytes.len);
39 try self.writeAll(bytes[0..to_write]);
40 remaining -= to_write;
41 }
42}
43
44pub fn writeBytesNTimes(self: Self, bytes: []const u8, n: usize) anyerror!void {
45 var i: usize = 0;
46 while (i < n) : (i += 1) {
47 try self.writeAll(bytes);
48 }
49}
50
51pub inline fn writeInt(self: Self, comptime T: type, value: T, endian: std.builtin.Endian) anyerror!void {
52 var bytes: [@divExact(@typeInfo(T).int.bits, 8)]u8 = undefined;
53 mem.writeInt(std.math.ByteAlignedInt(@TypeOf(value)), &bytes, value, endian);
54 return self.writeAll(&bytes);
55}
56
57pub fn writeStruct(self: Self, value: anytype) anyerror!void {
58 // Only extern and packed structs have defined in-memory layout.
59 comptime assert(@typeInfo(@TypeOf(value)).@"struct".layout != .auto);
60 return self.writeAll(mem.asBytes(&value));
61}
62
63pub fn writeStructEndian(self: Self, value: anytype, endian: std.builtin.Endian) anyerror!void {
64 // TODO: make sure this value is not a reference type
65 if (native_endian == endian) {
66 return self.writeStruct(value);
67 } else {
68 var copy = value;
69 mem.byteSwapAllFields(@TypeOf(value), &copy);
70 return self.writeStruct(copy);
71 }
72}
73
74pub fn writeFile(self: Self, file: std.fs.File) anyerror!void {
75 // TODO: figure out how to adjust std lib abstractions so that this ends up
76 // doing sendfile or maybe even copy_file_range under the right conditions.
77 var buf: [4000]u8 = undefined;
78 while (true) {
79 const n = try file.readAll(&buf);
80 try self.writeAll(buf[0..n]);
81 if (n < buf.len) return;
82 }
83}
84
85/// Helper for bridging to the new `Writer` API while upgrading.
86pub fn adaptToNewApi(self: *const Self) Adapter {
87 return .{
88 .derp_writer = self.*,
89 .new_interface = .{
90 .buffer = &.{},
91 .vtable = &.{ .drain = Adapter.drain },
92 },
93 };
94}
95
96pub const Adapter = struct {
97 derp_writer: Self,
98 new_interface: std.io.Writer,
99 err: ?Error = null,
100
101 fn drain(w: *std.io.Writer, data: []const []const u8, splat: usize) std.io.Writer.Error!usize {
102 _ = splat;
103 const a: *@This() = @fieldParentPtr("new_interface", w);
104 return a.derp_writer.write(data[0]) catch |err| {
105 a.err = err;
106 return error.WriteFailed;
107 };
108 }
109};
lib/std/Io/Reader.zig created+1740
...@@ -0,0 +1,1740 @@
1const Reader = @This();
2
3const builtin = @import("builtin");
4const native_endian = builtin.target.cpu.arch.endian();
5
6const std = @import("../std.zig");
7const Writer = std.io.Writer;
8const assert = std.debug.assert;
9const testing = std.testing;
10const Allocator = std.mem.Allocator;
11const ArrayList = std.ArrayListUnmanaged;
12const Limit = std.io.Limit;
13
14pub const Limited = @import("Reader/Limited.zig");
15
16vtable: *const VTable,
17buffer: []u8,
18/// Number of bytes which have been consumed from `buffer`.
19seek: usize,
20/// In `buffer` before this are buffered bytes, after this is `undefined`.
21end: usize,
22
23pub const VTable = struct {
24 /// Writes bytes from the internally tracked logical position to `w`.
25 ///
26 /// Returns the number of bytes written, which will be at minimum `0` and
27 /// at most `limit`. The number returned, including zero, does not indicate
28 /// end of stream. `limit` is guaranteed to be at least as large as the
29 /// buffer capacity of `w`, a value whose minimum size is determined by the
30 /// stream implementation.
31 ///
32 /// The reader's internal logical seek position moves forward in accordance
33 /// with the number of bytes returned from this function.
34 ///
35 /// Implementations are encouraged to utilize mandatory minimum buffer
36 /// sizes combined with short reads (returning a value less than `limit`)
37 /// in order to minimize complexity.
38 ///
39 /// Although this function is usually called when `buffer` is empty, it is
40 /// also called when it needs to be filled more due to the API user
41 /// requesting contiguous memory. In either case, the existing buffer data
42 /// should be ignored; new data written to `w`.
43 ///
44 /// In addition to, or instead of writing to `w`, the implementation may
45 /// choose to store data in `buffer`, modifying `seek` and `end`
46 /// accordingly. Stream implementations are encouraged to take advantage of
47 /// this if simplifies the logic.
48 stream: *const fn (r: *Reader, w: *Writer, limit: Limit) StreamError!usize,
49
50 /// Consumes bytes from the internally tracked stream position without
51 /// providing access to them.
52 ///
53 /// Returns the number of bytes discarded, which will be at minimum `0` and
54 /// at most `limit`. The number of bytes returned, including zero, does not
55 /// indicate end of stream.
56 ///
57 /// The reader's internal logical seek position moves forward in accordance
58 /// with the number of bytes returned from this function.
59 ///
60 /// Implementations are encouraged to utilize mandatory minimum buffer
61 /// sizes combined with short reads (returning a value less than `limit`)
62 /// in order to minimize complexity.
63 ///
64 /// The default implementation is is based on calling `stream`, borrowing
65 /// `buffer` to construct a temporary `Writer` and ignoring the written
66 /// data.
67 ///
68 /// This function is only called when `buffer` is empty.
69 discard: *const fn (r: *Reader, limit: Limit) Error!usize = defaultDiscard,
70};
71
72pub const StreamError = error{
73 /// See the `Reader` implementation for detailed diagnostics.
74 ReadFailed,
75 /// See the `Writer` implementation for detailed diagnostics.
76 WriteFailed,
77 /// End of stream indicated from the `Reader`. This error cannot originate
78 /// from the `Writer`.
79 EndOfStream,
80};
81
82pub const Error = error{
83 /// See the `Reader` implementation for detailed diagnostics.
84 ReadFailed,
85 EndOfStream,
86};
87
88pub const StreamRemainingError = error{
89 /// See the `Reader` implementation for detailed diagnostics.
90 ReadFailed,
91 /// See the `Writer` implementation for detailed diagnostics.
92 WriteFailed,
93};
94
95pub const ShortError = error{
96 /// See the `Reader` implementation for detailed diagnostics.
97 ReadFailed,
98};
99
100pub const failing: Reader = .{
101 .vtable = &.{
102 .read = failingStream,
103 .discard = failingDiscard,
104 },
105 .buffer = &.{},
106 .seek = 0,
107 .end = 0,
108};
109
110/// This is generally safe to `@constCast` because it has an empty buffer, so
111/// there is not really a way to accidentally attempt mutation of these fields.
112const ending_state: Reader = .fixed(&.{});
113pub const ending: *Reader = @constCast(&ending_state);
114
115pub fn limited(r: *Reader, limit: Limit, buffer: []u8) Limited {
116 return .init(r, limit, buffer);
117}
118
119/// Constructs a `Reader` such that it will read from `buffer` and then end.
120pub fn fixed(buffer: []const u8) Reader {
121 return .{
122 .vtable = &.{
123 .stream = endingStream,
124 .discard = endingDiscard,
125 },
126 // This cast is safe because all potential writes to it will instead
127 // return `error.EndOfStream`.
128 .buffer = @constCast(buffer),
129 .end = buffer.len,
130 .seek = 0,
131 };
132}
133
134pub fn stream(r: *Reader, w: *Writer, limit: Limit) StreamError!usize {
135 const buffer = limit.slice(r.buffer[r.seek..r.end]);
136 if (buffer.len > 0) {
137 @branchHint(.likely);
138 const n = try w.write(buffer);
139 r.seek += n;
140 return n;
141 }
142 const n = try r.vtable.stream(r, w, limit);
143 assert(n <= @intFromEnum(limit));
144 return n;
145}
146
147pub fn discard(r: *Reader, limit: Limit) Error!usize {
148 const buffered_len = r.end - r.seek;
149 const remaining: Limit = if (limit.toInt()) |n| l: {
150 if (buffered_len >= n) {
151 r.seek += n;
152 return n;
153 }
154 break :l .limited(n - buffered_len);
155 } else .unlimited;
156 r.seek = 0;
157 r.end = 0;
158 const n = try r.vtable.discard(r, remaining);
159 assert(n <= @intFromEnum(remaining));
160 return buffered_len + n;
161}
162
163pub fn defaultDiscard(r: *Reader, limit: Limit) Error!usize {
164 assert(r.seek == 0);
165 assert(r.end == 0);
166 var dw: Writer.Discarding = .init(r.buffer);
167 const n = r.stream(&dw.writer, limit) catch |err| switch (err) {
168 error.WriteFailed => unreachable,
169 error.ReadFailed => return error.ReadFailed,
170 error.EndOfStream => return error.EndOfStream,
171 };
172 assert(n <= @intFromEnum(limit));
173 return n;
174}
175
176/// "Pump" exactly `n` bytes from the reader to the writer.
177pub fn streamExact(r: *Reader, w: *Writer, n: usize) StreamError!void {
178 var remaining = n;
179 while (remaining != 0) remaining -= try r.stream(w, .limited(remaining));
180}
181
182/// "Pump" data from the reader to the writer, handling `error.EndOfStream` as
183/// a success case.
184///
185/// Returns total number of bytes written to `w`.
186pub fn streamRemaining(r: *Reader, w: *Writer) StreamRemainingError!usize {
187 var offset: usize = 0;
188 while (true) {
189 offset += r.stream(w, .unlimited) catch |err| switch (err) {
190 error.EndOfStream => return offset,
191 else => |e| return e,
192 };
193 }
194}
195
196/// Consumes the stream until the end, ignoring all the data, returning the
197/// number of bytes discarded.
198pub fn discardRemaining(r: *Reader) ShortError!usize {
199 var offset: usize = r.end - r.seek;
200 r.seek = 0;
201 r.end = 0;
202 while (true) {
203 offset += r.vtable.discard(r, .unlimited) catch |err| switch (err) {
204 error.EndOfStream => return offset,
205 else => |e| return e,
206 };
207 }
208}
209
210pub const LimitedAllocError = Allocator.Error || ShortError || error{StreamTooLong};
211
212/// Transfers all bytes from the current position to the end of the stream, up
213/// to `limit`, returning them as a caller-owned allocated slice.
214///
215/// If `limit` would be exceeded, `error.StreamTooLong` is returned instead. In
216/// such case, the next byte that would be read will be the first one to exceed
217/// `limit`, and all preceeding bytes have been discarded.
218///
219/// Asserts `buffer` has nonzero capacity.
220///
221/// See also:
222/// * `appendRemaining`
223pub fn allocRemaining(r: *Reader, gpa: Allocator, limit: Limit) LimitedAllocError![]u8 {
224 var buffer: ArrayList(u8) = .empty;
225 defer buffer.deinit(gpa);
226 try appendRemaining(r, gpa, null, &buffer, limit);
227 return buffer.toOwnedSlice(gpa);
228}
229
230/// Transfers all bytes from the current position to the end of the stream, up
231/// to `limit`, appending them to `list`.
232///
233/// If `limit` would be exceeded, `error.StreamTooLong` is returned instead. In
234/// such case, the next byte that would be read will be the first one to exceed
235/// `limit`, and all preceeding bytes have been appended to `list`.
236///
237/// Asserts `buffer` has nonzero capacity.
238///
239/// See also:
240/// * `allocRemaining`
241pub fn appendRemaining(
242 r: *Reader,
243 gpa: Allocator,
244 comptime alignment: ?std.mem.Alignment,
245 list: *std.ArrayListAlignedUnmanaged(u8, alignment),
246 limit: Limit,
247) LimitedAllocError!void {
248 const buffer = r.buffer;
249 const buffer_contents = buffer[r.seek..r.end];
250 const copy_len = limit.minInt(buffer_contents.len);
251 try list.ensureUnusedCapacity(gpa, copy_len);
252 @memcpy(list.unusedCapacitySlice()[0..copy_len], buffer[0..copy_len]);
253 list.items.len += copy_len;
254 r.seek += copy_len;
255 if (copy_len == buffer_contents.len) {
256 r.seek = 0;
257 r.end = 0;
258 }
259 var remaining = limit.subtract(copy_len).?;
260 while (true) {
261 try list.ensureUnusedCapacity(gpa, 1);
262 const dest = remaining.slice(list.unusedCapacitySlice());
263 const additional_buffer: []u8 = if (@intFromEnum(remaining) == dest.len) buffer else &.{};
264 const n = readVec(r, &.{ dest, additional_buffer }) catch |err| switch (err) {
265 error.EndOfStream => break,
266 error.ReadFailed => return error.ReadFailed,
267 };
268 if (n > dest.len) {
269 r.end = n - dest.len;
270 list.items.len += dest.len;
271 return error.StreamTooLong;
272 }
273 list.items.len += n;
274 remaining = remaining.subtract(n).?;
275 }
276}
277
278/// Writes bytes from the internally tracked stream position to `data`.
279///
280/// Returns the number of bytes written, which will be at minimum `0` and
281/// at most the sum of each data slice length. The number of bytes read,
282/// including zero, does not indicate end of stream.
283///
284/// The reader's internal logical seek position moves forward in accordance
285/// with the number of bytes returned from this function.
286pub fn readVec(r: *Reader, data: []const []u8) Error!usize {
287 return readVecLimit(r, data, .unlimited);
288}
289
290/// Equivalent to `readVec` but reads at most `limit` bytes.
291///
292/// This ultimately will lower to a call to `stream`, but it must ensure
293/// that the buffer used has at least as much capacity, in case that function
294/// depends on a minimum buffer capacity. It also ensures that if the `stream`
295/// implementation calls `Writer.writableVector`, it will get this data slice
296/// along with the buffer at the end.
297pub fn readVecLimit(r: *Reader, data: []const []u8, limit: Limit) Error!usize {
298 comptime assert(@intFromEnum(Limit.unlimited) == std.math.maxInt(usize));
299 var remaining = @intFromEnum(limit);
300 for (data, 0..) |buf, i| {
301 const buffer_contents = r.buffer[r.seek..r.end];
302 const copy_len = @min(buffer_contents.len, buf.len, remaining);
303 @memcpy(buf[0..copy_len], buffer_contents[0..copy_len]);
304 r.seek += copy_len;
305 remaining -= copy_len;
306 if (remaining == 0) break;
307 if (buf.len - copy_len == 0) continue;
308
309 // All of `buffer` has been copied to `data`. We now set up a structure
310 // that enables the `Writer.writableVector` API, while also ensuring
311 // API that directly operates on the `Writable.buffer` has its minimum
312 // buffer capacity requirements met.
313 r.seek = 0;
314 r.end = 0;
315 const first = buf[copy_len..];
316 const middle = data[i + 1 ..];
317 var wrapper: Writer.VectorWrapper = .{
318 .it = .{
319 .first = first,
320 .middle = middle,
321 .last = r.buffer,
322 },
323 .writer = .{
324 .buffer = if (first.len >= r.buffer.len) first else r.buffer,
325 .vtable = Writer.VectorWrapper.vtable,
326 },
327 };
328 var n = r.vtable.stream(r, &wrapper.writer, .limited(remaining)) catch |err| switch (err) {
329 error.WriteFailed => {
330 assert(!wrapper.used);
331 if (wrapper.writer.buffer.ptr == first.ptr) {
332 remaining -= wrapper.writer.end;
333 } else {
334 assert(wrapper.writer.end <= r.buffer.len);
335 r.end = wrapper.writer.end;
336 }
337 break;
338 },
339 else => |e| return e,
340 };
341 if (!wrapper.used) {
342 if (wrapper.writer.buffer.ptr == first.ptr) {
343 remaining -= n;
344 } else {
345 assert(n <= r.buffer.len);
346 r.end = n;
347 }
348 break;
349 }
350 if (n < first.len) {
351 remaining -= n;
352 break;
353 }
354 remaining -= first.len;
355 n -= first.len;
356 for (middle) |mid| {
357 if (n < mid.len) {
358 remaining -= n;
359 break;
360 }
361 remaining -= mid.len;
362 n -= mid.len;
363 }
364 assert(n <= r.buffer.len);
365 r.end = n;
366 break;
367 }
368 return @intFromEnum(limit) - remaining;
369}
370
371pub fn buffered(r: *Reader) []u8 {
372 return r.buffer[r.seek..r.end];
373}
374
375pub fn bufferedLen(r: *const Reader) usize {
376 return r.end - r.seek;
377}
378
379pub fn hashed(r: *Reader, hasher: anytype) Hashed(@TypeOf(hasher)) {
380 return .{ .in = r, .hasher = hasher };
381}
382
383pub fn readVecAll(r: *Reader, data: [][]u8) Error!void {
384 var index: usize = 0;
385 var truncate: usize = 0;
386 while (index < data.len) {
387 {
388 const untruncated = data[index];
389 data[index] = untruncated[truncate..];
390 defer data[index] = untruncated;
391 truncate += try r.readVec(data[index..]);
392 }
393 while (index < data.len and truncate >= data[index].len) {
394 truncate -= data[index].len;
395 index += 1;
396 }
397 }
398}
399
400/// Returns the next `len` bytes from the stream, filling the buffer as
401/// necessary.
402///
403/// Invalidates previously returned values from `peek`.
404///
405/// Asserts that the `Reader` was initialized with a buffer capacity at
406/// least as big as `len`.
407///
408/// If there are fewer than `len` bytes left in the stream, `error.EndOfStream`
409/// is returned instead.
410///
411/// See also:
412/// * `peek`
413/// * `toss`
414pub fn peek(r: *Reader, n: usize) Error![]u8 {
415 try r.fill(n);
416 return r.buffer[r.seek..][0..n];
417}
418
419/// Returns all the next buffered bytes, after filling the buffer to ensure it
420/// contains at least `n` bytes.
421///
422/// Invalidates previously returned values from `peek` and `peekGreedy`.
423///
424/// Asserts that the `Reader` was initialized with a buffer capacity at
425/// least as big as `n`.
426///
427/// If there are fewer than `n` bytes left in the stream, `error.EndOfStream`
428/// is returned instead.
429///
430/// See also:
431/// * `peek`
432/// * `toss`
433pub fn peekGreedy(r: *Reader, n: usize) Error![]u8 {
434 try r.fill(n);
435 return r.buffer[r.seek..r.end];
436}
437
438/// Skips the next `n` bytes from the stream, advancing the seek position. This
439/// is typically and safely used after `peek`.
440///
441/// Asserts that the number of bytes buffered is at least as many as `n`.
442///
443/// The "tossed" memory remains alive until a "peek" operation occurs.
444///
445/// See also:
446/// * `peek`.
447/// * `discard`.
448pub fn toss(r: *Reader, n: usize) void {
449 r.seek += n;
450 assert(r.seek <= r.end);
451}
452
453/// Equivalent to `toss(r.bufferedLen())`.
454pub fn tossBuffered(r: *Reader) void {
455 r.seek = 0;
456 r.end = 0;
457}
458
459/// Equivalent to `peek` followed by `toss`.
460///
461/// The data returned is invalidated by the next call to `take`, `peek`,
462/// `fill`, and functions with those prefixes.
463pub fn take(r: *Reader, n: usize) Error![]u8 {
464 const result = try r.peek(n);
465 r.toss(n);
466 return result;
467}
468
469/// Returns the next `n` bytes from the stream as an array, filling the buffer
470/// as necessary and advancing the seek position `n` bytes.
471///
472/// Asserts that the `Reader` was initialized with a buffer capacity at
473/// least as big as `n`.
474///
475/// If there are fewer than `n` bytes left in the stream, `error.EndOfStream`
476/// is returned instead.
477///
478/// See also:
479/// * `take`
480pub fn takeArray(r: *Reader, comptime n: usize) Error!*[n]u8 {
481 return (try r.take(n))[0..n];
482}
483
484/// Returns the next `n` bytes from the stream as an array, filling the buffer
485/// as necessary, without advancing the seek position.
486///
487/// Asserts that the `Reader` was initialized with a buffer capacity at
488/// least as big as `n`.
489///
490/// If there are fewer than `n` bytes left in the stream, `error.EndOfStream`
491/// is returned instead.
492///
493/// See also:
494/// * `peek`
495/// * `takeArray`
496pub fn peekArray(r: *Reader, comptime n: usize) Error!*[n]u8 {
497 return (try r.peek(n))[0..n];
498}
499
500/// Skips the next `n` bytes from the stream, advancing the seek position.
501///
502/// Unlike `toss` which is infallible, in this function `n` can be any amount.
503///
504/// Returns `error.EndOfStream` if fewer than `n` bytes could be discarded.
505///
506/// See also:
507/// * `toss`
508/// * `discardRemaining`
509/// * `discardShort`
510/// * `discard`
511pub fn discardAll(r: *Reader, n: usize) Error!void {
512 if ((try r.discardShort(n)) != n) return error.EndOfStream;
513}
514
515pub fn discardAll64(r: *Reader, n: u64) Error!void {
516 var remaining: u64 = n;
517 while (remaining > 0) {
518 const limited_remaining = std.math.cast(usize, remaining) orelse std.math.maxInt(usize);
519 try discardAll(r, limited_remaining);
520 remaining -= limited_remaining;
521 }
522}
523
524/// Skips the next `n` bytes from the stream, advancing the seek position.
525///
526/// Unlike `toss` which is infallible, in this function `n` can be any amount.
527///
528/// Returns the number of bytes discarded, which is less than `n` if and only
529/// if the stream reached the end.
530///
531/// See also:
532/// * `discardAll`
533/// * `discardRemaining`
534/// * `discard`
535pub fn discardShort(r: *Reader, n: usize) ShortError!usize {
536 const proposed_seek = r.seek + n;
537 if (proposed_seek <= r.end) {
538 @branchHint(.likely);
539 r.seek = proposed_seek;
540 return n;
541 }
542 var remaining = n - (r.end - r.seek);
543 r.end = 0;
544 r.seek = 0;
545 while (true) {
546 const discard_len = r.vtable.discard(r, .limited(remaining)) catch |err| switch (err) {
547 error.EndOfStream => return n - remaining,
548 error.ReadFailed => return error.ReadFailed,
549 };
550 remaining -= discard_len;
551 if (remaining == 0) return n;
552 }
553}
554
555/// Fill `buffer` with the next `buffer.len` bytes from the stream, advancing
556/// the seek position.
557///
558/// Invalidates previously returned values from `peek`.
559///
560/// If the provided buffer cannot be filled completely, `error.EndOfStream` is
561/// returned instead.
562///
563/// See also:
564/// * `peek`
565/// * `readSliceShort`
566pub fn readSliceAll(r: *Reader, buffer: []u8) Error!void {
567 const n = try readSliceShort(r, buffer);
568 if (n != buffer.len) return error.EndOfStream;
569}
570
571/// Fill `buffer` with the next `buffer.len` bytes from the stream, advancing
572/// the seek position.
573///
574/// Invalidates previously returned values from `peek`.
575///
576/// Returns the number of bytes read, which is less than `buffer.len` if and
577/// only if the stream reached the end.
578///
579/// See also:
580/// * `readSliceAll`
581pub fn readSliceShort(r: *Reader, buffer: []u8) ShortError!usize {
582 const in_buffer = r.buffer[r.seek..r.end];
583 const copy_len = @min(buffer.len, in_buffer.len);
584 @memcpy(buffer[0..copy_len], in_buffer[0..copy_len]);
585 if (buffer.len - copy_len == 0) {
586 r.seek += copy_len;
587 return buffer.len;
588 }
589 var i: usize = copy_len;
590 r.end = 0;
591 r.seek = 0;
592 while (true) {
593 const remaining = buffer[i..];
594 var wrapper: Writer.VectorWrapper = .{
595 .it = .{
596 .first = remaining,
597 .last = r.buffer,
598 },
599 .writer = .{
600 .buffer = if (remaining.len >= r.buffer.len) remaining else r.buffer,
601 .vtable = Writer.VectorWrapper.vtable,
602 },
603 };
604 const n = r.vtable.stream(r, &wrapper.writer, .unlimited) catch |err| switch (err) {
605 error.WriteFailed => {
606 if (!wrapper.used) {
607 assert(r.seek == 0);
608 r.seek = remaining.len;
609 r.end = wrapper.writer.end;
610 @memcpy(remaining, r.buffer[0..remaining.len]);
611 }
612 return buffer.len;
613 },
614 error.EndOfStream => return i,
615 error.ReadFailed => return error.ReadFailed,
616 };
617 if (n < remaining.len) {
618 i += n;
619 continue;
620 }
621 r.end = n - remaining.len;
622 return buffer.len;
623 }
624}
625
626/// Fill `buffer` with the next `buffer.len` bytes from the stream, advancing
627/// the seek position.
628///
629/// Invalidates previously returned values from `peek`.
630///
631/// If the provided buffer cannot be filled completely, `error.EndOfStream` is
632/// returned instead.
633///
634/// The function is inline to avoid the dead code in case `endian` is
635/// comptime-known and matches host endianness.
636///
637/// See also:
638/// * `readSliceAll`
639/// * `readSliceEndianAlloc`
640pub inline fn readSliceEndian(
641 r: *Reader,
642 comptime Elem: type,
643 buffer: []Elem,
644 endian: std.builtin.Endian,
645) Error!void {
646 try readSliceAll(r, @ptrCast(buffer));
647 if (native_endian != endian) for (buffer) |*elem| std.mem.byteSwapAllFields(Elem, elem);
648}
649
650pub const ReadAllocError = Error || Allocator.Error;
651
652/// The function is inline to avoid the dead code in case `endian` is
653/// comptime-known and matches host endianness.
654pub inline fn readSliceEndianAlloc(
655 r: *Reader,
656 allocator: Allocator,
657 comptime Elem: type,
658 len: usize,
659 endian: std.builtin.Endian,
660) ReadAllocError![]Elem {
661 const dest = try allocator.alloc(Elem, len);
662 errdefer allocator.free(dest);
663 try readSliceAll(r, @ptrCast(dest));
664 if (native_endian != endian) for (dest) |*elem| std.mem.byteSwapAllFields(Elem, elem);
665 return dest;
666}
667
668/// Shortcut for calling `readSliceAll` with a buffer provided by `allocator`.
669pub fn readAlloc(r: *Reader, allocator: Allocator, len: usize) ReadAllocError![]u8 {
670 const dest = try allocator.alloc(u8, len);
671 errdefer allocator.free(dest);
672 try readSliceAll(r, dest);
673 return dest;
674}
675
676pub const DelimiterError = error{
677 /// See the `Reader` implementation for detailed diagnostics.
678 ReadFailed,
679 /// For "inclusive" functions, stream ended before the delimiter was found.
680 /// For "exclusive" functions, stream ended and there are no more bytes to
681 /// return.
682 EndOfStream,
683 /// The delimiter was not found within a number of bytes matching the
684 /// capacity of the `Reader`.
685 StreamTooLong,
686};
687
688/// Returns a slice of the next bytes of buffered data from the stream until
689/// `sentinel` is found, advancing the seek position.
690///
691/// Returned slice has a sentinel.
692///
693/// Invalidates previously returned values from `peek`.
694///
695/// See also:
696/// * `peekSentinel`
697/// * `takeDelimiterExclusive`
698/// * `takeDelimiterInclusive`
699pub fn takeSentinel(r: *Reader, comptime sentinel: u8) DelimiterError![:sentinel]u8 {
700 const result = try r.peekSentinel(sentinel);
701 r.toss(result.len + 1);
702 return result;
703}
704
705/// Returns a slice of the next bytes of buffered data from the stream until
706/// `sentinel` is found, without advancing the seek position.
707///
708/// Returned slice has a sentinel; end of stream does not count as a delimiter.
709///
710/// Invalidates previously returned values from `peek`.
711///
712/// See also:
713/// * `takeSentinel`
714/// * `peekDelimiterExclusive`
715/// * `peekDelimiterInclusive`
716pub fn peekSentinel(r: *Reader, comptime sentinel: u8) DelimiterError![:sentinel]u8 {
717 const result = try r.peekDelimiterInclusive(sentinel);
718 return result[0 .. result.len - 1 :sentinel];
719}
720
721/// Returns a slice of the next bytes of buffered data from the stream until
722/// `delimiter` is found, advancing the seek position.
723///
724/// Returned slice includes the delimiter as the last byte.
725///
726/// Invalidates previously returned values from `peek`.
727///
728/// See also:
729/// * `takeSentinel`
730/// * `takeDelimiterExclusive`
731/// * `peekDelimiterInclusive`
732pub fn takeDelimiterInclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 {
733 const result = try r.peekDelimiterInclusive(delimiter);
734 r.toss(result.len);
735 return result;
736}
737
738/// Returns a slice of the next bytes of buffered data from the stream until
739/// `delimiter` is found, without advancing the seek position.
740///
741/// Returned slice includes the delimiter as the last byte.
742///
743/// Invalidates previously returned values from `peek`.
744///
745/// See also:
746/// * `peekSentinel`
747/// * `peekDelimiterExclusive`
748/// * `takeDelimiterInclusive`
749pub fn peekDelimiterInclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 {
750 const buffer = r.buffer[0..r.end];
751 const seek = r.seek;
752 if (std.mem.indexOfScalarPos(u8, buffer, seek, delimiter)) |end| {
753 @branchHint(.likely);
754 return buffer[seek .. end + 1];
755 }
756 if (r.vtable.stream == &endingStream) {
757 // Protect the `@constCast` of `fixed`.
758 return error.EndOfStream;
759 }
760 r.rebase();
761 while (r.buffer.len - r.end != 0) {
762 const end_cap = r.buffer[r.end..];
763 var writer: Writer = .fixed(end_cap);
764 const n = r.vtable.stream(r, &writer, .limited(end_cap.len)) catch |err| switch (err) {
765 error.WriteFailed => unreachable,
766 else => |e| return e,
767 };
768 r.end += n;
769 if (std.mem.indexOfScalarPos(u8, end_cap[0..n], 0, delimiter)) |end| {
770 return r.buffer[0 .. r.end - n + end + 1];
771 }
772 }
773 return error.StreamTooLong;
774}
775
776/// Returns a slice of the next bytes of buffered data from the stream until
777/// `delimiter` is found, advancing the seek position.
778///
779/// Returned slice excludes the delimiter. End-of-stream is treated equivalent
780/// to a delimiter, unless it would result in a length 0 return value, in which
781/// case `error.EndOfStream` is returned instead.
782///
783/// If the delimiter is not found within a number of bytes matching the
784/// capacity of this `Reader`, `error.StreamTooLong` is returned. In
785/// such case, the stream state is unmodified as if this function was never
786/// called.
787///
788/// Invalidates previously returned values from `peek`.
789///
790/// See also:
791/// * `takeDelimiterInclusive`
792/// * `peekDelimiterExclusive`
793pub fn takeDelimiterExclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 {
794 const result = r.peekDelimiterInclusive(delimiter) catch |err| switch (err) {
795 error.EndOfStream => {
796 const remaining = r.buffer[r.seek..r.end];
797 if (remaining.len == 0) return error.EndOfStream;
798 r.toss(remaining.len);
799 return remaining;
800 },
801 else => |e| return e,
802 };
803 r.toss(result.len);
804 return result[0 .. result.len - 1];
805}
806
807/// Returns a slice of the next bytes of buffered data from the stream until
808/// `delimiter` is found, without advancing the seek position.
809///
810/// Returned slice excludes the delimiter. End-of-stream is treated equivalent
811/// to a delimiter, unless it would result in a length 0 return value, in which
812/// case `error.EndOfStream` is returned instead.
813///
814/// If the delimiter is not found within a number of bytes matching the
815/// capacity of this `Reader`, `error.StreamTooLong` is returned. In
816/// such case, the stream state is unmodified as if this function was never
817/// called.
818///
819/// Invalidates previously returned values from `peek`.
820///
821/// See also:
822/// * `peekDelimiterInclusive`
823/// * `takeDelimiterExclusive`
824pub fn peekDelimiterExclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 {
825 const result = r.peekDelimiterInclusive(delimiter) catch |err| switch (err) {
826 error.EndOfStream => {
827 const remaining = r.buffer[r.seek..r.end];
828 if (remaining.len == 0) return error.EndOfStream;
829 r.toss(remaining.len);
830 return remaining;
831 },
832 else => |e| return e,
833 };
834 return result[0 .. result.len - 1];
835}
836
837/// Appends to `w` contents by reading from the stream until `delimiter` is
838/// found. Does not write the delimiter itself.
839///
840/// Returns number of bytes streamed, which may be zero, or error.EndOfStream
841/// if the delimiter was not found.
842///
843/// Asserts buffer capacity of at least one. This function performs better with
844/// larger buffers.
845///
846/// See also:
847/// * `streamDelimiterEnding`
848/// * `streamDelimiterLimit`
849pub fn streamDelimiter(r: *Reader, w: *Writer, delimiter: u8) StreamError!usize {
850 const n = streamDelimiterLimit(r, w, delimiter, .unlimited) catch |err| switch (err) {
851 error.StreamTooLong => unreachable, // unlimited is passed
852 else => |e| return e,
853 };
854 if (r.seek == r.end) return error.EndOfStream;
855 return n;
856}
857
858/// Appends to `w` contents by reading from the stream until `delimiter` is found.
859/// Does not write the delimiter itself.
860///
861/// Returns number of bytes streamed, which may be zero. End of stream can be
862/// detected by checking if the next byte in the stream is the delimiter.
863///
864/// Asserts buffer capacity of at least one. This function performs better with
865/// larger buffers.
866///
867/// See also:
868/// * `streamDelimiter`
869/// * `streamDelimiterLimit`
870pub fn streamDelimiterEnding(
871 r: *Reader,
872 w: *Writer,
873 delimiter: u8,
874) StreamRemainingError!usize {
875 return streamDelimiterLimit(r, w, delimiter, .unlimited) catch |err| switch (err) {
876 error.StreamTooLong => unreachable, // unlimited is passed
877 else => |e| return e,
878 };
879}
880
881pub const StreamDelimiterLimitError = error{
882 ReadFailed,
883 WriteFailed,
884 /// The delimiter was not found within the limit.
885 StreamTooLong,
886};
887
888/// Appends to `w` contents by reading from the stream until `delimiter` is found.
889/// Does not write the delimiter itself.
890///
891/// Returns number of bytes streamed, which may be zero. End of stream can be
892/// detected by checking if the next byte in the stream is the delimiter.
893///
894/// Asserts buffer capacity of at least one. This function performs better with
895/// larger buffers.
896pub fn streamDelimiterLimit(
897 r: *Reader,
898 w: *Writer,
899 delimiter: u8,
900 limit: Limit,
901) StreamDelimiterLimitError!usize {
902 var remaining = @intFromEnum(limit);
903 while (remaining != 0) {
904 const available = Limit.limited(remaining).slice(r.peekGreedy(1) catch |err| switch (err) {
905 error.ReadFailed => return error.ReadFailed,
906 error.EndOfStream => return @intFromEnum(limit) - remaining,
907 });
908 if (std.mem.indexOfScalar(u8, available, delimiter)) |delimiter_index| {
909 try w.writeAll(available[0..delimiter_index]);
910 r.toss(delimiter_index);
911 remaining -= delimiter_index;
912 return @intFromEnum(limit) - remaining;
913 }
914 try w.writeAll(available);
915 r.toss(available.len);
916 remaining -= available.len;
917 }
918 return error.StreamTooLong;
919}
920
921/// Reads from the stream until specified byte is found, discarding all data,
922/// including the delimiter.
923///
924/// Returns number of bytes discarded, or `error.EndOfStream` if the delimiter
925/// is not found.
926///
927/// See also:
928/// * `discardDelimiterExclusive`
929/// * `discardDelimiterLimit`
930pub fn discardDelimiterInclusive(r: *Reader, delimiter: u8) Error!usize {
931 const n = discardDelimiterLimit(r, delimiter, .unlimited) catch |err| switch (err) {
932 error.StreamTooLong => unreachable, // unlimited is passed
933 else => |e| return e,
934 };
935 if (r.seek == r.end) return error.EndOfStream;
936 assert(r.buffer[r.seek] == delimiter);
937 toss(r, 1);
938 return n + 1;
939}
940
941/// Reads from the stream until specified byte is found, discarding all data,
942/// excluding the delimiter.
943///
944/// Returns the number of bytes discarded.
945///
946/// Succeeds if stream ends before delimiter found. End of stream can be
947/// detected by checking if the delimiter is buffered.
948///
949/// See also:
950/// * `discardDelimiterInclusive`
951/// * `discardDelimiterLimit`
952pub fn discardDelimiterExclusive(r: *Reader, delimiter: u8) ShortError!usize {
953 return discardDelimiterLimit(r, delimiter, .unlimited) catch |err| switch (err) {
954 error.StreamTooLong => unreachable, // unlimited is passed
955 else => |e| return e,
956 };
957}
958
959pub const DiscardDelimiterLimitError = error{
960 ReadFailed,
961 /// The delimiter was not found within the limit.
962 StreamTooLong,
963};
964
965/// Reads from the stream until specified byte is found, discarding all data,
966/// excluding the delimiter.
967///
968/// Returns the number of bytes discarded.
969///
970/// Succeeds if stream ends before delimiter found. End of stream can be
971/// detected by checking if the delimiter is buffered.
972pub fn discardDelimiterLimit(r: *Reader, delimiter: u8, limit: Limit) DiscardDelimiterLimitError!usize {
973 var remaining = @intFromEnum(limit);
974 while (remaining != 0) {
975 const available = Limit.limited(remaining).slice(r.peekGreedy(1) catch |err| switch (err) {
976 error.ReadFailed => return error.ReadFailed,
977 error.EndOfStream => return @intFromEnum(limit) - remaining,
978 });
979 if (std.mem.indexOfScalar(u8, available, delimiter)) |delimiter_index| {
980 r.toss(delimiter_index);
981 remaining -= delimiter_index;
982 return @intFromEnum(limit) - remaining;
983 }
984 r.toss(available.len);
985 remaining -= available.len;
986 }
987 return error.StreamTooLong;
988}
989
990/// Fills the buffer such that it contains at least `n` bytes, without
991/// advancing the seek position.
992///
993/// Returns `error.EndOfStream` if and only if there are fewer than `n` bytes
994/// remaining.
995///
996/// Asserts buffer capacity is at least `n`.
997pub fn fill(r: *Reader, n: usize) Error!void {
998 assert(n <= r.buffer.len);
999 if (r.seek + n <= r.end) {
1000 @branchHint(.likely);
1001 return;
1002 }
1003 if (r.seek + n <= r.buffer.len) while (true) {
1004 const end_cap = r.buffer[r.end..];
1005 var writer: Writer = .fixed(end_cap);
1006 r.end += r.vtable.stream(r, &writer, .limited(end_cap.len)) catch |err| switch (err) {
1007 error.WriteFailed => unreachable,
1008 else => |e| return e,
1009 };
1010 if (r.seek + n <= r.end) return;
1011 };
1012 if (r.vtable.stream == &endingStream) {
1013 // Protect the `@constCast` of `fixed`.
1014 return error.EndOfStream;
1015 }
1016 rebaseCapacity(r, n);
1017 var writer: Writer = .{
1018 .buffer = r.buffer,
1019 .vtable = &.{ .drain = Writer.fixedDrain },
1020 };
1021 while (r.end < r.seek + n) {
1022 writer.end = r.end;
1023 r.end += r.vtable.stream(r, &writer, .limited(r.buffer.len - r.end)) catch |err| switch (err) {
1024 error.WriteFailed => unreachable,
1025 error.ReadFailed, error.EndOfStream => |e| return e,
1026 };
1027 }
1028}
1029
1030/// Without advancing the seek position, does exactly one underlying read, filling the buffer as
1031/// much as possible. This may result in zero bytes added to the buffer, which is not an end of
1032/// stream condition. End of stream is communicated via returning `error.EndOfStream`.
1033///
1034/// Asserts buffer capacity is at least 1.
1035pub fn fillMore(r: *Reader) Error!void {
1036 rebaseCapacity(r, 1);
1037 var writer: Writer = .{
1038 .buffer = r.buffer,
1039 .end = r.end,
1040 .vtable = &.{ .drain = Writer.fixedDrain },
1041 };
1042 r.end += r.vtable.stream(r, &writer, .limited(r.buffer.len - r.end)) catch |err| switch (err) {
1043 error.WriteFailed => unreachable,
1044 else => |e| return e,
1045 };
1046}
1047
1048/// Returns the next byte from the stream or returns `error.EndOfStream`.
1049///
1050/// Does not advance the seek position.
1051///
1052/// Asserts the buffer capacity is nonzero.
1053pub fn peekByte(r: *Reader) Error!u8 {
1054 const buffer = r.buffer[0..r.end];
1055 const seek = r.seek;
1056 if (seek < buffer.len) {
1057 @branchHint(.likely);
1058 return buffer[seek];
1059 }
1060 try fill(r, 1);
1061 return r.buffer[r.seek];
1062}
1063
1064/// Reads 1 byte from the stream or returns `error.EndOfStream`.
1065///
1066/// Asserts the buffer capacity is nonzero.
1067pub fn takeByte(r: *Reader) Error!u8 {
1068 const result = try peekByte(r);
1069 r.seek += 1;
1070 return result;
1071}
1072
1073/// Same as `takeByte` except the returned byte is signed.
1074pub fn takeByteSigned(r: *Reader) Error!i8 {
1075 return @bitCast(try r.takeByte());
1076}
1077
1078/// Asserts the buffer was initialized with a capacity at least `@bitSizeOf(T) / 8`.
1079pub inline fn takeInt(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T {
1080 const n = @divExact(@typeInfo(T).int.bits, 8);
1081 return std.mem.readInt(T, try r.takeArray(n), endian);
1082}
1083
1084/// Asserts the buffer was initialized with a capacity at least `n`.
1085pub fn takeVarInt(r: *Reader, comptime Int: type, endian: std.builtin.Endian, n: usize) Error!Int {
1086 assert(n <= @sizeOf(Int));
1087 return std.mem.readVarInt(Int, try r.take(n), endian);
1088}
1089
1090/// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`.
1091///
1092/// Advances the seek position.
1093///
1094/// See also:
1095/// * `peekStruct`
1096/// * `takeStructEndian`
1097pub fn takeStruct(r: *Reader, comptime T: type) Error!*align(1) T {
1098 // Only extern and packed structs have defined in-memory layout.
1099 comptime assert(@typeInfo(T).@"struct".layout != .auto);
1100 return @ptrCast(try r.takeArray(@sizeOf(T)));
1101}
1102
1103/// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`.
1104///
1105/// Does not advance the seek position.
1106///
1107/// See also:
1108/// * `takeStruct`
1109/// * `peekStructEndian`
1110pub fn peekStruct(r: *Reader, comptime T: type) Error!*align(1) T {
1111 // Only extern and packed structs have defined in-memory layout.
1112 comptime assert(@typeInfo(T).@"struct".layout != .auto);
1113 return @ptrCast(try r.peekArray(@sizeOf(T)));
1114}
1115
1116/// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`.
1117///
1118/// This function is inline to avoid referencing `std.mem.byteSwapAllFields`
1119/// when `endian` is comptime-known and matches the host endianness.
1120///
1121/// See also:
1122/// * `takeStruct`
1123/// * `peekStructEndian`
1124pub inline fn takeStructEndian(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T {
1125 var res = (try r.takeStruct(T)).*;
1126 if (native_endian != endian) std.mem.byteSwapAllFields(T, &res);
1127 return res;
1128}
1129
1130/// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`.
1131///
1132/// This function is inline to avoid referencing `std.mem.byteSwapAllFields`
1133/// when `endian` is comptime-known and matches the host endianness.
1134///
1135/// See also:
1136/// * `takeStructEndian`
1137/// * `peekStruct`
1138pub inline fn peekStructEndian(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T {
1139 var res = (try r.peekStruct(T)).*;
1140 if (native_endian != endian) std.mem.byteSwapAllFields(T, &res);
1141 return res;
1142}
1143
1144pub const TakeEnumError = Error || error{InvalidEnumTag};
1145
1146/// Reads an integer with the same size as the given enum's tag type. If the
1147/// integer matches an enum tag, casts the integer to the enum tag and returns
1148/// it. Otherwise, returns `error.InvalidEnumTag`.
1149///
1150/// Asserts the buffer was initialized with a capacity at least `@sizeOf(Enum)`.
1151pub fn takeEnum(r: *Reader, comptime Enum: type, endian: std.builtin.Endian) TakeEnumError!Enum {
1152 const Tag = @typeInfo(Enum).@"enum".tag_type;
1153 const int = try r.takeInt(Tag, endian);
1154 return std.meta.intToEnum(Enum, int);
1155}
1156
1157/// Reads an integer with the same size as the given nonexhaustive enum's tag type.
1158///
1159/// Asserts the buffer was initialized with a capacity at least `@sizeOf(Enum)`.
1160pub fn takeEnumNonexhaustive(r: *Reader, comptime Enum: type, endian: std.builtin.Endian) Error!Enum {
1161 const info = @typeInfo(Enum).@"enum";
1162 comptime assert(!info.is_exhaustive);
1163 comptime assert(@bitSizeOf(info.tag_type) == @sizeOf(info.tag_type) * 8);
1164 return takeEnum(r, Enum, endian) catch |err| switch (err) {
1165 error.InvalidEnumTag => unreachable,
1166 else => |e| return e,
1167 };
1168}
1169
1170pub const TakeLeb128Error = Error || error{Overflow};
1171
1172/// Read a single LEB128 value as type T, or `error.Overflow` if the value cannot fit.
1173pub fn takeLeb128(r: *Reader, comptime Result: type) TakeLeb128Error!Result {
1174 const result_info = @typeInfo(Result).int;
1175 return std.math.cast(Result, try r.takeMultipleOf7Leb128(@Type(.{ .int = .{
1176 .signedness = result_info.signedness,
1177 .bits = std.mem.alignForwardAnyAlign(u16, result_info.bits, 7),
1178 } }))) orelse error.Overflow;
1179}
1180
1181pub fn expandTotalCapacity(r: *Reader, allocator: Allocator, n: usize) Allocator.Error!void {
1182 if (n <= r.buffer.len) return;
1183 if (r.seek > 0) rebase(r);
1184 var list: ArrayList(u8) = .{
1185 .items = r.buffer[0..r.end],
1186 .capacity = r.buffer.len,
1187 };
1188 defer r.buffer = list.allocatedSlice();
1189 try list.ensureTotalCapacity(allocator, n);
1190}
1191
1192pub const FillAllocError = Error || Allocator.Error;
1193
1194pub fn fillAlloc(r: *Reader, allocator: Allocator, n: usize) FillAllocError!void {
1195 try expandTotalCapacity(r, allocator, n);
1196 return fill(r, n);
1197}
1198
1199/// Returns a slice into the unused capacity of `buffer` with at least
1200/// `min_len` bytes, extending `buffer` by resizing it with `gpa` as necessary.
1201///
1202/// After calling this function, typically the caller will follow up with a
1203/// call to `advanceBufferEnd` to report the actual number of bytes buffered.
1204pub fn writableSliceGreedyAlloc(r: *Reader, allocator: Allocator, min_len: usize) Allocator.Error![]u8 {
1205 {
1206 const unused = r.buffer[r.end..];
1207 if (unused.len >= min_len) return unused;
1208 }
1209 if (r.seek > 0) rebase(r);
1210 {
1211 var list: ArrayList(u8) = .{
1212 .items = r.buffer[0..r.end],
1213 .capacity = r.buffer.len,
1214 };
1215 defer r.buffer = list.allocatedSlice();
1216 try list.ensureUnusedCapacity(allocator, min_len);
1217 }
1218 const unused = r.buffer[r.end..];
1219 assert(unused.len >= min_len);
1220 return unused;
1221}
1222
1223/// After writing directly into the unused capacity of `buffer`, this function
1224/// updates `end` so that users of `Reader` can receive the data.
1225pub fn advanceBufferEnd(r: *Reader, n: usize) void {
1226 assert(n <= r.buffer.len - r.end);
1227 r.end += n;
1228}
1229
1230fn takeMultipleOf7Leb128(r: *Reader, comptime Result: type) TakeLeb128Error!Result {
1231 const result_info = @typeInfo(Result).int;
1232 comptime assert(result_info.bits % 7 == 0);
1233 var remaining_bits: std.math.Log2IntCeil(Result) = result_info.bits;
1234 const UnsignedResult = @Type(.{ .int = .{
1235 .signedness = .unsigned,
1236 .bits = result_info.bits,
1237 } });
1238 var result: UnsignedResult = 0;
1239 var fits = true;
1240 while (true) {
1241 const buffer: []const packed struct(u8) { bits: u7, more: bool } = @ptrCast(try r.peekGreedy(1));
1242 for (buffer, 1..) |byte, len| {
1243 if (remaining_bits > 0) {
1244 result = @shlExact(@as(UnsignedResult, byte.bits), result_info.bits - 7) |
1245 if (result_info.bits > 7) @shrExact(result, 7) else 0;
1246 remaining_bits -= 7;
1247 } else if (fits) fits = switch (result_info.signedness) {
1248 .signed => @as(i7, @bitCast(byte.bits)) ==
1249 @as(i7, @truncate(@as(Result, @bitCast(result)) >> (result_info.bits - 1))),
1250 .unsigned => byte.bits == 0,
1251 };
1252 if (byte.more) continue;
1253 r.toss(len);
1254 return if (fits) @as(Result, @bitCast(result)) >> remaining_bits else error.Overflow;
1255 }
1256 r.toss(buffer.len);
1257 }
1258}
1259
1260/// Left-aligns data such that `r.seek` becomes zero.
1261pub fn rebase(r: *Reader) void {
1262 if (r.seek == 0) return;
1263 const data = r.buffer[r.seek..r.end];
1264 @memmove(r.buffer[0..data.len], data);
1265 r.seek = 0;
1266 r.end = data.len;
1267}
1268
1269/// Ensures `capacity` more data can be buffered without rebasing, by rebasing
1270/// if necessary.
1271///
1272/// Asserts `capacity` is within the buffer capacity.
1273pub fn rebaseCapacity(r: *Reader, capacity: usize) void {
1274 if (r.end > r.buffer.len - capacity) rebase(r);
1275}
1276
1277/// Advances the stream and decreases the size of the storage buffer by `n`,
1278/// returning the range of bytes no longer accessible by `r`.
1279///
1280/// This action can be undone by `restitute`.
1281///
1282/// Asserts there are at least `n` buffered bytes already.
1283///
1284/// Asserts that `r.seek` is zero, i.e. the buffer is in a rebased state.
1285pub fn steal(r: *Reader, n: usize) []u8 {
1286 assert(r.seek == 0);
1287 assert(n <= r.end);
1288 const stolen = r.buffer[0..n];
1289 r.buffer = r.buffer[n..];
1290 r.end -= n;
1291 return stolen;
1292}
1293
1294/// Expands the storage buffer, undoing the effects of `steal`
1295/// Assumes that `n` does not exceed the total number of stolen bytes.
1296pub fn restitute(r: *Reader, n: usize) void {
1297 r.buffer = (r.buffer.ptr - n)[0 .. r.buffer.len + n];
1298 r.end += n;
1299 r.seek += n;
1300}
1301
1302test fixed {
1303 var r: Reader = .fixed("a\x02");
1304 try testing.expect((try r.takeByte()) == 'a');
1305 try testing.expect((try r.takeEnum(enum(u8) {
1306 a = 0,
1307 b = 99,
1308 c = 2,
1309 d = 3,
1310 }, builtin.cpu.arch.endian())) == .c);
1311 try testing.expectError(error.EndOfStream, r.takeByte());
1312}
1313
1314test peek {
1315 var r: Reader = .fixed("abc");
1316 try testing.expectEqualStrings("ab", try r.peek(2));
1317 try testing.expectEqualStrings("a", try r.peek(1));
1318}
1319
1320test peekGreedy {
1321 var r: Reader = .fixed("abc");
1322 try testing.expectEqualStrings("abc", try r.peekGreedy(1));
1323}
1324
1325test toss {
1326 var r: Reader = .fixed("abc");
1327 r.toss(1);
1328 try testing.expectEqualStrings("bc", r.buffered());
1329}
1330
1331test take {
1332 var r: Reader = .fixed("abc");
1333 try testing.expectEqualStrings("ab", try r.take(2));
1334 try testing.expectEqualStrings("c", try r.take(1));
1335}
1336
1337test takeArray {
1338 var r: Reader = .fixed("abc");
1339 try testing.expectEqualStrings("ab", try r.takeArray(2));
1340 try testing.expectEqualStrings("c", try r.takeArray(1));
1341}
1342
1343test peekArray {
1344 var r: Reader = .fixed("abc");
1345 try testing.expectEqualStrings("ab", try r.peekArray(2));
1346 try testing.expectEqualStrings("a", try r.peekArray(1));
1347}
1348
1349test discardAll {
1350 var r: Reader = .fixed("foobar");
1351 try r.discardAll(3);
1352 try testing.expectEqualStrings("bar", try r.take(3));
1353 try r.discardAll(0);
1354 try testing.expectError(error.EndOfStream, r.discardAll(1));
1355}
1356
1357test discardRemaining {
1358 var r: Reader = .fixed("foobar");
1359 r.toss(1);
1360 try testing.expectEqual(5, try r.discardRemaining());
1361 try testing.expectEqual(0, try r.discardRemaining());
1362}
1363
1364test stream {
1365 var out_buffer: [10]u8 = undefined;
1366 var r: Reader = .fixed("foobar");
1367 var w: Writer = .fixed(&out_buffer);
1368 // Short streams are possible with this function but not with fixed.
1369 try testing.expectEqual(2, try r.stream(&w, .limited(2)));
1370 try testing.expectEqualStrings("fo", w.buffered());
1371 try testing.expectEqual(4, try r.stream(&w, .unlimited));
1372 try testing.expectEqualStrings("foobar", w.buffered());
1373}
1374
1375test takeSentinel {
1376 var r: Reader = .fixed("ab\nc");
1377 try testing.expectEqualStrings("ab", try r.takeSentinel('\n'));
1378 try testing.expectError(error.EndOfStream, r.takeSentinel('\n'));
1379 try testing.expectEqualStrings("c", try r.peek(1));
1380}
1381
1382test peekSentinel {
1383 var r: Reader = .fixed("ab\nc");
1384 try testing.expectEqualStrings("ab", try r.peekSentinel('\n'));
1385 try testing.expectEqualStrings("ab", try r.peekSentinel('\n'));
1386}
1387
1388test takeDelimiterInclusive {
1389 var r: Reader = .fixed("ab\nc");
1390 try testing.expectEqualStrings("ab\n", try r.takeDelimiterInclusive('\n'));
1391 try testing.expectError(error.EndOfStream, r.takeDelimiterInclusive('\n'));
1392}
1393
1394test peekDelimiterInclusive {
1395 var r: Reader = .fixed("ab\nc");
1396 try testing.expectEqualStrings("ab\n", try r.peekDelimiterInclusive('\n'));
1397 try testing.expectEqualStrings("ab\n", try r.peekDelimiterInclusive('\n'));
1398 r.toss(3);
1399 try testing.expectError(error.EndOfStream, r.peekDelimiterInclusive('\n'));
1400}
1401
1402test takeDelimiterExclusive {
1403 var r: Reader = .fixed("ab\nc");
1404 try testing.expectEqualStrings("ab", try r.takeDelimiterExclusive('\n'));
1405 try testing.expectEqualStrings("c", try r.takeDelimiterExclusive('\n'));
1406 try testing.expectError(error.EndOfStream, r.takeDelimiterExclusive('\n'));
1407}
1408
1409test peekDelimiterExclusive {
1410 var r: Reader = .fixed("ab\nc");
1411 try testing.expectEqualStrings("ab", try r.peekDelimiterExclusive('\n'));
1412 try testing.expectEqualStrings("ab", try r.peekDelimiterExclusive('\n'));
1413 r.toss(3);
1414 try testing.expectEqualStrings("c", try r.peekDelimiterExclusive('\n'));
1415}
1416
1417test streamDelimiter {
1418 var out_buffer: [10]u8 = undefined;
1419 var r: Reader = .fixed("foo\nbars");
1420 var w: Writer = .fixed(&out_buffer);
1421 try testing.expectEqual(3, try r.streamDelimiter(&w, '\n'));
1422 try testing.expectEqualStrings("foo", w.buffered());
1423 try testing.expectEqual(0, try r.streamDelimiter(&w, '\n'));
1424 r.toss(1);
1425 try testing.expectError(error.EndOfStream, r.streamDelimiter(&w, '\n'));
1426}
1427
1428test streamDelimiterEnding {
1429 var out_buffer: [10]u8 = undefined;
1430 var r: Reader = .fixed("foo\nbars");
1431 var w: Writer = .fixed(&out_buffer);
1432 try testing.expectEqual(3, try r.streamDelimiterEnding(&w, '\n'));
1433 try testing.expectEqualStrings("foo", w.buffered());
1434 r.toss(1);
1435 try testing.expectEqual(4, try r.streamDelimiterEnding(&w, '\n'));
1436 try testing.expectEqualStrings("foobars", w.buffered());
1437 try testing.expectEqual(0, try r.streamDelimiterEnding(&w, '\n'));
1438 try testing.expectEqual(0, try r.streamDelimiterEnding(&w, '\n'));
1439}
1440
1441test streamDelimiterLimit {
1442 var out_buffer: [10]u8 = undefined;
1443 var r: Reader = .fixed("foo\nbars");
1444 var w: Writer = .fixed(&out_buffer);
1445 try testing.expectError(error.StreamTooLong, r.streamDelimiterLimit(&w, '\n', .limited(2)));
1446 try testing.expectEqual(1, try r.streamDelimiterLimit(&w, '\n', .limited(3)));
1447 try testing.expectEqualStrings("\n", try r.take(1));
1448 try testing.expectEqual(4, try r.streamDelimiterLimit(&w, '\n', .unlimited));
1449 try testing.expectEqualStrings("foobars", w.buffered());
1450}
1451
1452test discardDelimiterExclusive {
1453 var r: Reader = .fixed("foob\nar");
1454 try testing.expectEqual(4, try r.discardDelimiterExclusive('\n'));
1455 try testing.expectEqualStrings("\n", try r.take(1));
1456 try testing.expectEqual(2, try r.discardDelimiterExclusive('\n'));
1457 try testing.expectEqual(0, try r.discardDelimiterExclusive('\n'));
1458}
1459
1460test discardDelimiterInclusive {
1461 var r: Reader = .fixed("foob\nar");
1462 try testing.expectEqual(5, try r.discardDelimiterInclusive('\n'));
1463 try testing.expectError(error.EndOfStream, r.discardDelimiterInclusive('\n'));
1464}
1465
1466test discardDelimiterLimit {
1467 var r: Reader = .fixed("foob\nar");
1468 try testing.expectError(error.StreamTooLong, r.discardDelimiterLimit('\n', .limited(4)));
1469 try testing.expectEqual(0, try r.discardDelimiterLimit('\n', .limited(2)));
1470 try testing.expectEqualStrings("\n", try r.take(1));
1471 try testing.expectEqual(2, try r.discardDelimiterLimit('\n', .unlimited));
1472 try testing.expectEqual(0, try r.discardDelimiterLimit('\n', .unlimited));
1473}
1474
1475test fill {
1476 var r: Reader = .fixed("abc");
1477 try r.fill(1);
1478 try r.fill(3);
1479}
1480
1481test takeByte {
1482 var r: Reader = .fixed("ab");
1483 try testing.expectEqual('a', try r.takeByte());
1484 try testing.expectEqual('b', try r.takeByte());
1485 try testing.expectError(error.EndOfStream, r.takeByte());
1486}
1487
1488test takeByteSigned {
1489 var r: Reader = .fixed(&.{ 255, 5 });
1490 try testing.expectEqual(-1, try r.takeByteSigned());
1491 try testing.expectEqual(5, try r.takeByteSigned());
1492 try testing.expectError(error.EndOfStream, r.takeByteSigned());
1493}
1494
1495test takeInt {
1496 var r: Reader = .fixed(&.{ 0x12, 0x34, 0x56 });
1497 try testing.expectEqual(0x1234, try r.takeInt(u16, .big));
1498 try testing.expectError(error.EndOfStream, r.takeInt(u16, .little));
1499}
1500
1501test takeVarInt {
1502 var r: Reader = .fixed(&.{ 0x12, 0x34, 0x56 });
1503 try testing.expectEqual(0x123456, try r.takeVarInt(u64, .big, 3));
1504 try testing.expectError(error.EndOfStream, r.takeVarInt(u16, .little, 1));
1505}
1506
1507test takeStruct {
1508 var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 });
1509 const S = extern struct { a: u8, b: u16 };
1510 switch (native_endian) {
1511 .little => try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.takeStruct(S)).*),
1512 .big => try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.takeStruct(S)).*),
1513 }
1514 try testing.expectError(error.EndOfStream, r.takeStruct(S));
1515}
1516
1517test peekStruct {
1518 var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 });
1519 const S = extern struct { a: u8, b: u16 };
1520 switch (native_endian) {
1521 .little => {
1522 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.peekStruct(S)).*);
1523 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.peekStruct(S)).*);
1524 },
1525 .big => {
1526 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.peekStruct(S)).*);
1527 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.peekStruct(S)).*);
1528 },
1529 }
1530}
1531
1532test takeStructEndian {
1533 var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 });
1534 const S = extern struct { a: u8, b: u16 };
1535 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), try r.takeStructEndian(S, .big));
1536 try testing.expectError(error.EndOfStream, r.takeStructEndian(S, .little));
1537}
1538
1539test peekStructEndian {
1540 var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 });
1541 const S = extern struct { a: u8, b: u16 };
1542 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), try r.peekStructEndian(S, .big));
1543 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), try r.peekStructEndian(S, .little));
1544}
1545
1546test takeEnum {
1547 var r: Reader = .fixed(&.{ 2, 0, 1 });
1548 const E1 = enum(u8) { a, b, c };
1549 const E2 = enum(u16) { _ };
1550 try testing.expectEqual(E1.c, try r.takeEnum(E1, .little));
1551 try testing.expectEqual(@as(E2, @enumFromInt(0x0001)), try r.takeEnum(E2, .big));
1552}
1553
1554test takeLeb128 {
1555 var r: Reader = .fixed("\xc7\x9f\x7f\x80");
1556 try testing.expectEqual(-12345, try r.takeLeb128(i64));
1557 try testing.expectEqual(0x80, try r.peekByte());
1558 try testing.expectError(error.EndOfStream, r.takeLeb128(i64));
1559}
1560
1561test readSliceShort {
1562 var r: Reader = .fixed("HelloFren");
1563 var buf: [5]u8 = undefined;
1564 try testing.expectEqual(5, try r.readSliceShort(&buf));
1565 try testing.expectEqualStrings("Hello", buf[0..5]);
1566 try testing.expectEqual(4, try r.readSliceShort(&buf));
1567 try testing.expectEqualStrings("Fren", buf[0..4]);
1568 try testing.expectEqual(0, try r.readSliceShort(&buf));
1569}
1570
1571test readVec {
1572 var r: Reader = .fixed(std.ascii.letters);
1573 var flat_buffer: [52]u8 = undefined;
1574 var bufs: [2][]u8 = .{
1575 flat_buffer[0..26],
1576 flat_buffer[26..],
1577 };
1578 // Short reads are possible with this function but not with fixed.
1579 try testing.expectEqual(26 * 2, try r.readVec(&bufs));
1580 try testing.expectEqualStrings(std.ascii.letters[0..26], bufs[0]);
1581 try testing.expectEqualStrings(std.ascii.letters[26..], bufs[1]);
1582}
1583
1584test readVecLimit {
1585 var r: Reader = .fixed(std.ascii.letters);
1586 var flat_buffer: [52]u8 = undefined;
1587 var bufs: [2][]u8 = .{
1588 flat_buffer[0..26],
1589 flat_buffer[26..],
1590 };
1591 // Short reads are possible with this function but not with fixed.
1592 try testing.expectEqual(50, try r.readVecLimit(&bufs, .limited(50)));
1593 try testing.expectEqualStrings(std.ascii.letters[0..26], bufs[0]);
1594 try testing.expectEqualStrings(std.ascii.letters[26..50], bufs[1][0..24]);
1595}
1596
1597test "expected error.EndOfStream" {
1598 // Unit test inspired by https://github.com/ziglang/zig/issues/17733
1599 var buffer: [3]u8 = undefined;
1600 var r: std.io.Reader = .fixed(&buffer);
1601 r.end = 0; // capacity 3, but empty
1602 try std.testing.expectError(error.EndOfStream, r.takeEnum(enum(u8) { a, b }, .little));
1603 try std.testing.expectError(error.EndOfStream, r.take(3));
1604}
1605
1606fn endingStream(r: *Reader, w: *Writer, limit: Limit) StreamError!usize {
1607 _ = r;
1608 _ = w;
1609 _ = limit;
1610 return error.EndOfStream;
1611}
1612
1613fn endingDiscard(r: *Reader, limit: Limit) Error!usize {
1614 _ = r;
1615 _ = limit;
1616 return error.EndOfStream;
1617}
1618
1619fn failingStream(r: *Reader, w: *Writer, limit: Limit) StreamError!usize {
1620 _ = r;
1621 _ = w;
1622 _ = limit;
1623 return error.ReadFailed;
1624}
1625
1626fn failingDiscard(r: *Reader, limit: Limit) Error!usize {
1627 _ = r;
1628 _ = limit;
1629 return error.ReadFailed;
1630}
1631
1632test "readAlloc when the backing reader provides one byte at a time" {
1633 const OneByteReader = struct {
1634 str: []const u8,
1635 i: usize,
1636 reader: Reader,
1637
1638 fn stream(r: *Reader, w: *Writer, limit: Limit) StreamError!usize {
1639 assert(@intFromEnum(limit) >= 1);
1640 const self: *@This() = @fieldParentPtr("reader", r);
1641 if (self.str.len - self.i == 0) return error.EndOfStream;
1642 try w.writeByte(self.str[self.i]);
1643 self.i += 1;
1644 return 1;
1645 }
1646 };
1647 const str = "This is a test";
1648 var one_byte_stream: OneByteReader = .{
1649 .str = str,
1650 .i = 0,
1651 .reader = .{
1652 .buffer = &.{},
1653 .vtable = &.{ .stream = OneByteReader.stream },
1654 .seek = 0,
1655 .end = 0,
1656 },
1657 };
1658 const res = try one_byte_stream.reader.allocRemaining(std.testing.allocator, .unlimited);
1659 defer std.testing.allocator.free(res);
1660 try std.testing.expectEqualStrings(str, res);
1661}
1662
1663test "takeDelimiterInclusive when it rebases" {
1664 const written_line = "ABCDEFGHIJKLMNOPQRSTUVWXYZ\n";
1665 var buffer: [128]u8 = undefined;
1666 var tr: std.testing.Reader = .init(&buffer, &.{
1667 .{ .buffer = written_line },
1668 .{ .buffer = written_line },
1669 .{ .buffer = written_line },
1670 .{ .buffer = written_line },
1671 .{ .buffer = written_line },
1672 .{ .buffer = written_line },
1673 });
1674 const r = &tr.interface;
1675 for (0..6) |_| {
1676 try std.testing.expectEqualStrings(written_line, try r.takeDelimiterInclusive('\n'));
1677 }
1678}
1679
1680/// Provides a `Reader` implementation by passing data from an underlying
1681/// reader through `Hasher.update`.
1682///
1683/// The underlying reader is best unbuffered.
1684///
1685/// This implementation makes suboptimal buffering decisions due to being
1686/// generic. A better solution will involve creating a reader for each hash
1687/// function, where the discard buffer can be tailored to the hash
1688/// implementation details.
1689pub fn Hashed(comptime Hasher: type) type {
1690 return struct {
1691 in: *Reader,
1692 hasher: Hasher,
1693 interface: Reader,
1694
1695 pub fn init(in: *Reader, hasher: Hasher, buffer: []u8) @This() {
1696 return .{
1697 .in = in,
1698 .hasher = hasher,
1699 .interface = .{
1700 .vtable = &.{
1701 .read = @This().read,
1702 .discard = @This().discard,
1703 },
1704 .buffer = buffer,
1705 .end = 0,
1706 .seek = 0,
1707 },
1708 };
1709 }
1710
1711 fn read(r: *Reader, w: *Writer, limit: Limit) StreamError!usize {
1712 const this: *@This() = @alignCast(@fieldParentPtr("interface", r));
1713 const data = w.writableVector(limit);
1714 const n = try this.in.readVec(data);
1715 const result = w.advanceVector(n);
1716 var remaining: usize = n;
1717 for (data) |slice| {
1718 if (remaining < slice.len) {
1719 this.hasher.update(slice[0..remaining]);
1720 return result;
1721 } else {
1722 remaining -= slice.len;
1723 this.hasher.update(slice);
1724 }
1725 }
1726 assert(remaining == 0);
1727 return result;
1728 }
1729
1730 fn discard(r: *Reader, limit: Limit) Error!usize {
1731 const this: *@This() = @alignCast(@fieldParentPtr("interface", r));
1732 var w = this.hasher.writer(&.{});
1733 const n = this.in.stream(&w, limit) catch |err| switch (err) {
1734 error.WriteFailed => unreachable,
1735 else => |e| return e,
1736 };
1737 return n;
1738 }
1739 };
1740}
lib/std/Io/Reader/Limited.zig created+42
...@@ -0,0 +1,42 @@
1const Limited = @This();
2
3const std = @import("../../std.zig");
4const Reader = std.io.Reader;
5const Writer = std.io.Writer;
6const Limit = std.io.Limit;
7
8unlimited: *Reader,
9remaining: Limit,
10interface: Reader,
11
12pub fn init(reader: *Reader, limit: Limit, buffer: []u8) Limited {
13 return .{
14 .unlimited = reader,
15 .remaining = limit,
16 .interface = .{
17 .vtable = &.{
18 .stream = stream,
19 .discard = discard,
20 },
21 .buffer = buffer,
22 .seek = 0,
23 .end = 0,
24 },
25 };
26}
27
28fn stream(context: ?*anyopaque, w: *Writer, limit: Limit) Reader.StreamError!usize {
29 const l: *Limited = @alignCast(@ptrCast(context));
30 const combined_limit = limit.min(l.remaining);
31 const n = try l.unlimited_reader.read(w, combined_limit);
32 l.remaining = l.remaining.subtract(n).?;
33 return n;
34}
35
36fn discard(context: ?*anyopaque, limit: Limit) Reader.Error!usize {
37 const l: *Limited = @alignCast(@ptrCast(context));
38 const combined_limit = limit.min(l.remaining);
39 const n = try l.unlimited_reader.discard(combined_limit);
40 l.remaining = l.remaining.subtract(n).?;
41 return n;
42}
lib/std/Io/Writer.zig created+2491
...@@ -0,0 +1,2491 @@
1const builtin = @import("builtin");
2const native_endian = builtin.target.cpu.arch.endian();
3
4const Writer = @This();
5const std = @import("../std.zig");
6const assert = std.debug.assert;
7const Limit = std.io.Limit;
8const File = std.fs.File;
9const testing = std.testing;
10const Allocator = std.mem.Allocator;
11
12vtable: *const VTable,
13/// If this has length zero, the writer is unbuffered, and `flush` is a no-op.
14buffer: []u8,
15/// In `buffer` before this are buffered bytes, after this is `undefined`.
16end: usize = 0,
17
18pub const VTable = struct {
19 /// Sends bytes to the logical sink. A write will only be sent here if it
20 /// could not fit into `buffer`, or during a `flush` operation.
21 ///
22 /// `buffer[0..end]` is consumed first, followed by each slice of `data` in
23 /// order. Elements of `data` may alias each other but may not alias
24 /// `buffer`.
25 ///
26 /// This function modifies `Writer.end` and `Writer.buffer` in an
27 /// implementation-defined manner.
28 ///
29 /// `data.len` must be nonzero.
30 ///
31 /// The last element of `data` is repeated as necessary so that it is
32 /// written `splat` number of times, which may be zero.
33 ///
34 /// This function may not be called if the data to be written could have
35 /// been stored in `buffer` instead, including when the amount of data to
36 /// be written is zero and the buffer capacity is zero.
37 ///
38 /// Number of bytes consumed from `data` is returned, excluding bytes from
39 /// `buffer`.
40 ///
41 /// Number of bytes returned may be zero, which does not indicate stream
42 /// end. A subsequent call may return nonzero, or signal end of stream via
43 /// `error.WriteFailed`.
44 drain: *const fn (w: *Writer, data: []const []const u8, splat: usize) Error!usize,
45
46 /// Copies contents from an open file to the logical sink. `buffer[0..end]`
47 /// is consumed first, followed by `limit` bytes from `file_reader`.
48 ///
49 /// Number of bytes logically written is returned. This excludes bytes from
50 /// `buffer` because they have already been logically written. Number of
51 /// bytes consumed from `buffer` are tracked by modifying `end`.
52 ///
53 /// Number of bytes returned may be zero, which does not indicate stream
54 /// end. A subsequent call may return nonzero, or signal end of stream via
55 /// `error.WriteFailed`. Caller may check `file_reader` state
56 /// (`File.Reader.atEnd`) to disambiguate between a zero-length read or
57 /// write, and whether the file reached the end.
58 ///
59 /// `error.Unimplemented` indicates the callee cannot offer a more
60 /// efficient implementation than the caller performing its own reads.
61 sendFile: *const fn (
62 w: *Writer,
63 file_reader: *File.Reader,
64 /// Maximum amount of bytes to read from the file. Implementations may
65 /// assume that the file size does not exceed this amount. Data from
66 /// `buffer` does not count towards this limit.
67 limit: Limit,
68 ) FileError!usize = unimplementedSendFile,
69
70 /// Consumes all remaining buffer.
71 ///
72 /// The default flush implementation calls drain repeatedly until `end` is
73 /// zero, however it is legal for implementations to manage `end`
74 /// differently. For instance, `Allocating` flush is a no-op.
75 ///
76 /// There may be subsequent calls to `drain` and `sendFile` after a `flush`
77 /// operation.
78 flush: *const fn (w: *Writer) Error!void = defaultFlush,
79};
80
81pub const Error = error{
82 /// See the `Writer` implementation for detailed diagnostics.
83 WriteFailed,
84};
85
86pub const FileAllError = error{
87 /// Detailed diagnostics are found on the `File.Reader` struct.
88 ReadFailed,
89 /// See the `Writer` implementation for detailed diagnostics.
90 WriteFailed,
91};
92
93pub const FileReadingError = error{
94 /// Detailed diagnostics are found on the `File.Reader` struct.
95 ReadFailed,
96 /// See the `Writer` implementation for detailed diagnostics.
97 WriteFailed,
98 /// Reached the end of the file being read.
99 EndOfStream,
100};
101
102pub const FileError = error{
103 /// Detailed diagnostics are found on the `File.Reader` struct.
104 ReadFailed,
105 /// See the `Writer` implementation for detailed diagnostics.
106 WriteFailed,
107 /// Reached the end of the file being read.
108 EndOfStream,
109 /// Indicates the caller should do its own file reading; the callee cannot
110 /// offer a more efficient implementation.
111 Unimplemented,
112};
113
114/// Writes to `buffer` and returns `error.WriteFailed` when it is full.
115pub fn fixed(buffer: []u8) Writer {
116 return .{
117 .vtable = &.{ .drain = fixedDrain },
118 .buffer = buffer,
119 };
120}
121
122pub fn hashed(w: *Writer, hasher: anytype, buffer: []u8) Hashed(@TypeOf(hasher)) {
123 return .initHasher(w, hasher, buffer);
124}
125
126pub const failing: Writer = .{
127 .vtable = &.{
128 .drain = failingDrain,
129 .sendFile = failingSendFile,
130 },
131};
132
133/// Returns the contents not yet drained.
134pub fn buffered(w: *const Writer) []u8 {
135 return w.buffer[0..w.end];
136}
137
138pub fn countSplat(data: []const []const u8, splat: usize) usize {
139 var total: usize = 0;
140 for (data[0 .. data.len - 1]) |buf| total += buf.len;
141 total += data[data.len - 1].len * splat;
142 return total;
143}
144
145pub fn countSendFileLowerBound(n: usize, file_reader: *File.Reader, limit: Limit) ?usize {
146 const total: u64 = @min(@intFromEnum(limit), file_reader.getSize() catch return null);
147 return std.math.lossyCast(usize, total + n);
148}
149
150/// If the total number of bytes of `data` fits inside `unusedCapacitySlice`,
151/// this function is guaranteed to not fail, not call into `VTable`, and return
152/// the total bytes inside `data`.
153pub fn writeVec(w: *Writer, data: []const []const u8) Error!usize {
154 return writeSplat(w, data, 1);
155}
156
157/// If the number of bytes to write based on `data` and `splat` fits inside
158/// `unusedCapacitySlice`, this function is guaranteed to not fail, not call
159/// into `VTable`, and return the full number of bytes.
160pub fn writeSplat(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
161 assert(data.len > 0);
162 const buffer = w.buffer;
163 const count = countSplat(data, splat);
164 if (w.end + count > buffer.len) return w.vtable.drain(w, data, splat);
165 for (data[0 .. data.len - 1]) |bytes| {
166 @memcpy(buffer[w.end..][0..bytes.len], bytes);
167 w.end += bytes.len;
168 }
169 const pattern = data[data.len - 1];
170 switch (pattern.len) {
171 0 => {},
172 1 => {
173 @memset(buffer[w.end..][0..splat], pattern[0]);
174 w.end += splat;
175 },
176 else => for (0..splat) |_| {
177 @memcpy(buffer[w.end..][0..pattern.len], pattern);
178 w.end += pattern.len;
179 },
180 }
181 return count;
182}
183
184/// Returns how many bytes were consumed from `header` and `data`.
185pub fn writeSplatHeader(
186 w: *Writer,
187 header: []const u8,
188 data: []const []const u8,
189 splat: usize,
190) Error!usize {
191 const new_end = w.end + header.len;
192 if (new_end <= w.buffer.len) {
193 @memcpy(w.buffer[w.end..][0..header.len], header);
194 w.end = new_end;
195 return header.len + try writeSplat(w, data, splat);
196 }
197 var vecs: [8][]const u8 = undefined; // Arbitrarily chosen size.
198 var i: usize = 1;
199 vecs[0] = header;
200 for (data[0 .. data.len - 1]) |buf| {
201 if (buf.len == 0) continue;
202 vecs[i] = buf;
203 i += 1;
204 if (vecs.len - i == 0) break;
205 }
206 const pattern = data[data.len - 1];
207 const new_splat = s: {
208 if (pattern.len == 0 or vecs.len - i == 0) break :s 1;
209 vecs[i] = pattern;
210 i += 1;
211 break :s splat;
212 };
213 return w.vtable.drain(w, vecs[0..i], new_splat);
214}
215
216test "writeSplatHeader splatting avoids buffer aliasing temptation" {
217 const initial_buf = try testing.allocator.alloc(u8, 8);
218 var aw: std.io.Writer.Allocating = .initOwnedSlice(testing.allocator, initial_buf);
219 defer aw.deinit();
220 // This test assumes 8 vector buffer in this function.
221 const n = try aw.writer.writeSplatHeader("header which is longer than buf ", &.{
222 "1", "2", "3", "4", "5", "6", "foo", "bar", "foo",
223 }, 3);
224 try testing.expectEqual(41, n);
225 try testing.expectEqualStrings(
226 "header which is longer than buf 123456foo",
227 aw.writer.buffered(),
228 );
229}
230
231/// Drains all remaining buffered data.
232pub fn flush(w: *Writer) Error!void {
233 return w.vtable.flush(w);
234}
235
236/// Repeatedly calls `VTable.drain` until `end` is zero.
237pub fn defaultFlush(w: *Writer) Error!void {
238 const drainFn = w.vtable.drain;
239 while (w.end != 0) _ = try drainFn(w, &.{""}, 1);
240}
241
242/// Does nothing.
243pub fn noopFlush(w: *Writer) Error!void {
244 _ = w;
245}
246
247/// Calls `VTable.drain` but hides the last `preserve_length` bytes from the
248/// implementation, keeping them buffered.
249pub fn drainPreserve(w: *Writer, preserve_length: usize) Error!void {
250 const temp_end = w.end -| preserve_length;
251 const preserved = w.buffer[temp_end..w.end];
252 w.end = temp_end;
253 defer w.end += preserved.len;
254 assert(0 == try w.vtable.drain(w, &.{""}, 1));
255 assert(w.end <= temp_end + preserved.len);
256 @memmove(w.buffer[w.end..][0..preserved.len], preserved);
257}
258
259pub fn unusedCapacitySlice(w: *const Writer) []u8 {
260 return w.buffer[w.end..];
261}
262
263pub fn unusedCapacityLen(w: *const Writer) usize {
264 return w.buffer.len - w.end;
265}
266
267/// Asserts the provided buffer has total capacity enough for `len`.
268///
269/// Advances the buffer end position by `len`.
270pub fn writableArray(w: *Writer, comptime len: usize) Error!*[len]u8 {
271 const big_slice = try w.writableSliceGreedy(len);
272 advance(w, len);
273 return big_slice[0..len];
274}
275
276/// Asserts the provided buffer has total capacity enough for `len`.
277///
278/// Advances the buffer end position by `len`.
279pub fn writableSlice(w: *Writer, len: usize) Error![]u8 {
280 const big_slice = try w.writableSliceGreedy(len);
281 advance(w, len);
282 return big_slice[0..len];
283}
284
285/// Asserts the provided buffer has total capacity enough for `minimum_length`.
286///
287/// Does not `advance` the buffer end position.
288///
289/// If `minimum_length` is zero, this is equivalent to `unusedCapacitySlice`.
290pub fn writableSliceGreedy(w: *Writer, minimum_length: usize) Error![]u8 {
291 assert(w.buffer.len >= minimum_length);
292 while (w.buffer.len - w.end < minimum_length) {
293 assert(0 == try w.vtable.drain(w, &.{""}, 1));
294 } else {
295 @branchHint(.likely);
296 return w.buffer[w.end..];
297 }
298}
299
300/// Asserts the provided buffer has total capacity enough for `minimum_length`
301/// and `preserve_length` combined.
302///
303/// Does not `advance` the buffer end position.
304///
305/// When draining the buffer, ensures that at least `preserve_length` bytes
306/// remain buffered.
307///
308/// If `preserve_length` is zero, this is equivalent to `writableSliceGreedy`.
309pub fn writableSliceGreedyPreserve(w: *Writer, preserve_length: usize, minimum_length: usize) Error![]u8 {
310 assert(w.buffer.len >= preserve_length + minimum_length);
311 while (w.buffer.len - w.end < minimum_length) {
312 try drainPreserve(w, preserve_length);
313 } else {
314 @branchHint(.likely);
315 return w.buffer[w.end..];
316 }
317}
318
319pub const WritableVectorIterator = struct {
320 first: []u8,
321 middle: []const []u8 = &.{},
322 last: []u8 = &.{},
323 index: usize = 0,
324
325 pub fn next(it: *WritableVectorIterator) ?[]u8 {
326 while (true) {
327 const i = it.index;
328 it.index += 1;
329 if (i == 0) {
330 if (it.first.len == 0) continue;
331 return it.first;
332 }
333 const middle_index = i - 1;
334 if (middle_index < it.middle.len) {
335 const middle = it.middle[middle_index];
336 if (middle.len == 0) continue;
337 return middle;
338 }
339 if (middle_index == it.middle.len) {
340 if (it.last.len == 0) continue;
341 return it.last;
342 }
343 return null;
344 }
345 }
346};
347
348pub const VectorWrapper = struct {
349 writer: Writer,
350 it: WritableVectorIterator,
351 /// Tracks whether the "writable vector" API was used.
352 used: bool = false,
353 pub const vtable: *const VTable = &unique_vtable_allocation;
354 /// This is intended to be constant but it must be a unique address for
355 /// `@fieldParentPtr` to work.
356 var unique_vtable_allocation: VTable = .{ .drain = fixedDrain };
357};
358
359pub fn writableVectorIterator(w: *Writer) Error!WritableVectorIterator {
360 if (w.vtable == VectorWrapper.vtable) {
361 const wrapper: *VectorWrapper = @fieldParentPtr("writer", w);
362 wrapper.used = true;
363 return wrapper.it;
364 }
365 return .{ .first = try writableSliceGreedy(w, 1) };
366}
367
368pub fn writableVectorPosix(w: *Writer, buffer: []std.posix.iovec, limit: Limit) Error![]std.posix.iovec {
369 var it = try writableVectorIterator(w);
370 var i: usize = 0;
371 var remaining = limit;
372 while (it.next()) |full_buffer| {
373 if (!remaining.nonzero()) break;
374 if (buffer.len - i == 0) break;
375 const buf = remaining.slice(full_buffer);
376 if (buf.len == 0) continue;
377 buffer[i] = .{ .base = buf.ptr, .len = buf.len };
378 i += 1;
379 remaining = remaining.subtract(buf.len).?;
380 }
381 return buffer[0..i];
382}
383
384pub fn ensureUnusedCapacity(w: *Writer, n: usize) Error!void {
385 _ = try writableSliceGreedy(w, n);
386}
387
388pub fn undo(w: *Writer, n: usize) void {
389 w.end -= n;
390}
391
392/// After calling `writableSliceGreedy`, this function tracks how many bytes
393/// were written to it.
394///
395/// This is not needed when using `writableSlice` or `writableArray`.
396pub fn advance(w: *Writer, n: usize) void {
397 const new_end = w.end + n;
398 assert(new_end <= w.buffer.len);
399 w.end = new_end;
400}
401
402/// After calling `writableVector`, this function tracks how many bytes were
403/// written to it.
404pub fn advanceVector(w: *Writer, n: usize) usize {
405 return consume(w, n);
406}
407
408/// The `data` parameter is mutable because this function needs to mutate the
409/// fields in order to handle partial writes from `VTable.writeSplat`.
410pub fn writeVecAll(w: *Writer, data: [][]const u8) Error!void {
411 var index: usize = 0;
412 var truncate: usize = 0;
413 while (index < data.len) {
414 {
415 const untruncated = data[index];
416 data[index] = untruncated[truncate..];
417 defer data[index] = untruncated;
418 truncate += try w.writeVec(data[index..]);
419 }
420 while (index < data.len and truncate >= data[index].len) {
421 truncate -= data[index].len;
422 index += 1;
423 }
424 }
425}
426
427/// The `data` parameter is mutable because this function needs to mutate the
428/// fields in order to handle partial writes from `VTable.writeSplat`.
429pub fn writeSplatAll(w: *Writer, data: [][]const u8, splat: usize) Error!void {
430 var index: usize = 0;
431 var truncate: usize = 0;
432 var remaining_splat = splat;
433 while (index + 1 < data.len) {
434 {
435 const untruncated = data[index];
436 data[index] = untruncated[truncate..];
437 defer data[index] = untruncated;
438 truncate += try w.writeSplat(data[index..], remaining_splat);
439 }
440 while (truncate >= data[index].len) {
441 if (index + 1 < data.len) {
442 truncate -= data[index].len;
443 index += 1;
444 } else {
445 const last = data[data.len - 1];
446 remaining_splat -= @divExact(truncate, last.len);
447 while (remaining_splat > 0) {
448 const n = try w.writeSplat(data[data.len - 1 ..][0..1], remaining_splat);
449 remaining_splat -= @divExact(n, last.len);
450 }
451 return;
452 }
453 }
454 }
455}
456
457pub fn write(w: *Writer, bytes: []const u8) Error!usize {
458 if (w.end + bytes.len <= w.buffer.len) {
459 @branchHint(.likely);
460 @memcpy(w.buffer[w.end..][0..bytes.len], bytes);
461 w.end += bytes.len;
462 return bytes.len;
463 }
464 return w.vtable.drain(w, &.{bytes}, 1);
465}
466
467/// Asserts `buffer` capacity exceeds `preserve_length`.
468pub fn writePreserve(w: *Writer, preserve_length: usize, bytes: []const u8) Error!usize {
469 assert(preserve_length <= w.buffer.len);
470 if (w.end + bytes.len <= w.buffer.len) {
471 @branchHint(.likely);
472 @memcpy(w.buffer[w.end..][0..bytes.len], bytes);
473 w.end += bytes.len;
474 return bytes.len;
475 }
476 const temp_end = w.end -| preserve_length;
477 const preserved = w.buffer[temp_end..w.end];
478 w.end = temp_end;
479 defer w.end += preserved.len;
480 const n = try w.vtable.drain(w, &.{bytes}, 1);
481 assert(w.end <= temp_end + preserved.len);
482 @memmove(w.buffer[w.end..][0..preserved.len], preserved);
483 return n;
484}
485
486/// Calls `drain` as many times as necessary such that all of `bytes` are
487/// transferred.
488pub fn writeAll(w: *Writer, bytes: []const u8) Error!void {
489 var index: usize = 0;
490 while (index < bytes.len) index += try w.write(bytes[index..]);
491}
492
493/// Calls `drain` as many times as necessary such that all of `bytes` are
494/// transferred.
495///
496/// When draining the buffer, ensures that at least `preserve_length` bytes
497/// remain buffered.
498///
499/// Asserts `buffer` capacity exceeds `preserve_length`.
500pub fn writeAllPreserve(w: *Writer, preserve_length: usize, bytes: []const u8) Error!void {
501 var index: usize = 0;
502 while (index < bytes.len) index += try w.writePreserve(preserve_length, bytes[index..]);
503}
504
505/// Renders fmt string with args, calling `writer` with slices of bytes.
506/// If `writer` returns an error, the error is returned from `format` and
507/// `writer` is not called again.
508///
509/// The format string must be comptime-known and may contain placeholders following
510/// this format:
511/// `{[argument][specifier]:[fill][alignment][width].[precision]}`
512///
513/// Above, each word including its surrounding [ and ] is a parameter which you have to replace with something:
514///
515/// - *argument* is either the numeric index or the field name of the argument that should be inserted
516/// - when using a field name, you are required to enclose the field name (an identifier) in square
517/// brackets, e.g. {[score]...} as opposed to the numeric index form which can be written e.g. {2...}
518/// - *specifier* is a type-dependent formatting option that determines how a type should formatted (see below)
519/// - *fill* is a single byte which is used to pad formatted numbers.
520/// - *alignment* is one of the three bytes '<', '^', or '>' to make numbers
521/// left, center, or right-aligned, respectively.
522/// - Not all specifiers support alignment.
523/// - Alignment is not Unicode-aware; appropriate only when used with raw bytes or ASCII.
524/// - *width* is the total width of the field in bytes. This only applies to number formatting.
525/// - *precision* specifies how many decimals a formatted number should have.
526///
527/// Note that most of the parameters are optional and may be omitted. Also you
528/// can leave out separators like `:` and `.` when all parameters after the
529/// separator are omitted.
530///
531/// Only exception is the *fill* parameter. If a non-zero *fill* character is
532/// required at the same time as *width* is specified, one has to specify
533/// *alignment* as well, as otherwise the digit following `:` is interpreted as
534/// *width*, not *fill*.
535///
536/// The *specifier* has several options for types:
537/// - `x` and `X`: output numeric value in hexadecimal notation, or string in hexadecimal bytes
538/// - `s`:
539/// - for pointer-to-many and C pointers of u8, print as a C-string using zero-termination
540/// - for slices of u8, print the entire slice as a string without zero-termination
541/// - `t`:
542/// - for enums and tagged unions: prints the tag name
543/// - for error sets: prints the error name
544/// - `b64`: output string as standard base64
545/// - `e`: output floating point value in scientific notation
546/// - `d`: output numeric value in decimal notation
547/// - `b`: output integer value in binary notation
548/// - `o`: output integer value in octal notation
549/// - `c`: output integer as an ASCII character. Integer type must have 8 bits at max.
550/// - `u`: output integer as an UTF-8 sequence. Integer type must have 21 bits at max.
551/// - `D`: output nanoseconds as duration
552/// - `B`: output bytes in SI units (decimal)
553/// - `Bi`: output bytes in IEC units (binary)
554/// - `?`: output optional value as either the unwrapped value, or `null`; may be followed by a format specifier for the underlying value.
555/// - `!`: output error union value as either the unwrapped value, or the formatted error value; may be followed by a format specifier for the underlying value.
556/// - `*`: output the address of the value instead of the value itself.
557/// - `any`: output a value of any type using its default format.
558/// - `f`: delegates to a method on the type named "format" with the signature `fn (*Writer, args: anytype) Writer.Error!void`.
559///
560/// A user type may be a `struct`, `vector`, `union` or `enum` type.
561///
562/// To print literal curly braces, escape them by writing them twice, e.g. `{{` or `}}`.
563///
564/// Asserts `buffer` capacity of at least 2 if a union is printed. This
565/// requirement could be lifted by adjusting the code, but if you trigger that
566/// assertion it is a clue that you should probably be using a buffer.
567pub fn print(w: *Writer, comptime fmt: []const u8, args: anytype) Error!void {
568 const ArgsType = @TypeOf(args);
569 const args_type_info = @typeInfo(ArgsType);
570 if (args_type_info != .@"struct") {
571 @compileError("expected tuple or struct argument, found " ++ @typeName(ArgsType));
572 }
573
574 const fields_info = args_type_info.@"struct".fields;
575 const max_format_args = @typeInfo(std.fmt.ArgSetType).int.bits;
576 if (fields_info.len > max_format_args) {
577 @compileError("32 arguments max are supported per format call");
578 }
579
580 @setEvalBranchQuota(fmt.len * 1000);
581 comptime var arg_state: std.fmt.ArgState = .{ .args_len = fields_info.len };
582 comptime var i = 0;
583 comptime var literal: []const u8 = "";
584 inline while (true) {
585 const start_index = i;
586
587 inline while (i < fmt.len) : (i += 1) {
588 switch (fmt[i]) {
589 '{', '}' => break,
590 else => {},
591 }
592 }
593
594 comptime var end_index = i;
595 comptime var unescape_brace = false;
596
597 // Handle {{ and }}, those are un-escaped as single braces
598 if (i + 1 < fmt.len and fmt[i + 1] == fmt[i]) {
599 unescape_brace = true;
600 // Make the first brace part of the literal...
601 end_index += 1;
602 // ...and skip both
603 i += 2;
604 }
605
606 literal = literal ++ fmt[start_index..end_index];
607
608 // We've already skipped the other brace, restart the loop
609 if (unescape_brace) continue;
610
611 // Write out the literal
612 if (literal.len != 0) {
613 try w.writeAll(literal);
614 literal = "";
615 }
616
617 if (i >= fmt.len) break;
618
619 if (fmt[i] == '}') {
620 @compileError("missing opening {");
621 }
622
623 // Get past the {
624 comptime assert(fmt[i] == '{');
625 i += 1;
626
627 const fmt_begin = i;
628 // Find the closing brace
629 inline while (i < fmt.len and fmt[i] != '}') : (i += 1) {}
630 const fmt_end = i;
631
632 if (i >= fmt.len) {
633 @compileError("missing closing }");
634 }
635
636 // Get past the }
637 comptime assert(fmt[i] == '}');
638 i += 1;
639
640 const placeholder_array = fmt[fmt_begin..fmt_end].*;
641 const placeholder = comptime std.fmt.Placeholder.parse(&placeholder_array);
642 const arg_pos = comptime switch (placeholder.arg) {
643 .none => null,
644 .number => |pos| pos,
645 .named => |arg_name| std.meta.fieldIndex(ArgsType, arg_name) orelse
646 @compileError("no argument with name '" ++ arg_name ++ "'"),
647 };
648
649 const width = switch (placeholder.width) {
650 .none => null,
651 .number => |v| v,
652 .named => |arg_name| blk: {
653 const arg_i = comptime std.meta.fieldIndex(ArgsType, arg_name) orelse
654 @compileError("no argument with name '" ++ arg_name ++ "'");
655 _ = comptime arg_state.nextArg(arg_i) orelse @compileError("too few arguments");
656 break :blk @field(args, arg_name);
657 },
658 };
659
660 const precision = switch (placeholder.precision) {
661 .none => null,
662 .number => |v| v,
663 .named => |arg_name| blk: {
664 const arg_i = comptime std.meta.fieldIndex(ArgsType, arg_name) orelse
665 @compileError("no argument with name '" ++ arg_name ++ "'");
666 _ = comptime arg_state.nextArg(arg_i) orelse @compileError("too few arguments");
667 break :blk @field(args, arg_name);
668 },
669 };
670
671 const arg_to_print = comptime arg_state.nextArg(arg_pos) orelse
672 @compileError("too few arguments");
673
674 try w.printValue(
675 placeholder.specifier_arg,
676 .{
677 .fill = placeholder.fill,
678 .alignment = placeholder.alignment,
679 .width = width,
680 .precision = precision,
681 },
682 @field(args, fields_info[arg_to_print].name),
683 std.options.fmt_max_depth,
684 );
685 }
686
687 if (comptime arg_state.hasUnusedArgs()) {
688 const missing_count = arg_state.args_len - @popCount(arg_state.used_args);
689 switch (missing_count) {
690 0 => unreachable,
691 1 => @compileError("unused argument in '" ++ fmt ++ "'"),
692 else => @compileError(std.fmt.comptimePrint("{d}", .{missing_count}) ++ " unused arguments in '" ++ fmt ++ "'"),
693 }
694 }
695}
696
697/// Calls `drain` as many times as necessary such that `byte` is transferred.
698pub fn writeByte(w: *Writer, byte: u8) Error!void {
699 while (w.buffer.len - w.end == 0) {
700 const n = try w.vtable.drain(w, &.{&.{byte}}, 1);
701 if (n > 0) return;
702 } else {
703 @branchHint(.likely);
704 w.buffer[w.end] = byte;
705 w.end += 1;
706 }
707}
708
709/// When draining the buffer, ensures that at least `preserve_length` bytes
710/// remain buffered.
711pub fn writeBytePreserve(w: *Writer, preserve_length: usize, byte: u8) Error!void {
712 while (w.buffer.len - w.end == 0) {
713 try drainPreserve(w, preserve_length);
714 } else {
715 @branchHint(.likely);
716 w.buffer[w.end] = byte;
717 w.end += 1;
718 }
719}
720
721/// Writes the same byte many times, performing the underlying write call as
722/// many times as necessary.
723pub fn splatByteAll(w: *Writer, byte: u8, n: usize) Error!void {
724 var remaining: usize = n;
725 while (remaining > 0) remaining -= try w.splatByte(byte, remaining);
726}
727
728/// Writes the same byte many times, allowing short writes.
729///
730/// Does maximum of one underlying `VTable.drain`.
731pub fn splatByte(w: *Writer, byte: u8, n: usize) Error!usize {
732 return writeSplat(w, &.{&.{byte}}, n);
733}
734
735/// Writes the same slice many times, performing the underlying write call as
736/// many times as necessary.
737pub fn splatBytesAll(w: *Writer, bytes: []const u8, splat: usize) Error!void {
738 var remaining_bytes: usize = bytes.len * splat;
739 remaining_bytes -= try w.splatBytes(bytes, splat);
740 while (remaining_bytes > 0) {
741 const leftover = remaining_bytes % bytes.len;
742 const buffers: [2][]const u8 = .{ bytes[bytes.len - leftover ..], bytes };
743 remaining_bytes -= try w.splatBytes(&buffers, splat);
744 }
745}
746
747/// Writes the same slice many times, allowing short writes.
748///
749/// Does maximum of one underlying `VTable.writeSplat`.
750pub fn splatBytes(w: *Writer, bytes: []const u8, n: usize) Error!usize {
751 return writeSplat(w, &.{bytes}, n);
752}
753
754/// Asserts the `buffer` was initialized with a capacity of at least `@sizeOf(T)` bytes.
755pub inline fn writeInt(w: *Writer, comptime T: type, value: T, endian: std.builtin.Endian) Error!void {
756 var bytes: [@divExact(@typeInfo(T).int.bits, 8)]u8 = undefined;
757 std.mem.writeInt(std.math.ByteAlignedInt(@TypeOf(value)), &bytes, value, endian);
758 return w.writeAll(&bytes);
759}
760
761pub fn writeStruct(w: *Writer, value: anytype) Error!void {
762 // Only extern and packed structs have defined in-memory layout.
763 comptime assert(@typeInfo(@TypeOf(value)).@"struct".layout != .auto);
764 return w.writeAll(std.mem.asBytes(&value));
765}
766
767/// The function is inline to avoid the dead code in case `endian` is
768/// comptime-known and matches host endianness.
769/// TODO: make sure this value is not a reference type
770pub inline fn writeStructEndian(w: *Writer, value: anytype, endian: std.builtin.Endian) Error!void {
771 switch (@typeInfo(@TypeOf(value))) {
772 .@"struct" => |info| switch (info.layout) {
773 .auto => @compileError("ill-defined memory layout"),
774 .@"extern" => {
775 if (native_endian == endian) {
776 return w.writeStruct(value);
777 } else {
778 var copy = value;
779 std.mem.byteSwapAllFields(@TypeOf(value), &copy);
780 return w.writeStruct(copy);
781 }
782 },
783 .@"packed" => {
784 return writeInt(w, info.backing_integer.?, @bitCast(value), endian);
785 },
786 },
787 else => @compileError("not a struct"),
788 }
789}
790
791pub inline fn writeSliceEndian(
792 w: *Writer,
793 Elem: type,
794 slice: []const Elem,
795 endian: std.builtin.Endian,
796) Error!void {
797 if (native_endian == endian) {
798 return writeAll(w, @ptrCast(slice));
799 } else {
800 return w.writeArraySwap(w, Elem, slice);
801 }
802}
803
804/// Unlike `writeSplat` and `writeVec`, this function will call into `VTable`
805/// even if there is enough buffer capacity for the file contents.
806///
807/// Although it would be possible to eliminate `error.Unimplemented` from the
808/// error set by reading directly into the buffer in such case, this is not
809/// done because it is more efficient to do it higher up the call stack so that
810/// the error does not occur with each write.
811///
812/// See `sendFileReading` for an alternative that does not have
813/// `error.Unimplemented` in the error set.
814pub fn sendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize {
815 return w.vtable.sendFile(w, file_reader, limit);
816}
817
818/// Returns how many bytes from `header` and `file_reader` were consumed.
819pub fn sendFileHeader(
820 w: *Writer,
821 header: []const u8,
822 file_reader: *File.Reader,
823 limit: Limit,
824) FileError!usize {
825 const new_end = w.end + header.len;
826 if (new_end <= w.buffer.len) {
827 @memcpy(w.buffer[w.end..][0..header.len], header);
828 w.end = new_end;
829 return header.len + try w.vtable.sendFile(w, file_reader, limit);
830 }
831 const buffered_contents = limit.slice(file_reader.interface.buffered());
832 const n = try w.vtable.drain(w, &.{ header, buffered_contents }, 1);
833 file_reader.interface.toss(n - header.len);
834 return n;
835}
836
837/// Asserts nonzero buffer capacity.
838pub fn sendFileReading(w: *Writer, file_reader: *File.Reader, limit: Limit) FileReadingError!usize {
839 const dest = limit.slice(try w.writableSliceGreedy(1));
840 const n = try file_reader.read(dest);
841 w.advance(n);
842 return n;
843}
844
845/// Number of bytes logically written is returned. This excludes bytes from
846/// `buffer` because they have already been logically written.
847pub fn sendFileAll(w: *Writer, file_reader: *File.Reader, limit: Limit) FileAllError!usize {
848 var remaining = @intFromEnum(limit);
849 while (remaining > 0) {
850 const n = sendFile(w, file_reader, .limited(remaining)) catch |err| switch (err) {
851 error.EndOfStream => break,
852 error.Unimplemented => {
853 file_reader.mode = file_reader.mode.toReading();
854 remaining -= try w.sendFileReadingAll(file_reader, .limited(remaining));
855 break;
856 },
857 else => |e| return e,
858 };
859 remaining -= n;
860 }
861 return @intFromEnum(limit) - remaining;
862}
863
864/// Equivalent to `sendFileAll` but uses direct `pread` and `read` calls on
865/// `file` rather than `sendFile`. This is generally used as a fallback when
866/// the underlying implementation returns `error.Unimplemented`, which is why
867/// that error code does not appear in this function's error set.
868///
869/// Asserts nonzero buffer capacity.
870pub fn sendFileReadingAll(w: *Writer, file_reader: *File.Reader, limit: Limit) FileAllError!usize {
871 var remaining = @intFromEnum(limit);
872 while (remaining > 0) {
873 remaining -= sendFileReading(w, file_reader, .limited(remaining)) catch |err| switch (err) {
874 error.EndOfStream => break,
875 else => |e| return e,
876 };
877 }
878 return @intFromEnum(limit) - remaining;
879}
880
881pub fn alignBuffer(
882 w: *Writer,
883 buffer: []const u8,
884 width: usize,
885 alignment: std.fmt.Alignment,
886 fill: u8,
887) Error!void {
888 const padding = if (buffer.len < width) width - buffer.len else 0;
889 if (padding == 0) {
890 @branchHint(.likely);
891 return w.writeAll(buffer);
892 }
893 switch (alignment) {
894 .left => {
895 try w.writeAll(buffer);
896 try w.splatByteAll(fill, padding);
897 },
898 .center => {
899 const left_padding = padding / 2;
900 const right_padding = (padding + 1) / 2;
901 try w.splatByteAll(fill, left_padding);
902 try w.writeAll(buffer);
903 try w.splatByteAll(fill, right_padding);
904 },
905 .right => {
906 try w.splatByteAll(fill, padding);
907 try w.writeAll(buffer);
908 },
909 }
910}
911
912pub fn alignBufferOptions(w: *Writer, buffer: []const u8, options: std.fmt.Options) Error!void {
913 return w.alignBuffer(buffer, options.width orelse buffer.len, options.alignment, options.fill);
914}
915
916pub fn printAddress(w: *Writer, value: anytype) Error!void {
917 const T = @TypeOf(value);
918 switch (@typeInfo(T)) {
919 .pointer => |info| {
920 try w.writeAll(@typeName(info.child) ++ "@");
921 const int = if (info.size == .slice) @intFromPtr(value.ptr) else @intFromPtr(value);
922 return w.printInt(int, 16, .lower, .{});
923 },
924 .optional => |info| {
925 if (@typeInfo(info.child) == .pointer) {
926 try w.writeAll(@typeName(info.child) ++ "@");
927 try w.printInt(@intFromPtr(value), 16, .lower, .{});
928 return;
929 }
930 },
931 else => {},
932 }
933
934 @compileError("cannot format non-pointer type " ++ @typeName(T) ++ " with * specifier");
935}
936
937/// Asserts `buffer` capacity of at least 2 if `value` is a union.
938pub fn printValue(
939 w: *Writer,
940 comptime fmt: []const u8,
941 options: std.fmt.Options,
942 value: anytype,
943 max_depth: usize,
944) Error!void {
945 const T = @TypeOf(value);
946
947 switch (fmt.len) {
948 1 => switch (fmt[0]) {
949 '*' => return w.printAddress(value),
950 'f' => return value.format(w),
951 'd' => switch (@typeInfo(T)) {
952 .float, .comptime_float => return printFloat(w, value, options.toNumber(.decimal, .lower)),
953 .int, .comptime_int => return printInt(w, value, 10, .lower, options),
954 .@"struct" => return value.formatNumber(w, options.toNumber(.decimal, .lower)),
955 .@"enum" => return printInt(w, @intFromEnum(value), 10, .lower, options),
956 .vector => return printVector(w, fmt, options, value, max_depth),
957 else => invalidFmtError(fmt, value),
958 },
959 'c' => return w.printAsciiChar(value, options),
960 'u' => return w.printUnicodeCodepoint(value),
961 'b' => switch (@typeInfo(T)) {
962 .int, .comptime_int => return printInt(w, value, 2, .lower, options),
963 .@"enum" => return printInt(w, @intFromEnum(value), 2, .lower, options),
964 .@"struct" => return value.formatNumber(w, options.toNumber(.binary, .lower)),
965 .vector => return printVector(w, fmt, options, value, max_depth),
966 else => invalidFmtError(fmt, value),
967 },
968 'o' => switch (@typeInfo(T)) {
969 .int, .comptime_int => return printInt(w, value, 8, .lower, options),
970 .@"enum" => return printInt(w, @intFromEnum(value), 8, .lower, options),
971 .@"struct" => return value.formatNumber(w, options.toNumber(.octal, .lower)),
972 .vector => return printVector(w, fmt, options, value, max_depth),
973 else => invalidFmtError(fmt, value),
974 },
975 'x' => switch (@typeInfo(T)) {
976 .float, .comptime_float => return printFloatHexOptions(w, value, options.toNumber(.hex, .lower)),
977 .int, .comptime_int => return printInt(w, value, 16, .lower, options),
978 .@"enum" => return printInt(w, @intFromEnum(value), 16, .lower, options),
979 .@"struct" => return value.formatNumber(w, options.toNumber(.hex, .lower)),
980 .pointer => |info| switch (info.size) {
981 .one, .slice => {
982 const slice: []const u8 = value;
983 optionsForbidden(options);
984 return printHex(w, slice, .lower);
985 },
986 .many, .c => {
987 const slice: [:0]const u8 = std.mem.span(value);
988 optionsForbidden(options);
989 return printHex(w, slice, .lower);
990 },
991 },
992 .array => {
993 const slice: []const u8 = &value;
994 optionsForbidden(options);
995 return printHex(w, slice, .lower);
996 },
997 .vector => return printVector(w, fmt, options, value, max_depth),
998 else => invalidFmtError(fmt, value),
999 },
1000 'X' => switch (@typeInfo(T)) {
1001 .float, .comptime_float => return printFloatHexOptions(w, value, options.toNumber(.hex, .lower)),
1002 .int, .comptime_int => return printInt(w, value, 16, .upper, options),
1003 .@"enum" => return printInt(w, @intFromEnum(value), 16, .upper, options),
1004 .@"struct" => return value.formatNumber(w, options.toNumber(.hex, .upper)),
1005 .pointer => |info| switch (info.size) {
1006 .one, .slice => {
1007 const slice: []const u8 = value;
1008 optionsForbidden(options);
1009 return printHex(w, slice, .upper);
1010 },
1011 .many, .c => {
1012 const slice: [:0]const u8 = std.mem.span(value);
1013 optionsForbidden(options);
1014 return printHex(w, slice, .upper);
1015 },
1016 },
1017 .array => {
1018 const slice: []const u8 = &value;
1019 optionsForbidden(options);
1020 return printHex(w, slice, .upper);
1021 },
1022 .vector => return printVector(w, fmt, options, value, max_depth),
1023 else => invalidFmtError(fmt, value),
1024 },
1025 's' => switch (@typeInfo(T)) {
1026 .pointer => |info| switch (info.size) {
1027 .one, .slice => {
1028 const slice: []const u8 = value;
1029 return w.alignBufferOptions(slice, options);
1030 },
1031 .many, .c => {
1032 const slice: [:0]const u8 = std.mem.span(value);
1033 return w.alignBufferOptions(slice, options);
1034 },
1035 },
1036 .array => {
1037 const slice: []const u8 = &value;
1038 return w.alignBufferOptions(slice, options);
1039 },
1040 else => invalidFmtError(fmt, value),
1041 },
1042 'B' => switch (@typeInfo(T)) {
1043 .int, .comptime_int => return w.printByteSize(value, .decimal, options),
1044 .@"struct" => return value.formatByteSize(w, .decimal),
1045 else => invalidFmtError(fmt, value),
1046 },
1047 'D' => switch (@typeInfo(T)) {
1048 .int, .comptime_int => return w.printDuration(value, options),
1049 .@"struct" => return value.formatDuration(w),
1050 else => invalidFmtError(fmt, value),
1051 },
1052 'e' => switch (@typeInfo(T)) {
1053 .float, .comptime_float => return printFloat(w, value, options.toNumber(.scientific, .lower)),
1054 .@"struct" => return value.formatNumber(w, options.toNumber(.scientific, .lower)),
1055 else => invalidFmtError(fmt, value),
1056 },
1057 'E' => switch (@typeInfo(T)) {
1058 .float, .comptime_float => return printFloat(w, value, options.toNumber(.scientific, .upper)),
1059 .@"struct" => return value.formatNumber(w, options.toNumber(.scientific, .upper)),
1060 else => invalidFmtError(fmt, value),
1061 },
1062 't' => switch (@typeInfo(T)) {
1063 .error_set => return w.writeAll(@errorName(value)),
1064 .@"enum", .@"union" => return w.writeAll(@tagName(value)),
1065 else => invalidFmtError(fmt, value),
1066 },
1067 else => {},
1068 },
1069 2 => switch (fmt[0]) {
1070 'B' => switch (fmt[1]) {
1071 'i' => switch (@typeInfo(T)) {
1072 .int, .comptime_int => return w.printByteSize(value, .binary, options),
1073 .@"struct" => return value.formatByteSize(w, .binary),
1074 else => invalidFmtError(fmt, value),
1075 },
1076 else => {},
1077 },
1078 else => {},
1079 },
1080 3 => if (fmt[0] == 'b' and fmt[1] == '6' and fmt[2] == '4') switch (@typeInfo(T)) {
1081 .pointer => |info| switch (info.size) {
1082 .one, .slice => {
1083 const slice: []const u8 = value;
1084 optionsForbidden(options);
1085 return w.printBase64(slice);
1086 },
1087 .many, .c => {
1088 const slice: [:0]const u8 = std.mem.span(value);
1089 optionsForbidden(options);
1090 return w.printBase64(slice);
1091 },
1092 },
1093 .array => {
1094 const slice: []const u8 = &value;
1095 optionsForbidden(options);
1096 return w.printBase64(slice);
1097 },
1098 else => invalidFmtError(fmt, value),
1099 },
1100 else => {},
1101 }
1102
1103 const is_any = comptime std.mem.eql(u8, fmt, ANY);
1104 if (!is_any and std.meta.hasMethod(T, "format") and fmt.len == 0) {
1105 // after 0.15.0 is tagged, delete this compile error and its condition
1106 @compileError("ambiguous format string; specify {f} to call format method, or {any} to skip it");
1107 }
1108
1109 switch (@typeInfo(T)) {
1110 .float, .comptime_float => {
1111 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1112 return printFloat(w, value, options.toNumber(.decimal, .lower));
1113 },
1114 .int, .comptime_int => {
1115 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1116 return printInt(w, value, 10, .lower, options);
1117 },
1118 .bool => {
1119 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1120 const string: []const u8 = if (value) "true" else "false";
1121 return w.alignBufferOptions(string, options);
1122 },
1123 .void => {
1124 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1125 return w.alignBufferOptions("void", options);
1126 },
1127 .optional => {
1128 const remaining_fmt = comptime if (fmt.len > 0 and fmt[0] == '?')
1129 stripOptionalOrErrorUnionSpec(fmt)
1130 else if (is_any)
1131 ANY
1132 else
1133 @compileError("cannot print optional without a specifier (i.e. {?} or {any})");
1134 if (value) |payload| {
1135 return w.printValue(remaining_fmt, options, payload, max_depth);
1136 } else {
1137 return w.alignBufferOptions("null", options);
1138 }
1139 },
1140 .error_union => {
1141 const remaining_fmt = comptime if (fmt.len > 0 and fmt[0] == '!')
1142 stripOptionalOrErrorUnionSpec(fmt)
1143 else if (is_any)
1144 ANY
1145 else
1146 @compileError("cannot print error union without a specifier (i.e. {!} or {any})");
1147 if (value) |payload| {
1148 return w.printValue(remaining_fmt, options, payload, max_depth);
1149 } else |err| {
1150 return w.printValue("", options, err, max_depth);
1151 }
1152 },
1153 .error_set => {
1154 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1155 optionsForbidden(options);
1156 return printErrorSet(w, value);
1157 },
1158 .@"enum" => |info| {
1159 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1160 optionsForbidden(options);
1161 if (info.is_exhaustive) {
1162 return printEnumExhaustive(w, value);
1163 } else {
1164 return printEnumNonexhaustive(w, value);
1165 }
1166 },
1167 .@"union" => |info| {
1168 if (!is_any) {
1169 if (fmt.len != 0) invalidFmtError(fmt, value);
1170 return printValue(w, ANY, options, value, max_depth);
1171 }
1172 if (max_depth == 0) {
1173 try w.writeAll(".{ ... }");
1174 return;
1175 }
1176 if (info.tag_type) |UnionTagType| {
1177 try w.writeAll(".{ .");
1178 try w.writeAll(@tagName(@as(UnionTagType, value)));
1179 try w.writeAll(" = ");
1180 inline for (info.fields) |u_field| {
1181 if (value == @field(UnionTagType, u_field.name)) {
1182 try w.printValue(ANY, options, @field(value, u_field.name), max_depth - 1);
1183 }
1184 }
1185 try w.writeAll(" }");
1186 } else switch (info.layout) {
1187 .auto => {
1188 return w.writeAll(".{ ... }");
1189 },
1190 .@"extern", .@"packed" => {
1191 if (info.fields.len == 0) return w.writeAll(".{}");
1192 try w.writeAll(".{ ");
1193 inline for (info.fields) |field| {
1194 try w.writeByte('.');
1195 try w.writeAll(field.name);
1196 try w.writeAll(" = ");
1197 try w.printValue(ANY, options, @field(value, field.name), max_depth - 1);
1198 (try w.writableArray(2)).* = ", ".*;
1199 }
1200 w.buffer[w.end - 2 ..][0..2].* = " }".*;
1201 },
1202 }
1203 },
1204 .@"struct" => |info| {
1205 if (!is_any) {
1206 if (fmt.len != 0) invalidFmtError(fmt, value);
1207 return printValue(w, ANY, options, value, max_depth);
1208 }
1209 if (info.is_tuple) {
1210 // Skip the type and field names when formatting tuples.
1211 if (max_depth == 0) {
1212 try w.writeAll(".{ ... }");
1213 return;
1214 }
1215 try w.writeAll(".{");
1216 inline for (info.fields, 0..) |f, i| {
1217 if (i == 0) {
1218 try w.writeAll(" ");
1219 } else {
1220 try w.writeAll(", ");
1221 }
1222 try w.printValue(ANY, options, @field(value, f.name), max_depth - 1);
1223 }
1224 try w.writeAll(" }");
1225 return;
1226 }
1227 if (max_depth == 0) {
1228 try w.writeAll(".{ ... }");
1229 return;
1230 }
1231 try w.writeAll(".{");
1232 inline for (info.fields, 0..) |f, i| {
1233 if (i == 0) {
1234 try w.writeAll(" .");
1235 } else {
1236 try w.writeAll(", .");
1237 }
1238 try w.writeAll(f.name);
1239 try w.writeAll(" = ");
1240 try w.printValue(ANY, options, @field(value, f.name), max_depth - 1);
1241 }
1242 try w.writeAll(" }");
1243 },
1244 .pointer => |ptr_info| switch (ptr_info.size) {
1245 .one => switch (@typeInfo(ptr_info.child)) {
1246 .array => |array_info| return w.printValue(fmt, options, @as([]const array_info.child, value), max_depth),
1247 .@"enum", .@"union", .@"struct" => return w.printValue(fmt, options, value.*, max_depth),
1248 else => {
1249 var buffers: [2][]const u8 = .{ @typeName(ptr_info.child), "@" };
1250 try w.writeVecAll(&buffers);
1251 try w.printInt(@intFromPtr(value), 16, .lower, options);
1252 return;
1253 },
1254 },
1255 .many, .c => {
1256 if (!is_any) @compileError("cannot format pointer without a specifier (i.e. {s} or {*})");
1257 optionsForbidden(options);
1258 try w.printAddress(value);
1259 },
1260 .slice => {
1261 if (!is_any)
1262 @compileError("cannot format slice without a specifier (i.e. {s}, {x}, {b64}, or {any})");
1263 if (max_depth == 0) return w.writeAll("{ ... }");
1264 try w.writeAll("{ ");
1265 for (value, 0..) |elem, i| {
1266 try w.printValue(fmt, options, elem, max_depth - 1);
1267 if (i != value.len - 1) {
1268 try w.writeAll(", ");
1269 }
1270 }
1271 try w.writeAll(" }");
1272 },
1273 },
1274 .array => {
1275 if (!is_any) @compileError("cannot format array without a specifier (i.e. {s} or {any})");
1276 if (max_depth == 0) return w.writeAll("{ ... }");
1277 try w.writeAll("{ ");
1278 for (value, 0..) |elem, i| {
1279 try w.printValue(fmt, options, elem, max_depth - 1);
1280 if (i < value.len - 1) {
1281 try w.writeAll(", ");
1282 }
1283 }
1284 try w.writeAll(" }");
1285 },
1286 .vector => {
1287 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1288 return printVector(w, fmt, options, value, max_depth);
1289 },
1290 .@"fn" => @compileError("unable to format function body type, use '*const " ++ @typeName(T) ++ "' for a function pointer type"),
1291 .type => {
1292 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1293 return w.alignBufferOptions(@typeName(value), options);
1294 },
1295 .enum_literal => {
1296 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1297 optionsForbidden(options);
1298 var vecs: [2][]const u8 = .{ ".", @tagName(value) };
1299 return w.writeVecAll(&vecs);
1300 },
1301 .null => {
1302 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1303 return w.alignBufferOptions("null", options);
1304 },
1305 else => @compileError("unable to format type '" ++ @typeName(T) ++ "'"),
1306 }
1307}
1308
1309fn optionsForbidden(options: std.fmt.Options) void {
1310 assert(options.precision == null);
1311 assert(options.width == null);
1312}
1313
1314fn printErrorSet(w: *Writer, error_set: anyerror) Error!void {
1315 var vecs: [2][]const u8 = .{ "error.", @errorName(error_set) };
1316 try w.writeVecAll(&vecs);
1317}
1318
1319fn printEnumExhaustive(w: *Writer, value: anytype) Error!void {
1320 var vecs: [2][]const u8 = .{ ".", @tagName(value) };
1321 try w.writeVecAll(&vecs);
1322}
1323
1324fn printEnumNonexhaustive(w: *Writer, value: anytype) Error!void {
1325 if (std.enums.tagName(@TypeOf(value), value)) |tag_name| {
1326 var vecs: [2][]const u8 = .{ ".", tag_name };
1327 try w.writeVecAll(&vecs);
1328 return;
1329 }
1330 try w.writeAll("@enumFromInt(");
1331 try w.printInt(@intFromEnum(value), 10, .lower, .{});
1332 try w.writeByte(')');
1333}
1334
1335pub fn printVector(
1336 w: *Writer,
1337 comptime fmt: []const u8,
1338 options: std.fmt.Options,
1339 value: anytype,
1340 max_depth: usize,
1341) Error!void {
1342 const len = @typeInfo(@TypeOf(value)).vector.len;
1343 if (max_depth == 0) return w.writeAll("{ ... }");
1344 try w.writeAll("{ ");
1345 inline for (0..len) |i| {
1346 try w.printValue(fmt, options, value[i], max_depth - 1);
1347 if (i < len - 1) try w.writeAll(", ");
1348 }
1349 try w.writeAll(" }");
1350}
1351
1352// A wrapper around `printIntAny` to avoid the generic explosion of this
1353// function by funneling smaller integer types through `isize` and `usize`.
1354pub inline fn printInt(
1355 w: *Writer,
1356 value: anytype,
1357 base: u8,
1358 case: std.fmt.Case,
1359 options: std.fmt.Options,
1360) Error!void {
1361 switch (@TypeOf(value)) {
1362 isize, usize => {},
1363 comptime_int => {
1364 if (comptime std.math.cast(usize, value)) |x| return printIntAny(w, x, base, case, options);
1365 if (comptime std.math.cast(isize, value)) |x| return printIntAny(w, x, base, case, options);
1366 const Int = std.math.IntFittingRange(value, value);
1367 return printIntAny(w, @as(Int, value), base, case, options);
1368 },
1369 else => switch (@typeInfo(@TypeOf(value)).int.signedness) {
1370 .signed => if (std.math.cast(isize, value)) |x| return printIntAny(w, x, base, case, options),
1371 .unsigned => if (std.math.cast(usize, value)) |x| return printIntAny(w, x, base, case, options),
1372 },
1373 }
1374 return printIntAny(w, value, base, case, options);
1375}
1376
1377/// In general, prefer `printInt` to avoid generic explosion. However this
1378/// function may be used when optimal codegen for a particular integer type is
1379/// desired.
1380pub fn printIntAny(
1381 w: *Writer,
1382 value: anytype,
1383 base: u8,
1384 case: std.fmt.Case,
1385 options: std.fmt.Options,
1386) Error!void {
1387 assert(base >= 2);
1388 const value_info = @typeInfo(@TypeOf(value)).int;
1389
1390 // The type must have the same size as `base` or be wider in order for the
1391 // division to work
1392 const min_int_bits = comptime @max(value_info.bits, 8);
1393 const MinInt = std.meta.Int(.unsigned, min_int_bits);
1394
1395 const abs_value = @abs(value);
1396 // The worst case in terms of space needed is base 2, plus 1 for the sign
1397 var buf: [1 + @max(@as(comptime_int, value_info.bits), 1)]u8 = undefined;
1398
1399 var a: MinInt = abs_value;
1400 var index: usize = buf.len;
1401
1402 if (base == 10) {
1403 while (a >= 100) : (a = @divTrunc(a, 100)) {
1404 index -= 2;
1405 buf[index..][0..2].* = std.fmt.digits2(@intCast(a % 100));
1406 }
1407
1408 if (a < 10) {
1409 index -= 1;
1410 buf[index] = '0' + @as(u8, @intCast(a));
1411 } else {
1412 index -= 2;
1413 buf[index..][0..2].* = std.fmt.digits2(@intCast(a));
1414 }
1415 } else {
1416 while (true) {
1417 const digit = a % base;
1418 index -= 1;
1419 buf[index] = std.fmt.digitToChar(@intCast(digit), case);
1420 a /= base;
1421 if (a == 0) break;
1422 }
1423 }
1424
1425 if (value_info.signedness == .signed) {
1426 if (value < 0) {
1427 // Negative integer
1428 index -= 1;
1429 buf[index] = '-';
1430 } else if (options.width == null or options.width.? == 0) {
1431 // Positive integer, omit the plus sign
1432 } else {
1433 // Positive integer
1434 index -= 1;
1435 buf[index] = '+';
1436 }
1437 }
1438
1439 return w.alignBufferOptions(buf[index..], options);
1440}
1441
1442pub fn printAsciiChar(w: *Writer, c: u8, options: std.fmt.Options) Error!void {
1443 return w.alignBufferOptions(@as(*const [1]u8, &c), options);
1444}
1445
1446pub fn printAscii(w: *Writer, bytes: []const u8, options: std.fmt.Options) Error!void {
1447 return w.alignBufferOptions(bytes, options);
1448}
1449
1450pub fn printUnicodeCodepoint(w: *Writer, c: u21) Error!void {
1451 var buf: [4]u8 = undefined;
1452 const len = std.unicode.utf8Encode(c, &buf) catch |err| switch (err) {
1453 error.Utf8CannotEncodeSurrogateHalf, error.CodepointTooLarge => l: {
1454 buf[0..3].* = std.unicode.replacement_character_utf8;
1455 break :l 3;
1456 },
1457 };
1458 return w.writeAll(buf[0..len]);
1459}
1460
1461/// Uses a larger stack buffer; asserts mode is decimal or scientific.
1462pub fn printFloat(w: *Writer, value: anytype, options: std.fmt.Number) Error!void {
1463 const mode: std.fmt.float.Mode = switch (options.mode) {
1464 .decimal => .decimal,
1465 .scientific => .scientific,
1466 .binary, .octal, .hex => unreachable,
1467 };
1468 var buf: [std.fmt.float.bufferSize(.decimal, f64)]u8 = undefined;
1469 const s = std.fmt.float.render(&buf, value, .{
1470 .mode = mode,
1471 .precision = options.precision,
1472 }) catch |err| switch (err) {
1473 error.BufferTooSmall => "(float)",
1474 };
1475 return w.alignBuffer(s, options.width orelse s.len, options.alignment, options.fill);
1476}
1477
1478/// Uses a smaller stack buffer; asserts mode is not decimal or scientific.
1479pub fn printFloatHexOptions(w: *Writer, value: anytype, options: std.fmt.Number) Error!void {
1480 var buf: [50]u8 = undefined; // for aligning
1481 var sub_writer: Writer = .fixed(&buf);
1482 switch (options.mode) {
1483 .decimal => unreachable,
1484 .scientific => unreachable,
1485 .binary => @panic("TODO"),
1486 .octal => @panic("TODO"),
1487 .hex => {},
1488 }
1489 printFloatHex(&sub_writer, value, options.case, options.precision) catch unreachable; // buf is large enough
1490
1491 const printed = sub_writer.buffered();
1492 return w.alignBuffer(printed, options.width orelse printed.len, options.alignment, options.fill);
1493}
1494
1495pub fn printFloatHex(w: *Writer, value: anytype, case: std.fmt.Case, opt_precision: ?usize) Error!void {
1496 if (std.math.signbit(value)) try w.writeByte('-');
1497 if (std.math.isNan(value)) return w.writeAll(switch (case) {
1498 .lower => "nan",
1499 .upper => "NAN",
1500 });
1501 if (std.math.isInf(value)) return w.writeAll(switch (case) {
1502 .lower => "inf",
1503 .upper => "INF",
1504 });
1505
1506 const T = @TypeOf(value);
1507 const TU = std.meta.Int(.unsigned, @bitSizeOf(T));
1508
1509 const mantissa_bits = std.math.floatMantissaBits(T);
1510 const fractional_bits = std.math.floatFractionalBits(T);
1511 const exponent_bits = std.math.floatExponentBits(T);
1512 const mantissa_mask = (1 << mantissa_bits) - 1;
1513 const exponent_mask = (1 << exponent_bits) - 1;
1514 const exponent_bias = (1 << (exponent_bits - 1)) - 1;
1515
1516 const as_bits: TU = @bitCast(value);
1517 var mantissa = as_bits & mantissa_mask;
1518 var exponent: i32 = @as(u16, @truncate((as_bits >> mantissa_bits) & exponent_mask));
1519
1520 const is_denormal = exponent == 0 and mantissa != 0;
1521 const is_zero = exponent == 0 and mantissa == 0;
1522
1523 if (is_zero) {
1524 // Handle this case here to simplify the logic below.
1525 try w.writeAll("0x0");
1526 if (opt_precision) |precision| {
1527 if (precision > 0) {
1528 try w.writeAll(".");
1529 try w.splatByteAll('0', precision);
1530 }
1531 } else {
1532 try w.writeAll(".0");
1533 }
1534 try w.writeAll("p0");
1535 return;
1536 }
1537
1538 if (is_denormal) {
1539 // Adjust the exponent for printing.
1540 exponent += 1;
1541 } else {
1542 if (fractional_bits == mantissa_bits)
1543 mantissa |= 1 << fractional_bits; // Add the implicit integer bit.
1544 }
1545
1546 const mantissa_digits = (fractional_bits + 3) / 4;
1547 // Fill in zeroes to round the fraction width to a multiple of 4.
1548 mantissa <<= mantissa_digits * 4 - fractional_bits;
1549
1550 if (opt_precision) |precision| {
1551 // Round if needed.
1552 if (precision < mantissa_digits) {
1553 // We always have at least 4 extra bits.
1554 var extra_bits = (mantissa_digits - precision) * 4;
1555 // The result LSB is the Guard bit, we need two more (Round and
1556 // Sticky) to round the value.
1557 while (extra_bits > 2) {
1558 mantissa = (mantissa >> 1) | (mantissa & 1);
1559 extra_bits -= 1;
1560 }
1561 // Round to nearest, tie to even.
1562 mantissa |= @intFromBool(mantissa & 0b100 != 0);
1563 mantissa += 1;
1564 // Drop the excess bits.
1565 mantissa >>= 2;
1566 // Restore the alignment.
1567 mantissa <<= @as(std.math.Log2Int(TU), @intCast((mantissa_digits - precision) * 4));
1568
1569 const overflow = mantissa & (1 << 1 + mantissa_digits * 4) != 0;
1570 // Prefer a normalized result in case of overflow.
1571 if (overflow) {
1572 mantissa >>= 1;
1573 exponent += 1;
1574 }
1575 }
1576 }
1577
1578 // +1 for the decimal part.
1579 var buf: [1 + mantissa_digits]u8 = undefined;
1580 assert(std.fmt.printInt(&buf, mantissa, 16, case, .{ .fill = '0', .width = 1 + mantissa_digits }) == buf.len);
1581
1582 try w.writeAll("0x");
1583 try w.writeByte(buf[0]);
1584 const trimmed = std.mem.trimRight(u8, buf[1..], "0");
1585 if (opt_precision) |precision| {
1586 if (precision > 0) try w.writeAll(".");
1587 } else if (trimmed.len > 0) {
1588 try w.writeAll(".");
1589 }
1590 try w.writeAll(trimmed);
1591 // Add trailing zeros if explicitly requested.
1592 if (opt_precision) |precision| if (precision > 0) {
1593 if (precision > trimmed.len)
1594 try w.splatByteAll('0', precision - trimmed.len);
1595 };
1596 try w.writeAll("p");
1597 try w.printInt(exponent - exponent_bias, 10, case, .{});
1598}
1599
1600pub const ByteSizeUnits = enum {
1601 /// This formatter represents the number as multiple of 1000 and uses the SI
1602 /// measurement units (kB, MB, GB, ...).
1603 decimal,
1604 /// This formatter represents the number as multiple of 1024 and uses the IEC
1605 /// measurement units (KiB, MiB, GiB, ...).
1606 binary,
1607};
1608
1609/// Format option `precision` is ignored when `value` is less than 1kB
1610pub fn printByteSize(
1611 w: *std.io.Writer,
1612 value: u64,
1613 comptime units: ByteSizeUnits,
1614 options: std.fmt.Options,
1615) Error!void {
1616 if (value == 0) return w.alignBufferOptions("0B", options);
1617 // The worst case in terms of space needed is 32 bytes + 3 for the suffix.
1618 var buf: [std.fmt.float.min_buffer_size + 3]u8 = undefined;
1619
1620 const mags_si = " kMGTPEZY";
1621 const mags_iec = " KMGTPEZY";
1622
1623 const log2 = std.math.log2(value);
1624 const base = switch (units) {
1625 .decimal => 1000,
1626 .binary => 1024,
1627 };
1628 const magnitude = switch (units) {
1629 .decimal => @min(log2 / comptime std.math.log2(1000), mags_si.len - 1),
1630 .binary => @min(log2 / 10, mags_iec.len - 1),
1631 };
1632 const new_value = std.math.lossyCast(f64, value) / std.math.pow(f64, std.math.lossyCast(f64, base), std.math.lossyCast(f64, magnitude));
1633 const suffix = switch (units) {
1634 .decimal => mags_si[magnitude],
1635 .binary => mags_iec[magnitude],
1636 };
1637
1638 const s = switch (magnitude) {
1639 0 => buf[0..std.fmt.printInt(&buf, value, 10, .lower, .{})],
1640 else => std.fmt.float.render(&buf, new_value, .{ .mode = .decimal, .precision = options.precision }) catch |err| switch (err) {
1641 error.BufferTooSmall => unreachable,
1642 },
1643 };
1644
1645 var i: usize = s.len;
1646 if (suffix == ' ') {
1647 buf[i] = 'B';
1648 i += 1;
1649 } else switch (units) {
1650 .decimal => {
1651 buf[i..][0..2].* = [_]u8{ suffix, 'B' };
1652 i += 2;
1653 },
1654 .binary => {
1655 buf[i..][0..3].* = [_]u8{ suffix, 'i', 'B' };
1656 i += 3;
1657 },
1658 }
1659
1660 return w.alignBufferOptions(buf[0..i], options);
1661}
1662
1663// This ANY const is a workaround for: https://github.com/ziglang/zig/issues/7948
1664const ANY = "any";
1665
1666fn stripOptionalOrErrorUnionSpec(comptime fmt: []const u8) []const u8 {
1667 return if (std.mem.eql(u8, fmt[1..], ANY))
1668 ANY
1669 else
1670 fmt[1..];
1671}
1672
1673pub fn invalidFmtError(comptime fmt: []const u8, value: anytype) noreturn {
1674 @compileError("invalid format string '" ++ fmt ++ "' for type '" ++ @typeName(@TypeOf(value)) ++ "'");
1675}
1676
1677pub fn printDurationSigned(w: *Writer, ns: i64) Error!void {
1678 if (ns < 0) try w.writeByte('-');
1679 return w.printDurationUnsigned(@abs(ns));
1680}
1681
1682pub fn printDurationUnsigned(w: *Writer, ns: u64) Error!void {
1683 var ns_remaining = ns;
1684 inline for (.{
1685 .{ .ns = 365 * std.time.ns_per_day, .sep = 'y' },
1686 .{ .ns = std.time.ns_per_week, .sep = 'w' },
1687 .{ .ns = std.time.ns_per_day, .sep = 'd' },
1688 .{ .ns = std.time.ns_per_hour, .sep = 'h' },
1689 .{ .ns = std.time.ns_per_min, .sep = 'm' },
1690 }) |unit| {
1691 if (ns_remaining >= unit.ns) {
1692 const units = ns_remaining / unit.ns;
1693 try w.printInt(units, 10, .lower, .{});
1694 try w.writeByte(unit.sep);
1695 ns_remaining -= units * unit.ns;
1696 if (ns_remaining == 0) return;
1697 }
1698 }
1699
1700 inline for (.{
1701 .{ .ns = std.time.ns_per_s, .sep = "s" },
1702 .{ .ns = std.time.ns_per_ms, .sep = "ms" },
1703 .{ .ns = std.time.ns_per_us, .sep = "us" },
1704 }) |unit| {
1705 const kunits = ns_remaining * 1000 / unit.ns;
1706 if (kunits >= 1000) {
1707 try w.printInt(kunits / 1000, 10, .lower, .{});
1708 const frac = kunits % 1000;
1709 if (frac > 0) {
1710 // Write up to 3 decimal places
1711 var decimal_buf = [_]u8{ '.', 0, 0, 0 };
1712 var inner: Writer = .fixed(decimal_buf[1..]);
1713 inner.printInt(frac, 10, .lower, .{ .fill = '0', .width = 3 }) catch unreachable;
1714 var end: usize = 4;
1715 while (end > 1) : (end -= 1) {
1716 if (decimal_buf[end - 1] != '0') break;
1717 }
1718 try w.writeAll(decimal_buf[0..end]);
1719 }
1720 return w.writeAll(unit.sep);
1721 }
1722 }
1723
1724 try w.printInt(ns_remaining, 10, .lower, .{});
1725 try w.writeAll("ns");
1726}
1727
1728/// Writes number of nanoseconds according to its signed magnitude:
1729/// `[#y][#w][#d][#h][#m]#[.###][n|u|m]s`
1730/// `nanoseconds` must be an integer that coerces into `u64` or `i64`.
1731pub fn printDuration(w: *Writer, nanoseconds: anytype, options: std.fmt.Options) Error!void {
1732 // worst case: "-XXXyXXwXXdXXhXXmXX.XXXs".len = 24
1733 var buf: [24]u8 = undefined;
1734 var sub_writer: Writer = .fixed(&buf);
1735 if (@TypeOf(nanoseconds) == comptime_int) {
1736 if (nanoseconds >= 0) {
1737 sub_writer.printDurationUnsigned(nanoseconds) catch unreachable;
1738 } else {
1739 sub_writer.printDurationSigned(nanoseconds) catch unreachable;
1740 }
1741 } else switch (@typeInfo(@TypeOf(nanoseconds)).int.signedness) {
1742 .signed => sub_writer.printDurationSigned(nanoseconds) catch unreachable,
1743 .unsigned => sub_writer.printDurationUnsigned(nanoseconds) catch unreachable,
1744 }
1745 return w.alignBufferOptions(sub_writer.buffered(), options);
1746}
1747
1748pub fn printHex(w: *Writer, bytes: []const u8, case: std.fmt.Case) Error!void {
1749 const charset = switch (case) {
1750 .upper => "0123456789ABCDEF",
1751 .lower => "0123456789abcdef",
1752 };
1753 for (bytes) |c| {
1754 try w.writeByte(charset[c >> 4]);
1755 try w.writeByte(charset[c & 15]);
1756 }
1757}
1758
1759pub fn printBase64(w: *Writer, bytes: []const u8) Error!void {
1760 var chunker = std.mem.window(u8, bytes, 3, 3);
1761 var temp: [5]u8 = undefined;
1762 while (chunker.next()) |chunk| {
1763 try w.writeAll(std.base64.standard.Encoder.encode(&temp, chunk));
1764 }
1765}
1766
1767/// Write a single unsigned integer as LEB128 to the given writer.
1768pub fn writeUleb128(w: *Writer, value: anytype) Error!void {
1769 try w.writeLeb128(switch (@typeInfo(@TypeOf(value))) {
1770 .comptime_int => @as(std.math.IntFittingRange(0, @abs(value)), value),
1771 .int => |value_info| switch (value_info.signedness) {
1772 .signed => @as(@Type(.{ .int = .{ .signedness = .unsigned, .bits = value_info.bits -| 1 } }), @intCast(value)),
1773 .unsigned => value,
1774 },
1775 else => comptime unreachable,
1776 });
1777}
1778
1779/// Write a single signed integer as LEB128 to the given writer.
1780pub fn writeSleb128(w: *Writer, value: anytype) Error!void {
1781 try w.writeLeb128(switch (@typeInfo(@TypeOf(value))) {
1782 .comptime_int => @as(std.math.IntFittingRange(@min(value, -1), @max(0, value)), value),
1783 .int => |value_info| switch (value_info.signedness) {
1784 .signed => value,
1785 .unsigned => @as(@Type(.{ .int = .{ .signedness = .signed, .bits = value_info.bits + 1 } }), value),
1786 },
1787 else => comptime unreachable,
1788 });
1789}
1790
1791/// Write a single integer as LEB128 to the given writer.
1792pub fn writeLeb128(w: *Writer, value: anytype) Error!void {
1793 const value_info = @typeInfo(@TypeOf(value)).int;
1794 try w.writeMultipleOf7Leb128(@as(@Type(.{ .int = .{
1795 .signedness = value_info.signedness,
1796 .bits = std.mem.alignForwardAnyAlign(u16, value_info.bits, 7),
1797 } }), value));
1798}
1799
1800fn writeMultipleOf7Leb128(w: *Writer, value: anytype) Error!void {
1801 const value_info = @typeInfo(@TypeOf(value)).int;
1802 comptime assert(value_info.bits % 7 == 0);
1803 var remaining = value;
1804 while (true) {
1805 const buffer: []packed struct(u8) { bits: u7, more: bool } = @ptrCast(try w.writableSliceGreedy(1));
1806 for (buffer, 1..) |*byte, len| {
1807 const more = switch (value_info.signedness) {
1808 .signed => remaining >> 6 != remaining >> (value_info.bits - 1),
1809 .unsigned => remaining > std.math.maxInt(u7),
1810 };
1811 byte.* = if (@inComptime()) @typeInfo(@TypeOf(buffer)).pointer.child{
1812 .bits = @bitCast(@as(@Type(.{ .int = .{
1813 .signedness = value_info.signedness,
1814 .bits = 7,
1815 } }), @truncate(remaining))),
1816 .more = more,
1817 } else .{
1818 .bits = @bitCast(@as(@Type(.{ .int = .{
1819 .signedness = value_info.signedness,
1820 .bits = 7,
1821 } }), @truncate(remaining))),
1822 .more = more,
1823 };
1824 if (value_info.bits > 7) remaining >>= 7;
1825 if (!more) return w.advance(len);
1826 }
1827 w.advance(buffer.len);
1828 }
1829}
1830
1831test "printValue max_depth" {
1832 const Vec2 = struct {
1833 const SelfType = @This();
1834 x: f32,
1835 y: f32,
1836
1837 pub fn format(self: SelfType, w: *Writer) Error!void {
1838 return w.print("({d:.3},{d:.3})", .{ self.x, self.y });
1839 }
1840 };
1841 const E = enum {
1842 One,
1843 Two,
1844 Three,
1845 };
1846 const TU = union(enum) {
1847 const SelfType = @This();
1848 float: f32,
1849 int: u32,
1850 ptr: ?*SelfType,
1851 };
1852 const S = struct {
1853 const SelfType = @This();
1854 a: ?*SelfType,
1855 tu: TU,
1856 e: E,
1857 vec: Vec2,
1858 };
1859
1860 var inst = S{
1861 .a = null,
1862 .tu = TU{ .ptr = null },
1863 .e = E.Two,
1864 .vec = Vec2{ .x = 10.2, .y = 2.22 },
1865 };
1866 inst.a = &inst;
1867 inst.tu.ptr = &inst.tu;
1868
1869 var buf: [1000]u8 = undefined;
1870 var w: Writer = .fixed(&buf);
1871 try w.printValue("", .{}, inst, 0);
1872 try testing.expectEqualStrings(".{ ... }", w.buffered());
1873
1874 w = .fixed(&buf);
1875 try w.printValue("", .{}, inst, 1);
1876 try testing.expectEqualStrings(".{ .a = .{ ... }, .tu = .{ ... }, .e = .Two, .vec = .{ ... } }", w.buffered());
1877
1878 w = .fixed(&buf);
1879 try w.printValue("", .{}, inst, 2);
1880 try testing.expectEqualStrings(".{ .a = .{ .a = .{ ... }, .tu = .{ ... }, .e = .Two, .vec = .{ ... } }, .tu = .{ .ptr = .{ ... } }, .e = .Two, .vec = .{ .x = 10.2, .y = 2.22 } }", w.buffered());
1881
1882 w = .fixed(&buf);
1883 try w.printValue("", .{}, inst, 3);
1884 try testing.expectEqualStrings(".{ .a = .{ .a = .{ .a = .{ ... }, .tu = .{ ... }, .e = .Two, .vec = .{ ... } }, .tu = .{ .ptr = .{ ... } }, .e = .Two, .vec = .{ .x = 10.2, .y = 2.22 } }, .tu = .{ .ptr = .{ .ptr = .{ ... } } }, .e = .Two, .vec = .{ .x = 10.2, .y = 2.22 } }", w.buffered());
1885
1886 const vec: @Vector(4, i32) = .{ 1, 2, 3, 4 };
1887 w = .fixed(&buf);
1888 try w.printValue("", .{}, vec, 0);
1889 try testing.expectEqualStrings("{ ... }", w.buffered());
1890
1891 w = .fixed(&buf);
1892 try w.printValue("", .{}, vec, 1);
1893 try testing.expectEqualStrings("{ 1, 2, 3, 4 }", w.buffered());
1894}
1895
1896test printDuration {
1897 try testDurationCase("0ns", 0);
1898 try testDurationCase("1ns", 1);
1899 try testDurationCase("999ns", std.time.ns_per_us - 1);
1900 try testDurationCase("1us", std.time.ns_per_us);
1901 try testDurationCase("1.45us", 1450);
1902 try testDurationCase("1.5us", 3 * std.time.ns_per_us / 2);
1903 try testDurationCase("14.5us", 14500);
1904 try testDurationCase("145us", 145000);
1905 try testDurationCase("999.999us", std.time.ns_per_ms - 1);
1906 try testDurationCase("1ms", std.time.ns_per_ms + 1);
1907 try testDurationCase("1.5ms", 3 * std.time.ns_per_ms / 2);
1908 try testDurationCase("1.11ms", 1110000);
1909 try testDurationCase("1.111ms", 1111000);
1910 try testDurationCase("1.111ms", 1111100);
1911 try testDurationCase("999.999ms", std.time.ns_per_s - 1);
1912 try testDurationCase("1s", std.time.ns_per_s);
1913 try testDurationCase("59.999s", std.time.ns_per_min - 1);
1914 try testDurationCase("1m", std.time.ns_per_min);
1915 try testDurationCase("1h", std.time.ns_per_hour);
1916 try testDurationCase("1d", std.time.ns_per_day);
1917 try testDurationCase("1w", std.time.ns_per_week);
1918 try testDurationCase("1y", 365 * std.time.ns_per_day);
1919 try testDurationCase("1y52w23h59m59.999s", 730 * std.time.ns_per_day - 1); // 365d = 52w1
1920 try testDurationCase("1y1h1.001s", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + std.time.ns_per_ms);
1921 try testDurationCase("1y1h1s", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + 999 * std.time.ns_per_us);
1922 try testDurationCase("1y1h999.999us", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms - 1);
1923 try testDurationCase("1y1h1ms", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms);
1924 try testDurationCase("1y1h1ms", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms + 1);
1925 try testDurationCase("1y1m999ns", 365 * std.time.ns_per_day + std.time.ns_per_min + 999);
1926 try testDurationCase("584y49w23h34m33.709s", std.math.maxInt(u64));
1927
1928 try testing.expectFmt("=======0ns", "{D:=>10}", .{0});
1929 try testing.expectFmt("1ns=======", "{D:=<10}", .{1});
1930 try testing.expectFmt(" 999ns ", "{D:^10}", .{std.time.ns_per_us - 1});
1931}
1932
1933test printDurationSigned {
1934 try testDurationCaseSigned("0ns", 0);
1935 try testDurationCaseSigned("1ns", 1);
1936 try testDurationCaseSigned("-1ns", -(1));
1937 try testDurationCaseSigned("999ns", std.time.ns_per_us - 1);
1938 try testDurationCaseSigned("-999ns", -(std.time.ns_per_us - 1));
1939 try testDurationCaseSigned("1us", std.time.ns_per_us);
1940 try testDurationCaseSigned("-1us", -(std.time.ns_per_us));
1941 try testDurationCaseSigned("1.45us", 1450);
1942 try testDurationCaseSigned("-1.45us", -(1450));
1943 try testDurationCaseSigned("1.5us", 3 * std.time.ns_per_us / 2);
1944 try testDurationCaseSigned("-1.5us", -(3 * std.time.ns_per_us / 2));
1945 try testDurationCaseSigned("14.5us", 14500);
1946 try testDurationCaseSigned("-14.5us", -(14500));
1947 try testDurationCaseSigned("145us", 145000);
1948 try testDurationCaseSigned("-145us", -(145000));
1949 try testDurationCaseSigned("999.999us", std.time.ns_per_ms - 1);
1950 try testDurationCaseSigned("-999.999us", -(std.time.ns_per_ms - 1));
1951 try testDurationCaseSigned("1ms", std.time.ns_per_ms + 1);
1952 try testDurationCaseSigned("-1ms", -(std.time.ns_per_ms + 1));
1953 try testDurationCaseSigned("1.5ms", 3 * std.time.ns_per_ms / 2);
1954 try testDurationCaseSigned("-1.5ms", -(3 * std.time.ns_per_ms / 2));
1955 try testDurationCaseSigned("1.11ms", 1110000);
1956 try testDurationCaseSigned("-1.11ms", -(1110000));
1957 try testDurationCaseSigned("1.111ms", 1111000);
1958 try testDurationCaseSigned("-1.111ms", -(1111000));
1959 try testDurationCaseSigned("1.111ms", 1111100);
1960 try testDurationCaseSigned("-1.111ms", -(1111100));
1961 try testDurationCaseSigned("999.999ms", std.time.ns_per_s - 1);
1962 try testDurationCaseSigned("-999.999ms", -(std.time.ns_per_s - 1));
1963 try testDurationCaseSigned("1s", std.time.ns_per_s);
1964 try testDurationCaseSigned("-1s", -(std.time.ns_per_s));
1965 try testDurationCaseSigned("59.999s", std.time.ns_per_min - 1);
1966 try testDurationCaseSigned("-59.999s", -(std.time.ns_per_min - 1));
1967 try testDurationCaseSigned("1m", std.time.ns_per_min);
1968 try testDurationCaseSigned("-1m", -(std.time.ns_per_min));
1969 try testDurationCaseSigned("1h", std.time.ns_per_hour);
1970 try testDurationCaseSigned("-1h", -(std.time.ns_per_hour));
1971 try testDurationCaseSigned("1d", std.time.ns_per_day);
1972 try testDurationCaseSigned("-1d", -(std.time.ns_per_day));
1973 try testDurationCaseSigned("1w", std.time.ns_per_week);
1974 try testDurationCaseSigned("-1w", -(std.time.ns_per_week));
1975 try testDurationCaseSigned("1y", 365 * std.time.ns_per_day);
1976 try testDurationCaseSigned("-1y", -(365 * std.time.ns_per_day));
1977 try testDurationCaseSigned("1y52w23h59m59.999s", 730 * std.time.ns_per_day - 1); // 365d = 52w1d
1978 try testDurationCaseSigned("-1y52w23h59m59.999s", -(730 * std.time.ns_per_day - 1)); // 365d = 52w1d
1979 try testDurationCaseSigned("1y1h1.001s", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + std.time.ns_per_ms);
1980 try testDurationCaseSigned("-1y1h1.001s", -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + std.time.ns_per_ms));
1981 try testDurationCaseSigned("1y1h1s", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + 999 * std.time.ns_per_us);
1982 try testDurationCaseSigned("-1y1h1s", -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + 999 * std.time.ns_per_us));
1983 try testDurationCaseSigned("1y1h999.999us", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms - 1);
1984 try testDurationCaseSigned("-1y1h999.999us", -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms - 1));
1985 try testDurationCaseSigned("1y1h1ms", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms);
1986 try testDurationCaseSigned("-1y1h1ms", -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms));
1987 try testDurationCaseSigned("1y1h1ms", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms + 1);
1988 try testDurationCaseSigned("-1y1h1ms", -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms + 1));
1989 try testDurationCaseSigned("1y1m999ns", 365 * std.time.ns_per_day + std.time.ns_per_min + 999);
1990 try testDurationCaseSigned("-1y1m999ns", -(365 * std.time.ns_per_day + std.time.ns_per_min + 999));
1991 try testDurationCaseSigned("292y24w3d23h47m16.854s", std.math.maxInt(i64));
1992 try testDurationCaseSigned("-292y24w3d23h47m16.854s", std.math.minInt(i64) + 1);
1993 try testDurationCaseSigned("-292y24w3d23h47m16.854s", std.math.minInt(i64));
1994
1995 try testing.expectFmt("=======0ns", "{D:=>10}", .{0});
1996 try testing.expectFmt("1ns=======", "{D:=<10}", .{1});
1997 try testing.expectFmt("-1ns======", "{D:=<10}", .{-(1)});
1998 try testing.expectFmt(" -999ns ", "{D:^10}", .{-(std.time.ns_per_us - 1)});
1999}
2000
2001fn testDurationCase(expected: []const u8, input: u64) !void {
2002 var buf: [24]u8 = undefined;
2003 var w: Writer = .fixed(&buf);
2004 try w.printDurationUnsigned(input);
2005 try testing.expectEqualStrings(expected, w.buffered());
2006}
2007
2008fn testDurationCaseSigned(expected: []const u8, input: i64) !void {
2009 var buf: [24]u8 = undefined;
2010 var w: Writer = .fixed(&buf);
2011 try w.printDurationSigned(input);
2012 try testing.expectEqualStrings(expected, w.buffered());
2013}
2014
2015test printInt {
2016 try testPrintIntCase("-1", @as(i1, -1), 10, .lower, .{});
2017
2018 try testPrintIntCase("-101111000110000101001110", @as(i32, -12345678), 2, .lower, .{});
2019 try testPrintIntCase("-12345678", @as(i32, -12345678), 10, .lower, .{});
2020 try testPrintIntCase("-bc614e", @as(i32, -12345678), 16, .lower, .{});
2021 try testPrintIntCase("-BC614E", @as(i32, -12345678), 16, .upper, .{});
2022
2023 try testPrintIntCase("12345678", @as(u32, 12345678), 10, .upper, .{});
2024
2025 try testPrintIntCase(" 666", @as(u32, 666), 10, .lower, .{ .width = 6 });
2026 try testPrintIntCase(" 1234", @as(u32, 0x1234), 16, .lower, .{ .width = 6 });
2027 try testPrintIntCase("1234", @as(u32, 0x1234), 16, .lower, .{ .width = 1 });
2028
2029 try testPrintIntCase("+42", @as(i32, 42), 10, .lower, .{ .width = 3 });
2030 try testPrintIntCase("-42", @as(i32, -42), 10, .lower, .{ .width = 3 });
2031
2032 try testPrintIntCase("123456789123456789", @as(comptime_int, 123456789123456789), 10, .lower, .{});
2033}
2034
2035test "printFloat with comptime_float" {
2036 var buf: [20]u8 = undefined;
2037 var w: Writer = .fixed(&buf);
2038 try w.printFloat(@as(comptime_float, 1.0), std.fmt.Options.toNumber(.{}, .scientific, .lower));
2039 try testing.expectEqualStrings(w.buffered(), "1e0");
2040 try testing.expectFmt("1", "{}", .{1.0});
2041}
2042
2043fn testPrintIntCase(expected: []const u8, value: anytype, base: u8, case: std.fmt.Case, options: std.fmt.Options) !void {
2044 var buffer: [100]u8 = undefined;
2045 var w: Writer = .fixed(&buffer);
2046 try w.printInt(value, base, case, options);
2047 try testing.expectEqualStrings(expected, w.buffered());
2048}
2049
2050test printByteSize {
2051 try testing.expectFmt("file size: 42B\n", "file size: {B}\n", .{42});
2052 try testing.expectFmt("file size: 42B\n", "file size: {Bi}\n", .{42});
2053 try testing.expectFmt("file size: 63MB\n", "file size: {B}\n", .{63 * 1000 * 1000});
2054 try testing.expectFmt("file size: 63MiB\n", "file size: {Bi}\n", .{63 * 1024 * 1024});
2055 try testing.expectFmt("file size: 42B\n", "file size: {B:.2}\n", .{42});
2056 try testing.expectFmt("file size: 42B\n", "file size: {B:>9.2}\n", .{42});
2057 try testing.expectFmt("file size: 66.06MB\n", "file size: {B:.2}\n", .{63 * 1024 * 1024});
2058 try testing.expectFmt("file size: 60.08MiB\n", "file size: {Bi:.2}\n", .{63 * 1000 * 1000});
2059 try testing.expectFmt("file size: =66.06MB=\n", "file size: {B:=^9.2}\n", .{63 * 1024 * 1024});
2060 try testing.expectFmt("file size: 66.06MB\n", "file size: {B: >9.2}\n", .{63 * 1024 * 1024});
2061 try testing.expectFmt("file size: 66.06MB \n", "file size: {B: <9.2}\n", .{63 * 1024 * 1024});
2062 try testing.expectFmt("file size: 0.01844674407370955ZB\n", "file size: {B}\n", .{std.math.maxInt(u64)});
2063}
2064
2065test "bytes.hex" {
2066 const some_bytes = "\xCA\xFE\xBA\xBE";
2067 try testing.expectFmt("lowercase: cafebabe\n", "lowercase: {x}\n", .{some_bytes});
2068 try testing.expectFmt("uppercase: CAFEBABE\n", "uppercase: {X}\n", .{some_bytes});
2069 try testing.expectFmt("uppercase: CAFE\n", "uppercase: {X}\n", .{some_bytes[0..2]});
2070 try testing.expectFmt("lowercase: babe\n", "lowercase: {x}\n", .{some_bytes[2..]});
2071 const bytes_with_zeros = "\x00\x0E\xBA\xBE";
2072 try testing.expectFmt("lowercase: 000ebabe\n", "lowercase: {x}\n", .{bytes_with_zeros});
2073}
2074
2075test fixed {
2076 {
2077 var buf: [255]u8 = undefined;
2078 var w: Writer = .fixed(&buf);
2079 try w.print("{s}{s}!", .{ "Hello", "World" });
2080 try testing.expectEqualStrings("HelloWorld!", w.buffered());
2081 }
2082
2083 comptime {
2084 var buf: [255]u8 = undefined;
2085 var w: Writer = .fixed(&buf);
2086 try w.print("{s}{s}!", .{ "Hello", "World" });
2087 try testing.expectEqualStrings("HelloWorld!", w.buffered());
2088 }
2089}
2090
2091test "fixed output" {
2092 var buffer: [10]u8 = undefined;
2093 var w: Writer = .fixed(&buffer);
2094
2095 try w.writeAll("Hello");
2096 try testing.expect(std.mem.eql(u8, w.buffered(), "Hello"));
2097
2098 try w.writeAll("world");
2099 try testing.expect(std.mem.eql(u8, w.buffered(), "Helloworld"));
2100
2101 try testing.expectError(error.WriteFailed, w.writeAll("!"));
2102 try testing.expect(std.mem.eql(u8, w.buffered(), "Helloworld"));
2103
2104 w = .fixed(&buffer);
2105
2106 try testing.expect(w.buffered().len == 0);
2107
2108 try testing.expectError(error.WriteFailed, w.writeAll("Hello world!"));
2109 try testing.expect(std.mem.eql(u8, w.buffered(), "Hello worl"));
2110}
2111
2112test "writeSplat 0 len splat larger than capacity" {
2113 var buf: [8]u8 = undefined;
2114 var w: std.io.Writer = .fixed(&buf);
2115 const n = try w.writeSplat(&.{"something that overflows buf"}, 0);
2116 try testing.expectEqual(0, n);
2117}
2118
2119pub fn failingDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
2120 _ = w;
2121 _ = data;
2122 _ = splat;
2123 return error.WriteFailed;
2124}
2125
2126pub fn failingSendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize {
2127 _ = w;
2128 _ = file_reader;
2129 _ = limit;
2130 return error.WriteFailed;
2131}
2132
2133pub const Discarding = struct {
2134 count: u64,
2135 writer: Writer,
2136
2137 pub fn init(buffer: []u8) Discarding {
2138 return .{
2139 .count = 0,
2140 .writer = .{
2141 .vtable = &.{
2142 .drain = Discarding.drain,
2143 .sendFile = Discarding.sendFile,
2144 },
2145 .buffer = buffer,
2146 },
2147 };
2148 }
2149
2150 pub fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
2151 const d: *Discarding = @alignCast(@fieldParentPtr("writer", w));
2152 const slice = data[0 .. data.len - 1];
2153 const pattern = data[slice.len..];
2154 var written: usize = pattern.len * splat;
2155 for (slice) |bytes| written += bytes.len;
2156 d.count += w.end + written;
2157 w.end = 0;
2158 return written;
2159 }
2160
2161 pub fn sendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize {
2162 if (File.Handle == void) return error.Unimplemented;
2163 const d: *Discarding = @alignCast(@fieldParentPtr("writer", w));
2164 d.count += w.end;
2165 w.end = 0;
2166 if (file_reader.getSize()) |size| {
2167 const n = limit.minInt64(size - file_reader.pos);
2168 file_reader.seekBy(@intCast(n)) catch return error.Unimplemented;
2169 w.end = 0;
2170 d.count += n;
2171 return n;
2172 } else |_| {
2173 // Error is observable on `file_reader` instance, and it is better to
2174 // treat the file as a pipe.
2175 return error.Unimplemented;
2176 }
2177 }
2178};
2179
2180/// Removes the first `n` bytes from `buffer` by shifting buffer contents,
2181/// returning how many bytes are left after consuming the entire buffer, or
2182/// zero if the entire buffer was not consumed.
2183///
2184/// Useful for `VTable.drain` function implementations to implement partial
2185/// drains.
2186pub fn consume(w: *Writer, n: usize) usize {
2187 if (n < w.end) {
2188 const remaining = w.buffer[n..w.end];
2189 @memmove(w.buffer[0..remaining.len], remaining);
2190 w.end = remaining.len;
2191 return 0;
2192 }
2193 defer w.end = 0;
2194 return n - w.end;
2195}
2196
2197/// Shortcut for setting `end` to zero and returning zero. Equivalent to
2198/// calling `consume` with `end`.
2199pub fn consumeAll(w: *Writer) usize {
2200 w.end = 0;
2201 return 0;
2202}
2203
2204/// For use when the `Writer` implementation can cannot offer a more efficient
2205/// implementation than a basic read/write loop on the file.
2206pub fn unimplementedSendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize {
2207 _ = w;
2208 _ = file_reader;
2209 _ = limit;
2210 return error.Unimplemented;
2211}
2212
2213/// When this function is called it usually means the buffer got full, so it's
2214/// time to return an error. However, we still need to make sure all of the
2215/// available buffer has been filled. Also, it may be called from `flush` in
2216/// which case it should return successfully.
2217pub fn fixedDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
2218 if (data.len == 0) return 0;
2219 for (data[0 .. data.len - 1]) |bytes| {
2220 const dest = w.buffer[w.end..];
2221 const len = @min(bytes.len, dest.len);
2222 @memcpy(dest[0..len], bytes[0..len]);
2223 w.end += len;
2224 if (bytes.len > dest.len) return error.WriteFailed;
2225 }
2226 const pattern = data[data.len - 1];
2227 const dest = w.buffer[w.end..];
2228 switch (pattern.len) {
2229 0 => return w.end,
2230 1 => {
2231 assert(splat >= dest.len);
2232 @memset(dest, pattern[0]);
2233 w.end += dest.len;
2234 return error.WriteFailed;
2235 },
2236 else => {
2237 for (0..splat) |i| {
2238 const remaining = dest[i * pattern.len ..];
2239 const len = @min(pattern.len, remaining.len);
2240 @memcpy(remaining[0..len], pattern[0..len]);
2241 w.end += len;
2242 if (pattern.len > remaining.len) return error.WriteFailed;
2243 }
2244 unreachable;
2245 },
2246 }
2247}
2248
2249/// Provides a `Writer` implementation based on calling `Hasher.update`, sending
2250/// all data also to an underlying `Writer`.
2251///
2252/// When using this, the underlying writer is best unbuffered because all
2253/// writes are passed on directly to it.
2254///
2255/// This implementation makes suboptimal buffering decisions due to being
2256/// generic. A better solution will involve creating a writer for each hash
2257/// function, where the splat buffer can be tailored to the hash implementation
2258/// details.
2259pub fn Hashed(comptime Hasher: type) type {
2260 return struct {
2261 out: *Writer,
2262 hasher: Hasher,
2263 writer: Writer,
2264
2265 pub fn init(out: *Writer, buffer: []u8) @This() {
2266 return .initHasher(out, .{}, buffer);
2267 }
2268
2269 pub fn initHasher(out: *Writer, hasher: Hasher, buffer: []u8) @This() {
2270 return .{
2271 .out = out,
2272 .hasher = hasher,
2273 .writer = .{
2274 .buffer = buffer,
2275 .vtable = &.{ .drain = @This().drain },
2276 },
2277 };
2278 }
2279
2280 fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
2281 const this: *@This() = @alignCast(@fieldParentPtr("writer", w));
2282 const aux = w.buffered();
2283 const aux_n = try this.out.writeSplatHeader(aux, data, splat);
2284 if (aux_n < w.end) {
2285 this.hasher.update(w.buffer[0..aux_n]);
2286 const remaining = w.buffer[aux_n..w.end];
2287 @memmove(w.buffer[0..remaining.len], remaining);
2288 w.end = remaining.len;
2289 return 0;
2290 }
2291 this.hasher.update(aux);
2292 const n = aux_n - w.end;
2293 w.end = 0;
2294 var remaining: usize = n;
2295 for (data[0 .. data.len - 1]) |slice| {
2296 if (remaining <= slice.len) {
2297 this.hasher.update(slice[0..remaining]);
2298 return n;
2299 }
2300 remaining -= slice.len;
2301 this.hasher.update(slice);
2302 }
2303 const pattern = data[data.len - 1];
2304 assert(remaining == splat * pattern.len);
2305 switch (pattern.len) {
2306 0 => {
2307 assert(remaining == 0);
2308 },
2309 1 => {
2310 var buffer: [64]u8 = undefined;
2311 @memset(&buffer, pattern[0]);
2312 while (remaining > 0) {
2313 const update_len = @min(remaining, buffer.len);
2314 this.hasher.update(buffer[0..update_len]);
2315 remaining -= update_len;
2316 }
2317 },
2318 else => {
2319 while (remaining > 0) {
2320 const update_len = @min(remaining, pattern.len);
2321 this.hasher.update(pattern[0..update_len]);
2322 remaining -= update_len;
2323 }
2324 },
2325 }
2326 return n;
2327 }
2328 };
2329}
2330
2331/// Maintains `Writer` state such that it writes to the unused capacity of an
2332/// array list, filling it up completely before making a call through the
2333/// vtable, causing a resize. Consequently, the same, optimized, non-generic
2334/// machine code that uses `std.io.Reader`, such as formatted printing, takes
2335/// the hot paths when using this API.
2336///
2337/// When using this API, it is not necessary to call `flush`.
2338pub const Allocating = struct {
2339 allocator: Allocator,
2340 writer: Writer,
2341
2342 pub fn init(allocator: Allocator) Allocating {
2343 return .{
2344 .allocator = allocator,
2345 .writer = .{
2346 .buffer = &.{},
2347 .vtable = &vtable,
2348 },
2349 };
2350 }
2351
2352 pub fn initCapacity(allocator: Allocator, capacity: usize) error{OutOfMemory}!Allocating {
2353 return .{
2354 .allocator = allocator,
2355 .writer = .{
2356 .buffer = try allocator.alloc(u8, capacity),
2357 .vtable = &vtable,
2358 },
2359 };
2360 }
2361
2362 pub fn initOwnedSlice(allocator: Allocator, slice: []u8) Allocating {
2363 return .{
2364 .allocator = allocator,
2365 .writer = .{
2366 .buffer = slice,
2367 .vtable = &vtable,
2368 },
2369 };
2370 }
2371
2372 /// Replaces `array_list` with empty, taking ownership of the memory.
2373 pub fn fromArrayList(allocator: Allocator, array_list: *std.ArrayListUnmanaged(u8)) Allocating {
2374 defer array_list.* = .empty;
2375 return .{
2376 .allocator = allocator,
2377 .writer = .{
2378 .vtable = &vtable,
2379 .buffer = array_list.allocatedSlice(),
2380 .end = array_list.items.len,
2381 },
2382 };
2383 }
2384
2385 const vtable: VTable = .{
2386 .drain = Allocating.drain,
2387 .sendFile = Allocating.sendFile,
2388 .flush = noopFlush,
2389 };
2390
2391 pub fn deinit(a: *Allocating) void {
2392 a.allocator.free(a.writer.buffer);
2393 a.* = undefined;
2394 }
2395
2396 /// Returns an array list that takes ownership of the allocated memory.
2397 /// Resets the `Allocating` to an empty state.
2398 pub fn toArrayList(a: *Allocating) std.ArrayListUnmanaged(u8) {
2399 const w = &a.writer;
2400 const result: std.ArrayListUnmanaged(u8) = .{
2401 .items = w.buffer[0..w.end],
2402 .capacity = w.buffer.len,
2403 };
2404 w.buffer = &.{};
2405 w.end = 0;
2406 return result;
2407 }
2408
2409 pub fn toOwnedSlice(a: *Allocating) error{OutOfMemory}![]u8 {
2410 var list = a.toArrayList();
2411 return list.toOwnedSlice(a.allocator);
2412 }
2413
2414 pub fn toOwnedSliceSentinel(a: *Allocating, comptime sentinel: u8) error{OutOfMemory}![:sentinel]u8 {
2415 const gpa = a.allocator;
2416 var list = toArrayList(a);
2417 return list.toOwnedSliceSentinel(gpa, sentinel);
2418 }
2419
2420 pub fn getWritten(a: *Allocating) []u8 {
2421 return a.writer.buffered();
2422 }
2423
2424 pub fn shrinkRetainingCapacity(a: *Allocating, new_len: usize) void {
2425 a.writer.end = new_len;
2426 }
2427
2428 pub fn clearRetainingCapacity(a: *Allocating) void {
2429 a.shrinkRetainingCapacity(0);
2430 }
2431
2432 fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
2433 const a: *Allocating = @fieldParentPtr("writer", w);
2434 const gpa = a.allocator;
2435 const pattern = data[data.len - 1];
2436 const splat_len = pattern.len * splat;
2437 var list = a.toArrayList();
2438 defer setArrayList(a, list);
2439 const start_len = list.items.len;
2440 // Even if we append no data, this function needs to ensure there is more
2441 // capacity in the buffer to avoid infinite loop, hence the +1 in this loop.
2442 assert(data.len != 0);
2443 for (data) |bytes| {
2444 list.ensureUnusedCapacity(gpa, bytes.len + splat_len + 1) catch return error.WriteFailed;
2445 list.appendSliceAssumeCapacity(bytes);
2446 }
2447 if (splat == 0) {
2448 list.items.len -= pattern.len;
2449 } else switch (pattern.len) {
2450 0 => {},
2451 1 => list.appendNTimesAssumeCapacity(pattern[0], splat - 1),
2452 else => for (0..splat - 1) |_| list.appendSliceAssumeCapacity(pattern),
2453 }
2454 return list.items.len - start_len;
2455 }
2456
2457 fn sendFile(w: *Writer, file_reader: *File.Reader, limit: std.io.Limit) FileError!usize {
2458 if (File.Handle == void) return error.Unimplemented;
2459 const a: *Allocating = @fieldParentPtr("writer", w);
2460 const gpa = a.allocator;
2461 var list = a.toArrayList();
2462 defer setArrayList(a, list);
2463 const pos = file_reader.pos;
2464 const additional = if (file_reader.getSize()) |size| size - pos else |_| std.atomic.cache_line;
2465 list.ensureUnusedCapacity(gpa, limit.minInt64(additional)) catch return error.WriteFailed;
2466 const dest = limit.slice(list.unusedCapacitySlice());
2467 const n = file_reader.read(dest) catch |err| switch (err) {
2468 error.ReadFailed => return error.ReadFailed,
2469 error.EndOfStream => 0,
2470 };
2471 list.items.len += n;
2472 return n;
2473 }
2474
2475 fn setArrayList(a: *Allocating, list: std.ArrayListUnmanaged(u8)) void {
2476 a.writer.buffer = list.allocatedSlice();
2477 a.writer.end = list.items.len;
2478 }
2479
2480 test Allocating {
2481 var a: Allocating = .init(testing.allocator);
2482 defer a.deinit();
2483 const w = &a.writer;
2484
2485 const x: i32 = 42;
2486 const y: i32 = 1234;
2487 try w.print("x: {}\ny: {}\n", .{ x, y });
2488
2489 try testing.expectEqualSlices(u8, "x: 42\ny: 1234\n", a.getWritten());
2490 }
2491};
lib/std/Io/change_detection_stream.zig created+55
...@@ -0,0 +1,55 @@
1const std = @import("../std.zig");
2const io = std.io;
3const mem = std.mem;
4const assert = std.debug.assert;
5
6/// Used to detect if the data written to a stream differs from a source buffer
7pub fn ChangeDetectionStream(comptime WriterType: type) type {
8 return struct {
9 const Self = @This();
10 pub const Error = WriterType.Error;
11 pub const Writer = io.GenericWriter(*Self, Error, write);
12
13 anything_changed: bool,
14 underlying_writer: WriterType,
15 source_index: usize,
16 source: []const u8,
17
18 pub fn writer(self: *Self) Writer {
19 return .{ .context = self };
20 }
21
22 fn write(self: *Self, bytes: []const u8) Error!usize {
23 if (!self.anything_changed) {
24 const end = self.source_index + bytes.len;
25 if (end > self.source.len) {
26 self.anything_changed = true;
27 } else {
28 const src_slice = self.source[self.source_index..end];
29 self.source_index += bytes.len;
30 if (!mem.eql(u8, bytes, src_slice)) {
31 self.anything_changed = true;
32 }
33 }
34 }
35
36 return self.underlying_writer.write(bytes);
37 }
38
39 pub fn changeDetected(self: *Self) bool {
40 return self.anything_changed or (self.source_index != self.source.len);
41 }
42 };
43}
44
45pub fn changeDetectionStream(
46 source: []const u8,
47 underlying_writer: anytype,
48) ChangeDetectionStream(@TypeOf(underlying_writer)) {
49 return ChangeDetectionStream(@TypeOf(underlying_writer)){
50 .anything_changed = false,
51 .underlying_writer = underlying_writer,
52 .source_index = 0,
53 .source = source,
54 };
55}
lib/std/Io/find_byte_writer.zig created+40
...@@ -0,0 +1,40 @@
1const std = @import("../std.zig");
2const io = std.io;
3const assert = std.debug.assert;
4
5/// A Writer that returns whether the given character has been written to it.
6/// The contents are not written to anything.
7pub fn FindByteWriter(comptime UnderlyingWriter: type) type {
8 return struct {
9 const Self = @This();
10 pub const Error = UnderlyingWriter.Error;
11 pub const Writer = io.GenericWriter(*Self, Error, write);
12
13 underlying_writer: UnderlyingWriter,
14 byte_found: bool,
15 byte: u8,
16
17 pub fn writer(self: *Self) Writer {
18 return .{ .context = self };
19 }
20
21 fn write(self: *Self, bytes: []const u8) Error!usize {
22 if (!self.byte_found) {
23 self.byte_found = blk: {
24 for (bytes) |b|
25 if (b == self.byte) break :blk true;
26 break :blk false;
27 };
28 }
29 return self.underlying_writer.write(bytes);
30 }
31 };
32}
33
34pub fn findByteWriter(byte: u8, underlying_writer: anytype) FindByteWriter(@TypeOf(underlying_writer)) {
35 return FindByteWriter(@TypeOf(underlying_writer)){
36 .underlying_writer = underlying_writer,
37 .byte = byte,
38 .byte_found = false,
39 };
40}
lib/std/Io/test.zig created+169
...@@ -0,0 +1,169 @@
1const std = @import("std");
2const io = std.io;
3const DefaultPrng = std.Random.DefaultPrng;
4const expect = std.testing.expect;
5const expectEqual = std.testing.expectEqual;
6const expectError = std.testing.expectError;
7const mem = std.mem;
8const fs = std.fs;
9const File = std.fs.File;
10const native_endian = @import("builtin").target.cpu.arch.endian();
11
12const tmpDir = std.testing.tmpDir;
13
14test "write a file, read it, then delete it" {
15 var tmp = tmpDir(.{});
16 defer tmp.cleanup();
17
18 var data: [1024]u8 = undefined;
19 var prng = DefaultPrng.init(std.testing.random_seed);
20 const random = prng.random();
21 random.bytes(data[0..]);
22 const tmp_file_name = "temp_test_file.txt";
23 {
24 var file = try tmp.dir.createFile(tmp_file_name, .{});
25 defer file.close();
26
27 var buf_stream = io.bufferedWriter(file.deprecatedWriter());
28 const st = buf_stream.writer();
29 try st.print("begin", .{});
30 try st.writeAll(data[0..]);
31 try st.print("end", .{});
32 try buf_stream.flush();
33 }
34
35 {
36 // Make sure the exclusive flag is honored.
37 try expectError(File.OpenError.PathAlreadyExists, tmp.dir.createFile(tmp_file_name, .{ .exclusive = true }));
38 }
39
40 {
41 var file = try tmp.dir.openFile(tmp_file_name, .{});
42 defer file.close();
43
44 const file_size = try file.getEndPos();
45 const expected_file_size: u64 = "begin".len + data.len + "end".len;
46 try expectEqual(expected_file_size, file_size);
47
48 var buf_stream = io.bufferedReader(file.deprecatedReader());
49 const st = buf_stream.reader();
50 const contents = try st.readAllAlloc(std.testing.allocator, 2 * 1024);
51 defer std.testing.allocator.free(contents);
52
53 try expect(mem.eql(u8, contents[0.."begin".len], "begin"));
54 try expect(mem.eql(u8, contents["begin".len .. contents.len - "end".len], &data));
55 try expect(mem.eql(u8, contents[contents.len - "end".len ..], "end"));
56 }
57 try tmp.dir.deleteFile(tmp_file_name);
58}
59
60test "BitStreams with File Stream" {
61 var tmp = tmpDir(.{});
62 defer tmp.cleanup();
63
64 const tmp_file_name = "temp_test_file.txt";
65 {
66 var file = try tmp.dir.createFile(tmp_file_name, .{});
67 defer file.close();
68
69 var bit_stream = io.bitWriter(native_endian, file.deprecatedWriter());
70
71 try bit_stream.writeBits(@as(u2, 1), 1);
72 try bit_stream.writeBits(@as(u5, 2), 2);
73 try bit_stream.writeBits(@as(u128, 3), 3);
74 try bit_stream.writeBits(@as(u8, 4), 4);
75 try bit_stream.writeBits(@as(u9, 5), 5);
76 try bit_stream.writeBits(@as(u1, 1), 1);
77 try bit_stream.flushBits();
78 }
79 {
80 var file = try tmp.dir.openFile(tmp_file_name, .{});
81 defer file.close();
82
83 var bit_stream = io.bitReader(native_endian, file.deprecatedReader());
84
85 var out_bits: u16 = undefined;
86
87 try expect(1 == try bit_stream.readBits(u2, 1, &out_bits));
88 try expect(out_bits == 1);
89 try expect(2 == try bit_stream.readBits(u5, 2, &out_bits));
90 try expect(out_bits == 2);
91 try expect(3 == try bit_stream.readBits(u128, 3, &out_bits));
92 try expect(out_bits == 3);
93 try expect(4 == try bit_stream.readBits(u8, 4, &out_bits));
94 try expect(out_bits == 4);
95 try expect(5 == try bit_stream.readBits(u9, 5, &out_bits));
96 try expect(out_bits == 5);
97 try expect(1 == try bit_stream.readBits(u1, 1, &out_bits));
98 try expect(out_bits == 1);
99
100 try expectError(error.EndOfStream, bit_stream.readBitsNoEof(u1, 1));
101 }
102 try tmp.dir.deleteFile(tmp_file_name);
103}
104
105test "File seek ops" {
106 var tmp = tmpDir(.{});
107 defer tmp.cleanup();
108
109 const tmp_file_name = "temp_test_file.txt";
110 var file = try tmp.dir.createFile(tmp_file_name, .{});
111 defer file.close();
112
113 try file.writeAll(&([_]u8{0x55} ** 8192));
114
115 // Seek to the end
116 try file.seekFromEnd(0);
117 try expect((try file.getPos()) == try file.getEndPos());
118 // Negative delta
119 try file.seekBy(-4096);
120 try expect((try file.getPos()) == 4096);
121 // Positive delta
122 try file.seekBy(10);
123 try expect((try file.getPos()) == 4106);
124 // Absolute position
125 try file.seekTo(1234);
126 try expect((try file.getPos()) == 1234);
127}
128
129test "setEndPos" {
130 var tmp = tmpDir(.{});
131 defer tmp.cleanup();
132
133 const tmp_file_name = "temp_test_file.txt";
134 var file = try tmp.dir.createFile(tmp_file_name, .{});
135 defer file.close();
136
137 // Verify that the file size changes and the file offset is not moved
138 try std.testing.expect((try file.getEndPos()) == 0);
139 try std.testing.expect((try file.getPos()) == 0);
140 try file.setEndPos(8192);
141 try std.testing.expect((try file.getEndPos()) == 8192);
142 try std.testing.expect((try file.getPos()) == 0);
143 try file.seekTo(100);
144 try file.setEndPos(4096);
145 try std.testing.expect((try file.getEndPos()) == 4096);
146 try std.testing.expect((try file.getPos()) == 100);
147 try file.setEndPos(0);
148 try std.testing.expect((try file.getEndPos()) == 0);
149 try std.testing.expect((try file.getPos()) == 100);
150}
151
152test "updateTimes" {
153 var tmp = tmpDir(.{});
154 defer tmp.cleanup();
155
156 const tmp_file_name = "just_a_temporary_file.txt";
157 var file = try tmp.dir.createFile(tmp_file_name, .{ .read = true });
158 defer file.close();
159
160 const stat_old = try file.stat();
161 // Set atime and mtime to 5s before
162 try file.updateTimes(
163 stat_old.atime - 5 * std.time.ns_per_s,
164 stat_old.mtime - 5 * std.time.ns_per_s,
165 );
166 const stat_new = try file.stat();
167 try expect(stat_new.atime < stat_old.atime);
168 try expect(stat_new.mtime < stat_old.mtime);
169}
lib/std/Io/tty.zig created+138
...@@ -0,0 +1,138 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const File = std.fs.File;
4const process = std.process;
5const windows = std.os.windows;
6const native_os = builtin.os.tag;
7
8/// Deprecated in favor of `Config.detect`.
9pub fn detectConfig(file: File) Config {
10 return .detect(file);
11}
12
13pub const Color = enum {
14 black,
15 red,
16 green,
17 yellow,
18 blue,
19 magenta,
20 cyan,
21 white,
22 bright_black,
23 bright_red,
24 bright_green,
25 bright_yellow,
26 bright_blue,
27 bright_magenta,
28 bright_cyan,
29 bright_white,
30 dim,
31 bold,
32 reset,
33};
34
35/// Provides simple functionality for manipulating the terminal in some way,
36/// such as coloring text, etc.
37pub const Config = union(enum) {
38 no_color,
39 escape_codes,
40 windows_api: if (native_os == .windows) WindowsContext else void,
41
42 /// Detect suitable TTY configuration options for the given file (commonly stdout/stderr).
43 /// This includes feature checks for ANSI escape codes and the Windows console API, as well as
44 /// respecting the `NO_COLOR` and `CLICOLOR_FORCE` environment variables to override the default.
45 /// Will attempt to enable ANSI escape code support if necessary/possible.
46 pub fn detect(file: File) Config {
47 const force_color: ?bool = if (builtin.os.tag == .wasi)
48 null // wasi does not support environment variables
49 else if (process.hasNonEmptyEnvVarConstant("NO_COLOR"))
50 false
51 else if (process.hasNonEmptyEnvVarConstant("CLICOLOR_FORCE"))
52 true
53 else
54 null;
55
56 if (force_color == false) return .no_color;
57
58 if (file.getOrEnableAnsiEscapeSupport()) return .escape_codes;
59
60 if (native_os == .windows and file.isTty()) {
61 var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
62 if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) == windows.FALSE) {
63 return if (force_color == true) .escape_codes else .no_color;
64 }
65 return .{ .windows_api = .{
66 .handle = file.handle,
67 .reset_attributes = info.wAttributes,
68 } };
69 }
70
71 return if (force_color == true) .escape_codes else .no_color;
72 }
73
74 pub const WindowsContext = struct {
75 handle: File.Handle,
76 reset_attributes: u16,
77 };
78
79 pub const SetColorError = std.os.windows.SetConsoleTextAttributeError || std.io.Writer.Error;
80
81 pub fn setColor(conf: Config, w: *std.io.Writer, color: Color) SetColorError!void {
82 nosuspend switch (conf) {
83 .no_color => return,
84 .escape_codes => {
85 const color_string = switch (color) {
86 .black => "\x1b[30m",
87 .red => "\x1b[31m",
88 .green => "\x1b[32m",
89 .yellow => "\x1b[33m",
90 .blue => "\x1b[34m",
91 .magenta => "\x1b[35m",
92 .cyan => "\x1b[36m",
93 .white => "\x1b[37m",
94 .bright_black => "\x1b[90m",
95 .bright_red => "\x1b[91m",
96 .bright_green => "\x1b[92m",
97 .bright_yellow => "\x1b[93m",
98 .bright_blue => "\x1b[94m",
99 .bright_magenta => "\x1b[95m",
100 .bright_cyan => "\x1b[96m",
101 .bright_white => "\x1b[97m",
102 .bold => "\x1b[1m",
103 .dim => "\x1b[2m",
104 .reset => "\x1b[0m",
105 };
106 try w.writeAll(color_string);
107 },
108 .windows_api => |ctx| if (native_os == .windows) {
109 const attributes = switch (color) {
110 .black => 0,
111 .red => windows.FOREGROUND_RED,
112 .green => windows.FOREGROUND_GREEN,
113 .yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN,
114 .blue => windows.FOREGROUND_BLUE,
115 .magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE,
116 .cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE,
117 .white => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE,
118 .bright_black => windows.FOREGROUND_INTENSITY,
119 .bright_red => windows.FOREGROUND_RED | windows.FOREGROUND_INTENSITY,
120 .bright_green => windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY,
121 .bright_yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY,
122 .bright_blue => windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
123 .bright_magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
124 .bright_cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
125 .bright_white, .bold => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
126 // "dim" is not supported using basic character attributes, but let's still make it do *something*.
127 // This matches the old behavior of TTY.Color before the bright variants were added.
128 .dim => windows.FOREGROUND_INTENSITY,
129 .reset => ctx.reset_attributes,
130 };
131 try w.flush();
132 try windows.SetConsoleTextAttribute(ctx.handle, attributes);
133 } else {
134 unreachable;
135 },
136 };
137 }
138};
lib/std/debug.zig+8-5
...@@ -219,13 +219,16 @@ pub fn unlockStderrWriter() void {...@@ -219,13 +219,16 @@ pub fn unlockStderrWriter() void {
219 std.Progress.unlockStderrWriter();219 std.Progress.unlockStderrWriter();
220}220}
221221
222/// Print to stderr, unbuffered, and silently returning on failure. Intended222/// Print to stderr, silently returning on failure. Intended for use in "printf
223/// for use in "printf debugging". Use `std.log` functions for proper logging.223/// debugging". Use `std.log` functions for proper logging.
224///
225/// Uses a 64-byte buffer for formatted printing which is flushed before this
226/// function returns.
224pub fn print(comptime fmt: []const u8, args: anytype) void {227pub fn print(comptime fmt: []const u8, args: anytype) void {
225 var buffer: [32]u8 = undefined;228 var buffer: [64]u8 = undefined;
226 const bw = lockStderrWriter(&buffer);229 const w = lockStderrWriter(&buffer);
227 defer unlockStderrWriter();230 defer unlockStderrWriter();
228 nosuspend bw.print(fmt, args) catch return;231 nosuspend w.print(fmt, args) catch return;
229}232}
230233
231pub fn getStderrMutex() *std.Thread.Mutex {234pub fn getStderrMutex() *std.Thread.Mutex {
lib/std/fs/path.zig+4-4
...@@ -227,8 +227,8 @@ test join {...@@ -227,8 +227,8 @@ test join {
227 try testJoinMaybeZWindows(&[_][]const u8{ "c:\\a\\", "b\\", "c" }, "c:\\a\\b\\c", zero);227 try testJoinMaybeZWindows(&[_][]const u8{ "c:\\a\\", "b\\", "c" }, "c:\\a\\b\\c", zero);
228228
229 try testJoinMaybeZWindows(229 try testJoinMaybeZWindows(
230 &[_][]const u8{ "c:\\home\\andy\\dev\\zig\\build\\lib\\zig\\std", "io.zig" },230 &[_][]const u8{ "c:\\home\\andy\\dev\\zig\\build\\lib\\zig\\std", "ab.zig" },
231 "c:\\home\\andy\\dev\\zig\\build\\lib\\zig\\std\\io.zig",231 "c:\\home\\andy\\dev\\zig\\build\\lib\\zig\\std\\ab.zig",
232 zero,232 zero,
233 );233 );
234234
...@@ -252,8 +252,8 @@ test join {...@@ -252,8 +252,8 @@ test join {
252 try testJoinMaybeZPosix(&[_][]const u8{ "/a/", "b/", "c" }, "/a/b/c", zero);252 try testJoinMaybeZPosix(&[_][]const u8{ "/a/", "b/", "c" }, "/a/b/c", zero);
253253
254 try testJoinMaybeZPosix(254 try testJoinMaybeZPosix(
255 &[_][]const u8{ "/home/andy/dev/zig/build/lib/zig/std", "io.zig" },255 &[_][]const u8{ "/home/andy/dev/zig/build/lib/zig/std", "ab.zig" },
256 "/home/andy/dev/zig/build/lib/zig/std/io.zig",256 "/home/andy/dev/zig/build/lib/zig/std/ab.zig",
257 zero,257 zero,
258 );258 );
259259
lib/std/io.zig deleted-499
...@@ -1,499 +0,0 @@
1const builtin = @import("builtin");
2const is_windows = builtin.os.tag == .windows;
3
4const std = @import("std.zig");
5const windows = std.os.windows;
6const posix = std.posix;
7const math = std.math;
8const assert = std.debug.assert;
9const Allocator = std.mem.Allocator;
10const Alignment = std.mem.Alignment;
11
12pub const Limit = enum(usize) {
13 nothing = 0,
14 unlimited = std.math.maxInt(usize),
15 _,
16
17 /// `std.math.maxInt(usize)` is interpreted to mean `.unlimited`.
18 pub fn limited(n: usize) Limit {
19 return @enumFromInt(n);
20 }
21
22 /// Any value grater than `std.math.maxInt(usize)` is interpreted to mean
23 /// `.unlimited`.
24 pub fn limited64(n: u64) Limit {
25 return @enumFromInt(@min(n, std.math.maxInt(usize)));
26 }
27
28 pub fn countVec(data: []const []const u8) Limit {
29 var total: usize = 0;
30 for (data) |d| total += d.len;
31 return .limited(total);
32 }
33
34 pub fn min(a: Limit, b: Limit) Limit {
35 return @enumFromInt(@min(@intFromEnum(a), @intFromEnum(b)));
36 }
37
38 pub fn minInt(l: Limit, n: usize) usize {
39 return @min(n, @intFromEnum(l));
40 }
41
42 pub fn minInt64(l: Limit, n: u64) usize {
43 return @min(n, @intFromEnum(l));
44 }
45
46 pub fn slice(l: Limit, s: []u8) []u8 {
47 return s[0..l.minInt(s.len)];
48 }
49
50 pub fn sliceConst(l: Limit, s: []const u8) []const u8 {
51 return s[0..l.minInt(s.len)];
52 }
53
54 pub fn toInt(l: Limit) ?usize {
55 return switch (l) {
56 else => @intFromEnum(l),
57 .unlimited => null,
58 };
59 }
60
61 /// Reduces a slice to account for the limit, leaving room for one extra
62 /// byte above the limit, allowing for the use case of differentiating
63 /// between end-of-stream and reaching the limit.
64 pub fn slice1(l: Limit, non_empty_buffer: []u8) []u8 {
65 assert(non_empty_buffer.len >= 1);
66 return non_empty_buffer[0..@min(@intFromEnum(l) +| 1, non_empty_buffer.len)];
67 }
68
69 pub fn nonzero(l: Limit) bool {
70 return @intFromEnum(l) > 0;
71 }
72
73 /// Return a new limit reduced by `amount` or return `null` indicating
74 /// limit would be exceeded.
75 pub fn subtract(l: Limit, amount: usize) ?Limit {
76 if (l == .unlimited) return .unlimited;
77 if (amount > @intFromEnum(l)) return null;
78 return @enumFromInt(@intFromEnum(l) - amount);
79 }
80};
81
82pub const Reader = @import("io/Reader.zig");
83pub const Writer = @import("io/Writer.zig");
84
85pub const ChangeDetectionStream = @import("io/change_detection_stream.zig").ChangeDetectionStream;
86pub const changeDetectionStream = @import("io/change_detection_stream.zig").changeDetectionStream;
87
88pub const tty = @import("io/tty.zig");
89
90pub fn poll(
91 gpa: Allocator,
92 comptime StreamEnum: type,
93 files: PollFiles(StreamEnum),
94) Poller(StreamEnum) {
95 const enum_fields = @typeInfo(StreamEnum).@"enum".fields;
96 var result: Poller(StreamEnum) = .{
97 .gpa = gpa,
98 .readers = @splat(.{
99 .unbuffered_reader = .failing,
100 .buffer = &.{},
101 .end = 0,
102 .seek = 0,
103 }),
104 .poll_fds = undefined,
105 .windows = if (is_windows) .{
106 .first_read_done = false,
107 .overlapped = [1]windows.OVERLAPPED{
108 std.mem.zeroes(windows.OVERLAPPED),
109 } ** enum_fields.len,
110 .small_bufs = undefined,
111 .active = .{
112 .count = 0,
113 .handles_buf = undefined,
114 .stream_map = undefined,
115 },
116 } else {},
117 };
118
119 inline for (enum_fields, 0..) |field, i| {
120 if (is_windows) {
121 result.windows.active.handles_buf[i] = @field(files, field.name).handle;
122 } else {
123 result.poll_fds[i] = .{
124 .fd = @field(files, field.name).handle,
125 .events = posix.POLL.IN,
126 .revents = undefined,
127 };
128 }
129 }
130
131 return result;
132}
133
134pub fn Poller(comptime StreamEnum: type) type {
135 return struct {
136 const enum_fields = @typeInfo(StreamEnum).@"enum".fields;
137 const PollFd = if (is_windows) void else posix.pollfd;
138
139 gpa: Allocator,
140 readers: [enum_fields.len]Reader,
141 poll_fds: [enum_fields.len]PollFd,
142 windows: if (is_windows) struct {
143 first_read_done: bool,
144 overlapped: [enum_fields.len]windows.OVERLAPPED,
145 small_bufs: [enum_fields.len][128]u8,
146 active: struct {
147 count: math.IntFittingRange(0, enum_fields.len),
148 handles_buf: [enum_fields.len]windows.HANDLE,
149 stream_map: [enum_fields.len]StreamEnum,
150
151 pub fn removeAt(self: *@This(), index: u32) void {
152 assert(index < self.count);
153 for (index + 1..self.count) |i| {
154 self.handles_buf[i - 1] = self.handles_buf[i];
155 self.stream_map[i - 1] = self.stream_map[i];
156 }
157 self.count -= 1;
158 }
159 },
160 } else void,
161
162 const Self = @This();
163
164 pub fn deinit(self: *Self) void {
165 const gpa = self.gpa;
166 if (is_windows) {
167 // cancel any pending IO to prevent clobbering OVERLAPPED value
168 for (self.windows.active.handles_buf[0..self.windows.active.count]) |h| {
169 _ = windows.kernel32.CancelIo(h);
170 }
171 }
172 inline for (&self.readers) |*r| gpa.free(r.buffer);
173 self.* = undefined;
174 }
175
176 pub fn poll(self: *Self) !bool {
177 if (is_windows) {
178 return pollWindows(self, null);
179 } else {
180 return pollPosix(self, null);
181 }
182 }
183
184 pub fn pollTimeout(self: *Self, nanoseconds: u64) !bool {
185 if (is_windows) {
186 return pollWindows(self, nanoseconds);
187 } else {
188 return pollPosix(self, nanoseconds);
189 }
190 }
191
192 pub inline fn reader(self: *Self, comptime which: StreamEnum) *Reader {
193 return &self.readers[@intFromEnum(which)];
194 }
195
196 fn pollWindows(self: *Self, nanoseconds: ?u64) !bool {
197 const bump_amt = 512;
198
199 if (!self.windows.first_read_done) {
200 var already_read_data = false;
201 for (0..enum_fields.len) |i| {
202 const handle = self.windows.active.handles_buf[i];
203 switch (try windowsAsyncReadToFifoAndQueueSmallRead(
204 handle,
205 &self.windows.overlapped[i],
206 &self.fifos[i],
207 &self.windows.small_bufs[i],
208 bump_amt,
209 )) {
210 .populated, .empty => |state| {
211 if (state == .populated) already_read_data = true;
212 self.windows.active.handles_buf[self.windows.active.count] = handle;
213 self.windows.active.stream_map[self.windows.active.count] = @as(StreamEnum, @enumFromInt(i));
214 self.windows.active.count += 1;
215 },
216 .closed => {}, // don't add to the wait_objects list
217 .closed_populated => {
218 // don't add to the wait_objects list, but we did already get data
219 already_read_data = true;
220 },
221 }
222 }
223 self.windows.first_read_done = true;
224 if (already_read_data) return true;
225 }
226
227 while (true) {
228 if (self.windows.active.count == 0) return false;
229
230 const status = windows.kernel32.WaitForMultipleObjects(
231 self.windows.active.count,
232 &self.windows.active.handles_buf,
233 0,
234 if (nanoseconds) |ns|
235 @min(std.math.cast(u32, ns / std.time.ns_per_ms) orelse (windows.INFINITE - 1), windows.INFINITE - 1)
236 else
237 windows.INFINITE,
238 );
239 if (status == windows.WAIT_FAILED)
240 return windows.unexpectedError(windows.GetLastError());
241 if (status == windows.WAIT_TIMEOUT)
242 return true;
243
244 if (status < windows.WAIT_OBJECT_0 or status > windows.WAIT_OBJECT_0 + enum_fields.len - 1)
245 unreachable;
246
247 const active_idx = status - windows.WAIT_OBJECT_0;
248
249 const stream_idx = @intFromEnum(self.windows.active.stream_map[active_idx]);
250 const handle = self.windows.active.handles_buf[active_idx];
251
252 const overlapped = &self.windows.overlapped[stream_idx];
253 const stream_fifo = &self.fifos[stream_idx];
254 const small_buf = &self.windows.small_bufs[stream_idx];
255
256 const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) {
257 .success => |n| n,
258 .closed => {
259 self.windows.active.removeAt(active_idx);
260 continue;
261 },
262 .aborted => unreachable,
263 };
264 try stream_fifo.write(small_buf[0..num_bytes_read]);
265
266 switch (try windowsAsyncReadToFifoAndQueueSmallRead(
267 handle,
268 overlapped,
269 stream_fifo,
270 small_buf,
271 bump_amt,
272 )) {
273 .empty => {}, // irrelevant, we already got data from the small buffer
274 .populated => {},
275 .closed,
276 .closed_populated, // identical, since we already got data from the small buffer
277 => self.windows.active.removeAt(active_idx),
278 }
279 return true;
280 }
281 }
282
283 fn pollPosix(self: *Self, nanoseconds: ?u64) !bool {
284 const gpa = self.gpa;
285 // We ask for ensureUnusedCapacity with this much extra space. This
286 // has more of an effect on small reads because once the reads
287 // start to get larger the amount of space an ArrayList will
288 // allocate grows exponentially.
289 const bump_amt = 512;
290
291 const err_mask = posix.POLL.ERR | posix.POLL.NVAL | posix.POLL.HUP;
292
293 const events_len = try posix.poll(&self.poll_fds, if (nanoseconds) |ns|
294 std.math.cast(i32, ns / std.time.ns_per_ms) orelse std.math.maxInt(i32)
295 else
296 -1);
297 if (events_len == 0) {
298 for (self.poll_fds) |poll_fd| {
299 if (poll_fd.fd != -1) return true;
300 } else return false;
301 }
302
303 var keep_polling = false;
304 inline for (&self.poll_fds, &self.readers) |*poll_fd, *r| {
305 // Try reading whatever is available before checking the error
306 // conditions.
307 // It's still possible to read after a POLL.HUP is received,
308 // always check if there's some data waiting to be read first.
309 if (poll_fd.revents & posix.POLL.IN != 0) {
310 const buf = try r.writableSliceGreedyAlloc(gpa, bump_amt);
311 const amt = posix.read(poll_fd.fd, buf) catch |err| switch (err) {
312 error.BrokenPipe => 0, // Handle the same as EOF.
313 else => |e| return e,
314 };
315 r.advanceBufferEnd(amt);
316 if (amt == 0) {
317 // Remove the fd when the EOF condition is met.
318 poll_fd.fd = -1;
319 } else {
320 keep_polling = true;
321 }
322 } else if (poll_fd.revents & err_mask != 0) {
323 // Exclude the fds that signaled an error.
324 poll_fd.fd = -1;
325 } else if (poll_fd.fd != -1) {
326 keep_polling = true;
327 }
328 }
329 return keep_polling;
330 }
331 };
332}
333
334/// The `ReadFile` docuementation states that `lpNumberOfBytesRead` does not have a meaningful
335/// result when using overlapped I/O, but also that it cannot be `null` on Windows 7. For
336/// compatibility, we point it to this dummy variables, which we never otherwise access.
337/// See: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile
338var win_dummy_bytes_read: u32 = undefined;
339
340/// Read as much data as possible from `handle` with `overlapped`, and write it to the FIFO. Before
341/// returning, queue a read into `small_buf` so that `WaitForMultipleObjects` returns when more data
342/// is available. `handle` must have no pending asynchronous operation.
343fn windowsAsyncReadToFifoAndQueueSmallRead(
344 handle: windows.HANDLE,
345 overlapped: *windows.OVERLAPPED,
346 r: *Reader,
347 small_buf: *[128]u8,
348 bump_amt: usize,
349) !enum { empty, populated, closed_populated, closed } {
350 var read_any_data = false;
351 while (true) {
352 const fifo_read_pending = while (true) {
353 const buf = try r.writableWithSize(bump_amt);
354 const buf_len = math.cast(u32, buf.len) orelse math.maxInt(u32);
355
356 if (0 == windows.kernel32.ReadFile(
357 handle,
358 buf.ptr,
359 buf_len,
360 &win_dummy_bytes_read,
361 overlapped,
362 )) switch (windows.GetLastError()) {
363 .IO_PENDING => break true,
364 .BROKEN_PIPE => return if (read_any_data) .closed_populated else .closed,
365 else => |err| return windows.unexpectedError(err),
366 };
367
368 const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) {
369 .success => |n| n,
370 .closed => return if (read_any_data) .closed_populated else .closed,
371 .aborted => unreachable,
372 };
373
374 read_any_data = true;
375 r.update(num_bytes_read);
376
377 if (num_bytes_read == buf_len) {
378 // We filled the buffer, so there's probably more data available.
379 continue;
380 } else {
381 // We didn't fill the buffer, so assume we're out of data.
382 // There is no pending read.
383 break false;
384 }
385 };
386
387 if (fifo_read_pending) cancel_read: {
388 // Cancel the pending read into the FIFO.
389 _ = windows.kernel32.CancelIo(handle);
390
391 // We have to wait for the handle to be signalled, i.e. for the cancellation to complete.
392 switch (windows.kernel32.WaitForSingleObject(handle, windows.INFINITE)) {
393 windows.WAIT_OBJECT_0 => {},
394 windows.WAIT_FAILED => return windows.unexpectedError(windows.GetLastError()),
395 else => unreachable,
396 }
397
398 // If it completed before we canceled, make sure to tell the FIFO!
399 const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, true)) {
400 .success => |n| n,
401 .closed => return if (read_any_data) .closed_populated else .closed,
402 .aborted => break :cancel_read,
403 };
404 read_any_data = true;
405 r.update(num_bytes_read);
406 }
407
408 // Try to queue the 1-byte read.
409 if (0 == windows.kernel32.ReadFile(
410 handle,
411 small_buf,
412 small_buf.len,
413 &win_dummy_bytes_read,
414 overlapped,
415 )) switch (windows.GetLastError()) {
416 .IO_PENDING => {
417 // 1-byte read pending as intended
418 return if (read_any_data) .populated else .empty;
419 },
420 .BROKEN_PIPE => return if (read_any_data) .closed_populated else .closed,
421 else => |err| return windows.unexpectedError(err),
422 };
423
424 // We got data back this time. Write it to the FIFO and run the main loop again.
425 const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) {
426 .success => |n| n,
427 .closed => return if (read_any_data) .closed_populated else .closed,
428 .aborted => unreachable,
429 };
430 try r.write(small_buf[0..num_bytes_read]);
431 read_any_data = true;
432 }
433}
434
435/// Simple wrapper around `GetOverlappedResult` to determine the result of a `ReadFile` operation.
436/// If `!allow_aborted`, then `aborted` is never returned (`OPERATION_ABORTED` is considered unexpected).
437///
438/// The `ReadFile` documentation states that the number of bytes read by an overlapped `ReadFile` must be determined using `GetOverlappedResult`, even if the
439/// operation immediately returns data:
440/// "Use NULL for [lpNumberOfBytesRead] if this is an asynchronous operation to avoid potentially
441/// erroneous results."
442/// "If `hFile` was opened with `FILE_FLAG_OVERLAPPED`, the following conditions are in effect: [...]
443/// The lpNumberOfBytesRead parameter should be set to NULL. Use the GetOverlappedResult function to
444/// get the actual number of bytes read."
445/// See: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile
446fn windowsGetReadResult(
447 handle: windows.HANDLE,
448 overlapped: *windows.OVERLAPPED,
449 allow_aborted: bool,
450) !union(enum) {
451 success: u32,
452 closed,
453 aborted,
454} {
455 var num_bytes_read: u32 = undefined;
456 if (0 == windows.kernel32.GetOverlappedResult(
457 handle,
458 overlapped,
459 &num_bytes_read,
460 0,
461 )) switch (windows.GetLastError()) {
462 .BROKEN_PIPE => return .closed,
463 .OPERATION_ABORTED => |err| if (allow_aborted) {
464 return .aborted;
465 } else {
466 return windows.unexpectedError(err);
467 },
468 else => |err| return windows.unexpectedError(err),
469 };
470 return .{ .success = num_bytes_read };
471}
472
473/// Given an enum, returns a struct with fields of that enum, each field
474/// representing an I/O stream for polling.
475pub fn PollFiles(comptime StreamEnum: type) type {
476 const enum_fields = @typeInfo(StreamEnum).@"enum".fields;
477 var struct_fields: [enum_fields.len]std.builtin.Type.StructField = undefined;
478 for (&struct_fields, enum_fields) |*struct_field, enum_field| {
479 struct_field.* = .{
480 .name = enum_field.name,
481 .type = std.fs.File,
482 .default_value_ptr = null,
483 .is_comptime = false,
484 .alignment = @alignOf(std.fs.File),
485 };
486 }
487 return @Type(.{ .@"struct" = .{
488 .layout = .auto,
489 .fields = &struct_fields,
490 .decls = &.{},
491 .is_tuple = false,
492 } });
493}
494
495test {
496 _ = Reader;
497 _ = Writer;
498 _ = @import("io/test.zig");
499}
lib/std/io/DeprecatedReader.zig deleted-386
...@@ -1,386 +0,0 @@
1context: *const anyopaque,
2readFn: *const fn (context: *const anyopaque, buffer: []u8) anyerror!usize,
3
4pub const Error = anyerror;
5
6/// Returns the number of bytes read. It may be less than buffer.len.
7/// If the number of bytes read is 0, it means end of stream.
8/// End of stream is not an error condition.
9pub fn read(self: Self, buffer: []u8) anyerror!usize {
10 return self.readFn(self.context, buffer);
11}
12
13/// Returns the number of bytes read. If the number read is smaller than `buffer.len`, it
14/// means the stream reached the end. Reaching the end of a stream is not an error
15/// condition.
16pub fn readAll(self: Self, buffer: []u8) anyerror!usize {
17 return readAtLeast(self, buffer, buffer.len);
18}
19
20/// Returns the number of bytes read, calling the underlying read
21/// function the minimal number of times until the buffer has at least
22/// `len` bytes filled. If the number read is less than `len` it means
23/// the stream reached the end. Reaching the end of the stream is not
24/// an error condition.
25pub fn readAtLeast(self: Self, buffer: []u8, len: usize) anyerror!usize {
26 assert(len <= buffer.len);
27 var index: usize = 0;
28 while (index < len) {
29 const amt = try self.read(buffer[index..]);
30 if (amt == 0) break;
31 index += amt;
32 }
33 return index;
34}
35
36/// If the number read would be smaller than `buf.len`, `error.EndOfStream` is returned instead.
37pub fn readNoEof(self: Self, buf: []u8) anyerror!void {
38 const amt_read = try self.readAll(buf);
39 if (amt_read < buf.len) return error.EndOfStream;
40}
41
42/// Appends to the `std.ArrayList` contents by reading from the stream
43/// until end of stream is found.
44/// If the number of bytes appended would exceed `max_append_size`,
45/// `error.StreamTooLong` is returned
46/// and the `std.ArrayList` has exactly `max_append_size` bytes appended.
47pub fn readAllArrayList(
48 self: Self,
49 array_list: *std.ArrayList(u8),
50 max_append_size: usize,
51) anyerror!void {
52 return self.readAllArrayListAligned(null, array_list, max_append_size);
53}
54
55pub fn readAllArrayListAligned(
56 self: Self,
57 comptime alignment: ?Alignment,
58 array_list: *std.ArrayListAligned(u8, alignment),
59 max_append_size: usize,
60) anyerror!void {
61 try array_list.ensureTotalCapacity(@min(max_append_size, 4096));
62 const original_len = array_list.items.len;
63 var start_index: usize = original_len;
64 while (true) {
65 array_list.expandToCapacity();
66 const dest_slice = array_list.items[start_index..];
67 const bytes_read = try self.readAll(dest_slice);
68 start_index += bytes_read;
69
70 if (start_index - original_len > max_append_size) {
71 array_list.shrinkAndFree(original_len + max_append_size);
72 return error.StreamTooLong;
73 }
74
75 if (bytes_read != dest_slice.len) {
76 array_list.shrinkAndFree(start_index);
77 return;
78 }
79
80 // This will trigger ArrayList to expand superlinearly at whatever its growth rate is.
81 try array_list.ensureTotalCapacity(start_index + 1);
82 }
83}
84
85/// Allocates enough memory to hold all the contents of the stream. If the allocated
86/// memory would be greater than `max_size`, returns `error.StreamTooLong`.
87/// Caller owns returned memory.
88/// If this function returns an error, the contents from the stream read so far are lost.
89pub fn readAllAlloc(self: Self, allocator: mem.Allocator, max_size: usize) anyerror![]u8 {
90 var array_list = std.ArrayList(u8).init(allocator);
91 defer array_list.deinit();
92 try self.readAllArrayList(&array_list, max_size);
93 return try array_list.toOwnedSlice();
94}
95
96/// Deprecated: use `streamUntilDelimiter` with ArrayList's writer instead.
97/// Replaces the `std.ArrayList` contents by reading from the stream until `delimiter` is found.
98/// Does not include the delimiter in the result.
99/// If the `std.ArrayList` length would exceed `max_size`, `error.StreamTooLong` is returned and the
100/// `std.ArrayList` is populated with `max_size` bytes from the stream.
101pub fn readUntilDelimiterArrayList(
102 self: Self,
103 array_list: *std.ArrayList(u8),
104 delimiter: u8,
105 max_size: usize,
106) anyerror!void {
107 array_list.shrinkRetainingCapacity(0);
108 try self.streamUntilDelimiter(array_list.writer(), delimiter, max_size);
109}
110
111/// Deprecated: use `streamUntilDelimiter` with ArrayList's writer instead.
112/// Allocates enough memory to read until `delimiter`. If the allocated
113/// memory would be greater than `max_size`, returns `error.StreamTooLong`.
114/// Caller owns returned memory.
115/// If this function returns an error, the contents from the stream read so far are lost.
116pub fn readUntilDelimiterAlloc(
117 self: Self,
118 allocator: mem.Allocator,
119 delimiter: u8,
120 max_size: usize,
121) anyerror![]u8 {
122 var array_list = std.ArrayList(u8).init(allocator);
123 defer array_list.deinit();
124 try self.streamUntilDelimiter(array_list.writer(), delimiter, max_size);
125 return try array_list.toOwnedSlice();
126}
127
128/// Deprecated: use `streamUntilDelimiter` with FixedBufferStream's writer instead.
129/// Reads from the stream until specified byte is found. If the buffer is not
130/// large enough to hold the entire contents, `error.StreamTooLong` is returned.
131/// If end-of-stream is found, `error.EndOfStream` is returned.
132/// Returns a slice of the stream data, with ptr equal to `buf.ptr`. The
133/// delimiter byte is written to the output buffer but is not included
134/// in the returned slice.
135pub fn readUntilDelimiter(self: Self, buf: []u8, delimiter: u8) anyerror![]u8 {
136 var fbs = std.io.fixedBufferStream(buf);
137 try self.streamUntilDelimiter(fbs.writer(), delimiter, fbs.buffer.len);
138 const output = fbs.getWritten();
139 buf[output.len] = delimiter; // emulating old behaviour
140 return output;
141}
142
143/// Deprecated: use `streamUntilDelimiter` with ArrayList's (or any other's) writer instead.
144/// Allocates enough memory to read until `delimiter` or end-of-stream.
145/// If the allocated memory would be greater than `max_size`, returns
146/// `error.StreamTooLong`. If end-of-stream is found, returns the rest
147/// of the stream. If this function is called again after that, returns
148/// null.
149/// Caller owns returned memory.
150/// If this function returns an error, the contents from the stream read so far are lost.
151pub fn readUntilDelimiterOrEofAlloc(
152 self: Self,
153 allocator: mem.Allocator,
154 delimiter: u8,
155 max_size: usize,
156) anyerror!?[]u8 {
157 var array_list = std.ArrayList(u8).init(allocator);
158 defer array_list.deinit();
159 self.streamUntilDelimiter(array_list.writer(), delimiter, max_size) catch |err| switch (err) {
160 error.EndOfStream => if (array_list.items.len == 0) {
161 return null;
162 },
163 else => |e| return e,
164 };
165 return try array_list.toOwnedSlice();
166}
167
168/// Deprecated: use `streamUntilDelimiter` with FixedBufferStream's writer instead.
169/// Reads from the stream until specified byte is found. If the buffer is not
170/// large enough to hold the entire contents, `error.StreamTooLong` is returned.
171/// If end-of-stream is found, returns the rest of the stream. If this
172/// function is called again after that, returns null.
173/// Returns a slice of the stream data, with ptr equal to `buf.ptr`. The
174/// delimiter byte is written to the output buffer but is not included
175/// in the returned slice.
176pub fn readUntilDelimiterOrEof(self: Self, buf: []u8, delimiter: u8) anyerror!?[]u8 {
177 var fbs = std.io.fixedBufferStream(buf);
178 self.streamUntilDelimiter(fbs.writer(), delimiter, fbs.buffer.len) catch |err| switch (err) {
179 error.EndOfStream => if (fbs.getWritten().len == 0) {
180 return null;
181 },
182
183 else => |e| return e,
184 };
185 const output = fbs.getWritten();
186 buf[output.len] = delimiter; // emulating old behaviour
187 return output;
188}
189
190/// Appends to the `writer` contents by reading from the stream until `delimiter` is found.
191/// Does not write the delimiter itself.
192/// If `optional_max_size` is not null and amount of written bytes exceeds `optional_max_size`,
193/// returns `error.StreamTooLong` and finishes appending.
194/// If `optional_max_size` is null, appending is unbounded.
195pub fn streamUntilDelimiter(
196 self: Self,
197 writer: anytype,
198 delimiter: u8,
199 optional_max_size: ?usize,
200) anyerror!void {
201 if (optional_max_size) |max_size| {
202 for (0..max_size) |_| {
203 const byte: u8 = try self.readByte();
204 if (byte == delimiter) return;
205 try writer.writeByte(byte);
206 }
207 return error.StreamTooLong;
208 } else {
209 while (true) {
210 const byte: u8 = try self.readByte();
211 if (byte == delimiter) return;
212 try writer.writeByte(byte);
213 }
214 // Can not throw `error.StreamTooLong` since there are no boundary.
215 }
216}
217
218/// Reads from the stream until specified byte is found, discarding all data,
219/// including the delimiter.
220/// If end-of-stream is found, this function succeeds.
221pub fn skipUntilDelimiterOrEof(self: Self, delimiter: u8) anyerror!void {
222 while (true) {
223 const byte = self.readByte() catch |err| switch (err) {
224 error.EndOfStream => return,
225 else => |e| return e,
226 };
227 if (byte == delimiter) return;
228 }
229}
230
231/// Reads 1 byte from the stream or returns `error.EndOfStream`.
232pub fn readByte(self: Self) anyerror!u8 {
233 var result: [1]u8 = undefined;
234 const amt_read = try self.read(result[0..]);
235 if (amt_read < 1) return error.EndOfStream;
236 return result[0];
237}
238
239/// Same as `readByte` except the returned byte is signed.
240pub fn readByteSigned(self: Self) anyerror!i8 {
241 return @as(i8, @bitCast(try self.readByte()));
242}
243
244/// Reads exactly `num_bytes` bytes and returns as an array.
245/// `num_bytes` must be comptime-known
246pub fn readBytesNoEof(self: Self, comptime num_bytes: usize) anyerror![num_bytes]u8 {
247 var bytes: [num_bytes]u8 = undefined;
248 try self.readNoEof(&bytes);
249 return bytes;
250}
251
252/// Reads bytes until `bounded.len` is equal to `num_bytes`,
253/// or the stream ends.
254///
255/// * it is assumed that `num_bytes` will not exceed `bounded.capacity()`
256pub fn readIntoBoundedBytes(
257 self: Self,
258 comptime num_bytes: usize,
259 bounded: *std.BoundedArray(u8, num_bytes),
260) anyerror!void {
261 while (bounded.len < num_bytes) {
262 // get at most the number of bytes free in the bounded array
263 const bytes_read = try self.read(bounded.unusedCapacitySlice());
264 if (bytes_read == 0) return;
265
266 // bytes_read will never be larger than @TypeOf(bounded.len)
267 // due to `self.read` being bounded by `bounded.unusedCapacitySlice()`
268 bounded.len += @as(@TypeOf(bounded.len), @intCast(bytes_read));
269 }
270}
271
272/// Reads at most `num_bytes` and returns as a bounded array.
273pub fn readBoundedBytes(self: Self, comptime num_bytes: usize) anyerror!std.BoundedArray(u8, num_bytes) {
274 var result = std.BoundedArray(u8, num_bytes){};
275 try self.readIntoBoundedBytes(num_bytes, &result);
276 return result;
277}
278
279pub inline fn readInt(self: Self, comptime T: type, endian: std.builtin.Endian) anyerror!T {
280 const bytes = try self.readBytesNoEof(@divExact(@typeInfo(T).int.bits, 8));
281 return mem.readInt(T, &bytes, endian);
282}
283
284pub fn readVarInt(
285 self: Self,
286 comptime ReturnType: type,
287 endian: std.builtin.Endian,
288 size: usize,
289) anyerror!ReturnType {
290 assert(size <= @sizeOf(ReturnType));
291 var bytes_buf: [@sizeOf(ReturnType)]u8 = undefined;
292 const bytes = bytes_buf[0..size];
293 try self.readNoEof(bytes);
294 return mem.readVarInt(ReturnType, bytes, endian);
295}
296
297/// Optional parameters for `skipBytes`
298pub const SkipBytesOptions = struct {
299 buf_size: usize = 512,
300};
301
302// `num_bytes` is a `u64` to match `off_t`
303/// Reads `num_bytes` bytes from the stream and discards them
304pub fn skipBytes(self: Self, num_bytes: u64, comptime options: SkipBytesOptions) anyerror!void {
305 var buf: [options.buf_size]u8 = undefined;
306 var remaining = num_bytes;
307
308 while (remaining > 0) {
309 const amt = @min(remaining, options.buf_size);
310 try self.readNoEof(buf[0..amt]);
311 remaining -= amt;
312 }
313}
314
315/// Reads `slice.len` bytes from the stream and returns if they are the same as the passed slice
316pub fn isBytes(self: Self, slice: []const u8) anyerror!bool {
317 var i: usize = 0;
318 var matches = true;
319 while (i < slice.len) : (i += 1) {
320 if (slice[i] != try self.readByte()) {
321 matches = false;
322 }
323 }
324 return matches;
325}
326
327pub fn readStruct(self: Self, comptime T: type) anyerror!T {
328 // Only extern and packed structs have defined in-memory layout.
329 comptime assert(@typeInfo(T).@"struct".layout != .auto);
330 var res: [1]T = undefined;
331 try self.readNoEof(mem.sliceAsBytes(res[0..]));
332 return res[0];
333}
334
335pub fn readStructEndian(self: Self, comptime T: type, endian: std.builtin.Endian) anyerror!T {
336 var res = try self.readStruct(T);
337 if (native_endian != endian) {
338 mem.byteSwapAllFields(T, &res);
339 }
340 return res;
341}
342
343/// Reads an integer with the same size as the given enum's tag type. If the integer matches
344/// an enum tag, casts the integer to the enum tag and returns it. Otherwise, returns an `error.InvalidValue`.
345/// TODO optimization taking advantage of most fields being in order
346pub fn readEnum(self: Self, comptime Enum: type, endian: std.builtin.Endian) anyerror!Enum {
347 const E = error{
348 /// An integer was read, but it did not match any of the tags in the supplied enum.
349 InvalidValue,
350 };
351 const type_info = @typeInfo(Enum).@"enum";
352 const tag = try self.readInt(type_info.tag_type, endian);
353
354 inline for (std.meta.fields(Enum)) |field| {
355 if (tag == field.value) {
356 return @field(Enum, field.name);
357 }
358 }
359
360 return E.InvalidValue;
361}
362
363/// Reads the stream until the end, ignoring all the data.
364/// Returns the number of bytes discarded.
365pub fn discard(self: Self) anyerror!u64 {
366 var trash: [4096]u8 = undefined;
367 var index: u64 = 0;
368 while (true) {
369 const n = try self.read(&trash);
370 if (n == 0) return index;
371 index += n;
372 }
373}
374
375const std = @import("../std.zig");
376const Self = @This();
377const math = std.math;
378const assert = std.debug.assert;
379const mem = std.mem;
380const testing = std.testing;
381const native_endian = @import("builtin").target.cpu.arch.endian();
382const Alignment = std.mem.Alignment;
383
384test {
385 _ = @import("Reader/test.zig");
386}
lib/std/io/DeprecatedWriter.zig deleted-109
...@@ -1,109 +0,0 @@
1const std = @import("../std.zig");
2const assert = std.debug.assert;
3const mem = std.mem;
4const native_endian = @import("builtin").target.cpu.arch.endian();
5
6context: *const anyopaque,
7writeFn: *const fn (context: *const anyopaque, bytes: []const u8) anyerror!usize,
8
9const Self = @This();
10pub const Error = anyerror;
11
12pub fn write(self: Self, bytes: []const u8) anyerror!usize {
13 return self.writeFn(self.context, bytes);
14}
15
16pub fn writeAll(self: Self, bytes: []const u8) anyerror!void {
17 var index: usize = 0;
18 while (index != bytes.len) {
19 index += try self.write(bytes[index..]);
20 }
21}
22
23pub fn print(self: Self, comptime format: []const u8, args: anytype) anyerror!void {
24 return std.fmt.format(self, format, args);
25}
26
27pub fn writeByte(self: Self, byte: u8) anyerror!void {
28 const array = [1]u8{byte};
29 return self.writeAll(&array);
30}
31
32pub fn writeByteNTimes(self: Self, byte: u8, n: usize) anyerror!void {
33 var bytes: [256]u8 = undefined;
34 @memset(bytes[0..], byte);
35
36 var remaining: usize = n;
37 while (remaining > 0) {
38 const to_write = @min(remaining, bytes.len);
39 try self.writeAll(bytes[0..to_write]);
40 remaining -= to_write;
41 }
42}
43
44pub fn writeBytesNTimes(self: Self, bytes: []const u8, n: usize) anyerror!void {
45 var i: usize = 0;
46 while (i < n) : (i += 1) {
47 try self.writeAll(bytes);
48 }
49}
50
51pub inline fn writeInt(self: Self, comptime T: type, value: T, endian: std.builtin.Endian) anyerror!void {
52 var bytes: [@divExact(@typeInfo(T).int.bits, 8)]u8 = undefined;
53 mem.writeInt(std.math.ByteAlignedInt(@TypeOf(value)), &bytes, value, endian);
54 return self.writeAll(&bytes);
55}
56
57pub fn writeStruct(self: Self, value: anytype) anyerror!void {
58 // Only extern and packed structs have defined in-memory layout.
59 comptime assert(@typeInfo(@TypeOf(value)).@"struct".layout != .auto);
60 return self.writeAll(mem.asBytes(&value));
61}
62
63pub fn writeStructEndian(self: Self, value: anytype, endian: std.builtin.Endian) anyerror!void {
64 // TODO: make sure this value is not a reference type
65 if (native_endian == endian) {
66 return self.writeStruct(value);
67 } else {
68 var copy = value;
69 mem.byteSwapAllFields(@TypeOf(value), &copy);
70 return self.writeStruct(copy);
71 }
72}
73
74pub fn writeFile(self: Self, file: std.fs.File) anyerror!void {
75 // TODO: figure out how to adjust std lib abstractions so that this ends up
76 // doing sendfile or maybe even copy_file_range under the right conditions.
77 var buf: [4000]u8 = undefined;
78 while (true) {
79 const n = try file.readAll(&buf);
80 try self.writeAll(buf[0..n]);
81 if (n < buf.len) return;
82 }
83}
84
85/// Helper for bridging to the new `Writer` API while upgrading.
86pub fn adaptToNewApi(self: *const Self) Adapter {
87 return .{
88 .derp_writer = self.*,
89 .new_interface = .{
90 .buffer = &.{},
91 .vtable = &.{ .drain = Adapter.drain },
92 },
93 };
94}
95
96pub const Adapter = struct {
97 derp_writer: Self,
98 new_interface: std.io.Writer,
99 err: ?Error = null,
100
101 fn drain(w: *std.io.Writer, data: []const []const u8, splat: usize) std.io.Writer.Error!usize {
102 _ = splat;
103 const a: *@This() = @fieldParentPtr("new_interface", w);
104 return a.derp_writer.write(data[0]) catch |err| {
105 a.err = err;
106 return error.WriteFailed;
107 };
108 }
109};
lib/std/io/Reader.zig deleted-1740
...@@ -1,1740 +0,0 @@
1const Reader = @This();
2
3const builtin = @import("builtin");
4const native_endian = builtin.target.cpu.arch.endian();
5
6const std = @import("../std.zig");
7const Writer = std.io.Writer;
8const assert = std.debug.assert;
9const testing = std.testing;
10const Allocator = std.mem.Allocator;
11const ArrayList = std.ArrayListUnmanaged;
12const Limit = std.io.Limit;
13
14pub const Limited = @import("Reader/Limited.zig");
15
16vtable: *const VTable,
17buffer: []u8,
18/// Number of bytes which have been consumed from `buffer`.
19seek: usize,
20/// In `buffer` before this are buffered bytes, after this is `undefined`.
21end: usize,
22
23pub const VTable = struct {
24 /// Writes bytes from the internally tracked logical position to `w`.
25 ///
26 /// Returns the number of bytes written, which will be at minimum `0` and
27 /// at most `limit`. The number returned, including zero, does not indicate
28 /// end of stream. `limit` is guaranteed to be at least as large as the
29 /// buffer capacity of `w`, a value whose minimum size is determined by the
30 /// stream implementation.
31 ///
32 /// The reader's internal logical seek position moves forward in accordance
33 /// with the number of bytes returned from this function.
34 ///
35 /// Implementations are encouraged to utilize mandatory minimum buffer
36 /// sizes combined with short reads (returning a value less than `limit`)
37 /// in order to minimize complexity.
38 ///
39 /// Although this function is usually called when `buffer` is empty, it is
40 /// also called when it needs to be filled more due to the API user
41 /// requesting contiguous memory. In either case, the existing buffer data
42 /// should be ignored; new data written to `w`.
43 ///
44 /// In addition to, or instead of writing to `w`, the implementation may
45 /// choose to store data in `buffer`, modifying `seek` and `end`
46 /// accordingly. Stream implementations are encouraged to take advantage of
47 /// this if simplifies the logic.
48 stream: *const fn (r: *Reader, w: *Writer, limit: Limit) StreamError!usize,
49
50 /// Consumes bytes from the internally tracked stream position without
51 /// providing access to them.
52 ///
53 /// Returns the number of bytes discarded, which will be at minimum `0` and
54 /// at most `limit`. The number of bytes returned, including zero, does not
55 /// indicate end of stream.
56 ///
57 /// The reader's internal logical seek position moves forward in accordance
58 /// with the number of bytes returned from this function.
59 ///
60 /// Implementations are encouraged to utilize mandatory minimum buffer
61 /// sizes combined with short reads (returning a value less than `limit`)
62 /// in order to minimize complexity.
63 ///
64 /// The default implementation is is based on calling `stream`, borrowing
65 /// `buffer` to construct a temporary `Writer` and ignoring the written
66 /// data.
67 ///
68 /// This function is only called when `buffer` is empty.
69 discard: *const fn (r: *Reader, limit: Limit) Error!usize = defaultDiscard,
70};
71
72pub const StreamError = error{
73 /// See the `Reader` implementation for detailed diagnostics.
74 ReadFailed,
75 /// See the `Writer` implementation for detailed diagnostics.
76 WriteFailed,
77 /// End of stream indicated from the `Reader`. This error cannot originate
78 /// from the `Writer`.
79 EndOfStream,
80};
81
82pub const Error = error{
83 /// See the `Reader` implementation for detailed diagnostics.
84 ReadFailed,
85 EndOfStream,
86};
87
88pub const StreamRemainingError = error{
89 /// See the `Reader` implementation for detailed diagnostics.
90 ReadFailed,
91 /// See the `Writer` implementation for detailed diagnostics.
92 WriteFailed,
93};
94
95pub const ShortError = error{
96 /// See the `Reader` implementation for detailed diagnostics.
97 ReadFailed,
98};
99
100pub const failing: Reader = .{
101 .vtable = &.{
102 .read = failingStream,
103 .discard = failingDiscard,
104 },
105 .buffer = &.{},
106 .seek = 0,
107 .end = 0,
108};
109
110/// This is generally safe to `@constCast` because it has an empty buffer, so
111/// there is not really a way to accidentally attempt mutation of these fields.
112const ending_state: Reader = .fixed(&.{});
113pub const ending: *Reader = @constCast(&ending_state);
114
115pub fn limited(r: *Reader, limit: Limit, buffer: []u8) Limited {
116 return .init(r, limit, buffer);
117}
118
119/// Constructs a `Reader` such that it will read from `buffer` and then end.
120pub fn fixed(buffer: []const u8) Reader {
121 return .{
122 .vtable = &.{
123 .stream = endingStream,
124 .discard = endingDiscard,
125 },
126 // This cast is safe because all potential writes to it will instead
127 // return `error.EndOfStream`.
128 .buffer = @constCast(buffer),
129 .end = buffer.len,
130 .seek = 0,
131 };
132}
133
134pub fn stream(r: *Reader, w: *Writer, limit: Limit) StreamError!usize {
135 const buffer = limit.slice(r.buffer[r.seek..r.end]);
136 if (buffer.len > 0) {
137 @branchHint(.likely);
138 const n = try w.write(buffer);
139 r.seek += n;
140 return n;
141 }
142 const n = try r.vtable.stream(r, w, limit);
143 assert(n <= @intFromEnum(limit));
144 return n;
145}
146
147pub fn discard(r: *Reader, limit: Limit) Error!usize {
148 const buffered_len = r.end - r.seek;
149 const remaining: Limit = if (limit.toInt()) |n| l: {
150 if (buffered_len >= n) {
151 r.seek += n;
152 return n;
153 }
154 break :l .limited(n - buffered_len);
155 } else .unlimited;
156 r.seek = 0;
157 r.end = 0;
158 const n = try r.vtable.discard(r, remaining);
159 assert(n <= @intFromEnum(remaining));
160 return buffered_len + n;
161}
162
163pub fn defaultDiscard(r: *Reader, limit: Limit) Error!usize {
164 assert(r.seek == 0);
165 assert(r.end == 0);
166 var dw: Writer.Discarding = .init(r.buffer);
167 const n = r.stream(&dw.writer, limit) catch |err| switch (err) {
168 error.WriteFailed => unreachable,
169 error.ReadFailed => return error.ReadFailed,
170 error.EndOfStream => return error.EndOfStream,
171 };
172 assert(n <= @intFromEnum(limit));
173 return n;
174}
175
176/// "Pump" exactly `n` bytes from the reader to the writer.
177pub fn streamExact(r: *Reader, w: *Writer, n: usize) StreamError!void {
178 var remaining = n;
179 while (remaining != 0) remaining -= try r.stream(w, .limited(remaining));
180}
181
182/// "Pump" data from the reader to the writer, handling `error.EndOfStream` as
183/// a success case.
184///
185/// Returns total number of bytes written to `w`.
186pub fn streamRemaining(r: *Reader, w: *Writer) StreamRemainingError!usize {
187 var offset: usize = 0;
188 while (true) {
189 offset += r.stream(w, .unlimited) catch |err| switch (err) {
190 error.EndOfStream => return offset,
191 else => |e| return e,
192 };
193 }
194}
195
196/// Consumes the stream until the end, ignoring all the data, returning the
197/// number of bytes discarded.
198pub fn discardRemaining(r: *Reader) ShortError!usize {
199 var offset: usize = r.end - r.seek;
200 r.seek = 0;
201 r.end = 0;
202 while (true) {
203 offset += r.vtable.discard(r, .unlimited) catch |err| switch (err) {
204 error.EndOfStream => return offset,
205 else => |e| return e,
206 };
207 }
208}
209
210pub const LimitedAllocError = Allocator.Error || ShortError || error{StreamTooLong};
211
212/// Transfers all bytes from the current position to the end of the stream, up
213/// to `limit`, returning them as a caller-owned allocated slice.
214///
215/// If `limit` would be exceeded, `error.StreamTooLong` is returned instead. In
216/// such case, the next byte that would be read will be the first one to exceed
217/// `limit`, and all preceeding bytes have been discarded.
218///
219/// Asserts `buffer` has nonzero capacity.
220///
221/// See also:
222/// * `appendRemaining`
223pub fn allocRemaining(r: *Reader, gpa: Allocator, limit: Limit) LimitedAllocError![]u8 {
224 var buffer: ArrayList(u8) = .empty;
225 defer buffer.deinit(gpa);
226 try appendRemaining(r, gpa, null, &buffer, limit);
227 return buffer.toOwnedSlice(gpa);
228}
229
230/// Transfers all bytes from the current position to the end of the stream, up
231/// to `limit`, appending them to `list`.
232///
233/// If `limit` would be exceeded, `error.StreamTooLong` is returned instead. In
234/// such case, the next byte that would be read will be the first one to exceed
235/// `limit`, and all preceeding bytes have been appended to `list`.
236///
237/// Asserts `buffer` has nonzero capacity.
238///
239/// See also:
240/// * `allocRemaining`
241pub fn appendRemaining(
242 r: *Reader,
243 gpa: Allocator,
244 comptime alignment: ?std.mem.Alignment,
245 list: *std.ArrayListAlignedUnmanaged(u8, alignment),
246 limit: Limit,
247) LimitedAllocError!void {
248 const buffer = r.buffer;
249 const buffer_contents = buffer[r.seek..r.end];
250 const copy_len = limit.minInt(buffer_contents.len);
251 try list.ensureUnusedCapacity(gpa, copy_len);
252 @memcpy(list.unusedCapacitySlice()[0..copy_len], buffer[0..copy_len]);
253 list.items.len += copy_len;
254 r.seek += copy_len;
255 if (copy_len == buffer_contents.len) {
256 r.seek = 0;
257 r.end = 0;
258 }
259 var remaining = limit.subtract(copy_len).?;
260 while (true) {
261 try list.ensureUnusedCapacity(gpa, 1);
262 const dest = remaining.slice(list.unusedCapacitySlice());
263 const additional_buffer: []u8 = if (@intFromEnum(remaining) == dest.len) buffer else &.{};
264 const n = readVec(r, &.{ dest, additional_buffer }) catch |err| switch (err) {
265 error.EndOfStream => break,
266 error.ReadFailed => return error.ReadFailed,
267 };
268 if (n > dest.len) {
269 r.end = n - dest.len;
270 list.items.len += dest.len;
271 return error.StreamTooLong;
272 }
273 list.items.len += n;
274 remaining = remaining.subtract(n).?;
275 }
276}
277
278/// Writes bytes from the internally tracked stream position to `data`.
279///
280/// Returns the number of bytes written, which will be at minimum `0` and
281/// at most the sum of each data slice length. The number of bytes read,
282/// including zero, does not indicate end of stream.
283///
284/// The reader's internal logical seek position moves forward in accordance
285/// with the number of bytes returned from this function.
286pub fn readVec(r: *Reader, data: []const []u8) Error!usize {
287 return readVecLimit(r, data, .unlimited);
288}
289
290/// Equivalent to `readVec` but reads at most `limit` bytes.
291///
292/// This ultimately will lower to a call to `stream`, but it must ensure
293/// that the buffer used has at least as much capacity, in case that function
294/// depends on a minimum buffer capacity. It also ensures that if the `stream`
295/// implementation calls `Writer.writableVector`, it will get this data slice
296/// along with the buffer at the end.
297pub fn readVecLimit(r: *Reader, data: []const []u8, limit: Limit) Error!usize {
298 comptime assert(@intFromEnum(Limit.unlimited) == std.math.maxInt(usize));
299 var remaining = @intFromEnum(limit);
300 for (data, 0..) |buf, i| {
301 const buffer_contents = r.buffer[r.seek..r.end];
302 const copy_len = @min(buffer_contents.len, buf.len, remaining);
303 @memcpy(buf[0..copy_len], buffer_contents[0..copy_len]);
304 r.seek += copy_len;
305 remaining -= copy_len;
306 if (remaining == 0) break;
307 if (buf.len - copy_len == 0) continue;
308
309 // All of `buffer` has been copied to `data`. We now set up a structure
310 // that enables the `Writer.writableVector` API, while also ensuring
311 // API that directly operates on the `Writable.buffer` has its minimum
312 // buffer capacity requirements met.
313 r.seek = 0;
314 r.end = 0;
315 const first = buf[copy_len..];
316 const middle = data[i + 1 ..];
317 var wrapper: Writer.VectorWrapper = .{
318 .it = .{
319 .first = first,
320 .middle = middle,
321 .last = r.buffer,
322 },
323 .writer = .{
324 .buffer = if (first.len >= r.buffer.len) first else r.buffer,
325 .vtable = Writer.VectorWrapper.vtable,
326 },
327 };
328 var n = r.vtable.stream(r, &wrapper.writer, .limited(remaining)) catch |err| switch (err) {
329 error.WriteFailed => {
330 assert(!wrapper.used);
331 if (wrapper.writer.buffer.ptr == first.ptr) {
332 remaining -= wrapper.writer.end;
333 } else {
334 assert(wrapper.writer.end <= r.buffer.len);
335 r.end = wrapper.writer.end;
336 }
337 break;
338 },
339 else => |e| return e,
340 };
341 if (!wrapper.used) {
342 if (wrapper.writer.buffer.ptr == first.ptr) {
343 remaining -= n;
344 } else {
345 assert(n <= r.buffer.len);
346 r.end = n;
347 }
348 break;
349 }
350 if (n < first.len) {
351 remaining -= n;
352 break;
353 }
354 remaining -= first.len;
355 n -= first.len;
356 for (middle) |mid| {
357 if (n < mid.len) {
358 remaining -= n;
359 break;
360 }
361 remaining -= mid.len;
362 n -= mid.len;
363 }
364 assert(n <= r.buffer.len);
365 r.end = n;
366 break;
367 }
368 return @intFromEnum(limit) - remaining;
369}
370
371pub fn buffered(r: *Reader) []u8 {
372 return r.buffer[r.seek..r.end];
373}
374
375pub fn bufferedLen(r: *const Reader) usize {
376 return r.end - r.seek;
377}
378
379pub fn hashed(r: *Reader, hasher: anytype) Hashed(@TypeOf(hasher)) {
380 return .{ .in = r, .hasher = hasher };
381}
382
383pub fn readVecAll(r: *Reader, data: [][]u8) Error!void {
384 var index: usize = 0;
385 var truncate: usize = 0;
386 while (index < data.len) {
387 {
388 const untruncated = data[index];
389 data[index] = untruncated[truncate..];
390 defer data[index] = untruncated;
391 truncate += try r.readVec(data[index..]);
392 }
393 while (index < data.len and truncate >= data[index].len) {
394 truncate -= data[index].len;
395 index += 1;
396 }
397 }
398}
399
400/// Returns the next `len` bytes from the stream, filling the buffer as
401/// necessary.
402///
403/// Invalidates previously returned values from `peek`.
404///
405/// Asserts that the `Reader` was initialized with a buffer capacity at
406/// least as big as `len`.
407///
408/// If there are fewer than `len` bytes left in the stream, `error.EndOfStream`
409/// is returned instead.
410///
411/// See also:
412/// * `peek`
413/// * `toss`
414pub fn peek(r: *Reader, n: usize) Error![]u8 {
415 try r.fill(n);
416 return r.buffer[r.seek..][0..n];
417}
418
419/// Returns all the next buffered bytes, after filling the buffer to ensure it
420/// contains at least `n` bytes.
421///
422/// Invalidates previously returned values from `peek` and `peekGreedy`.
423///
424/// Asserts that the `Reader` was initialized with a buffer capacity at
425/// least as big as `n`.
426///
427/// If there are fewer than `n` bytes left in the stream, `error.EndOfStream`
428/// is returned instead.
429///
430/// See also:
431/// * `peek`
432/// * `toss`
433pub fn peekGreedy(r: *Reader, n: usize) Error![]u8 {
434 try r.fill(n);
435 return r.buffer[r.seek..r.end];
436}
437
438/// Skips the next `n` bytes from the stream, advancing the seek position. This
439/// is typically and safely used after `peek`.
440///
441/// Asserts that the number of bytes buffered is at least as many as `n`.
442///
443/// The "tossed" memory remains alive until a "peek" operation occurs.
444///
445/// See also:
446/// * `peek`.
447/// * `discard`.
448pub fn toss(r: *Reader, n: usize) void {
449 r.seek += n;
450 assert(r.seek <= r.end);
451}
452
453/// Equivalent to `toss(r.bufferedLen())`.
454pub fn tossBuffered(r: *Reader) void {
455 r.seek = 0;
456 r.end = 0;
457}
458
459/// Equivalent to `peek` followed by `toss`.
460///
461/// The data returned is invalidated by the next call to `take`, `peek`,
462/// `fill`, and functions with those prefixes.
463pub fn take(r: *Reader, n: usize) Error![]u8 {
464 const result = try r.peek(n);
465 r.toss(n);
466 return result;
467}
468
469/// Returns the next `n` bytes from the stream as an array, filling the buffer
470/// as necessary and advancing the seek position `n` bytes.
471///
472/// Asserts that the `Reader` was initialized with a buffer capacity at
473/// least as big as `n`.
474///
475/// If there are fewer than `n` bytes left in the stream, `error.EndOfStream`
476/// is returned instead.
477///
478/// See also:
479/// * `take`
480pub fn takeArray(r: *Reader, comptime n: usize) Error!*[n]u8 {
481 return (try r.take(n))[0..n];
482}
483
484/// Returns the next `n` bytes from the stream as an array, filling the buffer
485/// as necessary, without advancing the seek position.
486///
487/// Asserts that the `Reader` was initialized with a buffer capacity at
488/// least as big as `n`.
489///
490/// If there are fewer than `n` bytes left in the stream, `error.EndOfStream`
491/// is returned instead.
492///
493/// See also:
494/// * `peek`
495/// * `takeArray`
496pub fn peekArray(r: *Reader, comptime n: usize) Error!*[n]u8 {
497 return (try r.peek(n))[0..n];
498}
499
500/// Skips the next `n` bytes from the stream, advancing the seek position.
501///
502/// Unlike `toss` which is infallible, in this function `n` can be any amount.
503///
504/// Returns `error.EndOfStream` if fewer than `n` bytes could be discarded.
505///
506/// See also:
507/// * `toss`
508/// * `discardRemaining`
509/// * `discardShort`
510/// * `discard`
511pub fn discardAll(r: *Reader, n: usize) Error!void {
512 if ((try r.discardShort(n)) != n) return error.EndOfStream;
513}
514
515pub fn discardAll64(r: *Reader, n: u64) Error!void {
516 var remaining: u64 = n;
517 while (remaining > 0) {
518 const limited_remaining = std.math.cast(usize, remaining) orelse std.math.maxInt(usize);
519 try discardAll(r, limited_remaining);
520 remaining -= limited_remaining;
521 }
522}
523
524/// Skips the next `n` bytes from the stream, advancing the seek position.
525///
526/// Unlike `toss` which is infallible, in this function `n` can be any amount.
527///
528/// Returns the number of bytes discarded, which is less than `n` if and only
529/// if the stream reached the end.
530///
531/// See also:
532/// * `discardAll`
533/// * `discardRemaining`
534/// * `discard`
535pub fn discardShort(r: *Reader, n: usize) ShortError!usize {
536 const proposed_seek = r.seek + n;
537 if (proposed_seek <= r.end) {
538 @branchHint(.likely);
539 r.seek = proposed_seek;
540 return n;
541 }
542 var remaining = n - (r.end - r.seek);
543 r.end = 0;
544 r.seek = 0;
545 while (true) {
546 const discard_len = r.vtable.discard(r, .limited(remaining)) catch |err| switch (err) {
547 error.EndOfStream => return n - remaining,
548 error.ReadFailed => return error.ReadFailed,
549 };
550 remaining -= discard_len;
551 if (remaining == 0) return n;
552 }
553}
554
555/// Fill `buffer` with the next `buffer.len` bytes from the stream, advancing
556/// the seek position.
557///
558/// Invalidates previously returned values from `peek`.
559///
560/// If the provided buffer cannot be filled completely, `error.EndOfStream` is
561/// returned instead.
562///
563/// See also:
564/// * `peek`
565/// * `readSliceShort`
566pub fn readSliceAll(r: *Reader, buffer: []u8) Error!void {
567 const n = try readSliceShort(r, buffer);
568 if (n != buffer.len) return error.EndOfStream;
569}
570
571/// Fill `buffer` with the next `buffer.len` bytes from the stream, advancing
572/// the seek position.
573///
574/// Invalidates previously returned values from `peek`.
575///
576/// Returns the number of bytes read, which is less than `buffer.len` if and
577/// only if the stream reached the end.
578///
579/// See also:
580/// * `readSliceAll`
581pub fn readSliceShort(r: *Reader, buffer: []u8) ShortError!usize {
582 const in_buffer = r.buffer[r.seek..r.end];
583 const copy_len = @min(buffer.len, in_buffer.len);
584 @memcpy(buffer[0..copy_len], in_buffer[0..copy_len]);
585 if (buffer.len - copy_len == 0) {
586 r.seek += copy_len;
587 return buffer.len;
588 }
589 var i: usize = copy_len;
590 r.end = 0;
591 r.seek = 0;
592 while (true) {
593 const remaining = buffer[i..];
594 var wrapper: Writer.VectorWrapper = .{
595 .it = .{
596 .first = remaining,
597 .last = r.buffer,
598 },
599 .writer = .{
600 .buffer = if (remaining.len >= r.buffer.len) remaining else r.buffer,
601 .vtable = Writer.VectorWrapper.vtable,
602 },
603 };
604 const n = r.vtable.stream(r, &wrapper.writer, .unlimited) catch |err| switch (err) {
605 error.WriteFailed => {
606 if (!wrapper.used) {
607 assert(r.seek == 0);
608 r.seek = remaining.len;
609 r.end = wrapper.writer.end;
610 @memcpy(remaining, r.buffer[0..remaining.len]);
611 }
612 return buffer.len;
613 },
614 error.EndOfStream => return i,
615 error.ReadFailed => return error.ReadFailed,
616 };
617 if (n < remaining.len) {
618 i += n;
619 continue;
620 }
621 r.end = n - remaining.len;
622 return buffer.len;
623 }
624}
625
626/// Fill `buffer` with the next `buffer.len` bytes from the stream, advancing
627/// the seek position.
628///
629/// Invalidates previously returned values from `peek`.
630///
631/// If the provided buffer cannot be filled completely, `error.EndOfStream` is
632/// returned instead.
633///
634/// The function is inline to avoid the dead code in case `endian` is
635/// comptime-known and matches host endianness.
636///
637/// See also:
638/// * `readSliceAll`
639/// * `readSliceEndianAlloc`
640pub inline fn readSliceEndian(
641 r: *Reader,
642 comptime Elem: type,
643 buffer: []Elem,
644 endian: std.builtin.Endian,
645) Error!void {
646 try readSliceAll(r, @ptrCast(buffer));
647 if (native_endian != endian) for (buffer) |*elem| std.mem.byteSwapAllFields(Elem, elem);
648}
649
650pub const ReadAllocError = Error || Allocator.Error;
651
652/// The function is inline to avoid the dead code in case `endian` is
653/// comptime-known and matches host endianness.
654pub inline fn readSliceEndianAlloc(
655 r: *Reader,
656 allocator: Allocator,
657 comptime Elem: type,
658 len: usize,
659 endian: std.builtin.Endian,
660) ReadAllocError![]Elem {
661 const dest = try allocator.alloc(Elem, len);
662 errdefer allocator.free(dest);
663 try readSliceAll(r, @ptrCast(dest));
664 if (native_endian != endian) for (dest) |*elem| std.mem.byteSwapAllFields(Elem, elem);
665 return dest;
666}
667
668/// Shortcut for calling `readSliceAll` with a buffer provided by `allocator`.
669pub fn readAlloc(r: *Reader, allocator: Allocator, len: usize) ReadAllocError![]u8 {
670 const dest = try allocator.alloc(u8, len);
671 errdefer allocator.free(dest);
672 try readSliceAll(r, dest);
673 return dest;
674}
675
676pub const DelimiterError = error{
677 /// See the `Reader` implementation for detailed diagnostics.
678 ReadFailed,
679 /// For "inclusive" functions, stream ended before the delimiter was found.
680 /// For "exclusive" functions, stream ended and there are no more bytes to
681 /// return.
682 EndOfStream,
683 /// The delimiter was not found within a number of bytes matching the
684 /// capacity of the `Reader`.
685 StreamTooLong,
686};
687
688/// Returns a slice of the next bytes of buffered data from the stream until
689/// `sentinel` is found, advancing the seek position.
690///
691/// Returned slice has a sentinel.
692///
693/// Invalidates previously returned values from `peek`.
694///
695/// See also:
696/// * `peekSentinel`
697/// * `takeDelimiterExclusive`
698/// * `takeDelimiterInclusive`
699pub fn takeSentinel(r: *Reader, comptime sentinel: u8) DelimiterError![:sentinel]u8 {
700 const result = try r.peekSentinel(sentinel);
701 r.toss(result.len + 1);
702 return result;
703}
704
705/// Returns a slice of the next bytes of buffered data from the stream until
706/// `sentinel` is found, without advancing the seek position.
707///
708/// Returned slice has a sentinel; end of stream does not count as a delimiter.
709///
710/// Invalidates previously returned values from `peek`.
711///
712/// See also:
713/// * `takeSentinel`
714/// * `peekDelimiterExclusive`
715/// * `peekDelimiterInclusive`
716pub fn peekSentinel(r: *Reader, comptime sentinel: u8) DelimiterError![:sentinel]u8 {
717 const result = try r.peekDelimiterInclusive(sentinel);
718 return result[0 .. result.len - 1 :sentinel];
719}
720
721/// Returns a slice of the next bytes of buffered data from the stream until
722/// `delimiter` is found, advancing the seek position.
723///
724/// Returned slice includes the delimiter as the last byte.
725///
726/// Invalidates previously returned values from `peek`.
727///
728/// See also:
729/// * `takeSentinel`
730/// * `takeDelimiterExclusive`
731/// * `peekDelimiterInclusive`
732pub fn takeDelimiterInclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 {
733 const result = try r.peekDelimiterInclusive(delimiter);
734 r.toss(result.len);
735 return result;
736}
737
738/// Returns a slice of the next bytes of buffered data from the stream until
739/// `delimiter` is found, without advancing the seek position.
740///
741/// Returned slice includes the delimiter as the last byte.
742///
743/// Invalidates previously returned values from `peek`.
744///
745/// See also:
746/// * `peekSentinel`
747/// * `peekDelimiterExclusive`
748/// * `takeDelimiterInclusive`
749pub fn peekDelimiterInclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 {
750 const buffer = r.buffer[0..r.end];
751 const seek = r.seek;
752 if (std.mem.indexOfScalarPos(u8, buffer, seek, delimiter)) |end| {
753 @branchHint(.likely);
754 return buffer[seek .. end + 1];
755 }
756 if (r.vtable.stream == &endingStream) {
757 // Protect the `@constCast` of `fixed`.
758 return error.EndOfStream;
759 }
760 r.rebase();
761 while (r.buffer.len - r.end != 0) {
762 const end_cap = r.buffer[r.end..];
763 var writer: Writer = .fixed(end_cap);
764 const n = r.vtable.stream(r, &writer, .limited(end_cap.len)) catch |err| switch (err) {
765 error.WriteFailed => unreachable,
766 else => |e| return e,
767 };
768 r.end += n;
769 if (std.mem.indexOfScalarPos(u8, end_cap[0..n], 0, delimiter)) |end| {
770 return r.buffer[0 .. r.end - n + end + 1];
771 }
772 }
773 return error.StreamTooLong;
774}
775
776/// Returns a slice of the next bytes of buffered data from the stream until
777/// `delimiter` is found, advancing the seek position.
778///
779/// Returned slice excludes the delimiter. End-of-stream is treated equivalent
780/// to a delimiter, unless it would result in a length 0 return value, in which
781/// case `error.EndOfStream` is returned instead.
782///
783/// If the delimiter is not found within a number of bytes matching the
784/// capacity of this `Reader`, `error.StreamTooLong` is returned. In
785/// such case, the stream state is unmodified as if this function was never
786/// called.
787///
788/// Invalidates previously returned values from `peek`.
789///
790/// See also:
791/// * `takeDelimiterInclusive`
792/// * `peekDelimiterExclusive`
793pub fn takeDelimiterExclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 {
794 const result = r.peekDelimiterInclusive(delimiter) catch |err| switch (err) {
795 error.EndOfStream => {
796 const remaining = r.buffer[r.seek..r.end];
797 if (remaining.len == 0) return error.EndOfStream;
798 r.toss(remaining.len);
799 return remaining;
800 },
801 else => |e| return e,
802 };
803 r.toss(result.len);
804 return result[0 .. result.len - 1];
805}
806
807/// Returns a slice of the next bytes of buffered data from the stream until
808/// `delimiter` is found, without advancing the seek position.
809///
810/// Returned slice excludes the delimiter. End-of-stream is treated equivalent
811/// to a delimiter, unless it would result in a length 0 return value, in which
812/// case `error.EndOfStream` is returned instead.
813///
814/// If the delimiter is not found within a number of bytes matching the
815/// capacity of this `Reader`, `error.StreamTooLong` is returned. In
816/// such case, the stream state is unmodified as if this function was never
817/// called.
818///
819/// Invalidates previously returned values from `peek`.
820///
821/// See also:
822/// * `peekDelimiterInclusive`
823/// * `takeDelimiterExclusive`
824pub fn peekDelimiterExclusive(r: *Reader, delimiter: u8) DelimiterError![]u8 {
825 const result = r.peekDelimiterInclusive(delimiter) catch |err| switch (err) {
826 error.EndOfStream => {
827 const remaining = r.buffer[r.seek..r.end];
828 if (remaining.len == 0) return error.EndOfStream;
829 r.toss(remaining.len);
830 return remaining;
831 },
832 else => |e| return e,
833 };
834 return result[0 .. result.len - 1];
835}
836
837/// Appends to `w` contents by reading from the stream until `delimiter` is
838/// found. Does not write the delimiter itself.
839///
840/// Returns number of bytes streamed, which may be zero, or error.EndOfStream
841/// if the delimiter was not found.
842///
843/// Asserts buffer capacity of at least one. This function performs better with
844/// larger buffers.
845///
846/// See also:
847/// * `streamDelimiterEnding`
848/// * `streamDelimiterLimit`
849pub fn streamDelimiter(r: *Reader, w: *Writer, delimiter: u8) StreamError!usize {
850 const n = streamDelimiterLimit(r, w, delimiter, .unlimited) catch |err| switch (err) {
851 error.StreamTooLong => unreachable, // unlimited is passed
852 else => |e| return e,
853 };
854 if (r.seek == r.end) return error.EndOfStream;
855 return n;
856}
857
858/// Appends to `w` contents by reading from the stream until `delimiter` is found.
859/// Does not write the delimiter itself.
860///
861/// Returns number of bytes streamed, which may be zero. End of stream can be
862/// detected by checking if the next byte in the stream is the delimiter.
863///
864/// Asserts buffer capacity of at least one. This function performs better with
865/// larger buffers.
866///
867/// See also:
868/// * `streamDelimiter`
869/// * `streamDelimiterLimit`
870pub fn streamDelimiterEnding(
871 r: *Reader,
872 w: *Writer,
873 delimiter: u8,
874) StreamRemainingError!usize {
875 return streamDelimiterLimit(r, w, delimiter, .unlimited) catch |err| switch (err) {
876 error.StreamTooLong => unreachable, // unlimited is passed
877 else => |e| return e,
878 };
879}
880
881pub const StreamDelimiterLimitError = error{
882 ReadFailed,
883 WriteFailed,
884 /// The delimiter was not found within the limit.
885 StreamTooLong,
886};
887
888/// Appends to `w` contents by reading from the stream until `delimiter` is found.
889/// Does not write the delimiter itself.
890///
891/// Returns number of bytes streamed, which may be zero. End of stream can be
892/// detected by checking if the next byte in the stream is the delimiter.
893///
894/// Asserts buffer capacity of at least one. This function performs better with
895/// larger buffers.
896pub fn streamDelimiterLimit(
897 r: *Reader,
898 w: *Writer,
899 delimiter: u8,
900 limit: Limit,
901) StreamDelimiterLimitError!usize {
902 var remaining = @intFromEnum(limit);
903 while (remaining != 0) {
904 const available = Limit.limited(remaining).slice(r.peekGreedy(1) catch |err| switch (err) {
905 error.ReadFailed => return error.ReadFailed,
906 error.EndOfStream => return @intFromEnum(limit) - remaining,
907 });
908 if (std.mem.indexOfScalar(u8, available, delimiter)) |delimiter_index| {
909 try w.writeAll(available[0..delimiter_index]);
910 r.toss(delimiter_index);
911 remaining -= delimiter_index;
912 return @intFromEnum(limit) - remaining;
913 }
914 try w.writeAll(available);
915 r.toss(available.len);
916 remaining -= available.len;
917 }
918 return error.StreamTooLong;
919}
920
921/// Reads from the stream until specified byte is found, discarding all data,
922/// including the delimiter.
923///
924/// Returns number of bytes discarded, or `error.EndOfStream` if the delimiter
925/// is not found.
926///
927/// See also:
928/// * `discardDelimiterExclusive`
929/// * `discardDelimiterLimit`
930pub fn discardDelimiterInclusive(r: *Reader, delimiter: u8) Error!usize {
931 const n = discardDelimiterLimit(r, delimiter, .unlimited) catch |err| switch (err) {
932 error.StreamTooLong => unreachable, // unlimited is passed
933 else => |e| return e,
934 };
935 if (r.seek == r.end) return error.EndOfStream;
936 assert(r.buffer[r.seek] == delimiter);
937 toss(r, 1);
938 return n + 1;
939}
940
941/// Reads from the stream until specified byte is found, discarding all data,
942/// excluding the delimiter.
943///
944/// Returns the number of bytes discarded.
945///
946/// Succeeds if stream ends before delimiter found. End of stream can be
947/// detected by checking if the delimiter is buffered.
948///
949/// See also:
950/// * `discardDelimiterInclusive`
951/// * `discardDelimiterLimit`
952pub fn discardDelimiterExclusive(r: *Reader, delimiter: u8) ShortError!usize {
953 return discardDelimiterLimit(r, delimiter, .unlimited) catch |err| switch (err) {
954 error.StreamTooLong => unreachable, // unlimited is passed
955 else => |e| return e,
956 };
957}
958
959pub const DiscardDelimiterLimitError = error{
960 ReadFailed,
961 /// The delimiter was not found within the limit.
962 StreamTooLong,
963};
964
965/// Reads from the stream until specified byte is found, discarding all data,
966/// excluding the delimiter.
967///
968/// Returns the number of bytes discarded.
969///
970/// Succeeds if stream ends before delimiter found. End of stream can be
971/// detected by checking if the delimiter is buffered.
972pub fn discardDelimiterLimit(r: *Reader, delimiter: u8, limit: Limit) DiscardDelimiterLimitError!usize {
973 var remaining = @intFromEnum(limit);
974 while (remaining != 0) {
975 const available = Limit.limited(remaining).slice(r.peekGreedy(1) catch |err| switch (err) {
976 error.ReadFailed => return error.ReadFailed,
977 error.EndOfStream => return @intFromEnum(limit) - remaining,
978 });
979 if (std.mem.indexOfScalar(u8, available, delimiter)) |delimiter_index| {
980 r.toss(delimiter_index);
981 remaining -= delimiter_index;
982 return @intFromEnum(limit) - remaining;
983 }
984 r.toss(available.len);
985 remaining -= available.len;
986 }
987 return error.StreamTooLong;
988}
989
990/// Fills the buffer such that it contains at least `n` bytes, without
991/// advancing the seek position.
992///
993/// Returns `error.EndOfStream` if and only if there are fewer than `n` bytes
994/// remaining.
995///
996/// Asserts buffer capacity is at least `n`.
997pub fn fill(r: *Reader, n: usize) Error!void {
998 assert(n <= r.buffer.len);
999 if (r.seek + n <= r.end) {
1000 @branchHint(.likely);
1001 return;
1002 }
1003 if (r.seek + n <= r.buffer.len) while (true) {
1004 const end_cap = r.buffer[r.end..];
1005 var writer: Writer = .fixed(end_cap);
1006 r.end += r.vtable.stream(r, &writer, .limited(end_cap.len)) catch |err| switch (err) {
1007 error.WriteFailed => unreachable,
1008 else => |e| return e,
1009 };
1010 if (r.seek + n <= r.end) return;
1011 };
1012 if (r.vtable.stream == &endingStream) {
1013 // Protect the `@constCast` of `fixed`.
1014 return error.EndOfStream;
1015 }
1016 rebaseCapacity(r, n);
1017 var writer: Writer = .{
1018 .buffer = r.buffer,
1019 .vtable = &.{ .drain = Writer.fixedDrain },
1020 };
1021 while (r.end < r.seek + n) {
1022 writer.end = r.end;
1023 r.end += r.vtable.stream(r, &writer, .limited(r.buffer.len - r.end)) catch |err| switch (err) {
1024 error.WriteFailed => unreachable,
1025 error.ReadFailed, error.EndOfStream => |e| return e,
1026 };
1027 }
1028}
1029
1030/// Without advancing the seek position, does exactly one underlying read, filling the buffer as
1031/// much as possible. This may result in zero bytes added to the buffer, which is not an end of
1032/// stream condition. End of stream is communicated via returning `error.EndOfStream`.
1033///
1034/// Asserts buffer capacity is at least 1.
1035pub fn fillMore(r: *Reader) Error!void {
1036 rebaseCapacity(r, 1);
1037 var writer: Writer = .{
1038 .buffer = r.buffer,
1039 .end = r.end,
1040 .vtable = &.{ .drain = Writer.fixedDrain },
1041 };
1042 r.end += r.vtable.stream(r, &writer, .limited(r.buffer.len - r.end)) catch |err| switch (err) {
1043 error.WriteFailed => unreachable,
1044 else => |e| return e,
1045 };
1046}
1047
1048/// Returns the next byte from the stream or returns `error.EndOfStream`.
1049///
1050/// Does not advance the seek position.
1051///
1052/// Asserts the buffer capacity is nonzero.
1053pub fn peekByte(r: *Reader) Error!u8 {
1054 const buffer = r.buffer[0..r.end];
1055 const seek = r.seek;
1056 if (seek < buffer.len) {
1057 @branchHint(.likely);
1058 return buffer[seek];
1059 }
1060 try fill(r, 1);
1061 return r.buffer[r.seek];
1062}
1063
1064/// Reads 1 byte from the stream or returns `error.EndOfStream`.
1065///
1066/// Asserts the buffer capacity is nonzero.
1067pub fn takeByte(r: *Reader) Error!u8 {
1068 const result = try peekByte(r);
1069 r.seek += 1;
1070 return result;
1071}
1072
1073/// Same as `takeByte` except the returned byte is signed.
1074pub fn takeByteSigned(r: *Reader) Error!i8 {
1075 return @bitCast(try r.takeByte());
1076}
1077
1078/// Asserts the buffer was initialized with a capacity at least `@bitSizeOf(T) / 8`.
1079pub inline fn takeInt(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T {
1080 const n = @divExact(@typeInfo(T).int.bits, 8);
1081 return std.mem.readInt(T, try r.takeArray(n), endian);
1082}
1083
1084/// Asserts the buffer was initialized with a capacity at least `n`.
1085pub fn takeVarInt(r: *Reader, comptime Int: type, endian: std.builtin.Endian, n: usize) Error!Int {
1086 assert(n <= @sizeOf(Int));
1087 return std.mem.readVarInt(Int, try r.take(n), endian);
1088}
1089
1090/// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`.
1091///
1092/// Advances the seek position.
1093///
1094/// See also:
1095/// * `peekStruct`
1096/// * `takeStructEndian`
1097pub fn takeStruct(r: *Reader, comptime T: type) Error!*align(1) T {
1098 // Only extern and packed structs have defined in-memory layout.
1099 comptime assert(@typeInfo(T).@"struct".layout != .auto);
1100 return @ptrCast(try r.takeArray(@sizeOf(T)));
1101}
1102
1103/// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`.
1104///
1105/// Does not advance the seek position.
1106///
1107/// See also:
1108/// * `takeStruct`
1109/// * `peekStructEndian`
1110pub fn peekStruct(r: *Reader, comptime T: type) Error!*align(1) T {
1111 // Only extern and packed structs have defined in-memory layout.
1112 comptime assert(@typeInfo(T).@"struct".layout != .auto);
1113 return @ptrCast(try r.peekArray(@sizeOf(T)));
1114}
1115
1116/// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`.
1117///
1118/// This function is inline to avoid referencing `std.mem.byteSwapAllFields`
1119/// when `endian` is comptime-known and matches the host endianness.
1120///
1121/// See also:
1122/// * `takeStruct`
1123/// * `peekStructEndian`
1124pub inline fn takeStructEndian(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T {
1125 var res = (try r.takeStruct(T)).*;
1126 if (native_endian != endian) std.mem.byteSwapAllFields(T, &res);
1127 return res;
1128}
1129
1130/// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`.
1131///
1132/// This function is inline to avoid referencing `std.mem.byteSwapAllFields`
1133/// when `endian` is comptime-known and matches the host endianness.
1134///
1135/// See also:
1136/// * `takeStructEndian`
1137/// * `peekStruct`
1138pub inline fn peekStructEndian(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T {
1139 var res = (try r.peekStruct(T)).*;
1140 if (native_endian != endian) std.mem.byteSwapAllFields(T, &res);
1141 return res;
1142}
1143
1144pub const TakeEnumError = Error || error{InvalidEnumTag};
1145
1146/// Reads an integer with the same size as the given enum's tag type. If the
1147/// integer matches an enum tag, casts the integer to the enum tag and returns
1148/// it. Otherwise, returns `error.InvalidEnumTag`.
1149///
1150/// Asserts the buffer was initialized with a capacity at least `@sizeOf(Enum)`.
1151pub fn takeEnum(r: *Reader, comptime Enum: type, endian: std.builtin.Endian) TakeEnumError!Enum {
1152 const Tag = @typeInfo(Enum).@"enum".tag_type;
1153 const int = try r.takeInt(Tag, endian);
1154 return std.meta.intToEnum(Enum, int);
1155}
1156
1157/// Reads an integer with the same size as the given nonexhaustive enum's tag type.
1158///
1159/// Asserts the buffer was initialized with a capacity at least `@sizeOf(Enum)`.
1160pub fn takeEnumNonexhaustive(r: *Reader, comptime Enum: type, endian: std.builtin.Endian) Error!Enum {
1161 const info = @typeInfo(Enum).@"enum";
1162 comptime assert(!info.is_exhaustive);
1163 comptime assert(@bitSizeOf(info.tag_type) == @sizeOf(info.tag_type) * 8);
1164 return takeEnum(r, Enum, endian) catch |err| switch (err) {
1165 error.InvalidEnumTag => unreachable,
1166 else => |e| return e,
1167 };
1168}
1169
1170pub const TakeLeb128Error = Error || error{Overflow};
1171
1172/// Read a single LEB128 value as type T, or `error.Overflow` if the value cannot fit.
1173pub fn takeLeb128(r: *Reader, comptime Result: type) TakeLeb128Error!Result {
1174 const result_info = @typeInfo(Result).int;
1175 return std.math.cast(Result, try r.takeMultipleOf7Leb128(@Type(.{ .int = .{
1176 .signedness = result_info.signedness,
1177 .bits = std.mem.alignForwardAnyAlign(u16, result_info.bits, 7),
1178 } }))) orelse error.Overflow;
1179}
1180
1181pub fn expandTotalCapacity(r: *Reader, allocator: Allocator, n: usize) Allocator.Error!void {
1182 if (n <= r.buffer.len) return;
1183 if (r.seek > 0) rebase(r);
1184 var list: ArrayList(u8) = .{
1185 .items = r.buffer[0..r.end],
1186 .capacity = r.buffer.len,
1187 };
1188 defer r.buffer = list.allocatedSlice();
1189 try list.ensureTotalCapacity(allocator, n);
1190}
1191
1192pub const FillAllocError = Error || Allocator.Error;
1193
1194pub fn fillAlloc(r: *Reader, allocator: Allocator, n: usize) FillAllocError!void {
1195 try expandTotalCapacity(r, allocator, n);
1196 return fill(r, n);
1197}
1198
1199/// Returns a slice into the unused capacity of `buffer` with at least
1200/// `min_len` bytes, extending `buffer` by resizing it with `gpa` as necessary.
1201///
1202/// After calling this function, typically the caller will follow up with a
1203/// call to `advanceBufferEnd` to report the actual number of bytes buffered.
1204pub fn writableSliceGreedyAlloc(r: *Reader, allocator: Allocator, min_len: usize) Allocator.Error![]u8 {
1205 {
1206 const unused = r.buffer[r.end..];
1207 if (unused.len >= min_len) return unused;
1208 }
1209 if (r.seek > 0) rebase(r);
1210 {
1211 var list: ArrayList(u8) = .{
1212 .items = r.buffer[0..r.end],
1213 .capacity = r.buffer.len,
1214 };
1215 defer r.buffer = list.allocatedSlice();
1216 try list.ensureUnusedCapacity(allocator, min_len);
1217 }
1218 const unused = r.buffer[r.end..];
1219 assert(unused.len >= min_len);
1220 return unused;
1221}
1222
1223/// After writing directly into the unused capacity of `buffer`, this function
1224/// updates `end` so that users of `Reader` can receive the data.
1225pub fn advanceBufferEnd(r: *Reader, n: usize) void {
1226 assert(n <= r.buffer.len - r.end);
1227 r.end += n;
1228}
1229
1230fn takeMultipleOf7Leb128(r: *Reader, comptime Result: type) TakeLeb128Error!Result {
1231 const result_info = @typeInfo(Result).int;
1232 comptime assert(result_info.bits % 7 == 0);
1233 var remaining_bits: std.math.Log2IntCeil(Result) = result_info.bits;
1234 const UnsignedResult = @Type(.{ .int = .{
1235 .signedness = .unsigned,
1236 .bits = result_info.bits,
1237 } });
1238 var result: UnsignedResult = 0;
1239 var fits = true;
1240 while (true) {
1241 const buffer: []const packed struct(u8) { bits: u7, more: bool } = @ptrCast(try r.peekGreedy(1));
1242 for (buffer, 1..) |byte, len| {
1243 if (remaining_bits > 0) {
1244 result = @shlExact(@as(UnsignedResult, byte.bits), result_info.bits - 7) |
1245 if (result_info.bits > 7) @shrExact(result, 7) else 0;
1246 remaining_bits -= 7;
1247 } else if (fits) fits = switch (result_info.signedness) {
1248 .signed => @as(i7, @bitCast(byte.bits)) ==
1249 @as(i7, @truncate(@as(Result, @bitCast(result)) >> (result_info.bits - 1))),
1250 .unsigned => byte.bits == 0,
1251 };
1252 if (byte.more) continue;
1253 r.toss(len);
1254 return if (fits) @as(Result, @bitCast(result)) >> remaining_bits else error.Overflow;
1255 }
1256 r.toss(buffer.len);
1257 }
1258}
1259
1260/// Left-aligns data such that `r.seek` becomes zero.
1261pub fn rebase(r: *Reader) void {
1262 if (r.seek == 0) return;
1263 const data = r.buffer[r.seek..r.end];
1264 @memmove(r.buffer[0..data.len], data);
1265 r.seek = 0;
1266 r.end = data.len;
1267}
1268
1269/// Ensures `capacity` more data can be buffered without rebasing, by rebasing
1270/// if necessary.
1271///
1272/// Asserts `capacity` is within the buffer capacity.
1273pub fn rebaseCapacity(r: *Reader, capacity: usize) void {
1274 if (r.end > r.buffer.len - capacity) rebase(r);
1275}
1276
1277/// Advances the stream and decreases the size of the storage buffer by `n`,
1278/// returning the range of bytes no longer accessible by `r`.
1279///
1280/// This action can be undone by `restitute`.
1281///
1282/// Asserts there are at least `n` buffered bytes already.
1283///
1284/// Asserts that `r.seek` is zero, i.e. the buffer is in a rebased state.
1285pub fn steal(r: *Reader, n: usize) []u8 {
1286 assert(r.seek == 0);
1287 assert(n <= r.end);
1288 const stolen = r.buffer[0..n];
1289 r.buffer = r.buffer[n..];
1290 r.end -= n;
1291 return stolen;
1292}
1293
1294/// Expands the storage buffer, undoing the effects of `steal`
1295/// Assumes that `n` does not exceed the total number of stolen bytes.
1296pub fn restitute(r: *Reader, n: usize) void {
1297 r.buffer = (r.buffer.ptr - n)[0 .. r.buffer.len + n];
1298 r.end += n;
1299 r.seek += n;
1300}
1301
1302test fixed {
1303 var r: Reader = .fixed("a\x02");
1304 try testing.expect((try r.takeByte()) == 'a');
1305 try testing.expect((try r.takeEnum(enum(u8) {
1306 a = 0,
1307 b = 99,
1308 c = 2,
1309 d = 3,
1310 }, builtin.cpu.arch.endian())) == .c);
1311 try testing.expectError(error.EndOfStream, r.takeByte());
1312}
1313
1314test peek {
1315 var r: Reader = .fixed("abc");
1316 try testing.expectEqualStrings("ab", try r.peek(2));
1317 try testing.expectEqualStrings("a", try r.peek(1));
1318}
1319
1320test peekGreedy {
1321 var r: Reader = .fixed("abc");
1322 try testing.expectEqualStrings("abc", try r.peekGreedy(1));
1323}
1324
1325test toss {
1326 var r: Reader = .fixed("abc");
1327 r.toss(1);
1328 try testing.expectEqualStrings("bc", r.buffered());
1329}
1330
1331test take {
1332 var r: Reader = .fixed("abc");
1333 try testing.expectEqualStrings("ab", try r.take(2));
1334 try testing.expectEqualStrings("c", try r.take(1));
1335}
1336
1337test takeArray {
1338 var r: Reader = .fixed("abc");
1339 try testing.expectEqualStrings("ab", try r.takeArray(2));
1340 try testing.expectEqualStrings("c", try r.takeArray(1));
1341}
1342
1343test peekArray {
1344 var r: Reader = .fixed("abc");
1345 try testing.expectEqualStrings("ab", try r.peekArray(2));
1346 try testing.expectEqualStrings("a", try r.peekArray(1));
1347}
1348
1349test discardAll {
1350 var r: Reader = .fixed("foobar");
1351 try r.discardAll(3);
1352 try testing.expectEqualStrings("bar", try r.take(3));
1353 try r.discardAll(0);
1354 try testing.expectError(error.EndOfStream, r.discardAll(1));
1355}
1356
1357test discardRemaining {
1358 var r: Reader = .fixed("foobar");
1359 r.toss(1);
1360 try testing.expectEqual(5, try r.discardRemaining());
1361 try testing.expectEqual(0, try r.discardRemaining());
1362}
1363
1364test stream {
1365 var out_buffer: [10]u8 = undefined;
1366 var r: Reader = .fixed("foobar");
1367 var w: Writer = .fixed(&out_buffer);
1368 // Short streams are possible with this function but not with fixed.
1369 try testing.expectEqual(2, try r.stream(&w, .limited(2)));
1370 try testing.expectEqualStrings("fo", w.buffered());
1371 try testing.expectEqual(4, try r.stream(&w, .unlimited));
1372 try testing.expectEqualStrings("foobar", w.buffered());
1373}
1374
1375test takeSentinel {
1376 var r: Reader = .fixed("ab\nc");
1377 try testing.expectEqualStrings("ab", try r.takeSentinel('\n'));
1378 try testing.expectError(error.EndOfStream, r.takeSentinel('\n'));
1379 try testing.expectEqualStrings("c", try r.peek(1));
1380}
1381
1382test peekSentinel {
1383 var r: Reader = .fixed("ab\nc");
1384 try testing.expectEqualStrings("ab", try r.peekSentinel('\n'));
1385 try testing.expectEqualStrings("ab", try r.peekSentinel('\n'));
1386}
1387
1388test takeDelimiterInclusive {
1389 var r: Reader = .fixed("ab\nc");
1390 try testing.expectEqualStrings("ab\n", try r.takeDelimiterInclusive('\n'));
1391 try testing.expectError(error.EndOfStream, r.takeDelimiterInclusive('\n'));
1392}
1393
1394test peekDelimiterInclusive {
1395 var r: Reader = .fixed("ab\nc");
1396 try testing.expectEqualStrings("ab\n", try r.peekDelimiterInclusive('\n'));
1397 try testing.expectEqualStrings("ab\n", try r.peekDelimiterInclusive('\n'));
1398 r.toss(3);
1399 try testing.expectError(error.EndOfStream, r.peekDelimiterInclusive('\n'));
1400}
1401
1402test takeDelimiterExclusive {
1403 var r: Reader = .fixed("ab\nc");
1404 try testing.expectEqualStrings("ab", try r.takeDelimiterExclusive('\n'));
1405 try testing.expectEqualStrings("c", try r.takeDelimiterExclusive('\n'));
1406 try testing.expectError(error.EndOfStream, r.takeDelimiterExclusive('\n'));
1407}
1408
1409test peekDelimiterExclusive {
1410 var r: Reader = .fixed("ab\nc");
1411 try testing.expectEqualStrings("ab", try r.peekDelimiterExclusive('\n'));
1412 try testing.expectEqualStrings("ab", try r.peekDelimiterExclusive('\n'));
1413 r.toss(3);
1414 try testing.expectEqualStrings("c", try r.peekDelimiterExclusive('\n'));
1415}
1416
1417test streamDelimiter {
1418 var out_buffer: [10]u8 = undefined;
1419 var r: Reader = .fixed("foo\nbars");
1420 var w: Writer = .fixed(&out_buffer);
1421 try testing.expectEqual(3, try r.streamDelimiter(&w, '\n'));
1422 try testing.expectEqualStrings("foo", w.buffered());
1423 try testing.expectEqual(0, try r.streamDelimiter(&w, '\n'));
1424 r.toss(1);
1425 try testing.expectError(error.EndOfStream, r.streamDelimiter(&w, '\n'));
1426}
1427
1428test streamDelimiterEnding {
1429 var out_buffer: [10]u8 = undefined;
1430 var r: Reader = .fixed("foo\nbars");
1431 var w: Writer = .fixed(&out_buffer);
1432 try testing.expectEqual(3, try r.streamDelimiterEnding(&w, '\n'));
1433 try testing.expectEqualStrings("foo", w.buffered());
1434 r.toss(1);
1435 try testing.expectEqual(4, try r.streamDelimiterEnding(&w, '\n'));
1436 try testing.expectEqualStrings("foobars", w.buffered());
1437 try testing.expectEqual(0, try r.streamDelimiterEnding(&w, '\n'));
1438 try testing.expectEqual(0, try r.streamDelimiterEnding(&w, '\n'));
1439}
1440
1441test streamDelimiterLimit {
1442 var out_buffer: [10]u8 = undefined;
1443 var r: Reader = .fixed("foo\nbars");
1444 var w: Writer = .fixed(&out_buffer);
1445 try testing.expectError(error.StreamTooLong, r.streamDelimiterLimit(&w, '\n', .limited(2)));
1446 try testing.expectEqual(1, try r.streamDelimiterLimit(&w, '\n', .limited(3)));
1447 try testing.expectEqualStrings("\n", try r.take(1));
1448 try testing.expectEqual(4, try r.streamDelimiterLimit(&w, '\n', .unlimited));
1449 try testing.expectEqualStrings("foobars", w.buffered());
1450}
1451
1452test discardDelimiterExclusive {
1453 var r: Reader = .fixed("foob\nar");
1454 try testing.expectEqual(4, try r.discardDelimiterExclusive('\n'));
1455 try testing.expectEqualStrings("\n", try r.take(1));
1456 try testing.expectEqual(2, try r.discardDelimiterExclusive('\n'));
1457 try testing.expectEqual(0, try r.discardDelimiterExclusive('\n'));
1458}
1459
1460test discardDelimiterInclusive {
1461 var r: Reader = .fixed("foob\nar");
1462 try testing.expectEqual(5, try r.discardDelimiterInclusive('\n'));
1463 try testing.expectError(error.EndOfStream, r.discardDelimiterInclusive('\n'));
1464}
1465
1466test discardDelimiterLimit {
1467 var r: Reader = .fixed("foob\nar");
1468 try testing.expectError(error.StreamTooLong, r.discardDelimiterLimit('\n', .limited(4)));
1469 try testing.expectEqual(0, try r.discardDelimiterLimit('\n', .limited(2)));
1470 try testing.expectEqualStrings("\n", try r.take(1));
1471 try testing.expectEqual(2, try r.discardDelimiterLimit('\n', .unlimited));
1472 try testing.expectEqual(0, try r.discardDelimiterLimit('\n', .unlimited));
1473}
1474
1475test fill {
1476 var r: Reader = .fixed("abc");
1477 try r.fill(1);
1478 try r.fill(3);
1479}
1480
1481test takeByte {
1482 var r: Reader = .fixed("ab");
1483 try testing.expectEqual('a', try r.takeByte());
1484 try testing.expectEqual('b', try r.takeByte());
1485 try testing.expectError(error.EndOfStream, r.takeByte());
1486}
1487
1488test takeByteSigned {
1489 var r: Reader = .fixed(&.{ 255, 5 });
1490 try testing.expectEqual(-1, try r.takeByteSigned());
1491 try testing.expectEqual(5, try r.takeByteSigned());
1492 try testing.expectError(error.EndOfStream, r.takeByteSigned());
1493}
1494
1495test takeInt {
1496 var r: Reader = .fixed(&.{ 0x12, 0x34, 0x56 });
1497 try testing.expectEqual(0x1234, try r.takeInt(u16, .big));
1498 try testing.expectError(error.EndOfStream, r.takeInt(u16, .little));
1499}
1500
1501test takeVarInt {
1502 var r: Reader = .fixed(&.{ 0x12, 0x34, 0x56 });
1503 try testing.expectEqual(0x123456, try r.takeVarInt(u64, .big, 3));
1504 try testing.expectError(error.EndOfStream, r.takeVarInt(u16, .little, 1));
1505}
1506
1507test takeStruct {
1508 var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 });
1509 const S = extern struct { a: u8, b: u16 };
1510 switch (native_endian) {
1511 .little => try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.takeStruct(S)).*),
1512 .big => try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.takeStruct(S)).*),
1513 }
1514 try testing.expectError(error.EndOfStream, r.takeStruct(S));
1515}
1516
1517test peekStruct {
1518 var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 });
1519 const S = extern struct { a: u8, b: u16 };
1520 switch (native_endian) {
1521 .little => {
1522 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.peekStruct(S)).*);
1523 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.peekStruct(S)).*);
1524 },
1525 .big => {
1526 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.peekStruct(S)).*);
1527 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.peekStruct(S)).*);
1528 },
1529 }
1530}
1531
1532test takeStructEndian {
1533 var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 });
1534 const S = extern struct { a: u8, b: u16 };
1535 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), try r.takeStructEndian(S, .big));
1536 try testing.expectError(error.EndOfStream, r.takeStructEndian(S, .little));
1537}
1538
1539test peekStructEndian {
1540 var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 });
1541 const S = extern struct { a: u8, b: u16 };
1542 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), try r.peekStructEndian(S, .big));
1543 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), try r.peekStructEndian(S, .little));
1544}
1545
1546test takeEnum {
1547 var r: Reader = .fixed(&.{ 2, 0, 1 });
1548 const E1 = enum(u8) { a, b, c };
1549 const E2 = enum(u16) { _ };
1550 try testing.expectEqual(E1.c, try r.takeEnum(E1, .little));
1551 try testing.expectEqual(@as(E2, @enumFromInt(0x0001)), try r.takeEnum(E2, .big));
1552}
1553
1554test takeLeb128 {
1555 var r: Reader = .fixed("\xc7\x9f\x7f\x80");
1556 try testing.expectEqual(-12345, try r.takeLeb128(i64));
1557 try testing.expectEqual(0x80, try r.peekByte());
1558 try testing.expectError(error.EndOfStream, r.takeLeb128(i64));
1559}
1560
1561test readSliceShort {
1562 var r: Reader = .fixed("HelloFren");
1563 var buf: [5]u8 = undefined;
1564 try testing.expectEqual(5, try r.readSliceShort(&buf));
1565 try testing.expectEqualStrings("Hello", buf[0..5]);
1566 try testing.expectEqual(4, try r.readSliceShort(&buf));
1567 try testing.expectEqualStrings("Fren", buf[0..4]);
1568 try testing.expectEqual(0, try r.readSliceShort(&buf));
1569}
1570
1571test readVec {
1572 var r: Reader = .fixed(std.ascii.letters);
1573 var flat_buffer: [52]u8 = undefined;
1574 var bufs: [2][]u8 = .{
1575 flat_buffer[0..26],
1576 flat_buffer[26..],
1577 };
1578 // Short reads are possible with this function but not with fixed.
1579 try testing.expectEqual(26 * 2, try r.readVec(&bufs));
1580 try testing.expectEqualStrings(std.ascii.letters[0..26], bufs[0]);
1581 try testing.expectEqualStrings(std.ascii.letters[26..], bufs[1]);
1582}
1583
1584test readVecLimit {
1585 var r: Reader = .fixed(std.ascii.letters);
1586 var flat_buffer: [52]u8 = undefined;
1587 var bufs: [2][]u8 = .{
1588 flat_buffer[0..26],
1589 flat_buffer[26..],
1590 };
1591 // Short reads are possible with this function but not with fixed.
1592 try testing.expectEqual(50, try r.readVecLimit(&bufs, .limited(50)));
1593 try testing.expectEqualStrings(std.ascii.letters[0..26], bufs[0]);
1594 try testing.expectEqualStrings(std.ascii.letters[26..50], bufs[1][0..24]);
1595}
1596
1597test "expected error.EndOfStream" {
1598 // Unit test inspired by https://github.com/ziglang/zig/issues/17733
1599 var buffer: [3]u8 = undefined;
1600 var r: std.io.Reader = .fixed(&buffer);
1601 r.end = 0; // capacity 3, but empty
1602 try std.testing.expectError(error.EndOfStream, r.takeEnum(enum(u8) { a, b }, .little));
1603 try std.testing.expectError(error.EndOfStream, r.take(3));
1604}
1605
1606fn endingStream(r: *Reader, w: *Writer, limit: Limit) StreamError!usize {
1607 _ = r;
1608 _ = w;
1609 _ = limit;
1610 return error.EndOfStream;
1611}
1612
1613fn endingDiscard(r: *Reader, limit: Limit) Error!usize {
1614 _ = r;
1615 _ = limit;
1616 return error.EndOfStream;
1617}
1618
1619fn failingStream(r: *Reader, w: *Writer, limit: Limit) StreamError!usize {
1620 _ = r;
1621 _ = w;
1622 _ = limit;
1623 return error.ReadFailed;
1624}
1625
1626fn failingDiscard(r: *Reader, limit: Limit) Error!usize {
1627 _ = r;
1628 _ = limit;
1629 return error.ReadFailed;
1630}
1631
1632test "readAlloc when the backing reader provides one byte at a time" {
1633 const OneByteReader = struct {
1634 str: []const u8,
1635 i: usize,
1636 reader: Reader,
1637
1638 fn stream(r: *Reader, w: *Writer, limit: Limit) StreamError!usize {
1639 assert(@intFromEnum(limit) >= 1);
1640 const self: *@This() = @fieldParentPtr("reader", r);
1641 if (self.str.len - self.i == 0) return error.EndOfStream;
1642 try w.writeByte(self.str[self.i]);
1643 self.i += 1;
1644 return 1;
1645 }
1646 };
1647 const str = "This is a test";
1648 var one_byte_stream: OneByteReader = .{
1649 .str = str,
1650 .i = 0,
1651 .reader = .{
1652 .buffer = &.{},
1653 .vtable = &.{ .stream = OneByteReader.stream },
1654 .seek = 0,
1655 .end = 0,
1656 },
1657 };
1658 const res = try one_byte_stream.reader.allocRemaining(std.testing.allocator, .unlimited);
1659 defer std.testing.allocator.free(res);
1660 try std.testing.expectEqualStrings(str, res);
1661}
1662
1663test "takeDelimiterInclusive when it rebases" {
1664 const written_line = "ABCDEFGHIJKLMNOPQRSTUVWXYZ\n";
1665 var buffer: [128]u8 = undefined;
1666 var tr: std.testing.Reader = .init(&buffer, &.{
1667 .{ .buffer = written_line },
1668 .{ .buffer = written_line },
1669 .{ .buffer = written_line },
1670 .{ .buffer = written_line },
1671 .{ .buffer = written_line },
1672 .{ .buffer = written_line },
1673 });
1674 const r = &tr.interface;
1675 for (0..6) |_| {
1676 try std.testing.expectEqualStrings(written_line, try r.takeDelimiterInclusive('\n'));
1677 }
1678}
1679
1680/// Provides a `Reader` implementation by passing data from an underlying
1681/// reader through `Hasher.update`.
1682///
1683/// The underlying reader is best unbuffered.
1684///
1685/// This implementation makes suboptimal buffering decisions due to being
1686/// generic. A better solution will involve creating a reader for each hash
1687/// function, where the discard buffer can be tailored to the hash
1688/// implementation details.
1689pub fn Hashed(comptime Hasher: type) type {
1690 return struct {
1691 in: *Reader,
1692 hasher: Hasher,
1693 interface: Reader,
1694
1695 pub fn init(in: *Reader, hasher: Hasher, buffer: []u8) @This() {
1696 return .{
1697 .in = in,
1698 .hasher = hasher,
1699 .interface = .{
1700 .vtable = &.{
1701 .read = @This().read,
1702 .discard = @This().discard,
1703 },
1704 .buffer = buffer,
1705 .end = 0,
1706 .seek = 0,
1707 },
1708 };
1709 }
1710
1711 fn read(r: *Reader, w: *Writer, limit: Limit) StreamError!usize {
1712 const this: *@This() = @alignCast(@fieldParentPtr("interface", r));
1713 const data = w.writableVector(limit);
1714 const n = try this.in.readVec(data);
1715 const result = w.advanceVector(n);
1716 var remaining: usize = n;
1717 for (data) |slice| {
1718 if (remaining < slice.len) {
1719 this.hasher.update(slice[0..remaining]);
1720 return result;
1721 } else {
1722 remaining -= slice.len;
1723 this.hasher.update(slice);
1724 }
1725 }
1726 assert(remaining == 0);
1727 return result;
1728 }
1729
1730 fn discard(r: *Reader, limit: Limit) Error!usize {
1731 const this: *@This() = @alignCast(@fieldParentPtr("interface", r));
1732 var w = this.hasher.writer(&.{});
1733 const n = this.in.stream(&w, limit) catch |err| switch (err) {
1734 error.WriteFailed => unreachable,
1735 else => |e| return e,
1736 };
1737 return n;
1738 }
1739 };
1740}
lib/std/io/Reader/Limited.zig deleted-42
...@@ -1,42 +0,0 @@
1const Limited = @This();
2
3const std = @import("../../std.zig");
4const Reader = std.io.Reader;
5const Writer = std.io.Writer;
6const Limit = std.io.Limit;
7
8unlimited: *Reader,
9remaining: Limit,
10interface: Reader,
11
12pub fn init(reader: *Reader, limit: Limit, buffer: []u8) Limited {
13 return .{
14 .unlimited = reader,
15 .remaining = limit,
16 .interface = .{
17 .vtable = &.{
18 .stream = stream,
19 .discard = discard,
20 },
21 .buffer = buffer,
22 .seek = 0,
23 .end = 0,
24 },
25 };
26}
27
28fn stream(context: ?*anyopaque, w: *Writer, limit: Limit) Reader.StreamError!usize {
29 const l: *Limited = @alignCast(@ptrCast(context));
30 const combined_limit = limit.min(l.remaining);
31 const n = try l.unlimited_reader.read(w, combined_limit);
32 l.remaining = l.remaining.subtract(n).?;
33 return n;
34}
35
36fn discard(context: ?*anyopaque, limit: Limit) Reader.Error!usize {
37 const l: *Limited = @alignCast(@ptrCast(context));
38 const combined_limit = limit.min(l.remaining);
39 const n = try l.unlimited_reader.discard(combined_limit);
40 l.remaining = l.remaining.subtract(n).?;
41 return n;
42}
lib/std/io/Writer.zig deleted-2491
...@@ -1,2491 +0,0 @@
1const builtin = @import("builtin");
2const native_endian = builtin.target.cpu.arch.endian();
3
4const Writer = @This();
5const std = @import("../std.zig");
6const assert = std.debug.assert;
7const Limit = std.io.Limit;
8const File = std.fs.File;
9const testing = std.testing;
10const Allocator = std.mem.Allocator;
11
12vtable: *const VTable,
13/// If this has length zero, the writer is unbuffered, and `flush` is a no-op.
14buffer: []u8,
15/// In `buffer` before this are buffered bytes, after this is `undefined`.
16end: usize = 0,
17
18pub const VTable = struct {
19 /// Sends bytes to the logical sink. A write will only be sent here if it
20 /// could not fit into `buffer`, or during a `flush` operation.
21 ///
22 /// `buffer[0..end]` is consumed first, followed by each slice of `data` in
23 /// order. Elements of `data` may alias each other but may not alias
24 /// `buffer`.
25 ///
26 /// This function modifies `Writer.end` and `Writer.buffer` in an
27 /// implementation-defined manner.
28 ///
29 /// `data.len` must be nonzero.
30 ///
31 /// The last element of `data` is repeated as necessary so that it is
32 /// written `splat` number of times, which may be zero.
33 ///
34 /// This function may not be called if the data to be written could have
35 /// been stored in `buffer` instead, including when the amount of data to
36 /// be written is zero and the buffer capacity is zero.
37 ///
38 /// Number of bytes consumed from `data` is returned, excluding bytes from
39 /// `buffer`.
40 ///
41 /// Number of bytes returned may be zero, which does not indicate stream
42 /// end. A subsequent call may return nonzero, or signal end of stream via
43 /// `error.WriteFailed`.
44 drain: *const fn (w: *Writer, data: []const []const u8, splat: usize) Error!usize,
45
46 /// Copies contents from an open file to the logical sink. `buffer[0..end]`
47 /// is consumed first, followed by `limit` bytes from `file_reader`.
48 ///
49 /// Number of bytes logically written is returned. This excludes bytes from
50 /// `buffer` because they have already been logically written. Number of
51 /// bytes consumed from `buffer` are tracked by modifying `end`.
52 ///
53 /// Number of bytes returned may be zero, which does not indicate stream
54 /// end. A subsequent call may return nonzero, or signal end of stream via
55 /// `error.WriteFailed`. Caller may check `file_reader` state
56 /// (`File.Reader.atEnd`) to disambiguate between a zero-length read or
57 /// write, and whether the file reached the end.
58 ///
59 /// `error.Unimplemented` indicates the callee cannot offer a more
60 /// efficient implementation than the caller performing its own reads.
61 sendFile: *const fn (
62 w: *Writer,
63 file_reader: *File.Reader,
64 /// Maximum amount of bytes to read from the file. Implementations may
65 /// assume that the file size does not exceed this amount. Data from
66 /// `buffer` does not count towards this limit.
67 limit: Limit,
68 ) FileError!usize = unimplementedSendFile,
69
70 /// Consumes all remaining buffer.
71 ///
72 /// The default flush implementation calls drain repeatedly until `end` is
73 /// zero, however it is legal for implementations to manage `end`
74 /// differently. For instance, `Allocating` flush is a no-op.
75 ///
76 /// There may be subsequent calls to `drain` and `sendFile` after a `flush`
77 /// operation.
78 flush: *const fn (w: *Writer) Error!void = defaultFlush,
79};
80
81pub const Error = error{
82 /// See the `Writer` implementation for detailed diagnostics.
83 WriteFailed,
84};
85
86pub const FileAllError = error{
87 /// Detailed diagnostics are found on the `File.Reader` struct.
88 ReadFailed,
89 /// See the `Writer` implementation for detailed diagnostics.
90 WriteFailed,
91};
92
93pub const FileReadingError = error{
94 /// Detailed diagnostics are found on the `File.Reader` struct.
95 ReadFailed,
96 /// See the `Writer` implementation for detailed diagnostics.
97 WriteFailed,
98 /// Reached the end of the file being read.
99 EndOfStream,
100};
101
102pub const FileError = error{
103 /// Detailed diagnostics are found on the `File.Reader` struct.
104 ReadFailed,
105 /// See the `Writer` implementation for detailed diagnostics.
106 WriteFailed,
107 /// Reached the end of the file being read.
108 EndOfStream,
109 /// Indicates the caller should do its own file reading; the callee cannot
110 /// offer a more efficient implementation.
111 Unimplemented,
112};
113
114/// Writes to `buffer` and returns `error.WriteFailed` when it is full.
115pub fn fixed(buffer: []u8) Writer {
116 return .{
117 .vtable = &.{ .drain = fixedDrain },
118 .buffer = buffer,
119 };
120}
121
122pub fn hashed(w: *Writer, hasher: anytype, buffer: []u8) Hashed(@TypeOf(hasher)) {
123 return .initHasher(w, hasher, buffer);
124}
125
126pub const failing: Writer = .{
127 .vtable = &.{
128 .drain = failingDrain,
129 .sendFile = failingSendFile,
130 },
131};
132
133/// Returns the contents not yet drained.
134pub fn buffered(w: *const Writer) []u8 {
135 return w.buffer[0..w.end];
136}
137
138pub fn countSplat(data: []const []const u8, splat: usize) usize {
139 var total: usize = 0;
140 for (data[0 .. data.len - 1]) |buf| total += buf.len;
141 total += data[data.len - 1].len * splat;
142 return total;
143}
144
145pub fn countSendFileLowerBound(n: usize, file_reader: *File.Reader, limit: Limit) ?usize {
146 const total: u64 = @min(@intFromEnum(limit), file_reader.getSize() catch return null);
147 return std.math.lossyCast(usize, total + n);
148}
149
150/// If the total number of bytes of `data` fits inside `unusedCapacitySlice`,
151/// this function is guaranteed to not fail, not call into `VTable`, and return
152/// the total bytes inside `data`.
153pub fn writeVec(w: *Writer, data: []const []const u8) Error!usize {
154 return writeSplat(w, data, 1);
155}
156
157/// If the number of bytes to write based on `data` and `splat` fits inside
158/// `unusedCapacitySlice`, this function is guaranteed to not fail, not call
159/// into `VTable`, and return the full number of bytes.
160pub fn writeSplat(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
161 assert(data.len > 0);
162 const buffer = w.buffer;
163 const count = countSplat(data, splat);
164 if (w.end + count > buffer.len) return w.vtable.drain(w, data, splat);
165 for (data[0 .. data.len - 1]) |bytes| {
166 @memcpy(buffer[w.end..][0..bytes.len], bytes);
167 w.end += bytes.len;
168 }
169 const pattern = data[data.len - 1];
170 switch (pattern.len) {
171 0 => {},
172 1 => {
173 @memset(buffer[w.end..][0..splat], pattern[0]);
174 w.end += splat;
175 },
176 else => for (0..splat) |_| {
177 @memcpy(buffer[w.end..][0..pattern.len], pattern);
178 w.end += pattern.len;
179 },
180 }
181 return count;
182}
183
184/// Returns how many bytes were consumed from `header` and `data`.
185pub fn writeSplatHeader(
186 w: *Writer,
187 header: []const u8,
188 data: []const []const u8,
189 splat: usize,
190) Error!usize {
191 const new_end = w.end + header.len;
192 if (new_end <= w.buffer.len) {
193 @memcpy(w.buffer[w.end..][0..header.len], header);
194 w.end = new_end;
195 return header.len + try writeSplat(w, data, splat);
196 }
197 var vecs: [8][]const u8 = undefined; // Arbitrarily chosen size.
198 var i: usize = 1;
199 vecs[0] = header;
200 for (data[0 .. data.len - 1]) |buf| {
201 if (buf.len == 0) continue;
202 vecs[i] = buf;
203 i += 1;
204 if (vecs.len - i == 0) break;
205 }
206 const pattern = data[data.len - 1];
207 const new_splat = s: {
208 if (pattern.len == 0 or vecs.len - i == 0) break :s 1;
209 vecs[i] = pattern;
210 i += 1;
211 break :s splat;
212 };
213 return w.vtable.drain(w, vecs[0..i], new_splat);
214}
215
216test "writeSplatHeader splatting avoids buffer aliasing temptation" {
217 const initial_buf = try testing.allocator.alloc(u8, 8);
218 var aw: std.io.Writer.Allocating = .initOwnedSlice(testing.allocator, initial_buf);
219 defer aw.deinit();
220 // This test assumes 8 vector buffer in this function.
221 const n = try aw.writer.writeSplatHeader("header which is longer than buf ", &.{
222 "1", "2", "3", "4", "5", "6", "foo", "bar", "foo",
223 }, 3);
224 try testing.expectEqual(41, n);
225 try testing.expectEqualStrings(
226 "header which is longer than buf 123456foo",
227 aw.writer.buffered(),
228 );
229}
230
231/// Drains all remaining buffered data.
232pub fn flush(w: *Writer) Error!void {
233 return w.vtable.flush(w);
234}
235
236/// Repeatedly calls `VTable.drain` until `end` is zero.
237pub fn defaultFlush(w: *Writer) Error!void {
238 const drainFn = w.vtable.drain;
239 while (w.end != 0) _ = try drainFn(w, &.{""}, 1);
240}
241
242/// Does nothing.
243pub fn noopFlush(w: *Writer) Error!void {
244 _ = w;
245}
246
247/// Calls `VTable.drain` but hides the last `preserve_length` bytes from the
248/// implementation, keeping them buffered.
249pub fn drainPreserve(w: *Writer, preserve_length: usize) Error!void {
250 const temp_end = w.end -| preserve_length;
251 const preserved = w.buffer[temp_end..w.end];
252 w.end = temp_end;
253 defer w.end += preserved.len;
254 assert(0 == try w.vtable.drain(w, &.{""}, 1));
255 assert(w.end <= temp_end + preserved.len);
256 @memmove(w.buffer[w.end..][0..preserved.len], preserved);
257}
258
259pub fn unusedCapacitySlice(w: *const Writer) []u8 {
260 return w.buffer[w.end..];
261}
262
263pub fn unusedCapacityLen(w: *const Writer) usize {
264 return w.buffer.len - w.end;
265}
266
267/// Asserts the provided buffer has total capacity enough for `len`.
268///
269/// Advances the buffer end position by `len`.
270pub fn writableArray(w: *Writer, comptime len: usize) Error!*[len]u8 {
271 const big_slice = try w.writableSliceGreedy(len);
272 advance(w, len);
273 return big_slice[0..len];
274}
275
276/// Asserts the provided buffer has total capacity enough for `len`.
277///
278/// Advances the buffer end position by `len`.
279pub fn writableSlice(w: *Writer, len: usize) Error![]u8 {
280 const big_slice = try w.writableSliceGreedy(len);
281 advance(w, len);
282 return big_slice[0..len];
283}
284
285/// Asserts the provided buffer has total capacity enough for `minimum_length`.
286///
287/// Does not `advance` the buffer end position.
288///
289/// If `minimum_length` is zero, this is equivalent to `unusedCapacitySlice`.
290pub fn writableSliceGreedy(w: *Writer, minimum_length: usize) Error![]u8 {
291 assert(w.buffer.len >= minimum_length);
292 while (w.buffer.len - w.end < minimum_length) {
293 assert(0 == try w.vtable.drain(w, &.{""}, 1));
294 } else {
295 @branchHint(.likely);
296 return w.buffer[w.end..];
297 }
298}
299
300/// Asserts the provided buffer has total capacity enough for `minimum_length`
301/// and `preserve_length` combined.
302///
303/// Does not `advance` the buffer end position.
304///
305/// When draining the buffer, ensures that at least `preserve_length` bytes
306/// remain buffered.
307///
308/// If `preserve_length` is zero, this is equivalent to `writableSliceGreedy`.
309pub fn writableSliceGreedyPreserve(w: *Writer, preserve_length: usize, minimum_length: usize) Error![]u8 {
310 assert(w.buffer.len >= preserve_length + minimum_length);
311 while (w.buffer.len - w.end < minimum_length) {
312 try drainPreserve(w, preserve_length);
313 } else {
314 @branchHint(.likely);
315 return w.buffer[w.end..];
316 }
317}
318
319pub const WritableVectorIterator = struct {
320 first: []u8,
321 middle: []const []u8 = &.{},
322 last: []u8 = &.{},
323 index: usize = 0,
324
325 pub fn next(it: *WritableVectorIterator) ?[]u8 {
326 while (true) {
327 const i = it.index;
328 it.index += 1;
329 if (i == 0) {
330 if (it.first.len == 0) continue;
331 return it.first;
332 }
333 const middle_index = i - 1;
334 if (middle_index < it.middle.len) {
335 const middle = it.middle[middle_index];
336 if (middle.len == 0) continue;
337 return middle;
338 }
339 if (middle_index == it.middle.len) {
340 if (it.last.len == 0) continue;
341 return it.last;
342 }
343 return null;
344 }
345 }
346};
347
348pub const VectorWrapper = struct {
349 writer: Writer,
350 it: WritableVectorIterator,
351 /// Tracks whether the "writable vector" API was used.
352 used: bool = false,
353 pub const vtable: *const VTable = &unique_vtable_allocation;
354 /// This is intended to be constant but it must be a unique address for
355 /// `@fieldParentPtr` to work.
356 var unique_vtable_allocation: VTable = .{ .drain = fixedDrain };
357};
358
359pub fn writableVectorIterator(w: *Writer) Error!WritableVectorIterator {
360 if (w.vtable == VectorWrapper.vtable) {
361 const wrapper: *VectorWrapper = @fieldParentPtr("writer", w);
362 wrapper.used = true;
363 return wrapper.it;
364 }
365 return .{ .first = try writableSliceGreedy(w, 1) };
366}
367
368pub fn writableVectorPosix(w: *Writer, buffer: []std.posix.iovec, limit: Limit) Error![]std.posix.iovec {
369 var it = try writableVectorIterator(w);
370 var i: usize = 0;
371 var remaining = limit;
372 while (it.next()) |full_buffer| {
373 if (!remaining.nonzero()) break;
374 if (buffer.len - i == 0) break;
375 const buf = remaining.slice(full_buffer);
376 if (buf.len == 0) continue;
377 buffer[i] = .{ .base = buf.ptr, .len = buf.len };
378 i += 1;
379 remaining = remaining.subtract(buf.len).?;
380 }
381 return buffer[0..i];
382}
383
384pub fn ensureUnusedCapacity(w: *Writer, n: usize) Error!void {
385 _ = try writableSliceGreedy(w, n);
386}
387
388pub fn undo(w: *Writer, n: usize) void {
389 w.end -= n;
390}
391
392/// After calling `writableSliceGreedy`, this function tracks how many bytes
393/// were written to it.
394///
395/// This is not needed when using `writableSlice` or `writableArray`.
396pub fn advance(w: *Writer, n: usize) void {
397 const new_end = w.end + n;
398 assert(new_end <= w.buffer.len);
399 w.end = new_end;
400}
401
402/// After calling `writableVector`, this function tracks how many bytes were
403/// written to it.
404pub fn advanceVector(w: *Writer, n: usize) usize {
405 return consume(w, n);
406}
407
408/// The `data` parameter is mutable because this function needs to mutate the
409/// fields in order to handle partial writes from `VTable.writeSplat`.
410pub fn writeVecAll(w: *Writer, data: [][]const u8) Error!void {
411 var index: usize = 0;
412 var truncate: usize = 0;
413 while (index < data.len) {
414 {
415 const untruncated = data[index];
416 data[index] = untruncated[truncate..];
417 defer data[index] = untruncated;
418 truncate += try w.writeVec(data[index..]);
419 }
420 while (index < data.len and truncate >= data[index].len) {
421 truncate -= data[index].len;
422 index += 1;
423 }
424 }
425}
426
427/// The `data` parameter is mutable because this function needs to mutate the
428/// fields in order to handle partial writes from `VTable.writeSplat`.
429pub fn writeSplatAll(w: *Writer, data: [][]const u8, splat: usize) Error!void {
430 var index: usize = 0;
431 var truncate: usize = 0;
432 var remaining_splat = splat;
433 while (index + 1 < data.len) {
434 {
435 const untruncated = data[index];
436 data[index] = untruncated[truncate..];
437 defer data[index] = untruncated;
438 truncate += try w.writeSplat(data[index..], remaining_splat);
439 }
440 while (truncate >= data[index].len) {
441 if (index + 1 < data.len) {
442 truncate -= data[index].len;
443 index += 1;
444 } else {
445 const last = data[data.len - 1];
446 remaining_splat -= @divExact(truncate, last.len);
447 while (remaining_splat > 0) {
448 const n = try w.writeSplat(data[data.len - 1 ..][0..1], remaining_splat);
449 remaining_splat -= @divExact(n, last.len);
450 }
451 return;
452 }
453 }
454 }
455}
456
457pub fn write(w: *Writer, bytes: []const u8) Error!usize {
458 if (w.end + bytes.len <= w.buffer.len) {
459 @branchHint(.likely);
460 @memcpy(w.buffer[w.end..][0..bytes.len], bytes);
461 w.end += bytes.len;
462 return bytes.len;
463 }
464 return w.vtable.drain(w, &.{bytes}, 1);
465}
466
467/// Asserts `buffer` capacity exceeds `preserve_length`.
468pub fn writePreserve(w: *Writer, preserve_length: usize, bytes: []const u8) Error!usize {
469 assert(preserve_length <= w.buffer.len);
470 if (w.end + bytes.len <= w.buffer.len) {
471 @branchHint(.likely);
472 @memcpy(w.buffer[w.end..][0..bytes.len], bytes);
473 w.end += bytes.len;
474 return bytes.len;
475 }
476 const temp_end = w.end -| preserve_length;
477 const preserved = w.buffer[temp_end..w.end];
478 w.end = temp_end;
479 defer w.end += preserved.len;
480 const n = try w.vtable.drain(w, &.{bytes}, 1);
481 assert(w.end <= temp_end + preserved.len);
482 @memmove(w.buffer[w.end..][0..preserved.len], preserved);
483 return n;
484}
485
486/// Calls `drain` as many times as necessary such that all of `bytes` are
487/// transferred.
488pub fn writeAll(w: *Writer, bytes: []const u8) Error!void {
489 var index: usize = 0;
490 while (index < bytes.len) index += try w.write(bytes[index..]);
491}
492
493/// Calls `drain` as many times as necessary such that all of `bytes` are
494/// transferred.
495///
496/// When draining the buffer, ensures that at least `preserve_length` bytes
497/// remain buffered.
498///
499/// Asserts `buffer` capacity exceeds `preserve_length`.
500pub fn writeAllPreserve(w: *Writer, preserve_length: usize, bytes: []const u8) Error!void {
501 var index: usize = 0;
502 while (index < bytes.len) index += try w.writePreserve(preserve_length, bytes[index..]);
503}
504
505/// Renders fmt string with args, calling `writer` with slices of bytes.
506/// If `writer` returns an error, the error is returned from `format` and
507/// `writer` is not called again.
508///
509/// The format string must be comptime-known and may contain placeholders following
510/// this format:
511/// `{[argument][specifier]:[fill][alignment][width].[precision]}`
512///
513/// Above, each word including its surrounding [ and ] is a parameter which you have to replace with something:
514///
515/// - *argument* is either the numeric index or the field name of the argument that should be inserted
516/// - when using a field name, you are required to enclose the field name (an identifier) in square
517/// brackets, e.g. {[score]...} as opposed to the numeric index form which can be written e.g. {2...}
518/// - *specifier* is a type-dependent formatting option that determines how a type should formatted (see below)
519/// - *fill* is a single byte which is used to pad formatted numbers.
520/// - *alignment* is one of the three bytes '<', '^', or '>' to make numbers
521/// left, center, or right-aligned, respectively.
522/// - Not all specifiers support alignment.
523/// - Alignment is not Unicode-aware; appropriate only when used with raw bytes or ASCII.
524/// - *width* is the total width of the field in bytes. This only applies to number formatting.
525/// - *precision* specifies how many decimals a formatted number should have.
526///
527/// Note that most of the parameters are optional and may be omitted. Also you
528/// can leave out separators like `:` and `.` when all parameters after the
529/// separator are omitted.
530///
531/// Only exception is the *fill* parameter. If a non-zero *fill* character is
532/// required at the same time as *width* is specified, one has to specify
533/// *alignment* as well, as otherwise the digit following `:` is interpreted as
534/// *width*, not *fill*.
535///
536/// The *specifier* has several options for types:
537/// - `x` and `X`: output numeric value in hexadecimal notation, or string in hexadecimal bytes
538/// - `s`:
539/// - for pointer-to-many and C pointers of u8, print as a C-string using zero-termination
540/// - for slices of u8, print the entire slice as a string without zero-termination
541/// - `t`:
542/// - for enums and tagged unions: prints the tag name
543/// - for error sets: prints the error name
544/// - `b64`: output string as standard base64
545/// - `e`: output floating point value in scientific notation
546/// - `d`: output numeric value in decimal notation
547/// - `b`: output integer value in binary notation
548/// - `o`: output integer value in octal notation
549/// - `c`: output integer as an ASCII character. Integer type must have 8 bits at max.
550/// - `u`: output integer as an UTF-8 sequence. Integer type must have 21 bits at max.
551/// - `D`: output nanoseconds as duration
552/// - `B`: output bytes in SI units (decimal)
553/// - `Bi`: output bytes in IEC units (binary)
554/// - `?`: output optional value as either the unwrapped value, or `null`; may be followed by a format specifier for the underlying value.
555/// - `!`: output error union value as either the unwrapped value, or the formatted error value; may be followed by a format specifier for the underlying value.
556/// - `*`: output the address of the value instead of the value itself.
557/// - `any`: output a value of any type using its default format.
558/// - `f`: delegates to a method on the type named "format" with the signature `fn (*Writer, args: anytype) Writer.Error!void`.
559///
560/// A user type may be a `struct`, `vector`, `union` or `enum` type.
561///
562/// To print literal curly braces, escape them by writing them twice, e.g. `{{` or `}}`.
563///
564/// Asserts `buffer` capacity of at least 2 if a union is printed. This
565/// requirement could be lifted by adjusting the code, but if you trigger that
566/// assertion it is a clue that you should probably be using a buffer.
567pub fn print(w: *Writer, comptime fmt: []const u8, args: anytype) Error!void {
568 const ArgsType = @TypeOf(args);
569 const args_type_info = @typeInfo(ArgsType);
570 if (args_type_info != .@"struct") {
571 @compileError("expected tuple or struct argument, found " ++ @typeName(ArgsType));
572 }
573
574 const fields_info = args_type_info.@"struct".fields;
575 const max_format_args = @typeInfo(std.fmt.ArgSetType).int.bits;
576 if (fields_info.len > max_format_args) {
577 @compileError("32 arguments max are supported per format call");
578 }
579
580 @setEvalBranchQuota(fmt.len * 1000);
581 comptime var arg_state: std.fmt.ArgState = .{ .args_len = fields_info.len };
582 comptime var i = 0;
583 comptime var literal: []const u8 = "";
584 inline while (true) {
585 const start_index = i;
586
587 inline while (i < fmt.len) : (i += 1) {
588 switch (fmt[i]) {
589 '{', '}' => break,
590 else => {},
591 }
592 }
593
594 comptime var end_index = i;
595 comptime var unescape_brace = false;
596
597 // Handle {{ and }}, those are un-escaped as single braces
598 if (i + 1 < fmt.len and fmt[i + 1] == fmt[i]) {
599 unescape_brace = true;
600 // Make the first brace part of the literal...
601 end_index += 1;
602 // ...and skip both
603 i += 2;
604 }
605
606 literal = literal ++ fmt[start_index..end_index];
607
608 // We've already skipped the other brace, restart the loop
609 if (unescape_brace) continue;
610
611 // Write out the literal
612 if (literal.len != 0) {
613 try w.writeAll(literal);
614 literal = "";
615 }
616
617 if (i >= fmt.len) break;
618
619 if (fmt[i] == '}') {
620 @compileError("missing opening {");
621 }
622
623 // Get past the {
624 comptime assert(fmt[i] == '{');
625 i += 1;
626
627 const fmt_begin = i;
628 // Find the closing brace
629 inline while (i < fmt.len and fmt[i] != '}') : (i += 1) {}
630 const fmt_end = i;
631
632 if (i >= fmt.len) {
633 @compileError("missing closing }");
634 }
635
636 // Get past the }
637 comptime assert(fmt[i] == '}');
638 i += 1;
639
640 const placeholder_array = fmt[fmt_begin..fmt_end].*;
641 const placeholder = comptime std.fmt.Placeholder.parse(&placeholder_array);
642 const arg_pos = comptime switch (placeholder.arg) {
643 .none => null,
644 .number => |pos| pos,
645 .named => |arg_name| std.meta.fieldIndex(ArgsType, arg_name) orelse
646 @compileError("no argument with name '" ++ arg_name ++ "'"),
647 };
648
649 const width = switch (placeholder.width) {
650 .none => null,
651 .number => |v| v,
652 .named => |arg_name| blk: {
653 const arg_i = comptime std.meta.fieldIndex(ArgsType, arg_name) orelse
654 @compileError("no argument with name '" ++ arg_name ++ "'");
655 _ = comptime arg_state.nextArg(arg_i) orelse @compileError("too few arguments");
656 break :blk @field(args, arg_name);
657 },
658 };
659
660 const precision = switch (placeholder.precision) {
661 .none => null,
662 .number => |v| v,
663 .named => |arg_name| blk: {
664 const arg_i = comptime std.meta.fieldIndex(ArgsType, arg_name) orelse
665 @compileError("no argument with name '" ++ arg_name ++ "'");
666 _ = comptime arg_state.nextArg(arg_i) orelse @compileError("too few arguments");
667 break :blk @field(args, arg_name);
668 },
669 };
670
671 const arg_to_print = comptime arg_state.nextArg(arg_pos) orelse
672 @compileError("too few arguments");
673
674 try w.printValue(
675 placeholder.specifier_arg,
676 .{
677 .fill = placeholder.fill,
678 .alignment = placeholder.alignment,
679 .width = width,
680 .precision = precision,
681 },
682 @field(args, fields_info[arg_to_print].name),
683 std.options.fmt_max_depth,
684 );
685 }
686
687 if (comptime arg_state.hasUnusedArgs()) {
688 const missing_count = arg_state.args_len - @popCount(arg_state.used_args);
689 switch (missing_count) {
690 0 => unreachable,
691 1 => @compileError("unused argument in '" ++ fmt ++ "'"),
692 else => @compileError(std.fmt.comptimePrint("{d}", .{missing_count}) ++ " unused arguments in '" ++ fmt ++ "'"),
693 }
694 }
695}
696
697/// Calls `drain` as many times as necessary such that `byte` is transferred.
698pub fn writeByte(w: *Writer, byte: u8) Error!void {
699 while (w.buffer.len - w.end == 0) {
700 const n = try w.vtable.drain(w, &.{&.{byte}}, 1);
701 if (n > 0) return;
702 } else {
703 @branchHint(.likely);
704 w.buffer[w.end] = byte;
705 w.end += 1;
706 }
707}
708
709/// When draining the buffer, ensures that at least `preserve_length` bytes
710/// remain buffered.
711pub fn writeBytePreserve(w: *Writer, preserve_length: usize, byte: u8) Error!void {
712 while (w.buffer.len - w.end == 0) {
713 try drainPreserve(w, preserve_length);
714 } else {
715 @branchHint(.likely);
716 w.buffer[w.end] = byte;
717 w.end += 1;
718 }
719}
720
721/// Writes the same byte many times, performing the underlying write call as
722/// many times as necessary.
723pub fn splatByteAll(w: *Writer, byte: u8, n: usize) Error!void {
724 var remaining: usize = n;
725 while (remaining > 0) remaining -= try w.splatByte(byte, remaining);
726}
727
728/// Writes the same byte many times, allowing short writes.
729///
730/// Does maximum of one underlying `VTable.drain`.
731pub fn splatByte(w: *Writer, byte: u8, n: usize) Error!usize {
732 return writeSplat(w, &.{&.{byte}}, n);
733}
734
735/// Writes the same slice many times, performing the underlying write call as
736/// many times as necessary.
737pub fn splatBytesAll(w: *Writer, bytes: []const u8, splat: usize) Error!void {
738 var remaining_bytes: usize = bytes.len * splat;
739 remaining_bytes -= try w.splatBytes(bytes, splat);
740 while (remaining_bytes > 0) {
741 const leftover = remaining_bytes % bytes.len;
742 const buffers: [2][]const u8 = .{ bytes[bytes.len - leftover ..], bytes };
743 remaining_bytes -= try w.splatBytes(&buffers, splat);
744 }
745}
746
747/// Writes the same slice many times, allowing short writes.
748///
749/// Does maximum of one underlying `VTable.writeSplat`.
750pub fn splatBytes(w: *Writer, bytes: []const u8, n: usize) Error!usize {
751 return writeSplat(w, &.{bytes}, n);
752}
753
754/// Asserts the `buffer` was initialized with a capacity of at least `@sizeOf(T)` bytes.
755pub inline fn writeInt(w: *Writer, comptime T: type, value: T, endian: std.builtin.Endian) Error!void {
756 var bytes: [@divExact(@typeInfo(T).int.bits, 8)]u8 = undefined;
757 std.mem.writeInt(std.math.ByteAlignedInt(@TypeOf(value)), &bytes, value, endian);
758 return w.writeAll(&bytes);
759}
760
761pub fn writeStruct(w: *Writer, value: anytype) Error!void {
762 // Only extern and packed structs have defined in-memory layout.
763 comptime assert(@typeInfo(@TypeOf(value)).@"struct".layout != .auto);
764 return w.writeAll(std.mem.asBytes(&value));
765}
766
767/// The function is inline to avoid the dead code in case `endian` is
768/// comptime-known and matches host endianness.
769/// TODO: make sure this value is not a reference type
770pub inline fn writeStructEndian(w: *Writer, value: anytype, endian: std.builtin.Endian) Error!void {
771 switch (@typeInfo(@TypeOf(value))) {
772 .@"struct" => |info| switch (info.layout) {
773 .auto => @compileError("ill-defined memory layout"),
774 .@"extern" => {
775 if (native_endian == endian) {
776 return w.writeStruct(value);
777 } else {
778 var copy = value;
779 std.mem.byteSwapAllFields(@TypeOf(value), &copy);
780 return w.writeStruct(copy);
781 }
782 },
783 .@"packed" => {
784 return writeInt(w, info.backing_integer.?, @bitCast(value), endian);
785 },
786 },
787 else => @compileError("not a struct"),
788 }
789}
790
791pub inline fn writeSliceEndian(
792 w: *Writer,
793 Elem: type,
794 slice: []const Elem,
795 endian: std.builtin.Endian,
796) Error!void {
797 if (native_endian == endian) {
798 return writeAll(w, @ptrCast(slice));
799 } else {
800 return w.writeArraySwap(w, Elem, slice);
801 }
802}
803
804/// Unlike `writeSplat` and `writeVec`, this function will call into `VTable`
805/// even if there is enough buffer capacity for the file contents.
806///
807/// Although it would be possible to eliminate `error.Unimplemented` from the
808/// error set by reading directly into the buffer in such case, this is not
809/// done because it is more efficient to do it higher up the call stack so that
810/// the error does not occur with each write.
811///
812/// See `sendFileReading` for an alternative that does not have
813/// `error.Unimplemented` in the error set.
814pub fn sendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize {
815 return w.vtable.sendFile(w, file_reader, limit);
816}
817
818/// Returns how many bytes from `header` and `file_reader` were consumed.
819pub fn sendFileHeader(
820 w: *Writer,
821 header: []const u8,
822 file_reader: *File.Reader,
823 limit: Limit,
824) FileError!usize {
825 const new_end = w.end + header.len;
826 if (new_end <= w.buffer.len) {
827 @memcpy(w.buffer[w.end..][0..header.len], header);
828 w.end = new_end;
829 return header.len + try w.vtable.sendFile(w, file_reader, limit);
830 }
831 const buffered_contents = limit.slice(file_reader.interface.buffered());
832 const n = try w.vtable.drain(w, &.{ header, buffered_contents }, 1);
833 file_reader.interface.toss(n - header.len);
834 return n;
835}
836
837/// Asserts nonzero buffer capacity.
838pub fn sendFileReading(w: *Writer, file_reader: *File.Reader, limit: Limit) FileReadingError!usize {
839 const dest = limit.slice(try w.writableSliceGreedy(1));
840 const n = try file_reader.read(dest);
841 w.advance(n);
842 return n;
843}
844
845/// Number of bytes logically written is returned. This excludes bytes from
846/// `buffer` because they have already been logically written.
847pub fn sendFileAll(w: *Writer, file_reader: *File.Reader, limit: Limit) FileAllError!usize {
848 var remaining = @intFromEnum(limit);
849 while (remaining > 0) {
850 const n = sendFile(w, file_reader, .limited(remaining)) catch |err| switch (err) {
851 error.EndOfStream => break,
852 error.Unimplemented => {
853 file_reader.mode = file_reader.mode.toReading();
854 remaining -= try w.sendFileReadingAll(file_reader, .limited(remaining));
855 break;
856 },
857 else => |e| return e,
858 };
859 remaining -= n;
860 }
861 return @intFromEnum(limit) - remaining;
862}
863
864/// Equivalent to `sendFileAll` but uses direct `pread` and `read` calls on
865/// `file` rather than `sendFile`. This is generally used as a fallback when
866/// the underlying implementation returns `error.Unimplemented`, which is why
867/// that error code does not appear in this function's error set.
868///
869/// Asserts nonzero buffer capacity.
870pub fn sendFileReadingAll(w: *Writer, file_reader: *File.Reader, limit: Limit) FileAllError!usize {
871 var remaining = @intFromEnum(limit);
872 while (remaining > 0) {
873 remaining -= sendFileReading(w, file_reader, .limited(remaining)) catch |err| switch (err) {
874 error.EndOfStream => break,
875 else => |e| return e,
876 };
877 }
878 return @intFromEnum(limit) - remaining;
879}
880
881pub fn alignBuffer(
882 w: *Writer,
883 buffer: []const u8,
884 width: usize,
885 alignment: std.fmt.Alignment,
886 fill: u8,
887) Error!void {
888 const padding = if (buffer.len < width) width - buffer.len else 0;
889 if (padding == 0) {
890 @branchHint(.likely);
891 return w.writeAll(buffer);
892 }
893 switch (alignment) {
894 .left => {
895 try w.writeAll(buffer);
896 try w.splatByteAll(fill, padding);
897 },
898 .center => {
899 const left_padding = padding / 2;
900 const right_padding = (padding + 1) / 2;
901 try w.splatByteAll(fill, left_padding);
902 try w.writeAll(buffer);
903 try w.splatByteAll(fill, right_padding);
904 },
905 .right => {
906 try w.splatByteAll(fill, padding);
907 try w.writeAll(buffer);
908 },
909 }
910}
911
912pub fn alignBufferOptions(w: *Writer, buffer: []const u8, options: std.fmt.Options) Error!void {
913 return w.alignBuffer(buffer, options.width orelse buffer.len, options.alignment, options.fill);
914}
915
916pub fn printAddress(w: *Writer, value: anytype) Error!void {
917 const T = @TypeOf(value);
918 switch (@typeInfo(T)) {
919 .pointer => |info| {
920 try w.writeAll(@typeName(info.child) ++ "@");
921 const int = if (info.size == .slice) @intFromPtr(value.ptr) else @intFromPtr(value);
922 return w.printInt(int, 16, .lower, .{});
923 },
924 .optional => |info| {
925 if (@typeInfo(info.child) == .pointer) {
926 try w.writeAll(@typeName(info.child) ++ "@");
927 try w.printInt(@intFromPtr(value), 16, .lower, .{});
928 return;
929 }
930 },
931 else => {},
932 }
933
934 @compileError("cannot format non-pointer type " ++ @typeName(T) ++ " with * specifier");
935}
936
937/// Asserts `buffer` capacity of at least 2 if `value` is a union.
938pub fn printValue(
939 w: *Writer,
940 comptime fmt: []const u8,
941 options: std.fmt.Options,
942 value: anytype,
943 max_depth: usize,
944) Error!void {
945 const T = @TypeOf(value);
946
947 switch (fmt.len) {
948 1 => switch (fmt[0]) {
949 '*' => return w.printAddress(value),
950 'f' => return value.format(w),
951 'd' => switch (@typeInfo(T)) {
952 .float, .comptime_float => return printFloat(w, value, options.toNumber(.decimal, .lower)),
953 .int, .comptime_int => return printInt(w, value, 10, .lower, options),
954 .@"struct" => return value.formatNumber(w, options.toNumber(.decimal, .lower)),
955 .@"enum" => return printInt(w, @intFromEnum(value), 10, .lower, options),
956 .vector => return printVector(w, fmt, options, value, max_depth),
957 else => invalidFmtError(fmt, value),
958 },
959 'c' => return w.printAsciiChar(value, options),
960 'u' => return w.printUnicodeCodepoint(value),
961 'b' => switch (@typeInfo(T)) {
962 .int, .comptime_int => return printInt(w, value, 2, .lower, options),
963 .@"enum" => return printInt(w, @intFromEnum(value), 2, .lower, options),
964 .@"struct" => return value.formatNumber(w, options.toNumber(.binary, .lower)),
965 .vector => return printVector(w, fmt, options, value, max_depth),
966 else => invalidFmtError(fmt, value),
967 },
968 'o' => switch (@typeInfo(T)) {
969 .int, .comptime_int => return printInt(w, value, 8, .lower, options),
970 .@"enum" => return printInt(w, @intFromEnum(value), 8, .lower, options),
971 .@"struct" => return value.formatNumber(w, options.toNumber(.octal, .lower)),
972 .vector => return printVector(w, fmt, options, value, max_depth),
973 else => invalidFmtError(fmt, value),
974 },
975 'x' => switch (@typeInfo(T)) {
976 .float, .comptime_float => return printFloatHexOptions(w, value, options.toNumber(.hex, .lower)),
977 .int, .comptime_int => return printInt(w, value, 16, .lower, options),
978 .@"enum" => return printInt(w, @intFromEnum(value), 16, .lower, options),
979 .@"struct" => return value.formatNumber(w, options.toNumber(.hex, .lower)),
980 .pointer => |info| switch (info.size) {
981 .one, .slice => {
982 const slice: []const u8 = value;
983 optionsForbidden(options);
984 return printHex(w, slice, .lower);
985 },
986 .many, .c => {
987 const slice: [:0]const u8 = std.mem.span(value);
988 optionsForbidden(options);
989 return printHex(w, slice, .lower);
990 },
991 },
992 .array => {
993 const slice: []const u8 = &value;
994 optionsForbidden(options);
995 return printHex(w, slice, .lower);
996 },
997 .vector => return printVector(w, fmt, options, value, max_depth),
998 else => invalidFmtError(fmt, value),
999 },
1000 'X' => switch (@typeInfo(T)) {
1001 .float, .comptime_float => return printFloatHexOptions(w, value, options.toNumber(.hex, .lower)),
1002 .int, .comptime_int => return printInt(w, value, 16, .upper, options),
1003 .@"enum" => return printInt(w, @intFromEnum(value), 16, .upper, options),
1004 .@"struct" => return value.formatNumber(w, options.toNumber(.hex, .upper)),
1005 .pointer => |info| switch (info.size) {
1006 .one, .slice => {
1007 const slice: []const u8 = value;
1008 optionsForbidden(options);
1009 return printHex(w, slice, .upper);
1010 },
1011 .many, .c => {
1012 const slice: [:0]const u8 = std.mem.span(value);
1013 optionsForbidden(options);
1014 return printHex(w, slice, .upper);
1015 },
1016 },
1017 .array => {
1018 const slice: []const u8 = &value;
1019 optionsForbidden(options);
1020 return printHex(w, slice, .upper);
1021 },
1022 .vector => return printVector(w, fmt, options, value, max_depth),
1023 else => invalidFmtError(fmt, value),
1024 },
1025 's' => switch (@typeInfo(T)) {
1026 .pointer => |info| switch (info.size) {
1027 .one, .slice => {
1028 const slice: []const u8 = value;
1029 return w.alignBufferOptions(slice, options);
1030 },
1031 .many, .c => {
1032 const slice: [:0]const u8 = std.mem.span(value);
1033 return w.alignBufferOptions(slice, options);
1034 },
1035 },
1036 .array => {
1037 const slice: []const u8 = &value;
1038 return w.alignBufferOptions(slice, options);
1039 },
1040 else => invalidFmtError(fmt, value),
1041 },
1042 'B' => switch (@typeInfo(T)) {
1043 .int, .comptime_int => return w.printByteSize(value, .decimal, options),
1044 .@"struct" => return value.formatByteSize(w, .decimal),
1045 else => invalidFmtError(fmt, value),
1046 },
1047 'D' => switch (@typeInfo(T)) {
1048 .int, .comptime_int => return w.printDuration(value, options),
1049 .@"struct" => return value.formatDuration(w),
1050 else => invalidFmtError(fmt, value),
1051 },
1052 'e' => switch (@typeInfo(T)) {
1053 .float, .comptime_float => return printFloat(w, value, options.toNumber(.scientific, .lower)),
1054 .@"struct" => return value.formatNumber(w, options.toNumber(.scientific, .lower)),
1055 else => invalidFmtError(fmt, value),
1056 },
1057 'E' => switch (@typeInfo(T)) {
1058 .float, .comptime_float => return printFloat(w, value, options.toNumber(.scientific, .upper)),
1059 .@"struct" => return value.formatNumber(w, options.toNumber(.scientific, .upper)),
1060 else => invalidFmtError(fmt, value),
1061 },
1062 't' => switch (@typeInfo(T)) {
1063 .error_set => return w.writeAll(@errorName(value)),
1064 .@"enum", .@"union" => return w.writeAll(@tagName(value)),
1065 else => invalidFmtError(fmt, value),
1066 },
1067 else => {},
1068 },
1069 2 => switch (fmt[0]) {
1070 'B' => switch (fmt[1]) {
1071 'i' => switch (@typeInfo(T)) {
1072 .int, .comptime_int => return w.printByteSize(value, .binary, options),
1073 .@"struct" => return value.formatByteSize(w, .binary),
1074 else => invalidFmtError(fmt, value),
1075 },
1076 else => {},
1077 },
1078 else => {},
1079 },
1080 3 => if (fmt[0] == 'b' and fmt[1] == '6' and fmt[2] == '4') switch (@typeInfo(T)) {
1081 .pointer => |info| switch (info.size) {
1082 .one, .slice => {
1083 const slice: []const u8 = value;
1084 optionsForbidden(options);
1085 return w.printBase64(slice);
1086 },
1087 .many, .c => {
1088 const slice: [:0]const u8 = std.mem.span(value);
1089 optionsForbidden(options);
1090 return w.printBase64(slice);
1091 },
1092 },
1093 .array => {
1094 const slice: []const u8 = &value;
1095 optionsForbidden(options);
1096 return w.printBase64(slice);
1097 },
1098 else => invalidFmtError(fmt, value),
1099 },
1100 else => {},
1101 }
1102
1103 const is_any = comptime std.mem.eql(u8, fmt, ANY);
1104 if (!is_any and std.meta.hasMethod(T, "format") and fmt.len == 0) {
1105 // after 0.15.0 is tagged, delete this compile error and its condition
1106 @compileError("ambiguous format string; specify {f} to call format method, or {any} to skip it");
1107 }
1108
1109 switch (@typeInfo(T)) {
1110 .float, .comptime_float => {
1111 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1112 return printFloat(w, value, options.toNumber(.decimal, .lower));
1113 },
1114 .int, .comptime_int => {
1115 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1116 return printInt(w, value, 10, .lower, options);
1117 },
1118 .bool => {
1119 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1120 const string: []const u8 = if (value) "true" else "false";
1121 return w.alignBufferOptions(string, options);
1122 },
1123 .void => {
1124 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1125 return w.alignBufferOptions("void", options);
1126 },
1127 .optional => {
1128 const remaining_fmt = comptime if (fmt.len > 0 and fmt[0] == '?')
1129 stripOptionalOrErrorUnionSpec(fmt)
1130 else if (is_any)
1131 ANY
1132 else
1133 @compileError("cannot print optional without a specifier (i.e. {?} or {any})");
1134 if (value) |payload| {
1135 return w.printValue(remaining_fmt, options, payload, max_depth);
1136 } else {
1137 return w.alignBufferOptions("null", options);
1138 }
1139 },
1140 .error_union => {
1141 const remaining_fmt = comptime if (fmt.len > 0 and fmt[0] == '!')
1142 stripOptionalOrErrorUnionSpec(fmt)
1143 else if (is_any)
1144 ANY
1145 else
1146 @compileError("cannot print error union without a specifier (i.e. {!} or {any})");
1147 if (value) |payload| {
1148 return w.printValue(remaining_fmt, options, payload, max_depth);
1149 } else |err| {
1150 return w.printValue("", options, err, max_depth);
1151 }
1152 },
1153 .error_set => {
1154 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1155 optionsForbidden(options);
1156 return printErrorSet(w, value);
1157 },
1158 .@"enum" => |info| {
1159 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1160 optionsForbidden(options);
1161 if (info.is_exhaustive) {
1162 return printEnumExhaustive(w, value);
1163 } else {
1164 return printEnumNonexhaustive(w, value);
1165 }
1166 },
1167 .@"union" => |info| {
1168 if (!is_any) {
1169 if (fmt.len != 0) invalidFmtError(fmt, value);
1170 return printValue(w, ANY, options, value, max_depth);
1171 }
1172 if (max_depth == 0) {
1173 try w.writeAll(".{ ... }");
1174 return;
1175 }
1176 if (info.tag_type) |UnionTagType| {
1177 try w.writeAll(".{ .");
1178 try w.writeAll(@tagName(@as(UnionTagType, value)));
1179 try w.writeAll(" = ");
1180 inline for (info.fields) |u_field| {
1181 if (value == @field(UnionTagType, u_field.name)) {
1182 try w.printValue(ANY, options, @field(value, u_field.name), max_depth - 1);
1183 }
1184 }
1185 try w.writeAll(" }");
1186 } else switch (info.layout) {
1187 .auto => {
1188 return w.writeAll(".{ ... }");
1189 },
1190 .@"extern", .@"packed" => {
1191 if (info.fields.len == 0) return w.writeAll(".{}");
1192 try w.writeAll(".{ ");
1193 inline for (info.fields) |field| {
1194 try w.writeByte('.');
1195 try w.writeAll(field.name);
1196 try w.writeAll(" = ");
1197 try w.printValue(ANY, options, @field(value, field.name), max_depth - 1);
1198 (try w.writableArray(2)).* = ", ".*;
1199 }
1200 w.buffer[w.end - 2 ..][0..2].* = " }".*;
1201 },
1202 }
1203 },
1204 .@"struct" => |info| {
1205 if (!is_any) {
1206 if (fmt.len != 0) invalidFmtError(fmt, value);
1207 return printValue(w, ANY, options, value, max_depth);
1208 }
1209 if (info.is_tuple) {
1210 // Skip the type and field names when formatting tuples.
1211 if (max_depth == 0) {
1212 try w.writeAll(".{ ... }");
1213 return;
1214 }
1215 try w.writeAll(".{");
1216 inline for (info.fields, 0..) |f, i| {
1217 if (i == 0) {
1218 try w.writeAll(" ");
1219 } else {
1220 try w.writeAll(", ");
1221 }
1222 try w.printValue(ANY, options, @field(value, f.name), max_depth - 1);
1223 }
1224 try w.writeAll(" }");
1225 return;
1226 }
1227 if (max_depth == 0) {
1228 try w.writeAll(".{ ... }");
1229 return;
1230 }
1231 try w.writeAll(".{");
1232 inline for (info.fields, 0..) |f, i| {
1233 if (i == 0) {
1234 try w.writeAll(" .");
1235 } else {
1236 try w.writeAll(", .");
1237 }
1238 try w.writeAll(f.name);
1239 try w.writeAll(" = ");
1240 try w.printValue(ANY, options, @field(value, f.name), max_depth - 1);
1241 }
1242 try w.writeAll(" }");
1243 },
1244 .pointer => |ptr_info| switch (ptr_info.size) {
1245 .one => switch (@typeInfo(ptr_info.child)) {
1246 .array => |array_info| return w.printValue(fmt, options, @as([]const array_info.child, value), max_depth),
1247 .@"enum", .@"union", .@"struct" => return w.printValue(fmt, options, value.*, max_depth),
1248 else => {
1249 var buffers: [2][]const u8 = .{ @typeName(ptr_info.child), "@" };
1250 try w.writeVecAll(&buffers);
1251 try w.printInt(@intFromPtr(value), 16, .lower, options);
1252 return;
1253 },
1254 },
1255 .many, .c => {
1256 if (!is_any) @compileError("cannot format pointer without a specifier (i.e. {s} or {*})");
1257 optionsForbidden(options);
1258 try w.printAddress(value);
1259 },
1260 .slice => {
1261 if (!is_any)
1262 @compileError("cannot format slice without a specifier (i.e. {s}, {x}, {b64}, or {any})");
1263 if (max_depth == 0) return w.writeAll("{ ... }");
1264 try w.writeAll("{ ");
1265 for (value, 0..) |elem, i| {
1266 try w.printValue(fmt, options, elem, max_depth - 1);
1267 if (i != value.len - 1) {
1268 try w.writeAll(", ");
1269 }
1270 }
1271 try w.writeAll(" }");
1272 },
1273 },
1274 .array => {
1275 if (!is_any) @compileError("cannot format array without a specifier (i.e. {s} or {any})");
1276 if (max_depth == 0) return w.writeAll("{ ... }");
1277 try w.writeAll("{ ");
1278 for (value, 0..) |elem, i| {
1279 try w.printValue(fmt, options, elem, max_depth - 1);
1280 if (i < value.len - 1) {
1281 try w.writeAll(", ");
1282 }
1283 }
1284 try w.writeAll(" }");
1285 },
1286 .vector => {
1287 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1288 return printVector(w, fmt, options, value, max_depth);
1289 },
1290 .@"fn" => @compileError("unable to format function body type, use '*const " ++ @typeName(T) ++ "' for a function pointer type"),
1291 .type => {
1292 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1293 return w.alignBufferOptions(@typeName(value), options);
1294 },
1295 .enum_literal => {
1296 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1297 optionsForbidden(options);
1298 var vecs: [2][]const u8 = .{ ".", @tagName(value) };
1299 return w.writeVecAll(&vecs);
1300 },
1301 .null => {
1302 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1303 return w.alignBufferOptions("null", options);
1304 },
1305 else => @compileError("unable to format type '" ++ @typeName(T) ++ "'"),
1306 }
1307}
1308
1309fn optionsForbidden(options: std.fmt.Options) void {
1310 assert(options.precision == null);
1311 assert(options.width == null);
1312}
1313
1314fn printErrorSet(w: *Writer, error_set: anyerror) Error!void {
1315 var vecs: [2][]const u8 = .{ "error.", @errorName(error_set) };
1316 try w.writeVecAll(&vecs);
1317}
1318
1319fn printEnumExhaustive(w: *Writer, value: anytype) Error!void {
1320 var vecs: [2][]const u8 = .{ ".", @tagName(value) };
1321 try w.writeVecAll(&vecs);
1322}
1323
1324fn printEnumNonexhaustive(w: *Writer, value: anytype) Error!void {
1325 if (std.enums.tagName(@TypeOf(value), value)) |tag_name| {
1326 var vecs: [2][]const u8 = .{ ".", tag_name };
1327 try w.writeVecAll(&vecs);
1328 return;
1329 }
1330 try w.writeAll("@enumFromInt(");
1331 try w.printInt(@intFromEnum(value), 10, .lower, .{});
1332 try w.writeByte(')');
1333}
1334
1335pub fn printVector(
1336 w: *Writer,
1337 comptime fmt: []const u8,
1338 options: std.fmt.Options,
1339 value: anytype,
1340 max_depth: usize,
1341) Error!void {
1342 const len = @typeInfo(@TypeOf(value)).vector.len;
1343 if (max_depth == 0) return w.writeAll("{ ... }");
1344 try w.writeAll("{ ");
1345 inline for (0..len) |i| {
1346 try w.printValue(fmt, options, value[i], max_depth - 1);
1347 if (i < len - 1) try w.writeAll(", ");
1348 }
1349 try w.writeAll(" }");
1350}
1351
1352// A wrapper around `printIntAny` to avoid the generic explosion of this
1353// function by funneling smaller integer types through `isize` and `usize`.
1354pub inline fn printInt(
1355 w: *Writer,
1356 value: anytype,
1357 base: u8,
1358 case: std.fmt.Case,
1359 options: std.fmt.Options,
1360) Error!void {
1361 switch (@TypeOf(value)) {
1362 isize, usize => {},
1363 comptime_int => {
1364 if (comptime std.math.cast(usize, value)) |x| return printIntAny(w, x, base, case, options);
1365 if (comptime std.math.cast(isize, value)) |x| return printIntAny(w, x, base, case, options);
1366 const Int = std.math.IntFittingRange(value, value);
1367 return printIntAny(w, @as(Int, value), base, case, options);
1368 },
1369 else => switch (@typeInfo(@TypeOf(value)).int.signedness) {
1370 .signed => if (std.math.cast(isize, value)) |x| return printIntAny(w, x, base, case, options),
1371 .unsigned => if (std.math.cast(usize, value)) |x| return printIntAny(w, x, base, case, options),
1372 },
1373 }
1374 return printIntAny(w, value, base, case, options);
1375}
1376
1377/// In general, prefer `printInt` to avoid generic explosion. However this
1378/// function may be used when optimal codegen for a particular integer type is
1379/// desired.
1380pub fn printIntAny(
1381 w: *Writer,
1382 value: anytype,
1383 base: u8,
1384 case: std.fmt.Case,
1385 options: std.fmt.Options,
1386) Error!void {
1387 assert(base >= 2);
1388 const value_info = @typeInfo(@TypeOf(value)).int;
1389
1390 // The type must have the same size as `base` or be wider in order for the
1391 // division to work
1392 const min_int_bits = comptime @max(value_info.bits, 8);
1393 const MinInt = std.meta.Int(.unsigned, min_int_bits);
1394
1395 const abs_value = @abs(value);
1396 // The worst case in terms of space needed is base 2, plus 1 for the sign
1397 var buf: [1 + @max(@as(comptime_int, value_info.bits), 1)]u8 = undefined;
1398
1399 var a: MinInt = abs_value;
1400 var index: usize = buf.len;
1401
1402 if (base == 10) {
1403 while (a >= 100) : (a = @divTrunc(a, 100)) {
1404 index -= 2;
1405 buf[index..][0..2].* = std.fmt.digits2(@intCast(a % 100));
1406 }
1407
1408 if (a < 10) {
1409 index -= 1;
1410 buf[index] = '0' + @as(u8, @intCast(a));
1411 } else {
1412 index -= 2;
1413 buf[index..][0..2].* = std.fmt.digits2(@intCast(a));
1414 }
1415 } else {
1416 while (true) {
1417 const digit = a % base;
1418 index -= 1;
1419 buf[index] = std.fmt.digitToChar(@intCast(digit), case);
1420 a /= base;
1421 if (a == 0) break;
1422 }
1423 }
1424
1425 if (value_info.signedness == .signed) {
1426 if (value < 0) {
1427 // Negative integer
1428 index -= 1;
1429 buf[index] = '-';
1430 } else if (options.width == null or options.width.? == 0) {
1431 // Positive integer, omit the plus sign
1432 } else {
1433 // Positive integer
1434 index -= 1;
1435 buf[index] = '+';
1436 }
1437 }
1438
1439 return w.alignBufferOptions(buf[index..], options);
1440}
1441
1442pub fn printAsciiChar(w: *Writer, c: u8, options: std.fmt.Options) Error!void {
1443 return w.alignBufferOptions(@as(*const [1]u8, &c), options);
1444}
1445
1446pub fn printAscii(w: *Writer, bytes: []const u8, options: std.fmt.Options) Error!void {
1447 return w.alignBufferOptions(bytes, options);
1448}
1449
1450pub fn printUnicodeCodepoint(w: *Writer, c: u21) Error!void {
1451 var buf: [4]u8 = undefined;
1452 const len = std.unicode.utf8Encode(c, &buf) catch |err| switch (err) {
1453 error.Utf8CannotEncodeSurrogateHalf, error.CodepointTooLarge => l: {
1454 buf[0..3].* = std.unicode.replacement_character_utf8;
1455 break :l 3;
1456 },
1457 };
1458 return w.writeAll(buf[0..len]);
1459}
1460
1461/// Uses a larger stack buffer; asserts mode is decimal or scientific.
1462pub fn printFloat(w: *Writer, value: anytype, options: std.fmt.Number) Error!void {
1463 const mode: std.fmt.float.Mode = switch (options.mode) {
1464 .decimal => .decimal,
1465 .scientific => .scientific,
1466 .binary, .octal, .hex => unreachable,
1467 };
1468 var buf: [std.fmt.float.bufferSize(.decimal, f64)]u8 = undefined;
1469 const s = std.fmt.float.render(&buf, value, .{
1470 .mode = mode,
1471 .precision = options.precision,
1472 }) catch |err| switch (err) {
1473 error.BufferTooSmall => "(float)",
1474 };
1475 return w.alignBuffer(s, options.width orelse s.len, options.alignment, options.fill);
1476}
1477
1478/// Uses a smaller stack buffer; asserts mode is not decimal or scientific.
1479pub fn printFloatHexOptions(w: *Writer, value: anytype, options: std.fmt.Number) Error!void {
1480 var buf: [50]u8 = undefined; // for aligning
1481 var sub_writer: Writer = .fixed(&buf);
1482 switch (options.mode) {
1483 .decimal => unreachable,
1484 .scientific => unreachable,
1485 .binary => @panic("TODO"),
1486 .octal => @panic("TODO"),
1487 .hex => {},
1488 }
1489 printFloatHex(&sub_writer, value, options.case, options.precision) catch unreachable; // buf is large enough
1490
1491 const printed = sub_writer.buffered();
1492 return w.alignBuffer(printed, options.width orelse printed.len, options.alignment, options.fill);
1493}
1494
1495pub fn printFloatHex(w: *Writer, value: anytype, case: std.fmt.Case, opt_precision: ?usize) Error!void {
1496 if (std.math.signbit(value)) try w.writeByte('-');
1497 if (std.math.isNan(value)) return w.writeAll(switch (case) {
1498 .lower => "nan",
1499 .upper => "NAN",
1500 });
1501 if (std.math.isInf(value)) return w.writeAll(switch (case) {
1502 .lower => "inf",
1503 .upper => "INF",
1504 });
1505
1506 const T = @TypeOf(value);
1507 const TU = std.meta.Int(.unsigned, @bitSizeOf(T));
1508
1509 const mantissa_bits = std.math.floatMantissaBits(T);
1510 const fractional_bits = std.math.floatFractionalBits(T);
1511 const exponent_bits = std.math.floatExponentBits(T);
1512 const mantissa_mask = (1 << mantissa_bits) - 1;
1513 const exponent_mask = (1 << exponent_bits) - 1;
1514 const exponent_bias = (1 << (exponent_bits - 1)) - 1;
1515
1516 const as_bits: TU = @bitCast(value);
1517 var mantissa = as_bits & mantissa_mask;
1518 var exponent: i32 = @as(u16, @truncate((as_bits >> mantissa_bits) & exponent_mask));
1519
1520 const is_denormal = exponent == 0 and mantissa != 0;
1521 const is_zero = exponent == 0 and mantissa == 0;
1522
1523 if (is_zero) {
1524 // Handle this case here to simplify the logic below.
1525 try w.writeAll("0x0");
1526 if (opt_precision) |precision| {
1527 if (precision > 0) {
1528 try w.writeAll(".");
1529 try w.splatByteAll('0', precision);
1530 }
1531 } else {
1532 try w.writeAll(".0");
1533 }
1534 try w.writeAll("p0");
1535 return;
1536 }
1537
1538 if (is_denormal) {
1539 // Adjust the exponent for printing.
1540 exponent += 1;
1541 } else {
1542 if (fractional_bits == mantissa_bits)
1543 mantissa |= 1 << fractional_bits; // Add the implicit integer bit.
1544 }
1545
1546 const mantissa_digits = (fractional_bits + 3) / 4;
1547 // Fill in zeroes to round the fraction width to a multiple of 4.
1548 mantissa <<= mantissa_digits * 4 - fractional_bits;
1549
1550 if (opt_precision) |precision| {
1551 // Round if needed.
1552 if (precision < mantissa_digits) {
1553 // We always have at least 4 extra bits.
1554 var extra_bits = (mantissa_digits - precision) * 4;
1555 // The result LSB is the Guard bit, we need two more (Round and
1556 // Sticky) to round the value.
1557 while (extra_bits > 2) {
1558 mantissa = (mantissa >> 1) | (mantissa & 1);
1559 extra_bits -= 1;
1560 }
1561 // Round to nearest, tie to even.
1562 mantissa |= @intFromBool(mantissa & 0b100 != 0);
1563 mantissa += 1;
1564 // Drop the excess bits.
1565 mantissa >>= 2;
1566 // Restore the alignment.
1567 mantissa <<= @as(std.math.Log2Int(TU), @intCast((mantissa_digits - precision) * 4));
1568
1569 const overflow = mantissa & (1 << 1 + mantissa_digits * 4) != 0;
1570 // Prefer a normalized result in case of overflow.
1571 if (overflow) {
1572 mantissa >>= 1;
1573 exponent += 1;
1574 }
1575 }
1576 }
1577
1578 // +1 for the decimal part.
1579 var buf: [1 + mantissa_digits]u8 = undefined;
1580 assert(std.fmt.printInt(&buf, mantissa, 16, case, .{ .fill = '0', .width = 1 + mantissa_digits }) == buf.len);
1581
1582 try w.writeAll("0x");
1583 try w.writeByte(buf[0]);
1584 const trimmed = std.mem.trimRight(u8, buf[1..], "0");
1585 if (opt_precision) |precision| {
1586 if (precision > 0) try w.writeAll(".");
1587 } else if (trimmed.len > 0) {
1588 try w.writeAll(".");
1589 }
1590 try w.writeAll(trimmed);
1591 // Add trailing zeros if explicitly requested.
1592 if (opt_precision) |precision| if (precision > 0) {
1593 if (precision > trimmed.len)
1594 try w.splatByteAll('0', precision - trimmed.len);
1595 };
1596 try w.writeAll("p");
1597 try w.printInt(exponent - exponent_bias, 10, case, .{});
1598}
1599
1600pub const ByteSizeUnits = enum {
1601 /// This formatter represents the number as multiple of 1000 and uses the SI
1602 /// measurement units (kB, MB, GB, ...).
1603 decimal,
1604 /// This formatter represents the number as multiple of 1024 and uses the IEC
1605 /// measurement units (KiB, MiB, GiB, ...).
1606 binary,
1607};
1608
1609/// Format option `precision` is ignored when `value` is less than 1kB
1610pub fn printByteSize(
1611 w: *std.io.Writer,
1612 value: u64,
1613 comptime units: ByteSizeUnits,
1614 options: std.fmt.Options,
1615) Error!void {
1616 if (value == 0) return w.alignBufferOptions("0B", options);
1617 // The worst case in terms of space needed is 32 bytes + 3 for the suffix.
1618 var buf: [std.fmt.float.min_buffer_size + 3]u8 = undefined;
1619
1620 const mags_si = " kMGTPEZY";
1621 const mags_iec = " KMGTPEZY";
1622
1623 const log2 = std.math.log2(value);
1624 const base = switch (units) {
1625 .decimal => 1000,
1626 .binary => 1024,
1627 };
1628 const magnitude = switch (units) {
1629 .decimal => @min(log2 / comptime std.math.log2(1000), mags_si.len - 1),
1630 .binary => @min(log2 / 10, mags_iec.len - 1),
1631 };
1632 const new_value = std.math.lossyCast(f64, value) / std.math.pow(f64, std.math.lossyCast(f64, base), std.math.lossyCast(f64, magnitude));
1633 const suffix = switch (units) {
1634 .decimal => mags_si[magnitude],
1635 .binary => mags_iec[magnitude],
1636 };
1637
1638 const s = switch (magnitude) {
1639 0 => buf[0..std.fmt.printInt(&buf, value, 10, .lower, .{})],
1640 else => std.fmt.float.render(&buf, new_value, .{ .mode = .decimal, .precision = options.precision }) catch |err| switch (err) {
1641 error.BufferTooSmall => unreachable,
1642 },
1643 };
1644
1645 var i: usize = s.len;
1646 if (suffix == ' ') {
1647 buf[i] = 'B';
1648 i += 1;
1649 } else switch (units) {
1650 .decimal => {
1651 buf[i..][0..2].* = [_]u8{ suffix, 'B' };
1652 i += 2;
1653 },
1654 .binary => {
1655 buf[i..][0..3].* = [_]u8{ suffix, 'i', 'B' };
1656 i += 3;
1657 },
1658 }
1659
1660 return w.alignBufferOptions(buf[0..i], options);
1661}
1662
1663// This ANY const is a workaround for: https://github.com/ziglang/zig/issues/7948
1664const ANY = "any";
1665
1666fn stripOptionalOrErrorUnionSpec(comptime fmt: []const u8) []const u8 {
1667 return if (std.mem.eql(u8, fmt[1..], ANY))
1668 ANY
1669 else
1670 fmt[1..];
1671}
1672
1673pub fn invalidFmtError(comptime fmt: []const u8, value: anytype) noreturn {
1674 @compileError("invalid format string '" ++ fmt ++ "' for type '" ++ @typeName(@TypeOf(value)) ++ "'");
1675}
1676
1677pub fn printDurationSigned(w: *Writer, ns: i64) Error!void {
1678 if (ns < 0) try w.writeByte('-');
1679 return w.printDurationUnsigned(@abs(ns));
1680}
1681
1682pub fn printDurationUnsigned(w: *Writer, ns: u64) Error!void {
1683 var ns_remaining = ns;
1684 inline for (.{
1685 .{ .ns = 365 * std.time.ns_per_day, .sep = 'y' },
1686 .{ .ns = std.time.ns_per_week, .sep = 'w' },
1687 .{ .ns = std.time.ns_per_day, .sep = 'd' },
1688 .{ .ns = std.time.ns_per_hour, .sep = 'h' },
1689 .{ .ns = std.time.ns_per_min, .sep = 'm' },
1690 }) |unit| {
1691 if (ns_remaining >= unit.ns) {
1692 const units = ns_remaining / unit.ns;
1693 try w.printInt(units, 10, .lower, .{});
1694 try w.writeByte(unit.sep);
1695 ns_remaining -= units * unit.ns;
1696 if (ns_remaining == 0) return;
1697 }
1698 }
1699
1700 inline for (.{
1701 .{ .ns = std.time.ns_per_s, .sep = "s" },
1702 .{ .ns = std.time.ns_per_ms, .sep = "ms" },
1703 .{ .ns = std.time.ns_per_us, .sep = "us" },
1704 }) |unit| {
1705 const kunits = ns_remaining * 1000 / unit.ns;
1706 if (kunits >= 1000) {
1707 try w.printInt(kunits / 1000, 10, .lower, .{});
1708 const frac = kunits % 1000;
1709 if (frac > 0) {
1710 // Write up to 3 decimal places
1711 var decimal_buf = [_]u8{ '.', 0, 0, 0 };
1712 var inner: Writer = .fixed(decimal_buf[1..]);
1713 inner.printInt(frac, 10, .lower, .{ .fill = '0', .width = 3 }) catch unreachable;
1714 var end: usize = 4;
1715 while (end > 1) : (end -= 1) {
1716 if (decimal_buf[end - 1] != '0') break;
1717 }
1718 try w.writeAll(decimal_buf[0..end]);
1719 }
1720 return w.writeAll(unit.sep);
1721 }
1722 }
1723
1724 try w.printInt(ns_remaining, 10, .lower, .{});
1725 try w.writeAll("ns");
1726}
1727
1728/// Writes number of nanoseconds according to its signed magnitude:
1729/// `[#y][#w][#d][#h][#m]#[.###][n|u|m]s`
1730/// `nanoseconds` must be an integer that coerces into `u64` or `i64`.
1731pub fn printDuration(w: *Writer, nanoseconds: anytype, options: std.fmt.Options) Error!void {
1732 // worst case: "-XXXyXXwXXdXXhXXmXX.XXXs".len = 24
1733 var buf: [24]u8 = undefined;
1734 var sub_writer: Writer = .fixed(&buf);
1735 if (@TypeOf(nanoseconds) == comptime_int) {
1736 if (nanoseconds >= 0) {
1737 sub_writer.printDurationUnsigned(nanoseconds) catch unreachable;
1738 } else {
1739 sub_writer.printDurationSigned(nanoseconds) catch unreachable;
1740 }
1741 } else switch (@typeInfo(@TypeOf(nanoseconds)).int.signedness) {
1742 .signed => sub_writer.printDurationSigned(nanoseconds) catch unreachable,
1743 .unsigned => sub_writer.printDurationUnsigned(nanoseconds) catch unreachable,
1744 }
1745 return w.alignBufferOptions(sub_writer.buffered(), options);
1746}
1747
1748pub fn printHex(w: *Writer, bytes: []const u8, case: std.fmt.Case) Error!void {
1749 const charset = switch (case) {
1750 .upper => "0123456789ABCDEF",
1751 .lower => "0123456789abcdef",
1752 };
1753 for (bytes) |c| {
1754 try w.writeByte(charset[c >> 4]);
1755 try w.writeByte(charset[c & 15]);
1756 }
1757}
1758
1759pub fn printBase64(w: *Writer, bytes: []const u8) Error!void {
1760 var chunker = std.mem.window(u8, bytes, 3, 3);
1761 var temp: [5]u8 = undefined;
1762 while (chunker.next()) |chunk| {
1763 try w.writeAll(std.base64.standard.Encoder.encode(&temp, chunk));
1764 }
1765}
1766
1767/// Write a single unsigned integer as LEB128 to the given writer.
1768pub fn writeUleb128(w: *Writer, value: anytype) Error!void {
1769 try w.writeLeb128(switch (@typeInfo(@TypeOf(value))) {
1770 .comptime_int => @as(std.math.IntFittingRange(0, @abs(value)), value),
1771 .int => |value_info| switch (value_info.signedness) {
1772 .signed => @as(@Type(.{ .int = .{ .signedness = .unsigned, .bits = value_info.bits -| 1 } }), @intCast(value)),
1773 .unsigned => value,
1774 },
1775 else => comptime unreachable,
1776 });
1777}
1778
1779/// Write a single signed integer as LEB128 to the given writer.
1780pub fn writeSleb128(w: *Writer, value: anytype) Error!void {
1781 try w.writeLeb128(switch (@typeInfo(@TypeOf(value))) {
1782 .comptime_int => @as(std.math.IntFittingRange(@min(value, -1), @max(0, value)), value),
1783 .int => |value_info| switch (value_info.signedness) {
1784 .signed => value,
1785 .unsigned => @as(@Type(.{ .int = .{ .signedness = .signed, .bits = value_info.bits + 1 } }), value),
1786 },
1787 else => comptime unreachable,
1788 });
1789}
1790
1791/// Write a single integer as LEB128 to the given writer.
1792pub fn writeLeb128(w: *Writer, value: anytype) Error!void {
1793 const value_info = @typeInfo(@TypeOf(value)).int;
1794 try w.writeMultipleOf7Leb128(@as(@Type(.{ .int = .{
1795 .signedness = value_info.signedness,
1796 .bits = std.mem.alignForwardAnyAlign(u16, value_info.bits, 7),
1797 } }), value));
1798}
1799
1800fn writeMultipleOf7Leb128(w: *Writer, value: anytype) Error!void {
1801 const value_info = @typeInfo(@TypeOf(value)).int;
1802 comptime assert(value_info.bits % 7 == 0);
1803 var remaining = value;
1804 while (true) {
1805 const buffer: []packed struct(u8) { bits: u7, more: bool } = @ptrCast(try w.writableSliceGreedy(1));
1806 for (buffer, 1..) |*byte, len| {
1807 const more = switch (value_info.signedness) {
1808 .signed => remaining >> 6 != remaining >> (value_info.bits - 1),
1809 .unsigned => remaining > std.math.maxInt(u7),
1810 };
1811 byte.* = if (@inComptime()) @typeInfo(@TypeOf(buffer)).pointer.child{
1812 .bits = @bitCast(@as(@Type(.{ .int = .{
1813 .signedness = value_info.signedness,
1814 .bits = 7,
1815 } }), @truncate(remaining))),
1816 .more = more,
1817 } else .{
1818 .bits = @bitCast(@as(@Type(.{ .int = .{
1819 .signedness = value_info.signedness,
1820 .bits = 7,
1821 } }), @truncate(remaining))),
1822 .more = more,
1823 };
1824 if (value_info.bits > 7) remaining >>= 7;
1825 if (!more) return w.advance(len);
1826 }
1827 w.advance(buffer.len);
1828 }
1829}
1830
1831test "printValue max_depth" {
1832 const Vec2 = struct {
1833 const SelfType = @This();
1834 x: f32,
1835 y: f32,
1836
1837 pub fn format(self: SelfType, w: *Writer) Error!void {
1838 return w.print("({d:.3},{d:.3})", .{ self.x, self.y });
1839 }
1840 };
1841 const E = enum {
1842 One,
1843 Two,
1844 Three,
1845 };
1846 const TU = union(enum) {
1847 const SelfType = @This();
1848 float: f32,
1849 int: u32,
1850 ptr: ?*SelfType,
1851 };
1852 const S = struct {
1853 const SelfType = @This();
1854 a: ?*SelfType,
1855 tu: TU,
1856 e: E,
1857 vec: Vec2,
1858 };
1859
1860 var inst = S{
1861 .a = null,
1862 .tu = TU{ .ptr = null },
1863 .e = E.Two,
1864 .vec = Vec2{ .x = 10.2, .y = 2.22 },
1865 };
1866 inst.a = &inst;
1867 inst.tu.ptr = &inst.tu;
1868
1869 var buf: [1000]u8 = undefined;
1870 var w: Writer = .fixed(&buf);
1871 try w.printValue("", .{}, inst, 0);
1872 try testing.expectEqualStrings(".{ ... }", w.buffered());
1873
1874 w = .fixed(&buf);
1875 try w.printValue("", .{}, inst, 1);
1876 try testing.expectEqualStrings(".{ .a = .{ ... }, .tu = .{ ... }, .e = .Two, .vec = .{ ... } }", w.buffered());
1877
1878 w = .fixed(&buf);
1879 try w.printValue("", .{}, inst, 2);
1880 try testing.expectEqualStrings(".{ .a = .{ .a = .{ ... }, .tu = .{ ... }, .e = .Two, .vec = .{ ... } }, .tu = .{ .ptr = .{ ... } }, .e = .Two, .vec = .{ .x = 10.2, .y = 2.22 } }", w.buffered());
1881
1882 w = .fixed(&buf);
1883 try w.printValue("", .{}, inst, 3);
1884 try testing.expectEqualStrings(".{ .a = .{ .a = .{ .a = .{ ... }, .tu = .{ ... }, .e = .Two, .vec = .{ ... } }, .tu = .{ .ptr = .{ ... } }, .e = .Two, .vec = .{ .x = 10.2, .y = 2.22 } }, .tu = .{ .ptr = .{ .ptr = .{ ... } } }, .e = .Two, .vec = .{ .x = 10.2, .y = 2.22 } }", w.buffered());
1885
1886 const vec: @Vector(4, i32) = .{ 1, 2, 3, 4 };
1887 w = .fixed(&buf);
1888 try w.printValue("", .{}, vec, 0);
1889 try testing.expectEqualStrings("{ ... }", w.buffered());
1890
1891 w = .fixed(&buf);
1892 try w.printValue("", .{}, vec, 1);
1893 try testing.expectEqualStrings("{ 1, 2, 3, 4 }", w.buffered());
1894}
1895
1896test printDuration {
1897 try testDurationCase("0ns", 0);
1898 try testDurationCase("1ns", 1);
1899 try testDurationCase("999ns", std.time.ns_per_us - 1);
1900 try testDurationCase("1us", std.time.ns_per_us);
1901 try testDurationCase("1.45us", 1450);
1902 try testDurationCase("1.5us", 3 * std.time.ns_per_us / 2);
1903 try testDurationCase("14.5us", 14500);
1904 try testDurationCase("145us", 145000);
1905 try testDurationCase("999.999us", std.time.ns_per_ms - 1);
1906 try testDurationCase("1ms", std.time.ns_per_ms + 1);
1907 try testDurationCase("1.5ms", 3 * std.time.ns_per_ms / 2);
1908 try testDurationCase("1.11ms", 1110000);
1909 try testDurationCase("1.111ms", 1111000);
1910 try testDurationCase("1.111ms", 1111100);
1911 try testDurationCase("999.999ms", std.time.ns_per_s - 1);
1912 try testDurationCase("1s", std.time.ns_per_s);
1913 try testDurationCase("59.999s", std.time.ns_per_min - 1);
1914 try testDurationCase("1m", std.time.ns_per_min);
1915 try testDurationCase("1h", std.time.ns_per_hour);
1916 try testDurationCase("1d", std.time.ns_per_day);
1917 try testDurationCase("1w", std.time.ns_per_week);
1918 try testDurationCase("1y", 365 * std.time.ns_per_day);
1919 try testDurationCase("1y52w23h59m59.999s", 730 * std.time.ns_per_day - 1); // 365d = 52w1
1920 try testDurationCase("1y1h1.001s", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + std.time.ns_per_ms);
1921 try testDurationCase("1y1h1s", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + 999 * std.time.ns_per_us);
1922 try testDurationCase("1y1h999.999us", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms - 1);
1923 try testDurationCase("1y1h1ms", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms);
1924 try testDurationCase("1y1h1ms", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms + 1);
1925 try testDurationCase("1y1m999ns", 365 * std.time.ns_per_day + std.time.ns_per_min + 999);
1926 try testDurationCase("584y49w23h34m33.709s", std.math.maxInt(u64));
1927
1928 try testing.expectFmt("=======0ns", "{D:=>10}", .{0});
1929 try testing.expectFmt("1ns=======", "{D:=<10}", .{1});
1930 try testing.expectFmt(" 999ns ", "{D:^10}", .{std.time.ns_per_us - 1});
1931}
1932
1933test printDurationSigned {
1934 try testDurationCaseSigned("0ns", 0);
1935 try testDurationCaseSigned("1ns", 1);
1936 try testDurationCaseSigned("-1ns", -(1));
1937 try testDurationCaseSigned("999ns", std.time.ns_per_us - 1);
1938 try testDurationCaseSigned("-999ns", -(std.time.ns_per_us - 1));
1939 try testDurationCaseSigned("1us", std.time.ns_per_us);
1940 try testDurationCaseSigned("-1us", -(std.time.ns_per_us));
1941 try testDurationCaseSigned("1.45us", 1450);
1942 try testDurationCaseSigned("-1.45us", -(1450));
1943 try testDurationCaseSigned("1.5us", 3 * std.time.ns_per_us / 2);
1944 try testDurationCaseSigned("-1.5us", -(3 * std.time.ns_per_us / 2));
1945 try testDurationCaseSigned("14.5us", 14500);
1946 try testDurationCaseSigned("-14.5us", -(14500));
1947 try testDurationCaseSigned("145us", 145000);
1948 try testDurationCaseSigned("-145us", -(145000));
1949 try testDurationCaseSigned("999.999us", std.time.ns_per_ms - 1);
1950 try testDurationCaseSigned("-999.999us", -(std.time.ns_per_ms - 1));
1951 try testDurationCaseSigned("1ms", std.time.ns_per_ms + 1);
1952 try testDurationCaseSigned("-1ms", -(std.time.ns_per_ms + 1));
1953 try testDurationCaseSigned("1.5ms", 3 * std.time.ns_per_ms / 2);
1954 try testDurationCaseSigned("-1.5ms", -(3 * std.time.ns_per_ms / 2));
1955 try testDurationCaseSigned("1.11ms", 1110000);
1956 try testDurationCaseSigned("-1.11ms", -(1110000));
1957 try testDurationCaseSigned("1.111ms", 1111000);
1958 try testDurationCaseSigned("-1.111ms", -(1111000));
1959 try testDurationCaseSigned("1.111ms", 1111100);
1960 try testDurationCaseSigned("-1.111ms", -(1111100));
1961 try testDurationCaseSigned("999.999ms", std.time.ns_per_s - 1);
1962 try testDurationCaseSigned("-999.999ms", -(std.time.ns_per_s - 1));
1963 try testDurationCaseSigned("1s", std.time.ns_per_s);
1964 try testDurationCaseSigned("-1s", -(std.time.ns_per_s));
1965 try testDurationCaseSigned("59.999s", std.time.ns_per_min - 1);
1966 try testDurationCaseSigned("-59.999s", -(std.time.ns_per_min - 1));
1967 try testDurationCaseSigned("1m", std.time.ns_per_min);
1968 try testDurationCaseSigned("-1m", -(std.time.ns_per_min));
1969 try testDurationCaseSigned("1h", std.time.ns_per_hour);
1970 try testDurationCaseSigned("-1h", -(std.time.ns_per_hour));
1971 try testDurationCaseSigned("1d", std.time.ns_per_day);
1972 try testDurationCaseSigned("-1d", -(std.time.ns_per_day));
1973 try testDurationCaseSigned("1w", std.time.ns_per_week);
1974 try testDurationCaseSigned("-1w", -(std.time.ns_per_week));
1975 try testDurationCaseSigned("1y", 365 * std.time.ns_per_day);
1976 try testDurationCaseSigned("-1y", -(365 * std.time.ns_per_day));
1977 try testDurationCaseSigned("1y52w23h59m59.999s", 730 * std.time.ns_per_day - 1); // 365d = 52w1d
1978 try testDurationCaseSigned("-1y52w23h59m59.999s", -(730 * std.time.ns_per_day - 1)); // 365d = 52w1d
1979 try testDurationCaseSigned("1y1h1.001s", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + std.time.ns_per_ms);
1980 try testDurationCaseSigned("-1y1h1.001s", -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + std.time.ns_per_ms));
1981 try testDurationCaseSigned("1y1h1s", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + 999 * std.time.ns_per_us);
1982 try testDurationCaseSigned("-1y1h1s", -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_s + 999 * std.time.ns_per_us));
1983 try testDurationCaseSigned("1y1h999.999us", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms - 1);
1984 try testDurationCaseSigned("-1y1h999.999us", -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms - 1));
1985 try testDurationCaseSigned("1y1h1ms", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms);
1986 try testDurationCaseSigned("-1y1h1ms", -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms));
1987 try testDurationCaseSigned("1y1h1ms", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms + 1);
1988 try testDurationCaseSigned("-1y1h1ms", -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms + 1));
1989 try testDurationCaseSigned("1y1m999ns", 365 * std.time.ns_per_day + std.time.ns_per_min + 999);
1990 try testDurationCaseSigned("-1y1m999ns", -(365 * std.time.ns_per_day + std.time.ns_per_min + 999));
1991 try testDurationCaseSigned("292y24w3d23h47m16.854s", std.math.maxInt(i64));
1992 try testDurationCaseSigned("-292y24w3d23h47m16.854s", std.math.minInt(i64) + 1);
1993 try testDurationCaseSigned("-292y24w3d23h47m16.854s", std.math.minInt(i64));
1994
1995 try testing.expectFmt("=======0ns", "{D:=>10}", .{0});
1996 try testing.expectFmt("1ns=======", "{D:=<10}", .{1});
1997 try testing.expectFmt("-1ns======", "{D:=<10}", .{-(1)});
1998 try testing.expectFmt(" -999ns ", "{D:^10}", .{-(std.time.ns_per_us - 1)});
1999}
2000
2001fn testDurationCase(expected: []const u8, input: u64) !void {
2002 var buf: [24]u8 = undefined;
2003 var w: Writer = .fixed(&buf);
2004 try w.printDurationUnsigned(input);
2005 try testing.expectEqualStrings(expected, w.buffered());
2006}
2007
2008fn testDurationCaseSigned(expected: []const u8, input: i64) !void {
2009 var buf: [24]u8 = undefined;
2010 var w: Writer = .fixed(&buf);
2011 try w.printDurationSigned(input);
2012 try testing.expectEqualStrings(expected, w.buffered());
2013}
2014
2015test printInt {
2016 try testPrintIntCase("-1", @as(i1, -1), 10, .lower, .{});
2017
2018 try testPrintIntCase("-101111000110000101001110", @as(i32, -12345678), 2, .lower, .{});
2019 try testPrintIntCase("-12345678", @as(i32, -12345678), 10, .lower, .{});
2020 try testPrintIntCase("-bc614e", @as(i32, -12345678), 16, .lower, .{});
2021 try testPrintIntCase("-BC614E", @as(i32, -12345678), 16, .upper, .{});
2022
2023 try testPrintIntCase("12345678", @as(u32, 12345678), 10, .upper, .{});
2024
2025 try testPrintIntCase(" 666", @as(u32, 666), 10, .lower, .{ .width = 6 });
2026 try testPrintIntCase(" 1234", @as(u32, 0x1234), 16, .lower, .{ .width = 6 });
2027 try testPrintIntCase("1234", @as(u32, 0x1234), 16, .lower, .{ .width = 1 });
2028
2029 try testPrintIntCase("+42", @as(i32, 42), 10, .lower, .{ .width = 3 });
2030 try testPrintIntCase("-42", @as(i32, -42), 10, .lower, .{ .width = 3 });
2031
2032 try testPrintIntCase("123456789123456789", @as(comptime_int, 123456789123456789), 10, .lower, .{});
2033}
2034
2035test "printFloat with comptime_float" {
2036 var buf: [20]u8 = undefined;
2037 var w: Writer = .fixed(&buf);
2038 try w.printFloat(@as(comptime_float, 1.0), std.fmt.Options.toNumber(.{}, .scientific, .lower));
2039 try testing.expectEqualStrings(w.buffered(), "1e0");
2040 try testing.expectFmt("1", "{}", .{1.0});
2041}
2042
2043fn testPrintIntCase(expected: []const u8, value: anytype, base: u8, case: std.fmt.Case, options: std.fmt.Options) !void {
2044 var buffer: [100]u8 = undefined;
2045 var w: Writer = .fixed(&buffer);
2046 try w.printInt(value, base, case, options);
2047 try testing.expectEqualStrings(expected, w.buffered());
2048}
2049
2050test printByteSize {
2051 try testing.expectFmt("file size: 42B\n", "file size: {B}\n", .{42});
2052 try testing.expectFmt("file size: 42B\n", "file size: {Bi}\n", .{42});
2053 try testing.expectFmt("file size: 63MB\n", "file size: {B}\n", .{63 * 1000 * 1000});
2054 try testing.expectFmt("file size: 63MiB\n", "file size: {Bi}\n", .{63 * 1024 * 1024});
2055 try testing.expectFmt("file size: 42B\n", "file size: {B:.2}\n", .{42});
2056 try testing.expectFmt("file size: 42B\n", "file size: {B:>9.2}\n", .{42});
2057 try testing.expectFmt("file size: 66.06MB\n", "file size: {B:.2}\n", .{63 * 1024 * 1024});
2058 try testing.expectFmt("file size: 60.08MiB\n", "file size: {Bi:.2}\n", .{63 * 1000 * 1000});
2059 try testing.expectFmt("file size: =66.06MB=\n", "file size: {B:=^9.2}\n", .{63 * 1024 * 1024});
2060 try testing.expectFmt("file size: 66.06MB\n", "file size: {B: >9.2}\n", .{63 * 1024 * 1024});
2061 try testing.expectFmt("file size: 66.06MB \n", "file size: {B: <9.2}\n", .{63 * 1024 * 1024});
2062 try testing.expectFmt("file size: 0.01844674407370955ZB\n", "file size: {B}\n", .{std.math.maxInt(u64)});
2063}
2064
2065test "bytes.hex" {
2066 const some_bytes = "\xCA\xFE\xBA\xBE";
2067 try testing.expectFmt("lowercase: cafebabe\n", "lowercase: {x}\n", .{some_bytes});
2068 try testing.expectFmt("uppercase: CAFEBABE\n", "uppercase: {X}\n", .{some_bytes});
2069 try testing.expectFmt("uppercase: CAFE\n", "uppercase: {X}\n", .{some_bytes[0..2]});
2070 try testing.expectFmt("lowercase: babe\n", "lowercase: {x}\n", .{some_bytes[2..]});
2071 const bytes_with_zeros = "\x00\x0E\xBA\xBE";
2072 try testing.expectFmt("lowercase: 000ebabe\n", "lowercase: {x}\n", .{bytes_with_zeros});
2073}
2074
2075test fixed {
2076 {
2077 var buf: [255]u8 = undefined;
2078 var w: Writer = .fixed(&buf);
2079 try w.print("{s}{s}!", .{ "Hello", "World" });
2080 try testing.expectEqualStrings("HelloWorld!", w.buffered());
2081 }
2082
2083 comptime {
2084 var buf: [255]u8 = undefined;
2085 var w: Writer = .fixed(&buf);
2086 try w.print("{s}{s}!", .{ "Hello", "World" });
2087 try testing.expectEqualStrings("HelloWorld!", w.buffered());
2088 }
2089}
2090
2091test "fixed output" {
2092 var buffer: [10]u8 = undefined;
2093 var w: Writer = .fixed(&buffer);
2094
2095 try w.writeAll("Hello");
2096 try testing.expect(std.mem.eql(u8, w.buffered(), "Hello"));
2097
2098 try w.writeAll("world");
2099 try testing.expect(std.mem.eql(u8, w.buffered(), "Helloworld"));
2100
2101 try testing.expectError(error.WriteFailed, w.writeAll("!"));
2102 try testing.expect(std.mem.eql(u8, w.buffered(), "Helloworld"));
2103
2104 w = .fixed(&buffer);
2105
2106 try testing.expect(w.buffered().len == 0);
2107
2108 try testing.expectError(error.WriteFailed, w.writeAll("Hello world!"));
2109 try testing.expect(std.mem.eql(u8, w.buffered(), "Hello worl"));
2110}
2111
2112test "writeSplat 0 len splat larger than capacity" {
2113 var buf: [8]u8 = undefined;
2114 var w: std.io.Writer = .fixed(&buf);
2115 const n = try w.writeSplat(&.{"something that overflows buf"}, 0);
2116 try testing.expectEqual(0, n);
2117}
2118
2119pub fn failingDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
2120 _ = w;
2121 _ = data;
2122 _ = splat;
2123 return error.WriteFailed;
2124}
2125
2126pub fn failingSendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize {
2127 _ = w;
2128 _ = file_reader;
2129 _ = limit;
2130 return error.WriteFailed;
2131}
2132
2133pub const Discarding = struct {
2134 count: u64,
2135 writer: Writer,
2136
2137 pub fn init(buffer: []u8) Discarding {
2138 return .{
2139 .count = 0,
2140 .writer = .{
2141 .vtable = &.{
2142 .drain = Discarding.drain,
2143 .sendFile = Discarding.sendFile,
2144 },
2145 .buffer = buffer,
2146 },
2147 };
2148 }
2149
2150 pub fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
2151 const d: *Discarding = @alignCast(@fieldParentPtr("writer", w));
2152 const slice = data[0 .. data.len - 1];
2153 const pattern = data[slice.len..];
2154 var written: usize = pattern.len * splat;
2155 for (slice) |bytes| written += bytes.len;
2156 d.count += w.end + written;
2157 w.end = 0;
2158 return written;
2159 }
2160
2161 pub fn sendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize {
2162 if (File.Handle == void) return error.Unimplemented;
2163 const d: *Discarding = @alignCast(@fieldParentPtr("writer", w));
2164 d.count += w.end;
2165 w.end = 0;
2166 if (file_reader.getSize()) |size| {
2167 const n = limit.minInt64(size - file_reader.pos);
2168 file_reader.seekBy(@intCast(n)) catch return error.Unimplemented;
2169 w.end = 0;
2170 d.count += n;
2171 return n;
2172 } else |_| {
2173 // Error is observable on `file_reader` instance, and it is better to
2174 // treat the file as a pipe.
2175 return error.Unimplemented;
2176 }
2177 }
2178};
2179
2180/// Removes the first `n` bytes from `buffer` by shifting buffer contents,
2181/// returning how many bytes are left after consuming the entire buffer, or
2182/// zero if the entire buffer was not consumed.
2183///
2184/// Useful for `VTable.drain` function implementations to implement partial
2185/// drains.
2186pub fn consume(w: *Writer, n: usize) usize {
2187 if (n < w.end) {
2188 const remaining = w.buffer[n..w.end];
2189 @memmove(w.buffer[0..remaining.len], remaining);
2190 w.end = remaining.len;
2191 return 0;
2192 }
2193 defer w.end = 0;
2194 return n - w.end;
2195}
2196
2197/// Shortcut for setting `end` to zero and returning zero. Equivalent to
2198/// calling `consume` with `end`.
2199pub fn consumeAll(w: *Writer) usize {
2200 w.end = 0;
2201 return 0;
2202}
2203
2204/// For use when the `Writer` implementation can cannot offer a more efficient
2205/// implementation than a basic read/write loop on the file.
2206pub fn unimplementedSendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize {
2207 _ = w;
2208 _ = file_reader;
2209 _ = limit;
2210 return error.Unimplemented;
2211}
2212
2213/// When this function is called it usually means the buffer got full, so it's
2214/// time to return an error. However, we still need to make sure all of the
2215/// available buffer has been filled. Also, it may be called from `flush` in
2216/// which case it should return successfully.
2217pub fn fixedDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
2218 if (data.len == 0) return 0;
2219 for (data[0 .. data.len - 1]) |bytes| {
2220 const dest = w.buffer[w.end..];
2221 const len = @min(bytes.len, dest.len);
2222 @memcpy(dest[0..len], bytes[0..len]);
2223 w.end += len;
2224 if (bytes.len > dest.len) return error.WriteFailed;
2225 }
2226 const pattern = data[data.len - 1];
2227 const dest = w.buffer[w.end..];
2228 switch (pattern.len) {
2229 0 => return w.end,
2230 1 => {
2231 assert(splat >= dest.len);
2232 @memset(dest, pattern[0]);
2233 w.end += dest.len;
2234 return error.WriteFailed;
2235 },
2236 else => {
2237 for (0..splat) |i| {
2238 const remaining = dest[i * pattern.len ..];
2239 const len = @min(pattern.len, remaining.len);
2240 @memcpy(remaining[0..len], pattern[0..len]);
2241 w.end += len;
2242 if (pattern.len > remaining.len) return error.WriteFailed;
2243 }
2244 unreachable;
2245 },
2246 }
2247}
2248
2249/// Provides a `Writer` implementation based on calling `Hasher.update`, sending
2250/// all data also to an underlying `Writer`.
2251///
2252/// When using this, the underlying writer is best unbuffered because all
2253/// writes are passed on directly to it.
2254///
2255/// This implementation makes suboptimal buffering decisions due to being
2256/// generic. A better solution will involve creating a writer for each hash
2257/// function, where the splat buffer can be tailored to the hash implementation
2258/// details.
2259pub fn Hashed(comptime Hasher: type) type {
2260 return struct {
2261 out: *Writer,
2262 hasher: Hasher,
2263 writer: Writer,
2264
2265 pub fn init(out: *Writer, buffer: []u8) @This() {
2266 return .initHasher(out, .{}, buffer);
2267 }
2268
2269 pub fn initHasher(out: *Writer, hasher: Hasher, buffer: []u8) @This() {
2270 return .{
2271 .out = out,
2272 .hasher = hasher,
2273 .writer = .{
2274 .buffer = buffer,
2275 .vtable = &.{ .drain = @This().drain },
2276 },
2277 };
2278 }
2279
2280 fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
2281 const this: *@This() = @alignCast(@fieldParentPtr("writer", w));
2282 const aux = w.buffered();
2283 const aux_n = try this.out.writeSplatHeader(aux, data, splat);
2284 if (aux_n < w.end) {
2285 this.hasher.update(w.buffer[0..aux_n]);
2286 const remaining = w.buffer[aux_n..w.end];
2287 @memmove(w.buffer[0..remaining.len], remaining);
2288 w.end = remaining.len;
2289 return 0;
2290 }
2291 this.hasher.update(aux);
2292 const n = aux_n - w.end;
2293 w.end = 0;
2294 var remaining: usize = n;
2295 for (data[0 .. data.len - 1]) |slice| {
2296 if (remaining <= slice.len) {
2297 this.hasher.update(slice[0..remaining]);
2298 return n;
2299 }
2300 remaining -= slice.len;
2301 this.hasher.update(slice);
2302 }
2303 const pattern = data[data.len - 1];
2304 assert(remaining == splat * pattern.len);
2305 switch (pattern.len) {
2306 0 => {
2307 assert(remaining == 0);
2308 },
2309 1 => {
2310 var buffer: [64]u8 = undefined;
2311 @memset(&buffer, pattern[0]);
2312 while (remaining > 0) {
2313 const update_len = @min(remaining, buffer.len);
2314 this.hasher.update(buffer[0..update_len]);
2315 remaining -= update_len;
2316 }
2317 },
2318 else => {
2319 while (remaining > 0) {
2320 const update_len = @min(remaining, pattern.len);
2321 this.hasher.update(pattern[0..update_len]);
2322 remaining -= update_len;
2323 }
2324 },
2325 }
2326 return n;
2327 }
2328 };
2329}
2330
2331/// Maintains `Writer` state such that it writes to the unused capacity of an
2332/// array list, filling it up completely before making a call through the
2333/// vtable, causing a resize. Consequently, the same, optimized, non-generic
2334/// machine code that uses `std.io.Reader`, such as formatted printing, takes
2335/// the hot paths when using this API.
2336///
2337/// When using this API, it is not necessary to call `flush`.
2338pub const Allocating = struct {
2339 allocator: Allocator,
2340 writer: Writer,
2341
2342 pub fn init(allocator: Allocator) Allocating {
2343 return .{
2344 .allocator = allocator,
2345 .writer = .{
2346 .buffer = &.{},
2347 .vtable = &vtable,
2348 },
2349 };
2350 }
2351
2352 pub fn initCapacity(allocator: Allocator, capacity: usize) error{OutOfMemory}!Allocating {
2353 return .{
2354 .allocator = allocator,
2355 .writer = .{
2356 .buffer = try allocator.alloc(u8, capacity),
2357 .vtable = &vtable,
2358 },
2359 };
2360 }
2361
2362 pub fn initOwnedSlice(allocator: Allocator, slice: []u8) Allocating {
2363 return .{
2364 .allocator = allocator,
2365 .writer = .{
2366 .buffer = slice,
2367 .vtable = &vtable,
2368 },
2369 };
2370 }
2371
2372 /// Replaces `array_list` with empty, taking ownership of the memory.
2373 pub fn fromArrayList(allocator: Allocator, array_list: *std.ArrayListUnmanaged(u8)) Allocating {
2374 defer array_list.* = .empty;
2375 return .{
2376 .allocator = allocator,
2377 .writer = .{
2378 .vtable = &vtable,
2379 .buffer = array_list.allocatedSlice(),
2380 .end = array_list.items.len,
2381 },
2382 };
2383 }
2384
2385 const vtable: VTable = .{
2386 .drain = Allocating.drain,
2387 .sendFile = Allocating.sendFile,
2388 .flush = noopFlush,
2389 };
2390
2391 pub fn deinit(a: *Allocating) void {
2392 a.allocator.free(a.writer.buffer);
2393 a.* = undefined;
2394 }
2395
2396 /// Returns an array list that takes ownership of the allocated memory.
2397 /// Resets the `Allocating` to an empty state.
2398 pub fn toArrayList(a: *Allocating) std.ArrayListUnmanaged(u8) {
2399 const w = &a.writer;
2400 const result: std.ArrayListUnmanaged(u8) = .{
2401 .items = w.buffer[0..w.end],
2402 .capacity = w.buffer.len,
2403 };
2404 w.buffer = &.{};
2405 w.end = 0;
2406 return result;
2407 }
2408
2409 pub fn toOwnedSlice(a: *Allocating) error{OutOfMemory}![]u8 {
2410 var list = a.toArrayList();
2411 return list.toOwnedSlice(a.allocator);
2412 }
2413
2414 pub fn toOwnedSliceSentinel(a: *Allocating, comptime sentinel: u8) error{OutOfMemory}![:sentinel]u8 {
2415 const gpa = a.allocator;
2416 var list = toArrayList(a);
2417 return list.toOwnedSliceSentinel(gpa, sentinel);
2418 }
2419
2420 pub fn getWritten(a: *Allocating) []u8 {
2421 return a.writer.buffered();
2422 }
2423
2424 pub fn shrinkRetainingCapacity(a: *Allocating, new_len: usize) void {
2425 a.writer.end = new_len;
2426 }
2427
2428 pub fn clearRetainingCapacity(a: *Allocating) void {
2429 a.shrinkRetainingCapacity(0);
2430 }
2431
2432 fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
2433 const a: *Allocating = @fieldParentPtr("writer", w);
2434 const gpa = a.allocator;
2435 const pattern = data[data.len - 1];
2436 const splat_len = pattern.len * splat;
2437 var list = a.toArrayList();
2438 defer setArrayList(a, list);
2439 const start_len = list.items.len;
2440 // Even if we append no data, this function needs to ensure there is more
2441 // capacity in the buffer to avoid infinite loop, hence the +1 in this loop.
2442 assert(data.len != 0);
2443 for (data) |bytes| {
2444 list.ensureUnusedCapacity(gpa, bytes.len + splat_len + 1) catch return error.WriteFailed;
2445 list.appendSliceAssumeCapacity(bytes);
2446 }
2447 if (splat == 0) {
2448 list.items.len -= pattern.len;
2449 } else switch (pattern.len) {
2450 0 => {},
2451 1 => list.appendNTimesAssumeCapacity(pattern[0], splat - 1),
2452 else => for (0..splat - 1) |_| list.appendSliceAssumeCapacity(pattern),
2453 }
2454 return list.items.len - start_len;
2455 }
2456
2457 fn sendFile(w: *Writer, file_reader: *File.Reader, limit: std.io.Limit) FileError!usize {
2458 if (File.Handle == void) return error.Unimplemented;
2459 const a: *Allocating = @fieldParentPtr("writer", w);
2460 const gpa = a.allocator;
2461 var list = a.toArrayList();
2462 defer setArrayList(a, list);
2463 const pos = file_reader.pos;
2464 const additional = if (file_reader.getSize()) |size| size - pos else |_| std.atomic.cache_line;
2465 list.ensureUnusedCapacity(gpa, limit.minInt64(additional)) catch return error.WriteFailed;
2466 const dest = limit.slice(list.unusedCapacitySlice());
2467 const n = file_reader.read(dest) catch |err| switch (err) {
2468 error.ReadFailed => return error.ReadFailed,
2469 error.EndOfStream => 0,
2470 };
2471 list.items.len += n;
2472 return n;
2473 }
2474
2475 fn setArrayList(a: *Allocating, list: std.ArrayListUnmanaged(u8)) void {
2476 a.writer.buffer = list.allocatedSlice();
2477 a.writer.end = list.items.len;
2478 }
2479
2480 test Allocating {
2481 var a: Allocating = .init(testing.allocator);
2482 defer a.deinit();
2483 const w = &a.writer;
2484
2485 const x: i32 = 42;
2486 const y: i32 = 1234;
2487 try w.print("x: {}\ny: {}\n", .{ x, y });
2488
2489 try testing.expectEqualSlices(u8, "x: 42\ny: 1234\n", a.getWritten());
2490 }
2491};
lib/std/io/change_detection_stream.zig deleted-55
...@@ -1,55 +0,0 @@
1const std = @import("../std.zig");
2const io = std.io;
3const mem = std.mem;
4const assert = std.debug.assert;
5
6/// Used to detect if the data written to a stream differs from a source buffer
7pub fn ChangeDetectionStream(comptime WriterType: type) type {
8 return struct {
9 const Self = @This();
10 pub const Error = WriterType.Error;
11 pub const Writer = io.GenericWriter(*Self, Error, write);
12
13 anything_changed: bool,
14 underlying_writer: WriterType,
15 source_index: usize,
16 source: []const u8,
17
18 pub fn writer(self: *Self) Writer {
19 return .{ .context = self };
20 }
21
22 fn write(self: *Self, bytes: []const u8) Error!usize {
23 if (!self.anything_changed) {
24 const end = self.source_index + bytes.len;
25 if (end > self.source.len) {
26 self.anything_changed = true;
27 } else {
28 const src_slice = self.source[self.source_index..end];
29 self.source_index += bytes.len;
30 if (!mem.eql(u8, bytes, src_slice)) {
31 self.anything_changed = true;
32 }
33 }
34 }
35
36 return self.underlying_writer.write(bytes);
37 }
38
39 pub fn changeDetected(self: *Self) bool {
40 return self.anything_changed or (self.source_index != self.source.len);
41 }
42 };
43}
44
45pub fn changeDetectionStream(
46 source: []const u8,
47 underlying_writer: anytype,
48) ChangeDetectionStream(@TypeOf(underlying_writer)) {
49 return ChangeDetectionStream(@TypeOf(underlying_writer)){
50 .anything_changed = false,
51 .underlying_writer = underlying_writer,
52 .source_index = 0,
53 .source = source,
54 };
55}
lib/std/io/find_byte_writer.zig deleted-40
...@@ -1,40 +0,0 @@
1const std = @import("../std.zig");
2const io = std.io;
3const assert = std.debug.assert;
4
5/// A Writer that returns whether the given character has been written to it.
6/// The contents are not written to anything.
7pub fn FindByteWriter(comptime UnderlyingWriter: type) type {
8 return struct {
9 const Self = @This();
10 pub const Error = UnderlyingWriter.Error;
11 pub const Writer = io.GenericWriter(*Self, Error, write);
12
13 underlying_writer: UnderlyingWriter,
14 byte_found: bool,
15 byte: u8,
16
17 pub fn writer(self: *Self) Writer {
18 return .{ .context = self };
19 }
20
21 fn write(self: *Self, bytes: []const u8) Error!usize {
22 if (!self.byte_found) {
23 self.byte_found = blk: {
24 for (bytes) |b|
25 if (b == self.byte) break :blk true;
26 break :blk false;
27 };
28 }
29 return self.underlying_writer.write(bytes);
30 }
31 };
32}
33
34pub fn findByteWriter(byte: u8, underlying_writer: anytype) FindByteWriter(@TypeOf(underlying_writer)) {
35 return FindByteWriter(@TypeOf(underlying_writer)){
36 .underlying_writer = underlying_writer,
37 .byte = byte,
38 .byte_found = false,
39 };
40}
lib/std/io/test.zig deleted-169
...@@ -1,169 +0,0 @@
1const std = @import("std");
2const io = std.io;
3const DefaultPrng = std.Random.DefaultPrng;
4const expect = std.testing.expect;
5const expectEqual = std.testing.expectEqual;
6const expectError = std.testing.expectError;
7const mem = std.mem;
8const fs = std.fs;
9const File = std.fs.File;
10const native_endian = @import("builtin").target.cpu.arch.endian();
11
12const tmpDir = std.testing.tmpDir;
13
14test "write a file, read it, then delete it" {
15 var tmp = tmpDir(.{});
16 defer tmp.cleanup();
17
18 var data: [1024]u8 = undefined;
19 var prng = DefaultPrng.init(std.testing.random_seed);
20 const random = prng.random();
21 random.bytes(data[0..]);
22 const tmp_file_name = "temp_test_file.txt";
23 {
24 var file = try tmp.dir.createFile(tmp_file_name, .{});
25 defer file.close();
26
27 var buf_stream = io.bufferedWriter(file.deprecatedWriter());
28 const st = buf_stream.writer();
29 try st.print("begin", .{});
30 try st.writeAll(data[0..]);
31 try st.print("end", .{});
32 try buf_stream.flush();
33 }
34
35 {
36 // Make sure the exclusive flag is honored.
37 try expectError(File.OpenError.PathAlreadyExists, tmp.dir.createFile(tmp_file_name, .{ .exclusive = true }));
38 }
39
40 {
41 var file = try tmp.dir.openFile(tmp_file_name, .{});
42 defer file.close();
43
44 const file_size = try file.getEndPos();
45 const expected_file_size: u64 = "begin".len + data.len + "end".len;
46 try expectEqual(expected_file_size, file_size);
47
48 var buf_stream = io.bufferedReader(file.deprecatedReader());
49 const st = buf_stream.reader();
50 const contents = try st.readAllAlloc(std.testing.allocator, 2 * 1024);
51 defer std.testing.allocator.free(contents);
52
53 try expect(mem.eql(u8, contents[0.."begin".len], "begin"));
54 try expect(mem.eql(u8, contents["begin".len .. contents.len - "end".len], &data));
55 try expect(mem.eql(u8, contents[contents.len - "end".len ..], "end"));
56 }
57 try tmp.dir.deleteFile(tmp_file_name);
58}
59
60test "BitStreams with File Stream" {
61 var tmp = tmpDir(.{});
62 defer tmp.cleanup();
63
64 const tmp_file_name = "temp_test_file.txt";
65 {
66 var file = try tmp.dir.createFile(tmp_file_name, .{});
67 defer file.close();
68
69 var bit_stream = io.bitWriter(native_endian, file.deprecatedWriter());
70
71 try bit_stream.writeBits(@as(u2, 1), 1);
72 try bit_stream.writeBits(@as(u5, 2), 2);
73 try bit_stream.writeBits(@as(u128, 3), 3);
74 try bit_stream.writeBits(@as(u8, 4), 4);
75 try bit_stream.writeBits(@as(u9, 5), 5);
76 try bit_stream.writeBits(@as(u1, 1), 1);
77 try bit_stream.flushBits();
78 }
79 {
80 var file = try tmp.dir.openFile(tmp_file_name, .{});
81 defer file.close();
82
83 var bit_stream = io.bitReader(native_endian, file.deprecatedReader());
84
85 var out_bits: u16 = undefined;
86
87 try expect(1 == try bit_stream.readBits(u2, 1, &out_bits));
88 try expect(out_bits == 1);
89 try expect(2 == try bit_stream.readBits(u5, 2, &out_bits));
90 try expect(out_bits == 2);
91 try expect(3 == try bit_stream.readBits(u128, 3, &out_bits));
92 try expect(out_bits == 3);
93 try expect(4 == try bit_stream.readBits(u8, 4, &out_bits));
94 try expect(out_bits == 4);
95 try expect(5 == try bit_stream.readBits(u9, 5, &out_bits));
96 try expect(out_bits == 5);
97 try expect(1 == try bit_stream.readBits(u1, 1, &out_bits));
98 try expect(out_bits == 1);
99
100 try expectError(error.EndOfStream, bit_stream.readBitsNoEof(u1, 1));
101 }
102 try tmp.dir.deleteFile(tmp_file_name);
103}
104
105test "File seek ops" {
106 var tmp = tmpDir(.{});
107 defer tmp.cleanup();
108
109 const tmp_file_name = "temp_test_file.txt";
110 var file = try tmp.dir.createFile(tmp_file_name, .{});
111 defer file.close();
112
113 try file.writeAll(&([_]u8{0x55} ** 8192));
114
115 // Seek to the end
116 try file.seekFromEnd(0);
117 try expect((try file.getPos()) == try file.getEndPos());
118 // Negative delta
119 try file.seekBy(-4096);
120 try expect((try file.getPos()) == 4096);
121 // Positive delta
122 try file.seekBy(10);
123 try expect((try file.getPos()) == 4106);
124 // Absolute position
125 try file.seekTo(1234);
126 try expect((try file.getPos()) == 1234);
127}
128
129test "setEndPos" {
130 var tmp = tmpDir(.{});
131 defer tmp.cleanup();
132
133 const tmp_file_name = "temp_test_file.txt";
134 var file = try tmp.dir.createFile(tmp_file_name, .{});
135 defer file.close();
136
137 // Verify that the file size changes and the file offset is not moved
138 try std.testing.expect((try file.getEndPos()) == 0);
139 try std.testing.expect((try file.getPos()) == 0);
140 try file.setEndPos(8192);
141 try std.testing.expect((try file.getEndPos()) == 8192);
142 try std.testing.expect((try file.getPos()) == 0);
143 try file.seekTo(100);
144 try file.setEndPos(4096);
145 try std.testing.expect((try file.getEndPos()) == 4096);
146 try std.testing.expect((try file.getPos()) == 100);
147 try file.setEndPos(0);
148 try std.testing.expect((try file.getEndPos()) == 0);
149 try std.testing.expect((try file.getPos()) == 100);
150}
151
152test "updateTimes" {
153 var tmp = tmpDir(.{});
154 defer tmp.cleanup();
155
156 const tmp_file_name = "just_a_temporary_file.txt";
157 var file = try tmp.dir.createFile(tmp_file_name, .{ .read = true });
158 defer file.close();
159
160 const stat_old = try file.stat();
161 // Set atime and mtime to 5s before
162 try file.updateTimes(
163 stat_old.atime - 5 * std.time.ns_per_s,
164 stat_old.mtime - 5 * std.time.ns_per_s,
165 );
166 const stat_new = try file.stat();
167 try expect(stat_new.atime < stat_old.atime);
168 try expect(stat_new.mtime < stat_old.mtime);
169}
lib/std/io/tty.zig deleted-138
...@@ -1,138 +0,0 @@
1const std = @import("std");
2const builtin = @import("builtin");
3const File = std.fs.File;
4const process = std.process;
5const windows = std.os.windows;
6const native_os = builtin.os.tag;
7
8/// Deprecated in favor of `Config.detect`.
9pub fn detectConfig(file: File) Config {
10 return .detect(file);
11}
12
13pub const Color = enum {
14 black,
15 red,
16 green,
17 yellow,
18 blue,
19 magenta,
20 cyan,
21 white,
22 bright_black,
23 bright_red,
24 bright_green,
25 bright_yellow,
26 bright_blue,
27 bright_magenta,
28 bright_cyan,
29 bright_white,
30 dim,
31 bold,
32 reset,
33};
34
35/// Provides simple functionality for manipulating the terminal in some way,
36/// such as coloring text, etc.
37pub const Config = union(enum) {
38 no_color,
39 escape_codes,
40 windows_api: if (native_os == .windows) WindowsContext else void,
41
42 /// Detect suitable TTY configuration options for the given file (commonly stdout/stderr).
43 /// This includes feature checks for ANSI escape codes and the Windows console API, as well as
44 /// respecting the `NO_COLOR` and `CLICOLOR_FORCE` environment variables to override the default.
45 /// Will attempt to enable ANSI escape code support if necessary/possible.
46 pub fn detect(file: File) Config {
47 const force_color: ?bool = if (builtin.os.tag == .wasi)
48 null // wasi does not support environment variables
49 else if (process.hasNonEmptyEnvVarConstant("NO_COLOR"))
50 false
51 else if (process.hasNonEmptyEnvVarConstant("CLICOLOR_FORCE"))
52 true
53 else
54 null;
55
56 if (force_color == false) return .no_color;
57
58 if (file.getOrEnableAnsiEscapeSupport()) return .escape_codes;
59
60 if (native_os == .windows and file.isTty()) {
61 var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined;
62 if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) == windows.FALSE) {
63 return if (force_color == true) .escape_codes else .no_color;
64 }
65 return .{ .windows_api = .{
66 .handle = file.handle,
67 .reset_attributes = info.wAttributes,
68 } };
69 }
70
71 return if (force_color == true) .escape_codes else .no_color;
72 }
73
74 pub const WindowsContext = struct {
75 handle: File.Handle,
76 reset_attributes: u16,
77 };
78
79 pub const SetColorError = std.os.windows.SetConsoleTextAttributeError || std.io.Writer.Error;
80
81 pub fn setColor(conf: Config, w: *std.io.Writer, color: Color) SetColorError!void {
82 nosuspend switch (conf) {
83 .no_color => return,
84 .escape_codes => {
85 const color_string = switch (color) {
86 .black => "\x1b[30m",
87 .red => "\x1b[31m",
88 .green => "\x1b[32m",
89 .yellow => "\x1b[33m",
90 .blue => "\x1b[34m",
91 .magenta => "\x1b[35m",
92 .cyan => "\x1b[36m",
93 .white => "\x1b[37m",
94 .bright_black => "\x1b[90m",
95 .bright_red => "\x1b[91m",
96 .bright_green => "\x1b[92m",
97 .bright_yellow => "\x1b[93m",
98 .bright_blue => "\x1b[94m",
99 .bright_magenta => "\x1b[95m",
100 .bright_cyan => "\x1b[96m",
101 .bright_white => "\x1b[97m",
102 .bold => "\x1b[1m",
103 .dim => "\x1b[2m",
104 .reset => "\x1b[0m",
105 };
106 try w.writeAll(color_string);
107 },
108 .windows_api => |ctx| if (native_os == .windows) {
109 const attributes = switch (color) {
110 .black => 0,
111 .red => windows.FOREGROUND_RED,
112 .green => windows.FOREGROUND_GREEN,
113 .yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN,
114 .blue => windows.FOREGROUND_BLUE,
115 .magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE,
116 .cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE,
117 .white => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE,
118 .bright_black => windows.FOREGROUND_INTENSITY,
119 .bright_red => windows.FOREGROUND_RED | windows.FOREGROUND_INTENSITY,
120 .bright_green => windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY,
121 .bright_yellow => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_INTENSITY,
122 .bright_blue => windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
123 .bright_magenta => windows.FOREGROUND_RED | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
124 .bright_cyan => windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
125 .bright_white, .bold => windows.FOREGROUND_RED | windows.FOREGROUND_GREEN | windows.FOREGROUND_BLUE | windows.FOREGROUND_INTENSITY,
126 // "dim" is not supported using basic character attributes, but let's still make it do *something*.
127 // This matches the old behavior of TTY.Color before the bright variants were added.
128 .dim => windows.FOREGROUND_INTENSITY,
129 .reset => ctx.reset_attributes,
130 };
131 try w.flush();
132 try windows.SetConsoleTextAttribute(ctx.handle, attributes);
133 } else {
134 unreachable;
135 },
136 };
137 }
138};
lib/std/log.zig+5-2
...@@ -136,8 +136,11 @@ pub fn defaultLogEnabled(comptime message_level: Level) bool {...@@ -136,8 +136,11 @@ pub fn defaultLogEnabled(comptime message_level: Level) bool {
136 return comptime logEnabled(message_level, default_log_scope);136 return comptime logEnabled(message_level, default_log_scope);
137}137}
138138
139/// The default implementation for the log function, custom log functions may139/// The default implementation for the log function. Custom log functions may
140/// forward log messages to this function.140/// forward log messages to this function.
141///
142/// Uses a 64-byte buffer for formatted printing which is flushed before this
143/// function returns.
141pub fn defaultLog(144pub fn defaultLog(
142 comptime message_level: Level,145 comptime message_level: Level,
143 comptime scope: @Type(.enum_literal),146 comptime scope: @Type(.enum_literal),
...@@ -146,7 +149,7 @@ pub fn defaultLog(...@@ -146,7 +149,7 @@ pub fn defaultLog(
146) void {149) void {
147 const level_txt = comptime message_level.asText();150 const level_txt = comptime message_level.asText();
148 const prefix2 = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): ";151 const prefix2 = if (scope == .default) ": " else "(" ++ @tagName(scope) ++ "): ";
149 var buffer: [32]u8 = undefined;152 var buffer: [64]u8 = undefined;
150 const stderr = std.debug.lockStderrWriter(&buffer);153 const stderr = std.debug.lockStderrWriter(&buffer);
151 defer std.debug.unlockStderrWriter();154 defer std.debug.unlockStderrWriter();
152 nosuspend stderr.print(level_txt ++ prefix2 ++ format ++ "\n", args) catch return;155 nosuspend stderr.print(level_txt ++ prefix2 ++ format ++ "\n", args) catch return;
lib/std/std.zig+3-1
...@@ -25,6 +25,7 @@ pub const EnumMap = enums.EnumMap;...@@ -25,6 +25,7 @@ pub const EnumMap = enums.EnumMap;
25pub const EnumSet = enums.EnumSet;25pub const EnumSet = enums.EnumSet;
26pub const HashMap = hash_map.HashMap;26pub const HashMap = hash_map.HashMap;
27pub const HashMapUnmanaged = hash_map.HashMapUnmanaged;27pub const HashMapUnmanaged = hash_map.HashMapUnmanaged;
28pub const Io = @import("Io.zig");
28pub const MultiArrayList = @import("multi_array_list.zig").MultiArrayList;29pub const MultiArrayList = @import("multi_array_list.zig").MultiArrayList;
29pub const PriorityQueue = @import("priority_queue.zig").PriorityQueue;30pub const PriorityQueue = @import("priority_queue.zig").PriorityQueue;
30pub const PriorityDequeue = @import("priority_dequeue.zig").PriorityDequeue;31pub const PriorityDequeue = @import("priority_dequeue.zig").PriorityDequeue;
...@@ -65,7 +66,8 @@ pub const hash = @import("hash.zig");...@@ -65,7 +66,8 @@ pub const hash = @import("hash.zig");
65pub const hash_map = @import("hash_map.zig");66pub const hash_map = @import("hash_map.zig");
66pub const heap = @import("heap.zig");67pub const heap = @import("heap.zig");
67pub const http = @import("http.zig");68pub const http = @import("http.zig");
68pub const io = @import("io.zig");69/// Deprecated
70pub const io = Io;
69pub const json = @import("json.zig");71pub const json = @import("json.zig");
70pub const leb = @import("leb128.zig");72pub const leb = @import("leb128.zig");
71pub const log = @import("log.zig");73pub const log = @import("log.zig");