authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-10 10:28:32-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-07-11 01:16:27+02:00
log5360968e03525be4d312ca61a7ba2dcb7890ec42
tree61a6e784902998cc3f59a05adb3251a78db5ee09
parent43fba5ea83849ec901bb2cd4f98bd0222f51f7f6

std: rename `io` to `Io` in preparation

This commit is non-breaking. std.io is deprecated in favor of std.Io, in preparation for that namespace becoming an interface.

51 files changed, 7744 insertions(+), 7742 deletions(-)

CMakeLists.txt+12-12
...@@ -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
...@@ -449,18 +461,6 @@ set(ZIG_STAGE2_SOURCES...@@ -449,18 +461,6 @@ set(ZIG_STAGE2_SOURCES
449 lib/std/hash_map.zig461 lib/std/hash_map.zig
450 lib/std/heap.zig462 lib/std/heap.zig
451 lib/std/heap/arena_allocator.zig463 lib/std/heap/arena_allocator.zig
452 lib/std/io.zig
453 lib/std/io/Reader.zig
454 lib/std/io/Writer.zig
455 lib/std/io/buffered_atomic_file.zig
456 lib/std/io/buffered_writer.zig
457 lib/std/io/change_detection_stream.zig
458 lib/std/io/counting_reader.zig
459 lib/std/io/counting_writer.zig
460 lib/std/io/find_byte_writer.zig
461 lib/std/io/fixed_buffer_stream.zig
462 lib/std/io/limited_reader.zig
463 lib/std/io/seekable_stream.zig
464 lib/std/json.zig464 lib/std/json.zig
465 lib/std/json/stringify.zig465 lib/std/json/stringify.zig
466 lib/std/leb128.zig466 lib/std/leb128.zig
lib/std/Io.zig created+884
...@@ -0,0 +1,884 @@
1const std = @import("std.zig");
2const builtin = @import("builtin");
3const root = @import("root");
4const c = std.c;
5const is_windows = builtin.os.tag == .windows;
6const windows = std.os.windows;
7const posix = std.posix;
8const math = std.math;
9const assert = std.debug.assert;
10const fs = std.fs;
11const mem = std.mem;
12const meta = std.meta;
13const File = std.fs.File;
14const Allocator = std.mem.Allocator;
15const Alignment = std.mem.Alignment;
16
17pub const Limit = enum(usize) {
18 nothing = 0,
19 unlimited = std.math.maxInt(usize),
20 _,
21
22 /// `std.math.maxInt(usize)` is interpreted to mean `.unlimited`.
23 pub fn limited(n: usize) Limit {
24 return @enumFromInt(n);
25 }
26
27 /// Any value grater than `std.math.maxInt(usize)` is interpreted to mean
28 /// `.unlimited`.
29 pub fn limited64(n: u64) Limit {
30 return @enumFromInt(@min(n, std.math.maxInt(usize)));
31 }
32
33 pub fn countVec(data: []const []const u8) Limit {
34 var total: usize = 0;
35 for (data) |d| total += d.len;
36 return .limited(total);
37 }
38
39 pub fn min(a: Limit, b: Limit) Limit {
40 return @enumFromInt(@min(@intFromEnum(a), @intFromEnum(b)));
41 }
42
43 pub fn minInt(l: Limit, n: usize) usize {
44 return @min(n, @intFromEnum(l));
45 }
46
47 pub fn minInt64(l: Limit, n: u64) usize {
48 return @min(n, @intFromEnum(l));
49 }
50
51 pub fn slice(l: Limit, s: []u8) []u8 {
52 return s[0..l.minInt(s.len)];
53 }
54
55 pub fn sliceConst(l: Limit, s: []const u8) []const u8 {
56 return s[0..l.minInt(s.len)];
57 }
58
59 pub fn toInt(l: Limit) ?usize {
60 return switch (l) {
61 else => @intFromEnum(l),
62 .unlimited => null,
63 };
64 }
65
66 /// Reduces a slice to account for the limit, leaving room for one extra
67 /// byte above the limit, allowing for the use case of differentiating
68 /// between end-of-stream and reaching the limit.
69 pub fn slice1(l: Limit, non_empty_buffer: []u8) []u8 {
70 assert(non_empty_buffer.len >= 1);
71 return non_empty_buffer[0..@min(@intFromEnum(l) +| 1, non_empty_buffer.len)];
72 }
73
74 pub fn nonzero(l: Limit) bool {
75 return @intFromEnum(l) > 0;
76 }
77
78 /// Return a new limit reduced by `amount` or return `null` indicating
79 /// limit would be exceeded.
80 pub fn subtract(l: Limit, amount: usize) ?Limit {
81 if (l == .unlimited) return .unlimited;
82 if (amount > @intFromEnum(l)) return null;
83 return @enumFromInt(@intFromEnum(l) - amount);
84 }
85};
86
87pub const Reader = @import("Io/Reader.zig");
88pub const Writer = @import("Io/Writer.zig");
89
90/// Deprecated in favor of `Reader`.
91pub fn GenericReader(
92 comptime Context: type,
93 comptime ReadError: type,
94 /// Returns the number of bytes read. It may be less than buffer.len.
95 /// If the number of bytes read is 0, it means end of stream.
96 /// End of stream is not an error condition.
97 comptime readFn: fn (context: Context, buffer: []u8) ReadError!usize,
98) type {
99 return struct {
100 context: Context,
101
102 pub const Error = ReadError;
103 pub const NoEofError = ReadError || error{
104 EndOfStream,
105 };
106
107 pub inline fn read(self: Self, buffer: []u8) Error!usize {
108 return readFn(self.context, buffer);
109 }
110
111 pub inline fn readAll(self: Self, buffer: []u8) Error!usize {
112 return @errorCast(self.any().readAll(buffer));
113 }
114
115 pub inline fn readAtLeast(self: Self, buffer: []u8, len: usize) Error!usize {
116 return @errorCast(self.any().readAtLeast(buffer, len));
117 }
118
119 pub inline fn readNoEof(self: Self, buf: []u8) NoEofError!void {
120 return @errorCast(self.any().readNoEof(buf));
121 }
122
123 pub inline fn readAllArrayList(
124 self: Self,
125 array_list: *std.ArrayList(u8),
126 max_append_size: usize,
127 ) (error{StreamTooLong} || Allocator.Error || Error)!void {
128 return @errorCast(self.any().readAllArrayList(array_list, max_append_size));
129 }
130
131 pub inline fn readAllArrayListAligned(
132 self: Self,
133 comptime alignment: ?Alignment,
134 array_list: *std.ArrayListAligned(u8, alignment),
135 max_append_size: usize,
136 ) (error{StreamTooLong} || Allocator.Error || Error)!void {
137 return @errorCast(self.any().readAllArrayListAligned(
138 alignment,
139 array_list,
140 max_append_size,
141 ));
142 }
143
144 pub inline fn readAllAlloc(
145 self: Self,
146 allocator: Allocator,
147 max_size: usize,
148 ) (Error || Allocator.Error || error{StreamTooLong})![]u8 {
149 return @errorCast(self.any().readAllAlloc(allocator, max_size));
150 }
151
152 pub inline fn readUntilDelimiterArrayList(
153 self: Self,
154 array_list: *std.ArrayList(u8),
155 delimiter: u8,
156 max_size: usize,
157 ) (NoEofError || Allocator.Error || error{StreamTooLong})!void {
158 return @errorCast(self.any().readUntilDelimiterArrayList(
159 array_list,
160 delimiter,
161 max_size,
162 ));
163 }
164
165 pub inline fn readUntilDelimiterAlloc(
166 self: Self,
167 allocator: Allocator,
168 delimiter: u8,
169 max_size: usize,
170 ) (NoEofError || Allocator.Error || error{StreamTooLong})![]u8 {
171 return @errorCast(self.any().readUntilDelimiterAlloc(
172 allocator,
173 delimiter,
174 max_size,
175 ));
176 }
177
178 pub inline fn readUntilDelimiter(
179 self: Self,
180 buf: []u8,
181 delimiter: u8,
182 ) (NoEofError || error{StreamTooLong})![]u8 {
183 return @errorCast(self.any().readUntilDelimiter(buf, delimiter));
184 }
185
186 pub inline fn readUntilDelimiterOrEofAlloc(
187 self: Self,
188 allocator: Allocator,
189 delimiter: u8,
190 max_size: usize,
191 ) (Error || Allocator.Error || error{StreamTooLong})!?[]u8 {
192 return @errorCast(self.any().readUntilDelimiterOrEofAlloc(
193 allocator,
194 delimiter,
195 max_size,
196 ));
197 }
198
199 pub inline fn readUntilDelimiterOrEof(
200 self: Self,
201 buf: []u8,
202 delimiter: u8,
203 ) (Error || error{StreamTooLong})!?[]u8 {
204 return @errorCast(self.any().readUntilDelimiterOrEof(buf, delimiter));
205 }
206
207 pub inline fn streamUntilDelimiter(
208 self: Self,
209 writer: anytype,
210 delimiter: u8,
211 optional_max_size: ?usize,
212 ) (NoEofError || error{StreamTooLong} || @TypeOf(writer).Error)!void {
213 return @errorCast(self.any().streamUntilDelimiter(
214 writer,
215 delimiter,
216 optional_max_size,
217 ));
218 }
219
220 pub inline fn skipUntilDelimiterOrEof(self: Self, delimiter: u8) Error!void {
221 return @errorCast(self.any().skipUntilDelimiterOrEof(delimiter));
222 }
223
224 pub inline fn readByte(self: Self) NoEofError!u8 {
225 return @errorCast(self.any().readByte());
226 }
227
228 pub inline fn readByteSigned(self: Self) NoEofError!i8 {
229 return @errorCast(self.any().readByteSigned());
230 }
231
232 pub inline fn readBytesNoEof(
233 self: Self,
234 comptime num_bytes: usize,
235 ) NoEofError![num_bytes]u8 {
236 return @errorCast(self.any().readBytesNoEof(num_bytes));
237 }
238
239 pub inline fn readIntoBoundedBytes(
240 self: Self,
241 comptime num_bytes: usize,
242 bounded: *std.BoundedArray(u8, num_bytes),
243 ) Error!void {
244 return @errorCast(self.any().readIntoBoundedBytes(num_bytes, bounded));
245 }
246
247 pub inline fn readBoundedBytes(
248 self: Self,
249 comptime num_bytes: usize,
250 ) Error!std.BoundedArray(u8, num_bytes) {
251 return @errorCast(self.any().readBoundedBytes(num_bytes));
252 }
253
254 pub inline fn readInt(self: Self, comptime T: type, endian: std.builtin.Endian) NoEofError!T {
255 return @errorCast(self.any().readInt(T, endian));
256 }
257
258 pub inline fn readVarInt(
259 self: Self,
260 comptime ReturnType: type,
261 endian: std.builtin.Endian,
262 size: usize,
263 ) NoEofError!ReturnType {
264 return @errorCast(self.any().readVarInt(ReturnType, endian, size));
265 }
266
267 pub const SkipBytesOptions = AnyReader.SkipBytesOptions;
268
269 pub inline fn skipBytes(
270 self: Self,
271 num_bytes: u64,
272 comptime options: SkipBytesOptions,
273 ) NoEofError!void {
274 return @errorCast(self.any().skipBytes(num_bytes, options));
275 }
276
277 pub inline fn isBytes(self: Self, slice: []const u8) NoEofError!bool {
278 return @errorCast(self.any().isBytes(slice));
279 }
280
281 pub inline fn readStruct(self: Self, comptime T: type) NoEofError!T {
282 return @errorCast(self.any().readStruct(T));
283 }
284
285 pub inline fn readStructEndian(self: Self, comptime T: type, endian: std.builtin.Endian) NoEofError!T {
286 return @errorCast(self.any().readStructEndian(T, endian));
287 }
288
289 pub const ReadEnumError = NoEofError || error{
290 /// An integer was read, but it did not match any of the tags in the supplied enum.
291 InvalidValue,
292 };
293
294 pub inline fn readEnum(
295 self: Self,
296 comptime Enum: type,
297 endian: std.builtin.Endian,
298 ) ReadEnumError!Enum {
299 return @errorCast(self.any().readEnum(Enum, endian));
300 }
301
302 pub inline fn any(self: *const Self) AnyReader {
303 return .{
304 .context = @ptrCast(&self.context),
305 .readFn = typeErasedReadFn,
306 };
307 }
308
309 const Self = @This();
310
311 fn typeErasedReadFn(context: *const anyopaque, buffer: []u8) anyerror!usize {
312 const ptr: *const Context = @alignCast(@ptrCast(context));
313 return readFn(ptr.*, buffer);
314 }
315 };
316}
317
318/// Deprecated in favor of `Writer`.
319pub fn GenericWriter(
320 comptime Context: type,
321 comptime WriteError: type,
322 comptime writeFn: fn (context: Context, bytes: []const u8) WriteError!usize,
323) type {
324 return struct {
325 context: Context,
326
327 const Self = @This();
328 pub const Error = WriteError;
329
330 pub inline fn write(self: Self, bytes: []const u8) Error!usize {
331 return writeFn(self.context, bytes);
332 }
333
334 pub inline fn writeAll(self: Self, bytes: []const u8) Error!void {
335 return @errorCast(self.any().writeAll(bytes));
336 }
337
338 pub inline fn print(self: Self, comptime format: []const u8, args: anytype) Error!void {
339 return @errorCast(self.any().print(format, args));
340 }
341
342 pub inline fn writeByte(self: Self, byte: u8) Error!void {
343 return @errorCast(self.any().writeByte(byte));
344 }
345
346 pub inline fn writeByteNTimes(self: Self, byte: u8, n: usize) Error!void {
347 return @errorCast(self.any().writeByteNTimes(byte, n));
348 }
349
350 pub inline fn writeBytesNTimes(self: Self, bytes: []const u8, n: usize) Error!void {
351 return @errorCast(self.any().writeBytesNTimes(bytes, n));
352 }
353
354 pub inline fn writeInt(self: Self, comptime T: type, value: T, endian: std.builtin.Endian) Error!void {
355 return @errorCast(self.any().writeInt(T, value, endian));
356 }
357
358 pub inline fn writeStruct(self: Self, value: anytype) Error!void {
359 return @errorCast(self.any().writeStruct(value));
360 }
361
362 pub inline fn writeStructEndian(self: Self, value: anytype, endian: std.builtin.Endian) Error!void {
363 return @errorCast(self.any().writeStructEndian(value, endian));
364 }
365
366 pub inline fn any(self: *const Self) AnyWriter {
367 return .{
368 .context = @ptrCast(&self.context),
369 .writeFn = typeErasedWriteFn,
370 };
371 }
372
373 fn typeErasedWriteFn(context: *const anyopaque, bytes: []const u8) anyerror!usize {
374 const ptr: *const Context = @alignCast(@ptrCast(context));
375 return writeFn(ptr.*, bytes);
376 }
377
378 /// Helper for bridging to the new `Writer` API while upgrading.
379 pub fn adaptToNewApi(self: *const Self) Adapter {
380 return .{
381 .derp_writer = self.*,
382 .new_interface = .{
383 .buffer = &.{},
384 .vtable = &.{ .drain = Adapter.drain },
385 },
386 };
387 }
388
389 pub const Adapter = struct {
390 derp_writer: Self,
391 new_interface: Writer,
392 err: ?Error = null,
393
394 fn drain(w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize {
395 _ = splat;
396 const a: *@This() = @fieldParentPtr("new_interface", w);
397 return a.derp_writer.write(data[0]) catch |err| {
398 a.err = err;
399 return error.WriteFailed;
400 };
401 }
402 };
403 };
404}
405
406/// Deprecated in favor of `Reader`.
407pub const AnyReader = @import("Io/DeprecatedReader.zig");
408/// Deprecated in favor of `Writer`.
409pub const AnyWriter = @import("Io/DeprecatedWriter.zig");
410
411pub const SeekableStream = @import("Io/seekable_stream.zig").SeekableStream;
412
413pub const BufferedWriter = @import("Io/buffered_writer.zig").BufferedWriter;
414pub const bufferedWriter = @import("Io/buffered_writer.zig").bufferedWriter;
415
416pub const BufferedReader = @import("Io/buffered_reader.zig").BufferedReader;
417pub const bufferedReader = @import("Io/buffered_reader.zig").bufferedReader;
418pub const bufferedReaderSize = @import("Io/buffered_reader.zig").bufferedReaderSize;
419
420pub const FixedBufferStream = @import("Io/fixed_buffer_stream.zig").FixedBufferStream;
421pub const fixedBufferStream = @import("Io/fixed_buffer_stream.zig").fixedBufferStream;
422
423pub const CWriter = @import("Io/c_writer.zig").CWriter;
424pub const cWriter = @import("Io/c_writer.zig").cWriter;
425
426pub const LimitedReader = @import("Io/limited_reader.zig").LimitedReader;
427pub const limitedReader = @import("Io/limited_reader.zig").limitedReader;
428
429pub const CountingWriter = @import("Io/counting_writer.zig").CountingWriter;
430pub const countingWriter = @import("Io/counting_writer.zig").countingWriter;
431pub const CountingReader = @import("Io/counting_reader.zig").CountingReader;
432pub const countingReader = @import("Io/counting_reader.zig").countingReader;
433
434pub const MultiWriter = @import("Io/multi_writer.zig").MultiWriter;
435pub const multiWriter = @import("Io/multi_writer.zig").multiWriter;
436
437pub const BitReader = @import("Io/bit_reader.zig").BitReader;
438pub const bitReader = @import("Io/bit_reader.zig").bitReader;
439
440pub const BitWriter = @import("Io/bit_writer.zig").BitWriter;
441pub const bitWriter = @import("Io/bit_writer.zig").bitWriter;
442
443pub const ChangeDetectionStream = @import("Io/change_detection_stream.zig").ChangeDetectionStream;
444pub const changeDetectionStream = @import("Io/change_detection_stream.zig").changeDetectionStream;
445
446pub const FindByteWriter = @import("Io/find_byte_writer.zig").FindByteWriter;
447pub const findByteWriter = @import("Io/find_byte_writer.zig").findByteWriter;
448
449pub const BufferedAtomicFile = @import("Io/buffered_atomic_file.zig").BufferedAtomicFile;
450
451pub const StreamSource = @import("Io/stream_source.zig").StreamSource;
452
453pub const tty = @import("Io/tty.zig");
454
455/// A Writer that doesn't write to anything.
456pub const null_writer: NullWriter = .{ .context = {} };
457
458pub const NullWriter = GenericWriter(void, error{}, dummyWrite);
459fn dummyWrite(context: void, data: []const u8) error{}!usize {
460 _ = context;
461 return data.len;
462}
463
464test null_writer {
465 null_writer.writeAll("yay" ** 10) catch |err| switch (err) {};
466}
467
468pub fn poll(
469 allocator: Allocator,
470 comptime StreamEnum: type,
471 files: PollFiles(StreamEnum),
472) Poller(StreamEnum) {
473 const enum_fields = @typeInfo(StreamEnum).@"enum".fields;
474 var result: Poller(StreamEnum) = undefined;
475
476 if (is_windows) result.windows = .{
477 .first_read_done = false,
478 .overlapped = [1]windows.OVERLAPPED{
479 mem.zeroes(windows.OVERLAPPED),
480 } ** enum_fields.len,
481 .small_bufs = undefined,
482 .active = .{
483 .count = 0,
484 .handles_buf = undefined,
485 .stream_map = undefined,
486 },
487 };
488
489 inline for (0..enum_fields.len) |i| {
490 result.fifos[i] = .{
491 .allocator = allocator,
492 .buf = &.{},
493 .head = 0,
494 .count = 0,
495 };
496 if (is_windows) {
497 result.windows.active.handles_buf[i] = @field(files, enum_fields[i].name).handle;
498 } else {
499 result.poll_fds[i] = .{
500 .fd = @field(files, enum_fields[i].name).handle,
501 .events = posix.POLL.IN,
502 .revents = undefined,
503 };
504 }
505 }
506 return result;
507}
508
509pub const PollFifo = std.fifo.LinearFifo(u8, .Dynamic);
510
511pub fn Poller(comptime StreamEnum: type) type {
512 return struct {
513 const enum_fields = @typeInfo(StreamEnum).@"enum".fields;
514 const PollFd = if (is_windows) void else posix.pollfd;
515
516 fifos: [enum_fields.len]PollFifo,
517 poll_fds: [enum_fields.len]PollFd,
518 windows: if (is_windows) struct {
519 first_read_done: bool,
520 overlapped: [enum_fields.len]windows.OVERLAPPED,
521 small_bufs: [enum_fields.len][128]u8,
522 active: struct {
523 count: math.IntFittingRange(0, enum_fields.len),
524 handles_buf: [enum_fields.len]windows.HANDLE,
525 stream_map: [enum_fields.len]StreamEnum,
526
527 pub fn removeAt(self: *@This(), index: u32) void {
528 std.debug.assert(index < self.count);
529 for (index + 1..self.count) |i| {
530 self.handles_buf[i - 1] = self.handles_buf[i];
531 self.stream_map[i - 1] = self.stream_map[i];
532 }
533 self.count -= 1;
534 }
535 },
536 } else void,
537
538 const Self = @This();
539
540 pub fn deinit(self: *Self) void {
541 if (is_windows) {
542 // cancel any pending IO to prevent clobbering OVERLAPPED value
543 for (self.windows.active.handles_buf[0..self.windows.active.count]) |h| {
544 _ = windows.kernel32.CancelIo(h);
545 }
546 }
547 inline for (&self.fifos) |*q| q.deinit();
548 self.* = undefined;
549 }
550
551 pub fn poll(self: *Self) !bool {
552 if (is_windows) {
553 return pollWindows(self, null);
554 } else {
555 return pollPosix(self, null);
556 }
557 }
558
559 pub fn pollTimeout(self: *Self, nanoseconds: u64) !bool {
560 if (is_windows) {
561 return pollWindows(self, nanoseconds);
562 } else {
563 return pollPosix(self, nanoseconds);
564 }
565 }
566
567 pub inline fn fifo(self: *Self, comptime which: StreamEnum) *PollFifo {
568 return &self.fifos[@intFromEnum(which)];
569 }
570
571 fn pollWindows(self: *Self, nanoseconds: ?u64) !bool {
572 const bump_amt = 512;
573
574 if (!self.windows.first_read_done) {
575 var already_read_data = false;
576 for (0..enum_fields.len) |i| {
577 const handle = self.windows.active.handles_buf[i];
578 switch (try windowsAsyncReadToFifoAndQueueSmallRead(
579 handle,
580 &self.windows.overlapped[i],
581 &self.fifos[i],
582 &self.windows.small_bufs[i],
583 bump_amt,
584 )) {
585 .populated, .empty => |state| {
586 if (state == .populated) already_read_data = true;
587 self.windows.active.handles_buf[self.windows.active.count] = handle;
588 self.windows.active.stream_map[self.windows.active.count] = @as(StreamEnum, @enumFromInt(i));
589 self.windows.active.count += 1;
590 },
591 .closed => {}, // don't add to the wait_objects list
592 .closed_populated => {
593 // don't add to the wait_objects list, but we did already get data
594 already_read_data = true;
595 },
596 }
597 }
598 self.windows.first_read_done = true;
599 if (already_read_data) return true;
600 }
601
602 while (true) {
603 if (self.windows.active.count == 0) return false;
604
605 const status = windows.kernel32.WaitForMultipleObjects(
606 self.windows.active.count,
607 &self.windows.active.handles_buf,
608 0,
609 if (nanoseconds) |ns|
610 @min(std.math.cast(u32, ns / std.time.ns_per_ms) orelse (windows.INFINITE - 1), windows.INFINITE - 1)
611 else
612 windows.INFINITE,
613 );
614 if (status == windows.WAIT_FAILED)
615 return windows.unexpectedError(windows.GetLastError());
616 if (status == windows.WAIT_TIMEOUT)
617 return true;
618
619 if (status < windows.WAIT_OBJECT_0 or status > windows.WAIT_OBJECT_0 + enum_fields.len - 1)
620 unreachable;
621
622 const active_idx = status - windows.WAIT_OBJECT_0;
623
624 const stream_idx = @intFromEnum(self.windows.active.stream_map[active_idx]);
625 const handle = self.windows.active.handles_buf[active_idx];
626
627 const overlapped = &self.windows.overlapped[stream_idx];
628 const stream_fifo = &self.fifos[stream_idx];
629 const small_buf = &self.windows.small_bufs[stream_idx];
630
631 const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) {
632 .success => |n| n,
633 .closed => {
634 self.windows.active.removeAt(active_idx);
635 continue;
636 },
637 .aborted => unreachable,
638 };
639 try stream_fifo.write(small_buf[0..num_bytes_read]);
640
641 switch (try windowsAsyncReadToFifoAndQueueSmallRead(
642 handle,
643 overlapped,
644 stream_fifo,
645 small_buf,
646 bump_amt,
647 )) {
648 .empty => {}, // irrelevant, we already got data from the small buffer
649 .populated => {},
650 .closed,
651 .closed_populated, // identical, since we already got data from the small buffer
652 => self.windows.active.removeAt(active_idx),
653 }
654 return true;
655 }
656 }
657
658 fn pollPosix(self: *Self, nanoseconds: ?u64) !bool {
659 // We ask for ensureUnusedCapacity with this much extra space. This
660 // has more of an effect on small reads because once the reads
661 // start to get larger the amount of space an ArrayList will
662 // allocate grows exponentially.
663 const bump_amt = 512;
664
665 const err_mask = posix.POLL.ERR | posix.POLL.NVAL | posix.POLL.HUP;
666
667 const events_len = try posix.poll(&self.poll_fds, if (nanoseconds) |ns|
668 std.math.cast(i32, ns / std.time.ns_per_ms) orelse std.math.maxInt(i32)
669 else
670 -1);
671 if (events_len == 0) {
672 for (self.poll_fds) |poll_fd| {
673 if (poll_fd.fd != -1) return true;
674 } else return false;
675 }
676
677 var keep_polling = false;
678 inline for (&self.poll_fds, &self.fifos) |*poll_fd, *q| {
679 // Try reading whatever is available before checking the error
680 // conditions.
681 // It's still possible to read after a POLL.HUP is received,
682 // always check if there's some data waiting to be read first.
683 if (poll_fd.revents & posix.POLL.IN != 0) {
684 const buf = try q.writableWithSize(bump_amt);
685 const amt = posix.read(poll_fd.fd, buf) catch |err| switch (err) {
686 error.BrokenPipe => 0, // Handle the same as EOF.
687 else => |e| return e,
688 };
689 q.update(amt);
690 if (amt == 0) {
691 // Remove the fd when the EOF condition is met.
692 poll_fd.fd = -1;
693 } else {
694 keep_polling = true;
695 }
696 } else if (poll_fd.revents & err_mask != 0) {
697 // Exclude the fds that signaled an error.
698 poll_fd.fd = -1;
699 } else if (poll_fd.fd != -1) {
700 keep_polling = true;
701 }
702 }
703 return keep_polling;
704 }
705 };
706}
707
708/// The `ReadFile` docuementation states that `lpNumberOfBytesRead` does not have a meaningful
709/// result when using overlapped I/O, but also that it cannot be `null` on Windows 7. For
710/// compatibility, we point it to this dummy variables, which we never otherwise access.
711/// See: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile
712var win_dummy_bytes_read: u32 = undefined;
713
714/// Read as much data as possible from `handle` with `overlapped`, and write it to the FIFO. Before
715/// returning, queue a read into `small_buf` so that `WaitForMultipleObjects` returns when more data
716/// is available. `handle` must have no pending asynchronous operation.
717fn windowsAsyncReadToFifoAndQueueSmallRead(
718 handle: windows.HANDLE,
719 overlapped: *windows.OVERLAPPED,
720 fifo: *PollFifo,
721 small_buf: *[128]u8,
722 bump_amt: usize,
723) !enum { empty, populated, closed_populated, closed } {
724 var read_any_data = false;
725 while (true) {
726 const fifo_read_pending = while (true) {
727 const buf = try fifo.writableWithSize(bump_amt);
728 const buf_len = math.cast(u32, buf.len) orelse math.maxInt(u32);
729
730 if (0 == windows.kernel32.ReadFile(
731 handle,
732 buf.ptr,
733 buf_len,
734 &win_dummy_bytes_read,
735 overlapped,
736 )) switch (windows.GetLastError()) {
737 .IO_PENDING => break true,
738 .BROKEN_PIPE => return if (read_any_data) .closed_populated else .closed,
739 else => |err| return windows.unexpectedError(err),
740 };
741
742 const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) {
743 .success => |n| n,
744 .closed => return if (read_any_data) .closed_populated else .closed,
745 .aborted => unreachable,
746 };
747
748 read_any_data = true;
749 fifo.update(num_bytes_read);
750
751 if (num_bytes_read == buf_len) {
752 // We filled the buffer, so there's probably more data available.
753 continue;
754 } else {
755 // We didn't fill the buffer, so assume we're out of data.
756 // There is no pending read.
757 break false;
758 }
759 };
760
761 if (fifo_read_pending) cancel_read: {
762 // Cancel the pending read into the FIFO.
763 _ = windows.kernel32.CancelIo(handle);
764
765 // We have to wait for the handle to be signalled, i.e. for the cancellation to complete.
766 switch (windows.kernel32.WaitForSingleObject(handle, windows.INFINITE)) {
767 windows.WAIT_OBJECT_0 => {},
768 windows.WAIT_FAILED => return windows.unexpectedError(windows.GetLastError()),
769 else => unreachable,
770 }
771
772 // If it completed before we canceled, make sure to tell the FIFO!
773 const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, true)) {
774 .success => |n| n,
775 .closed => return if (read_any_data) .closed_populated else .closed,
776 .aborted => break :cancel_read,
777 };
778 read_any_data = true;
779 fifo.update(num_bytes_read);
780 }
781
782 // Try to queue the 1-byte read.
783 if (0 == windows.kernel32.ReadFile(
784 handle,
785 small_buf,
786 small_buf.len,
787 &win_dummy_bytes_read,
788 overlapped,
789 )) switch (windows.GetLastError()) {
790 .IO_PENDING => {
791 // 1-byte read pending as intended
792 return if (read_any_data) .populated else .empty;
793 },
794 .BROKEN_PIPE => return if (read_any_data) .closed_populated else .closed,
795 else => |err| return windows.unexpectedError(err),
796 };
797
798 // We got data back this time. Write it to the FIFO and run the main loop again.
799 const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) {
800 .success => |n| n,
801 .closed => return if (read_any_data) .closed_populated else .closed,
802 .aborted => unreachable,
803 };
804 try fifo.write(small_buf[0..num_bytes_read]);
805 read_any_data = true;
806 }
807}
808
809/// Simple wrapper around `GetOverlappedResult` to determine the result of a `ReadFile` operation.
810/// If `!allow_aborted`, then `aborted` is never returned (`OPERATION_ABORTED` is considered unexpected).
811///
812/// The `ReadFile` documentation states that the number of bytes read by an overlapped `ReadFile` must be determined using `GetOverlappedResult`, even if the
813/// operation immediately returns data:
814/// "Use NULL for [lpNumberOfBytesRead] if this is an asynchronous operation to avoid potentially
815/// erroneous results."
816/// "If `hFile` was opened with `FILE_FLAG_OVERLAPPED`, the following conditions are in effect: [...]
817/// The lpNumberOfBytesRead parameter should be set to NULL. Use the GetOverlappedResult function to
818/// get the actual number of bytes read."
819/// See: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile
820fn windowsGetReadResult(
821 handle: windows.HANDLE,
822 overlapped: *windows.OVERLAPPED,
823 allow_aborted: bool,
824) !union(enum) {
825 success: u32,
826 closed,
827 aborted,
828} {
829 var num_bytes_read: u32 = undefined;
830 if (0 == windows.kernel32.GetOverlappedResult(
831 handle,
832 overlapped,
833 &num_bytes_read,
834 0,
835 )) switch (windows.GetLastError()) {
836 .BROKEN_PIPE => return .closed,
837 .OPERATION_ABORTED => |err| if (allow_aborted) {
838 return .aborted;
839 } else {
840 return windows.unexpectedError(err);
841 },
842 else => |err| return windows.unexpectedError(err),
843 };
844 return .{ .success = num_bytes_read };
845}
846
847/// Given an enum, returns a struct with fields of that enum, each field
848/// representing an I/O stream for polling.
849pub fn PollFiles(comptime StreamEnum: type) type {
850 const enum_fields = @typeInfo(StreamEnum).@"enum".fields;
851 var struct_fields: [enum_fields.len]std.builtin.Type.StructField = undefined;
852 for (&struct_fields, enum_fields) |*struct_field, enum_field| {
853 struct_field.* = .{
854 .name = enum_field.name,
855 .type = fs.File,
856 .default_value_ptr = null,
857 .is_comptime = false,
858 .alignment = @alignOf(fs.File),
859 };
860 }
861 return @Type(.{ .@"struct" = .{
862 .layout = .auto,
863 .fields = &struct_fields,
864 .decls = &.{},
865 .is_tuple = false,
866 } });
867}
868
869test {
870 _ = Reader;
871 _ = Writer;
872 _ = @import("Io/bit_reader.zig");
873 _ = @import("Io/bit_writer.zig");
874 _ = @import("Io/buffered_atomic_file.zig");
875 _ = @import("Io/buffered_reader.zig");
876 _ = @import("Io/buffered_writer.zig");
877 _ = @import("Io/c_writer.zig");
878 _ = @import("Io/counting_writer.zig");
879 _ = @import("Io/counting_reader.zig");
880 _ = @import("Io/fixed_buffer_stream.zig");
881 _ = @import("Io/seekable_stream.zig");
882 _ = @import("Io/stream_source.zig");
883 _ = @import("Io/test.zig");
884}
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+1731
...@@ -0,0 +1,1731 @@
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/// See also:
844/// * `streamDelimiterEnding`
845/// * `streamDelimiterLimit`
846pub fn streamDelimiter(r: *Reader, w: *Writer, delimiter: u8) StreamError!usize {
847 const n = streamDelimiterLimit(r, w, delimiter, .unlimited) catch |err| switch (err) {
848 error.StreamTooLong => unreachable, // unlimited is passed
849 else => |e| return e,
850 };
851 if (r.seek == r.end) return error.EndOfStream;
852 return n;
853}
854
855/// Appends to `w` contents by reading from the stream until `delimiter` is found.
856/// Does not write the delimiter itself.
857///
858/// Returns number of bytes streamed, which may be zero. End of stream can be
859/// detected by checking if the next byte in the stream is the delimiter.
860///
861/// See also:
862/// * `streamDelimiter`
863/// * `streamDelimiterLimit`
864pub fn streamDelimiterEnding(
865 r: *Reader,
866 w: *Writer,
867 delimiter: u8,
868) StreamRemainingError!usize {
869 return streamDelimiterLimit(r, w, delimiter, .unlimited) catch |err| switch (err) {
870 error.StreamTooLong => unreachable, // unlimited is passed
871 else => |e| return e,
872 };
873}
874
875pub const StreamDelimiterLimitError = error{
876 ReadFailed,
877 WriteFailed,
878 /// The delimiter was not found within the limit.
879 StreamTooLong,
880};
881
882/// Appends to `w` contents by reading from the stream until `delimiter` is found.
883/// Does not write the delimiter itself.
884///
885/// Returns number of bytes streamed, which may be zero. End of stream can be
886/// detected by checking if the next byte in the stream is the delimiter.
887pub fn streamDelimiterLimit(
888 r: *Reader,
889 w: *Writer,
890 delimiter: u8,
891 limit: Limit,
892) StreamDelimiterLimitError!usize {
893 var remaining = @intFromEnum(limit);
894 while (remaining != 0) {
895 const available = Limit.limited(remaining).slice(r.peekGreedy(1) catch |err| switch (err) {
896 error.ReadFailed => return error.ReadFailed,
897 error.EndOfStream => return @intFromEnum(limit) - remaining,
898 });
899 if (std.mem.indexOfScalar(u8, available, delimiter)) |delimiter_index| {
900 try w.writeAll(available[0..delimiter_index]);
901 r.toss(delimiter_index);
902 remaining -= delimiter_index;
903 return @intFromEnum(limit) - remaining;
904 }
905 try w.writeAll(available);
906 r.toss(available.len);
907 remaining -= available.len;
908 }
909 return error.StreamTooLong;
910}
911
912/// Reads from the stream until specified byte is found, discarding all data,
913/// including the delimiter.
914///
915/// Returns number of bytes discarded, or `error.EndOfStream` if the delimiter
916/// is not found.
917///
918/// See also:
919/// * `discardDelimiterExclusive`
920/// * `discardDelimiterLimit`
921pub fn discardDelimiterInclusive(r: *Reader, delimiter: u8) Error!usize {
922 const n = discardDelimiterLimit(r, delimiter, .unlimited) catch |err| switch (err) {
923 error.StreamTooLong => unreachable, // unlimited is passed
924 else => |e| return e,
925 };
926 if (r.seek == r.end) return error.EndOfStream;
927 assert(r.buffer[r.seek] == delimiter);
928 toss(r, 1);
929 return n + 1;
930}
931
932/// Reads from the stream until specified byte is found, discarding all data,
933/// excluding the delimiter.
934///
935/// Returns the number of bytes discarded.
936///
937/// Succeeds if stream ends before delimiter found. End of stream can be
938/// detected by checking if the delimiter is buffered.
939///
940/// See also:
941/// * `discardDelimiterInclusive`
942/// * `discardDelimiterLimit`
943pub fn discardDelimiterExclusive(r: *Reader, delimiter: u8) ShortError!usize {
944 return discardDelimiterLimit(r, delimiter, .unlimited) catch |err| switch (err) {
945 error.StreamTooLong => unreachable, // unlimited is passed
946 else => |e| return e,
947 };
948}
949
950pub const DiscardDelimiterLimitError = error{
951 ReadFailed,
952 /// The delimiter was not found within the limit.
953 StreamTooLong,
954};
955
956/// Reads from the stream until specified byte is found, discarding all data,
957/// excluding the delimiter.
958///
959/// Returns the number of bytes discarded.
960///
961/// Succeeds if stream ends before delimiter found. End of stream can be
962/// detected by checking if the delimiter is buffered.
963pub fn discardDelimiterLimit(r: *Reader, delimiter: u8, limit: Limit) DiscardDelimiterLimitError!usize {
964 var remaining = @intFromEnum(limit);
965 while (remaining != 0) {
966 const available = Limit.limited(remaining).slice(r.peekGreedy(1) catch |err| switch (err) {
967 error.ReadFailed => return error.ReadFailed,
968 error.EndOfStream => return @intFromEnum(limit) - remaining,
969 });
970 if (std.mem.indexOfScalar(u8, available, delimiter)) |delimiter_index| {
971 r.toss(delimiter_index);
972 remaining -= delimiter_index;
973 return @intFromEnum(limit) - remaining;
974 }
975 r.toss(available.len);
976 remaining -= available.len;
977 }
978 return error.StreamTooLong;
979}
980
981/// Fills the buffer such that it contains at least `n` bytes, without
982/// advancing the seek position.
983///
984/// Returns `error.EndOfStream` if and only if there are fewer than `n` bytes
985/// remaining.
986///
987/// Asserts buffer capacity is at least `n`.
988pub fn fill(r: *Reader, n: usize) Error!void {
989 assert(n <= r.buffer.len);
990 if (r.seek + n <= r.end) {
991 @branchHint(.likely);
992 return;
993 }
994 if (r.seek + n <= r.buffer.len) while (true) {
995 const end_cap = r.buffer[r.end..];
996 var writer: Writer = .fixed(end_cap);
997 r.end += r.vtable.stream(r, &writer, .limited(end_cap.len)) catch |err| switch (err) {
998 error.WriteFailed => unreachable,
999 else => |e| return e,
1000 };
1001 if (r.seek + n <= r.end) return;
1002 };
1003 if (r.vtable.stream == &endingStream) {
1004 // Protect the `@constCast` of `fixed`.
1005 return error.EndOfStream;
1006 }
1007 rebaseCapacity(r, n);
1008 var writer: Writer = .{
1009 .buffer = r.buffer,
1010 .vtable = &.{ .drain = Writer.fixedDrain },
1011 };
1012 while (r.end < r.seek + n) {
1013 writer.end = r.end;
1014 r.end += r.vtable.stream(r, &writer, .limited(r.buffer.len - r.end)) catch |err| switch (err) {
1015 error.WriteFailed => unreachable,
1016 error.ReadFailed, error.EndOfStream => |e| return e,
1017 };
1018 }
1019}
1020
1021/// Without advancing the seek position, does exactly one underlying read, filling the buffer as
1022/// much as possible. This may result in zero bytes added to the buffer, which is not an end of
1023/// stream condition. End of stream is communicated via returning `error.EndOfStream`.
1024///
1025/// Asserts buffer capacity is at least 1.
1026pub fn fillMore(r: *Reader) Error!void {
1027 rebaseCapacity(r, 1);
1028 var writer: Writer = .{
1029 .buffer = r.buffer,
1030 .end = r.end,
1031 .vtable = &.{ .drain = Writer.fixedDrain },
1032 };
1033 r.end += r.vtable.stream(r, &writer, .limited(r.buffer.len - r.end)) catch |err| switch (err) {
1034 error.WriteFailed => unreachable,
1035 else => |e| return e,
1036 };
1037}
1038
1039/// Returns the next byte from the stream or returns `error.EndOfStream`.
1040///
1041/// Does not advance the seek position.
1042///
1043/// Asserts the buffer capacity is nonzero.
1044pub fn peekByte(r: *Reader) Error!u8 {
1045 const buffer = r.buffer[0..r.end];
1046 const seek = r.seek;
1047 if (seek < buffer.len) {
1048 @branchHint(.likely);
1049 return buffer[seek];
1050 }
1051 try fill(r, 1);
1052 return r.buffer[r.seek];
1053}
1054
1055/// Reads 1 byte from the stream or returns `error.EndOfStream`.
1056///
1057/// Asserts the buffer capacity is nonzero.
1058pub fn takeByte(r: *Reader) Error!u8 {
1059 const result = try peekByte(r);
1060 r.seek += 1;
1061 return result;
1062}
1063
1064/// Same as `takeByte` except the returned byte is signed.
1065pub fn takeByteSigned(r: *Reader) Error!i8 {
1066 return @bitCast(try r.takeByte());
1067}
1068
1069/// Asserts the buffer was initialized with a capacity at least `@bitSizeOf(T) / 8`.
1070pub inline fn takeInt(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T {
1071 const n = @divExact(@typeInfo(T).int.bits, 8);
1072 return std.mem.readInt(T, try r.takeArray(n), endian);
1073}
1074
1075/// Asserts the buffer was initialized with a capacity at least `n`.
1076pub fn takeVarInt(r: *Reader, comptime Int: type, endian: std.builtin.Endian, n: usize) Error!Int {
1077 assert(n <= @sizeOf(Int));
1078 return std.mem.readVarInt(Int, try r.take(n), endian);
1079}
1080
1081/// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`.
1082///
1083/// Advances the seek position.
1084///
1085/// See also:
1086/// * `peekStruct`
1087/// * `takeStructEndian`
1088pub fn takeStruct(r: *Reader, comptime T: type) Error!*align(1) T {
1089 // Only extern and packed structs have defined in-memory layout.
1090 comptime assert(@typeInfo(T).@"struct".layout != .auto);
1091 return @ptrCast(try r.takeArray(@sizeOf(T)));
1092}
1093
1094/// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`.
1095///
1096/// Does not advance the seek position.
1097///
1098/// See also:
1099/// * `takeStruct`
1100/// * `peekStructEndian`
1101pub fn peekStruct(r: *Reader, comptime T: type) Error!*align(1) T {
1102 // Only extern and packed structs have defined in-memory layout.
1103 comptime assert(@typeInfo(T).@"struct".layout != .auto);
1104 return @ptrCast(try r.peekArray(@sizeOf(T)));
1105}
1106
1107/// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`.
1108///
1109/// This function is inline to avoid referencing `std.mem.byteSwapAllFields`
1110/// when `endian` is comptime-known and matches the host endianness.
1111///
1112/// See also:
1113/// * `takeStruct`
1114/// * `peekStructEndian`
1115pub inline fn takeStructEndian(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T {
1116 var res = (try r.takeStruct(T)).*;
1117 if (native_endian != endian) std.mem.byteSwapAllFields(T, &res);
1118 return res;
1119}
1120
1121/// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`.
1122///
1123/// This function is inline to avoid referencing `std.mem.byteSwapAllFields`
1124/// when `endian` is comptime-known and matches the host endianness.
1125///
1126/// See also:
1127/// * `takeStructEndian`
1128/// * `peekStruct`
1129pub inline fn peekStructEndian(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T {
1130 var res = (try r.peekStruct(T)).*;
1131 if (native_endian != endian) std.mem.byteSwapAllFields(T, &res);
1132 return res;
1133}
1134
1135pub const TakeEnumError = Error || error{InvalidEnumTag};
1136
1137/// Reads an integer with the same size as the given enum's tag type. If the
1138/// integer matches an enum tag, casts the integer to the enum tag and returns
1139/// it. Otherwise, returns `error.InvalidEnumTag`.
1140///
1141/// Asserts the buffer was initialized with a capacity at least `@sizeOf(Enum)`.
1142pub fn takeEnum(r: *Reader, comptime Enum: type, endian: std.builtin.Endian) TakeEnumError!Enum {
1143 const Tag = @typeInfo(Enum).@"enum".tag_type;
1144 const int = try r.takeInt(Tag, endian);
1145 return std.meta.intToEnum(Enum, int);
1146}
1147
1148/// Reads an integer with the same size as the given nonexhaustive enum's tag type.
1149///
1150/// Asserts the buffer was initialized with a capacity at least `@sizeOf(Enum)`.
1151pub fn takeEnumNonexhaustive(r: *Reader, comptime Enum: type, endian: std.builtin.Endian) Error!Enum {
1152 const info = @typeInfo(Enum).@"enum";
1153 comptime assert(!info.is_exhaustive);
1154 comptime assert(@bitSizeOf(info.tag_type) == @sizeOf(info.tag_type) * 8);
1155 return takeEnum(r, Enum, endian) catch |err| switch (err) {
1156 error.InvalidEnumTag => unreachable,
1157 else => |e| return e,
1158 };
1159}
1160
1161pub const TakeLeb128Error = Error || error{Overflow};
1162
1163/// Read a single LEB128 value as type T, or `error.Overflow` if the value cannot fit.
1164pub fn takeLeb128(r: *Reader, comptime Result: type) TakeLeb128Error!Result {
1165 const result_info = @typeInfo(Result).int;
1166 return std.math.cast(Result, try r.takeMultipleOf7Leb128(@Type(.{ .int = .{
1167 .signedness = result_info.signedness,
1168 .bits = std.mem.alignForwardAnyAlign(u16, result_info.bits, 7),
1169 } }))) orelse error.Overflow;
1170}
1171
1172pub fn expandTotalCapacity(r: *Reader, allocator: Allocator, n: usize) Allocator.Error!void {
1173 if (n <= r.buffer.len) return;
1174 if (r.seek > 0) rebase(r);
1175 var list: ArrayList(u8) = .{
1176 .items = r.buffer[0..r.end],
1177 .capacity = r.buffer.len,
1178 };
1179 defer r.buffer = list.allocatedSlice();
1180 try list.ensureTotalCapacity(allocator, n);
1181}
1182
1183pub const FillAllocError = Error || Allocator.Error;
1184
1185pub fn fillAlloc(r: *Reader, allocator: Allocator, n: usize) FillAllocError!void {
1186 try expandTotalCapacity(r, allocator, n);
1187 return fill(r, n);
1188}
1189
1190/// Returns a slice into the unused capacity of `buffer` with at least
1191/// `min_len` bytes, extending `buffer` by resizing it with `gpa` as necessary.
1192///
1193/// After calling this function, typically the caller will follow up with a
1194/// call to `advanceBufferEnd` to report the actual number of bytes buffered.
1195pub fn writableSliceGreedyAlloc(r: *Reader, allocator: Allocator, min_len: usize) Allocator.Error![]u8 {
1196 {
1197 const unused = r.buffer[r.end..];
1198 if (unused.len >= min_len) return unused;
1199 }
1200 if (r.seek > 0) rebase(r);
1201 {
1202 var list: ArrayList(u8) = .{
1203 .items = r.buffer[0..r.end],
1204 .capacity = r.buffer.len,
1205 };
1206 defer r.buffer = list.allocatedSlice();
1207 try list.ensureUnusedCapacity(allocator, min_len);
1208 }
1209 const unused = r.buffer[r.end..];
1210 assert(unused.len >= min_len);
1211 return unused;
1212}
1213
1214/// After writing directly into the unused capacity of `buffer`, this function
1215/// updates `end` so that users of `Reader` can receive the data.
1216pub fn advanceBufferEnd(r: *Reader, n: usize) void {
1217 assert(n <= r.buffer.len - r.end);
1218 r.end += n;
1219}
1220
1221fn takeMultipleOf7Leb128(r: *Reader, comptime Result: type) TakeLeb128Error!Result {
1222 const result_info = @typeInfo(Result).int;
1223 comptime assert(result_info.bits % 7 == 0);
1224 var remaining_bits: std.math.Log2IntCeil(Result) = result_info.bits;
1225 const UnsignedResult = @Type(.{ .int = .{
1226 .signedness = .unsigned,
1227 .bits = result_info.bits,
1228 } });
1229 var result: UnsignedResult = 0;
1230 var fits = true;
1231 while (true) {
1232 const buffer: []const packed struct(u8) { bits: u7, more: bool } = @ptrCast(try r.peekGreedy(1));
1233 for (buffer, 1..) |byte, len| {
1234 if (remaining_bits > 0) {
1235 result = @shlExact(@as(UnsignedResult, byte.bits), result_info.bits - 7) |
1236 if (result_info.bits > 7) @shrExact(result, 7) else 0;
1237 remaining_bits -= 7;
1238 } else if (fits) fits = switch (result_info.signedness) {
1239 .signed => @as(i7, @bitCast(byte.bits)) ==
1240 @as(i7, @truncate(@as(Result, @bitCast(result)) >> (result_info.bits - 1))),
1241 .unsigned => byte.bits == 0,
1242 };
1243 if (byte.more) continue;
1244 r.toss(len);
1245 return if (fits) @as(Result, @bitCast(result)) >> remaining_bits else error.Overflow;
1246 }
1247 r.toss(buffer.len);
1248 }
1249}
1250
1251/// Left-aligns data such that `r.seek` becomes zero.
1252pub fn rebase(r: *Reader) void {
1253 if (r.seek == 0) return;
1254 const data = r.buffer[r.seek..r.end];
1255 @memmove(r.buffer[0..data.len], data);
1256 r.seek = 0;
1257 r.end = data.len;
1258}
1259
1260/// Ensures `capacity` more data can be buffered without rebasing, by rebasing
1261/// if necessary.
1262///
1263/// Asserts `capacity` is within the buffer capacity.
1264pub fn rebaseCapacity(r: *Reader, capacity: usize) void {
1265 if (r.end > r.buffer.len - capacity) rebase(r);
1266}
1267
1268/// Advances the stream and decreases the size of the storage buffer by `n`,
1269/// returning the range of bytes no longer accessible by `r`.
1270///
1271/// This action can be undone by `restitute`.
1272///
1273/// Asserts there are at least `n` buffered bytes already.
1274///
1275/// Asserts that `r.seek` is zero, i.e. the buffer is in a rebased state.
1276pub fn steal(r: *Reader, n: usize) []u8 {
1277 assert(r.seek == 0);
1278 assert(n <= r.end);
1279 const stolen = r.buffer[0..n];
1280 r.buffer = r.buffer[n..];
1281 r.end -= n;
1282 return stolen;
1283}
1284
1285/// Expands the storage buffer, undoing the effects of `steal`
1286/// Assumes that `n` does not exceed the total number of stolen bytes.
1287pub fn restitute(r: *Reader, n: usize) void {
1288 r.buffer = (r.buffer.ptr - n)[0 .. r.buffer.len + n];
1289 r.end += n;
1290 r.seek += n;
1291}
1292
1293test fixed {
1294 var r: Reader = .fixed("a\x02");
1295 try testing.expect((try r.takeByte()) == 'a');
1296 try testing.expect((try r.takeEnum(enum(u8) {
1297 a = 0,
1298 b = 99,
1299 c = 2,
1300 d = 3,
1301 }, builtin.cpu.arch.endian())) == .c);
1302 try testing.expectError(error.EndOfStream, r.takeByte());
1303}
1304
1305test peek {
1306 var r: Reader = .fixed("abc");
1307 try testing.expectEqualStrings("ab", try r.peek(2));
1308 try testing.expectEqualStrings("a", try r.peek(1));
1309}
1310
1311test peekGreedy {
1312 var r: Reader = .fixed("abc");
1313 try testing.expectEqualStrings("abc", try r.peekGreedy(1));
1314}
1315
1316test toss {
1317 var r: Reader = .fixed("abc");
1318 r.toss(1);
1319 try testing.expectEqualStrings("bc", r.buffered());
1320}
1321
1322test take {
1323 var r: Reader = .fixed("abc");
1324 try testing.expectEqualStrings("ab", try r.take(2));
1325 try testing.expectEqualStrings("c", try r.take(1));
1326}
1327
1328test takeArray {
1329 var r: Reader = .fixed("abc");
1330 try testing.expectEqualStrings("ab", try r.takeArray(2));
1331 try testing.expectEqualStrings("c", try r.takeArray(1));
1332}
1333
1334test peekArray {
1335 var r: Reader = .fixed("abc");
1336 try testing.expectEqualStrings("ab", try r.peekArray(2));
1337 try testing.expectEqualStrings("a", try r.peekArray(1));
1338}
1339
1340test discardAll {
1341 var r: Reader = .fixed("foobar");
1342 try r.discardAll(3);
1343 try testing.expectEqualStrings("bar", try r.take(3));
1344 try r.discardAll(0);
1345 try testing.expectError(error.EndOfStream, r.discardAll(1));
1346}
1347
1348test discardRemaining {
1349 var r: Reader = .fixed("foobar");
1350 r.toss(1);
1351 try testing.expectEqual(5, try r.discardRemaining());
1352 try testing.expectEqual(0, try r.discardRemaining());
1353}
1354
1355test stream {
1356 var out_buffer: [10]u8 = undefined;
1357 var r: Reader = .fixed("foobar");
1358 var w: Writer = .fixed(&out_buffer);
1359 // Short streams are possible with this function but not with fixed.
1360 try testing.expectEqual(2, try r.stream(&w, .limited(2)));
1361 try testing.expectEqualStrings("fo", w.buffered());
1362 try testing.expectEqual(4, try r.stream(&w, .unlimited));
1363 try testing.expectEqualStrings("foobar", w.buffered());
1364}
1365
1366test takeSentinel {
1367 var r: Reader = .fixed("ab\nc");
1368 try testing.expectEqualStrings("ab", try r.takeSentinel('\n'));
1369 try testing.expectError(error.EndOfStream, r.takeSentinel('\n'));
1370 try testing.expectEqualStrings("c", try r.peek(1));
1371}
1372
1373test peekSentinel {
1374 var r: Reader = .fixed("ab\nc");
1375 try testing.expectEqualStrings("ab", try r.peekSentinel('\n'));
1376 try testing.expectEqualStrings("ab", try r.peekSentinel('\n'));
1377}
1378
1379test takeDelimiterInclusive {
1380 var r: Reader = .fixed("ab\nc");
1381 try testing.expectEqualStrings("ab\n", try r.takeDelimiterInclusive('\n'));
1382 try testing.expectError(error.EndOfStream, r.takeDelimiterInclusive('\n'));
1383}
1384
1385test peekDelimiterInclusive {
1386 var r: Reader = .fixed("ab\nc");
1387 try testing.expectEqualStrings("ab\n", try r.peekDelimiterInclusive('\n'));
1388 try testing.expectEqualStrings("ab\n", try r.peekDelimiterInclusive('\n'));
1389 r.toss(3);
1390 try testing.expectError(error.EndOfStream, r.peekDelimiterInclusive('\n'));
1391}
1392
1393test takeDelimiterExclusive {
1394 var r: Reader = .fixed("ab\nc");
1395 try testing.expectEqualStrings("ab", try r.takeDelimiterExclusive('\n'));
1396 try testing.expectEqualStrings("c", try r.takeDelimiterExclusive('\n'));
1397 try testing.expectError(error.EndOfStream, r.takeDelimiterExclusive('\n'));
1398}
1399
1400test peekDelimiterExclusive {
1401 var r: Reader = .fixed("ab\nc");
1402 try testing.expectEqualStrings("ab", try r.peekDelimiterExclusive('\n'));
1403 try testing.expectEqualStrings("ab", try r.peekDelimiterExclusive('\n'));
1404 r.toss(3);
1405 try testing.expectEqualStrings("c", try r.peekDelimiterExclusive('\n'));
1406}
1407
1408test streamDelimiter {
1409 var out_buffer: [10]u8 = undefined;
1410 var r: Reader = .fixed("foo\nbars");
1411 var w: Writer = .fixed(&out_buffer);
1412 try testing.expectEqual(3, try r.streamDelimiter(&w, '\n'));
1413 try testing.expectEqualStrings("foo", w.buffered());
1414 try testing.expectEqual(0, try r.streamDelimiter(&w, '\n'));
1415 r.toss(1);
1416 try testing.expectError(error.EndOfStream, r.streamDelimiter(&w, '\n'));
1417}
1418
1419test streamDelimiterEnding {
1420 var out_buffer: [10]u8 = undefined;
1421 var r: Reader = .fixed("foo\nbars");
1422 var w: Writer = .fixed(&out_buffer);
1423 try testing.expectEqual(3, try r.streamDelimiterEnding(&w, '\n'));
1424 try testing.expectEqualStrings("foo", w.buffered());
1425 r.toss(1);
1426 try testing.expectEqual(4, try r.streamDelimiterEnding(&w, '\n'));
1427 try testing.expectEqualStrings("foobars", w.buffered());
1428 try testing.expectEqual(0, try r.streamDelimiterEnding(&w, '\n'));
1429 try testing.expectEqual(0, try r.streamDelimiterEnding(&w, '\n'));
1430}
1431
1432test streamDelimiterLimit {
1433 var out_buffer: [10]u8 = undefined;
1434 var r: Reader = .fixed("foo\nbars");
1435 var w: Writer = .fixed(&out_buffer);
1436 try testing.expectError(error.StreamTooLong, r.streamDelimiterLimit(&w, '\n', .limited(2)));
1437 try testing.expectEqual(1, try r.streamDelimiterLimit(&w, '\n', .limited(3)));
1438 try testing.expectEqualStrings("\n", try r.take(1));
1439 try testing.expectEqual(4, try r.streamDelimiterLimit(&w, '\n', .unlimited));
1440 try testing.expectEqualStrings("foobars", w.buffered());
1441}
1442
1443test discardDelimiterExclusive {
1444 var r: Reader = .fixed("foob\nar");
1445 try testing.expectEqual(4, try r.discardDelimiterExclusive('\n'));
1446 try testing.expectEqualStrings("\n", try r.take(1));
1447 try testing.expectEqual(2, try r.discardDelimiterExclusive('\n'));
1448 try testing.expectEqual(0, try r.discardDelimiterExclusive('\n'));
1449}
1450
1451test discardDelimiterInclusive {
1452 var r: Reader = .fixed("foob\nar");
1453 try testing.expectEqual(5, try r.discardDelimiterInclusive('\n'));
1454 try testing.expectError(error.EndOfStream, r.discardDelimiterInclusive('\n'));
1455}
1456
1457test discardDelimiterLimit {
1458 var r: Reader = .fixed("foob\nar");
1459 try testing.expectError(error.StreamTooLong, r.discardDelimiterLimit('\n', .limited(4)));
1460 try testing.expectEqual(0, try r.discardDelimiterLimit('\n', .limited(2)));
1461 try testing.expectEqualStrings("\n", try r.take(1));
1462 try testing.expectEqual(2, try r.discardDelimiterLimit('\n', .unlimited));
1463 try testing.expectEqual(0, try r.discardDelimiterLimit('\n', .unlimited));
1464}
1465
1466test fill {
1467 var r: Reader = .fixed("abc");
1468 try r.fill(1);
1469 try r.fill(3);
1470}
1471
1472test takeByte {
1473 var r: Reader = .fixed("ab");
1474 try testing.expectEqual('a', try r.takeByte());
1475 try testing.expectEqual('b', try r.takeByte());
1476 try testing.expectError(error.EndOfStream, r.takeByte());
1477}
1478
1479test takeByteSigned {
1480 var r: Reader = .fixed(&.{ 255, 5 });
1481 try testing.expectEqual(-1, try r.takeByteSigned());
1482 try testing.expectEqual(5, try r.takeByteSigned());
1483 try testing.expectError(error.EndOfStream, r.takeByteSigned());
1484}
1485
1486test takeInt {
1487 var r: Reader = .fixed(&.{ 0x12, 0x34, 0x56 });
1488 try testing.expectEqual(0x1234, try r.takeInt(u16, .big));
1489 try testing.expectError(error.EndOfStream, r.takeInt(u16, .little));
1490}
1491
1492test takeVarInt {
1493 var r: Reader = .fixed(&.{ 0x12, 0x34, 0x56 });
1494 try testing.expectEqual(0x123456, try r.takeVarInt(u64, .big, 3));
1495 try testing.expectError(error.EndOfStream, r.takeVarInt(u16, .little, 1));
1496}
1497
1498test takeStruct {
1499 var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 });
1500 const S = extern struct { a: u8, b: u16 };
1501 switch (native_endian) {
1502 .little => try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.takeStruct(S)).*),
1503 .big => try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.takeStruct(S)).*),
1504 }
1505 try testing.expectError(error.EndOfStream, r.takeStruct(S));
1506}
1507
1508test peekStruct {
1509 var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 });
1510 const S = extern struct { a: u8, b: u16 };
1511 switch (native_endian) {
1512 .little => {
1513 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.peekStruct(S)).*);
1514 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.peekStruct(S)).*);
1515 },
1516 .big => {
1517 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.peekStruct(S)).*);
1518 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.peekStruct(S)).*);
1519 },
1520 }
1521}
1522
1523test takeStructEndian {
1524 var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 });
1525 const S = extern struct { a: u8, b: u16 };
1526 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), try r.takeStructEndian(S, .big));
1527 try testing.expectError(error.EndOfStream, r.takeStructEndian(S, .little));
1528}
1529
1530test peekStructEndian {
1531 var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 });
1532 const S = extern struct { a: u8, b: u16 };
1533 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), try r.peekStructEndian(S, .big));
1534 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), try r.peekStructEndian(S, .little));
1535}
1536
1537test takeEnum {
1538 var r: Reader = .fixed(&.{ 2, 0, 1 });
1539 const E1 = enum(u8) { a, b, c };
1540 const E2 = enum(u16) { _ };
1541 try testing.expectEqual(E1.c, try r.takeEnum(E1, .little));
1542 try testing.expectEqual(@as(E2, @enumFromInt(0x0001)), try r.takeEnum(E2, .big));
1543}
1544
1545test takeLeb128 {
1546 var r: Reader = .fixed("\xc7\x9f\x7f\x80");
1547 try testing.expectEqual(-12345, try r.takeLeb128(i64));
1548 try testing.expectEqual(0x80, try r.peekByte());
1549 try testing.expectError(error.EndOfStream, r.takeLeb128(i64));
1550}
1551
1552test readSliceShort {
1553 var r: Reader = .fixed("HelloFren");
1554 var buf: [5]u8 = undefined;
1555 try testing.expectEqual(5, try r.readSliceShort(&buf));
1556 try testing.expectEqualStrings("Hello", buf[0..5]);
1557 try testing.expectEqual(4, try r.readSliceShort(&buf));
1558 try testing.expectEqualStrings("Fren", buf[0..4]);
1559 try testing.expectEqual(0, try r.readSliceShort(&buf));
1560}
1561
1562test readVec {
1563 var r: Reader = .fixed(std.ascii.letters);
1564 var flat_buffer: [52]u8 = undefined;
1565 var bufs: [2][]u8 = .{
1566 flat_buffer[0..26],
1567 flat_buffer[26..],
1568 };
1569 // Short reads are possible with this function but not with fixed.
1570 try testing.expectEqual(26 * 2, try r.readVec(&bufs));
1571 try testing.expectEqualStrings(std.ascii.letters[0..26], bufs[0]);
1572 try testing.expectEqualStrings(std.ascii.letters[26..], bufs[1]);
1573}
1574
1575test readVecLimit {
1576 var r: Reader = .fixed(std.ascii.letters);
1577 var flat_buffer: [52]u8 = undefined;
1578 var bufs: [2][]u8 = .{
1579 flat_buffer[0..26],
1580 flat_buffer[26..],
1581 };
1582 // Short reads are possible with this function but not with fixed.
1583 try testing.expectEqual(50, try r.readVecLimit(&bufs, .limited(50)));
1584 try testing.expectEqualStrings(std.ascii.letters[0..26], bufs[0]);
1585 try testing.expectEqualStrings(std.ascii.letters[26..50], bufs[1][0..24]);
1586}
1587
1588test "expected error.EndOfStream" {
1589 // Unit test inspired by https://github.com/ziglang/zig/issues/17733
1590 var buffer: [3]u8 = undefined;
1591 var r: std.io.Reader = .fixed(&buffer);
1592 r.end = 0; // capacity 3, but empty
1593 try std.testing.expectError(error.EndOfStream, r.takeEnum(enum(u8) { a, b }, .little));
1594 try std.testing.expectError(error.EndOfStream, r.take(3));
1595}
1596
1597fn endingStream(r: *Reader, w: *Writer, limit: Limit) StreamError!usize {
1598 _ = r;
1599 _ = w;
1600 _ = limit;
1601 return error.EndOfStream;
1602}
1603
1604fn endingDiscard(r: *Reader, limit: Limit) Error!usize {
1605 _ = r;
1606 _ = limit;
1607 return error.EndOfStream;
1608}
1609
1610fn failingStream(r: *Reader, w: *Writer, limit: Limit) StreamError!usize {
1611 _ = r;
1612 _ = w;
1613 _ = limit;
1614 return error.ReadFailed;
1615}
1616
1617fn failingDiscard(r: *Reader, limit: Limit) Error!usize {
1618 _ = r;
1619 _ = limit;
1620 return error.ReadFailed;
1621}
1622
1623test "readAlloc when the backing reader provides one byte at a time" {
1624 const OneByteReader = struct {
1625 str: []const u8,
1626 i: usize,
1627 reader: Reader,
1628
1629 fn stream(r: *Reader, w: *Writer, limit: Limit) StreamError!usize {
1630 assert(@intFromEnum(limit) >= 1);
1631 const self: *@This() = @fieldParentPtr("reader", r);
1632 if (self.str.len - self.i == 0) return error.EndOfStream;
1633 try w.writeByte(self.str[self.i]);
1634 self.i += 1;
1635 return 1;
1636 }
1637 };
1638 const str = "This is a test";
1639 var one_byte_stream: OneByteReader = .{
1640 .str = str,
1641 .i = 0,
1642 .reader = .{
1643 .buffer = &.{},
1644 .vtable = &.{ .stream = OneByteReader.stream },
1645 .seek = 0,
1646 .end = 0,
1647 },
1648 };
1649 const res = try one_byte_stream.reader.allocRemaining(std.testing.allocator, .unlimited);
1650 defer std.testing.allocator.free(res);
1651 try std.testing.expectEqualStrings(str, res);
1652}
1653
1654test "takeDelimiterInclusive when it rebases" {
1655 const written_line = "ABCDEFGHIJKLMNOPQRSTUVWXYZ\n";
1656 var buffer: [128]u8 = undefined;
1657 var tr: std.testing.Reader = .init(&buffer, &.{
1658 .{ .buffer = written_line },
1659 .{ .buffer = written_line },
1660 .{ .buffer = written_line },
1661 .{ .buffer = written_line },
1662 .{ .buffer = written_line },
1663 .{ .buffer = written_line },
1664 });
1665 const r = &tr.interface;
1666 for (0..6) |_| {
1667 try std.testing.expectEqualStrings(written_line, try r.takeDelimiterInclusive('\n'));
1668 }
1669}
1670
1671/// Provides a `Reader` implementation by passing data from an underlying
1672/// reader through `Hasher.update`.
1673///
1674/// The underlying reader is best unbuffered.
1675///
1676/// This implementation makes suboptimal buffering decisions due to being
1677/// generic. A better solution will involve creating a reader for each hash
1678/// function, where the discard buffer can be tailored to the hash
1679/// implementation details.
1680pub fn Hashed(comptime Hasher: type) type {
1681 return struct {
1682 in: *Reader,
1683 hasher: Hasher,
1684 interface: Reader,
1685
1686 pub fn init(in: *Reader, hasher: Hasher, buffer: []u8) @This() {
1687 return .{
1688 .in = in,
1689 .hasher = hasher,
1690 .interface = .{
1691 .vtable = &.{
1692 .read = @This().read,
1693 .discard = @This().discard,
1694 },
1695 .buffer = buffer,
1696 .end = 0,
1697 .seek = 0,
1698 },
1699 };
1700 }
1701
1702 fn read(r: *Reader, w: *Writer, limit: Limit) StreamError!usize {
1703 const this: *@This() = @alignCast(@fieldParentPtr("interface", r));
1704 const data = w.writableVector(limit);
1705 const n = try this.in.readVec(data);
1706 const result = w.advanceVector(n);
1707 var remaining: usize = n;
1708 for (data) |slice| {
1709 if (remaining < slice.len) {
1710 this.hasher.update(slice[0..remaining]);
1711 return result;
1712 } else {
1713 remaining -= slice.len;
1714 this.hasher.update(slice);
1715 }
1716 }
1717 assert(remaining == 0);
1718 return result;
1719 }
1720
1721 fn discard(r: *Reader, limit: Limit) Error!usize {
1722 const this: *@This() = @alignCast(@fieldParentPtr("interface", r));
1723 var w = this.hasher.writer(&.{});
1724 const n = this.in.stream(&w, limit) catch |err| switch (err) {
1725 error.WriteFailed => unreachable,
1726 else => |e| return e,
1727 };
1728 return n;
1729 }
1730 };
1731}
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/Reader/test.zig created+372
...@@ -0,0 +1,372 @@
1const builtin = @import("builtin");
2const std = @import("../../std.zig");
3const testing = std.testing;
4
5test "Reader" {
6 var buf = "a\x02".*;
7 var fis = std.io.fixedBufferStream(&buf);
8 const reader = fis.reader();
9 try testing.expect((try reader.readByte()) == 'a');
10 try testing.expect((try reader.readEnum(enum(u8) {
11 a = 0,
12 b = 99,
13 c = 2,
14 d = 3,
15 }, builtin.cpu.arch.endian())) == .c);
16 try testing.expectError(error.EndOfStream, reader.readByte());
17}
18
19test "isBytes" {
20 var fis = std.io.fixedBufferStream("foobar");
21 const reader = fis.reader();
22 try testing.expectEqual(true, try reader.isBytes("foo"));
23 try testing.expectEqual(false, try reader.isBytes("qux"));
24}
25
26test "skipBytes" {
27 var fis = std.io.fixedBufferStream("foobar");
28 const reader = fis.reader();
29 try reader.skipBytes(3, .{});
30 try testing.expect(try reader.isBytes("bar"));
31 try reader.skipBytes(0, .{});
32 try testing.expectError(error.EndOfStream, reader.skipBytes(1, .{}));
33}
34
35test "readUntilDelimiterArrayList returns ArrayLists with bytes read until the delimiter, then EndOfStream" {
36 const a = std.testing.allocator;
37 var list = std.ArrayList(u8).init(a);
38 defer list.deinit();
39
40 var fis = std.io.fixedBufferStream("0000\n1234\n");
41 const reader = fis.reader();
42
43 try reader.readUntilDelimiterArrayList(&list, '\n', 5);
44 try std.testing.expectEqualStrings("0000", list.items);
45 try reader.readUntilDelimiterArrayList(&list, '\n', 5);
46 try std.testing.expectEqualStrings("1234", list.items);
47 try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiterArrayList(&list, '\n', 5));
48}
49
50test "readUntilDelimiterArrayList returns an empty ArrayList" {
51 const a = std.testing.allocator;
52 var list = std.ArrayList(u8).init(a);
53 defer list.deinit();
54
55 var fis = std.io.fixedBufferStream("\n");
56 const reader = fis.reader();
57
58 try reader.readUntilDelimiterArrayList(&list, '\n', 5);
59 try std.testing.expectEqualStrings("", list.items);
60}
61
62test "readUntilDelimiterArrayList returns StreamTooLong, then an ArrayList with bytes read until the delimiter" {
63 const a = std.testing.allocator;
64 var list = std.ArrayList(u8).init(a);
65 defer list.deinit();
66
67 var fis = std.io.fixedBufferStream("1234567\n");
68 const reader = fis.reader();
69
70 try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterArrayList(&list, '\n', 5));
71 try std.testing.expectEqualStrings("12345", list.items);
72 try reader.readUntilDelimiterArrayList(&list, '\n', 5);
73 try std.testing.expectEqualStrings("67", list.items);
74}
75
76test "readUntilDelimiterArrayList returns EndOfStream" {
77 const a = std.testing.allocator;
78 var list = std.ArrayList(u8).init(a);
79 defer list.deinit();
80
81 var fis = std.io.fixedBufferStream("1234");
82 const reader = fis.reader();
83
84 try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiterArrayList(&list, '\n', 5));
85 try std.testing.expectEqualStrings("1234", list.items);
86}
87
88test "readUntilDelimiterAlloc returns ArrayLists with bytes read until the delimiter, then EndOfStream" {
89 const a = std.testing.allocator;
90
91 var fis = std.io.fixedBufferStream("0000\n1234\n");
92 const reader = fis.reader();
93
94 {
95 const result = try reader.readUntilDelimiterAlloc(a, '\n', 5);
96 defer a.free(result);
97 try std.testing.expectEqualStrings("0000", result);
98 }
99
100 {
101 const result = try reader.readUntilDelimiterAlloc(a, '\n', 5);
102 defer a.free(result);
103 try std.testing.expectEqualStrings("1234", result);
104 }
105
106 try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiterAlloc(a, '\n', 5));
107}
108
109test "readUntilDelimiterAlloc returns an empty ArrayList" {
110 const a = std.testing.allocator;
111
112 var fis = std.io.fixedBufferStream("\n");
113 const reader = fis.reader();
114
115 {
116 const result = try reader.readUntilDelimiterAlloc(a, '\n', 5);
117 defer a.free(result);
118 try std.testing.expectEqualStrings("", result);
119 }
120}
121
122test "readUntilDelimiterAlloc returns StreamTooLong, then an ArrayList with bytes read until the delimiter" {
123 const a = std.testing.allocator;
124
125 var fis = std.io.fixedBufferStream("1234567\n");
126 const reader = fis.reader();
127
128 try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterAlloc(a, '\n', 5));
129
130 const result = try reader.readUntilDelimiterAlloc(a, '\n', 5);
131 defer a.free(result);
132 try std.testing.expectEqualStrings("67", result);
133}
134
135test "readUntilDelimiterAlloc returns EndOfStream" {
136 const a = std.testing.allocator;
137
138 var fis = std.io.fixedBufferStream("1234");
139 const reader = fis.reader();
140
141 try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiterAlloc(a, '\n', 5));
142}
143
144test "readUntilDelimiter returns bytes read until the delimiter" {
145 var buf: [5]u8 = undefined;
146 var fis = std.io.fixedBufferStream("0000\n1234\n");
147 const reader = fis.reader();
148 try std.testing.expectEqualStrings("0000", try reader.readUntilDelimiter(&buf, '\n'));
149 try std.testing.expectEqualStrings("1234", try reader.readUntilDelimiter(&buf, '\n'));
150}
151
152test "readUntilDelimiter returns an empty string" {
153 var buf: [5]u8 = undefined;
154 var fis = std.io.fixedBufferStream("\n");
155 const reader = fis.reader();
156 try std.testing.expectEqualStrings("", try reader.readUntilDelimiter(&buf, '\n'));
157}
158
159test "readUntilDelimiter returns StreamTooLong, then an empty string" {
160 var buf: [5]u8 = undefined;
161 var fis = std.io.fixedBufferStream("12345\n");
162 const reader = fis.reader();
163 try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiter(&buf, '\n'));
164 try std.testing.expectEqualStrings("", try reader.readUntilDelimiter(&buf, '\n'));
165}
166
167test "readUntilDelimiter returns StreamTooLong, then bytes read until the delimiter" {
168 var buf: [5]u8 = undefined;
169 var fis = std.io.fixedBufferStream("1234567\n");
170 const reader = fis.reader();
171 try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiter(&buf, '\n'));
172 try std.testing.expectEqualStrings("67", try reader.readUntilDelimiter(&buf, '\n'));
173}
174
175test "readUntilDelimiter returns EndOfStream" {
176 {
177 var buf: [5]u8 = undefined;
178 var fis = std.io.fixedBufferStream("");
179 const reader = fis.reader();
180 try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiter(&buf, '\n'));
181 }
182 {
183 var buf: [5]u8 = undefined;
184 var fis = std.io.fixedBufferStream("1234");
185 const reader = fis.reader();
186 try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiter(&buf, '\n'));
187 }
188}
189
190test "readUntilDelimiter returns bytes read until delimiter, then EndOfStream" {
191 var buf: [5]u8 = undefined;
192 var fis = std.io.fixedBufferStream("1234\n");
193 const reader = fis.reader();
194 try std.testing.expectEqualStrings("1234", try reader.readUntilDelimiter(&buf, '\n'));
195 try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiter(&buf, '\n'));
196}
197
198test "readUntilDelimiter returns StreamTooLong, then EndOfStream" {
199 var buf: [5]u8 = undefined;
200 var fis = std.io.fixedBufferStream("12345");
201 const reader = fis.reader();
202 try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiter(&buf, '\n'));
203 try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiter(&buf, '\n'));
204}
205
206test "readUntilDelimiter writes all bytes read to the output buffer" {
207 var buf: [5]u8 = undefined;
208 var fis = std.io.fixedBufferStream("0000\n12345");
209 const reader = fis.reader();
210 _ = try reader.readUntilDelimiter(&buf, '\n');
211 try std.testing.expectEqualStrings("0000\n", &buf);
212 try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiter(&buf, '\n'));
213 try std.testing.expectEqualStrings("12345", &buf);
214}
215
216test "readUntilDelimiterOrEofAlloc returns ArrayLists with bytes read until the delimiter, then EndOfStream" {
217 const a = std.testing.allocator;
218
219 var fis = std.io.fixedBufferStream("0000\n1234\n");
220 const reader = fis.reader();
221
222 {
223 const result = (try reader.readUntilDelimiterOrEofAlloc(a, '\n', 5)).?;
224 defer a.free(result);
225 try std.testing.expectEqualStrings("0000", result);
226 }
227
228 {
229 const result = (try reader.readUntilDelimiterOrEofAlloc(a, '\n', 5)).?;
230 defer a.free(result);
231 try std.testing.expectEqualStrings("1234", result);
232 }
233
234 try std.testing.expect((try reader.readUntilDelimiterOrEofAlloc(a, '\n', 5)) == null);
235}
236
237test "readUntilDelimiterOrEofAlloc returns an empty ArrayList" {
238 const a = std.testing.allocator;
239
240 var fis = std.io.fixedBufferStream("\n");
241 const reader = fis.reader();
242
243 {
244 const result = (try reader.readUntilDelimiterOrEofAlloc(a, '\n', 5)).?;
245 defer a.free(result);
246 try std.testing.expectEqualStrings("", result);
247 }
248}
249
250test "readUntilDelimiterOrEofAlloc returns StreamTooLong, then an ArrayList with bytes read until the delimiter" {
251 const a = std.testing.allocator;
252
253 var fis = std.io.fixedBufferStream("1234567\n");
254 const reader = fis.reader();
255
256 try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterOrEofAlloc(a, '\n', 5));
257
258 const result = (try reader.readUntilDelimiterOrEofAlloc(a, '\n', 5)).?;
259 defer a.free(result);
260 try std.testing.expectEqualStrings("67", result);
261}
262
263test "readUntilDelimiterOrEof returns bytes read until the delimiter" {
264 var buf: [5]u8 = undefined;
265 var fis = std.io.fixedBufferStream("0000\n1234\n");
266 const reader = fis.reader();
267 try std.testing.expectEqualStrings("0000", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?);
268 try std.testing.expectEqualStrings("1234", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?);
269}
270
271test "readUntilDelimiterOrEof returns an empty string" {
272 var buf: [5]u8 = undefined;
273 var fis = std.io.fixedBufferStream("\n");
274 const reader = fis.reader();
275 try std.testing.expectEqualStrings("", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?);
276}
277
278test "readUntilDelimiterOrEof returns StreamTooLong, then an empty string" {
279 var buf: [5]u8 = undefined;
280 var fis = std.io.fixedBufferStream("12345\n");
281 const reader = fis.reader();
282 try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterOrEof(&buf, '\n'));
283 try std.testing.expectEqualStrings("", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?);
284}
285
286test "readUntilDelimiterOrEof returns StreamTooLong, then bytes read until the delimiter" {
287 var buf: [5]u8 = undefined;
288 var fis = std.io.fixedBufferStream("1234567\n");
289 const reader = fis.reader();
290 try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterOrEof(&buf, '\n'));
291 try std.testing.expectEqualStrings("67", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?);
292}
293
294test "readUntilDelimiterOrEof returns null" {
295 var buf: [5]u8 = undefined;
296 var fis = std.io.fixedBufferStream("");
297 const reader = fis.reader();
298 try std.testing.expect((try reader.readUntilDelimiterOrEof(&buf, '\n')) == null);
299}
300
301test "readUntilDelimiterOrEof returns bytes read until delimiter, then null" {
302 var buf: [5]u8 = undefined;
303 var fis = std.io.fixedBufferStream("1234\n");
304 const reader = fis.reader();
305 try std.testing.expectEqualStrings("1234", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?);
306 try std.testing.expect((try reader.readUntilDelimiterOrEof(&buf, '\n')) == null);
307}
308
309test "readUntilDelimiterOrEof returns bytes read until end-of-stream" {
310 var buf: [5]u8 = undefined;
311 var fis = std.io.fixedBufferStream("1234");
312 const reader = fis.reader();
313 try std.testing.expectEqualStrings("1234", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?);
314}
315
316test "readUntilDelimiterOrEof returns StreamTooLong, then bytes read until end-of-stream" {
317 var buf: [5]u8 = undefined;
318 var fis = std.io.fixedBufferStream("1234567");
319 const reader = fis.reader();
320 try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterOrEof(&buf, '\n'));
321 try std.testing.expectEqualStrings("67", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?);
322}
323
324test "readUntilDelimiterOrEof writes all bytes read to the output buffer" {
325 var buf: [5]u8 = undefined;
326 var fis = std.io.fixedBufferStream("0000\n12345");
327 const reader = fis.reader();
328 _ = try reader.readUntilDelimiterOrEof(&buf, '\n');
329 try std.testing.expectEqualStrings("0000\n", &buf);
330 try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterOrEof(&buf, '\n'));
331 try std.testing.expectEqualStrings("12345", &buf);
332}
333
334test "streamUntilDelimiter writes all bytes without delimiter to the output" {
335 const input_string = "some_string_with_delimiter!";
336 var input_fbs = std.io.fixedBufferStream(input_string);
337 const reader = input_fbs.reader();
338
339 var output: [input_string.len]u8 = undefined;
340 var output_fbs = std.io.fixedBufferStream(&output);
341 const writer = output_fbs.writer();
342
343 try reader.streamUntilDelimiter(writer, '!', input_fbs.buffer.len);
344 try std.testing.expectEqualStrings("some_string_with_delimiter", output_fbs.getWritten());
345 try std.testing.expectError(error.EndOfStream, reader.streamUntilDelimiter(writer, '!', input_fbs.buffer.len));
346
347 input_fbs.reset();
348 output_fbs.reset();
349
350 try std.testing.expectError(error.StreamTooLong, reader.streamUntilDelimiter(writer, '!', 5));
351}
352
353test "readBoundedBytes correctly reads into a new bounded array" {
354 const test_string = "abcdefg";
355 var fis = std.io.fixedBufferStream(test_string);
356 const reader = fis.reader();
357
358 var array = try reader.readBoundedBytes(10000);
359 try testing.expectEqualStrings(array.slice(), test_string);
360}
361
362test "readIntoBoundedBytes correctly reads into a provided bounded array" {
363 const test_string = "abcdefg";
364 var fis = std.io.fixedBufferStream(test_string);
365 const reader = fis.reader();
366
367 var bounded_array = std.BoundedArray(u8, 10000){};
368
369 // compile time error if the size is not the same at the provided `bounded.capacity()`
370 try reader.readIntoBoundedBytes(10000, &bounded_array);
371 try testing.expectEqualStrings(bounded_array.slice(), test_string);
372}
lib/std/Io/Writer.zig created+2486
...@@ -0,0 +1,2486 @@
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 `}}`.
563pub fn print(w: *Writer, comptime fmt: []const u8, args: anytype) Error!void {
564 const ArgsType = @TypeOf(args);
565 const args_type_info = @typeInfo(ArgsType);
566 if (args_type_info != .@"struct") {
567 @compileError("expected tuple or struct argument, found " ++ @typeName(ArgsType));
568 }
569
570 const fields_info = args_type_info.@"struct".fields;
571 const max_format_args = @typeInfo(std.fmt.ArgSetType).int.bits;
572 if (fields_info.len > max_format_args) {
573 @compileError("32 arguments max are supported per format call");
574 }
575
576 @setEvalBranchQuota(fmt.len * 1000);
577 comptime var arg_state: std.fmt.ArgState = .{ .args_len = fields_info.len };
578 comptime var i = 0;
579 comptime var literal: []const u8 = "";
580 inline while (true) {
581 const start_index = i;
582
583 inline while (i < fmt.len) : (i += 1) {
584 switch (fmt[i]) {
585 '{', '}' => break,
586 else => {},
587 }
588 }
589
590 comptime var end_index = i;
591 comptime var unescape_brace = false;
592
593 // Handle {{ and }}, those are un-escaped as single braces
594 if (i + 1 < fmt.len and fmt[i + 1] == fmt[i]) {
595 unescape_brace = true;
596 // Make the first brace part of the literal...
597 end_index += 1;
598 // ...and skip both
599 i += 2;
600 }
601
602 literal = literal ++ fmt[start_index..end_index];
603
604 // We've already skipped the other brace, restart the loop
605 if (unescape_brace) continue;
606
607 // Write out the literal
608 if (literal.len != 0) {
609 try w.writeAll(literal);
610 literal = "";
611 }
612
613 if (i >= fmt.len) break;
614
615 if (fmt[i] == '}') {
616 @compileError("missing opening {");
617 }
618
619 // Get past the {
620 comptime assert(fmt[i] == '{');
621 i += 1;
622
623 const fmt_begin = i;
624 // Find the closing brace
625 inline while (i < fmt.len and fmt[i] != '}') : (i += 1) {}
626 const fmt_end = i;
627
628 if (i >= fmt.len) {
629 @compileError("missing closing }");
630 }
631
632 // Get past the }
633 comptime assert(fmt[i] == '}');
634 i += 1;
635
636 const placeholder_array = fmt[fmt_begin..fmt_end].*;
637 const placeholder = comptime std.fmt.Placeholder.parse(&placeholder_array);
638 const arg_pos = comptime switch (placeholder.arg) {
639 .none => null,
640 .number => |pos| pos,
641 .named => |arg_name| std.meta.fieldIndex(ArgsType, arg_name) orelse
642 @compileError("no argument with name '" ++ arg_name ++ "'"),
643 };
644
645 const width = switch (placeholder.width) {
646 .none => null,
647 .number => |v| v,
648 .named => |arg_name| blk: {
649 const arg_i = comptime std.meta.fieldIndex(ArgsType, arg_name) orelse
650 @compileError("no argument with name '" ++ arg_name ++ "'");
651 _ = comptime arg_state.nextArg(arg_i) orelse @compileError("too few arguments");
652 break :blk @field(args, arg_name);
653 },
654 };
655
656 const precision = switch (placeholder.precision) {
657 .none => null,
658 .number => |v| v,
659 .named => |arg_name| blk: {
660 const arg_i = comptime std.meta.fieldIndex(ArgsType, arg_name) orelse
661 @compileError("no argument with name '" ++ arg_name ++ "'");
662 _ = comptime arg_state.nextArg(arg_i) orelse @compileError("too few arguments");
663 break :blk @field(args, arg_name);
664 },
665 };
666
667 const arg_to_print = comptime arg_state.nextArg(arg_pos) orelse
668 @compileError("too few arguments");
669
670 try w.printValue(
671 placeholder.specifier_arg,
672 .{
673 .fill = placeholder.fill,
674 .alignment = placeholder.alignment,
675 .width = width,
676 .precision = precision,
677 },
678 @field(args, fields_info[arg_to_print].name),
679 std.options.fmt_max_depth,
680 );
681 }
682
683 if (comptime arg_state.hasUnusedArgs()) {
684 const missing_count = arg_state.args_len - @popCount(arg_state.used_args);
685 switch (missing_count) {
686 0 => unreachable,
687 1 => @compileError("unused argument in '" ++ fmt ++ "'"),
688 else => @compileError(std.fmt.comptimePrint("{d}", .{missing_count}) ++ " unused arguments in '" ++ fmt ++ "'"),
689 }
690 }
691}
692
693/// Calls `drain` as many times as necessary such that `byte` is transferred.
694pub fn writeByte(w: *Writer, byte: u8) Error!void {
695 while (w.buffer.len - w.end == 0) {
696 const n = try w.vtable.drain(w, &.{&.{byte}}, 1);
697 if (n > 0) return;
698 } else {
699 @branchHint(.likely);
700 w.buffer[w.end] = byte;
701 w.end += 1;
702 }
703}
704
705/// When draining the buffer, ensures that at least `preserve_length` bytes
706/// remain buffered.
707pub fn writeBytePreserve(w: *Writer, preserve_length: usize, byte: u8) Error!void {
708 while (w.buffer.len - w.end == 0) {
709 try drainPreserve(w, preserve_length);
710 } else {
711 @branchHint(.likely);
712 w.buffer[w.end] = byte;
713 w.end += 1;
714 }
715}
716
717/// Writes the same byte many times, performing the underlying write call as
718/// many times as necessary.
719pub fn splatByteAll(w: *Writer, byte: u8, n: usize) Error!void {
720 var remaining: usize = n;
721 while (remaining > 0) remaining -= try w.splatByte(byte, remaining);
722}
723
724/// Writes the same byte many times, allowing short writes.
725///
726/// Does maximum of one underlying `VTable.drain`.
727pub fn splatByte(w: *Writer, byte: u8, n: usize) Error!usize {
728 return writeSplat(w, &.{&.{byte}}, n);
729}
730
731/// Writes the same slice many times, performing the underlying write call as
732/// many times as necessary.
733pub fn splatBytesAll(w: *Writer, bytes: []const u8, splat: usize) Error!void {
734 var remaining_bytes: usize = bytes.len * splat;
735 remaining_bytes -= try w.splatBytes(bytes, splat);
736 while (remaining_bytes > 0) {
737 const leftover = remaining_bytes % bytes.len;
738 const buffers: [2][]const u8 = .{ bytes[bytes.len - leftover ..], bytes };
739 remaining_bytes -= try w.splatBytes(&buffers, splat);
740 }
741}
742
743/// Writes the same slice many times, allowing short writes.
744///
745/// Does maximum of one underlying `VTable.writeSplat`.
746pub fn splatBytes(w: *Writer, bytes: []const u8, n: usize) Error!usize {
747 return writeSplat(w, &.{bytes}, n);
748}
749
750/// Asserts the `buffer` was initialized with a capacity of at least `@sizeOf(T)` bytes.
751pub inline fn writeInt(w: *Writer, comptime T: type, value: T, endian: std.builtin.Endian) Error!void {
752 var bytes: [@divExact(@typeInfo(T).int.bits, 8)]u8 = undefined;
753 std.mem.writeInt(std.math.ByteAlignedInt(@TypeOf(value)), &bytes, value, endian);
754 return w.writeAll(&bytes);
755}
756
757pub fn writeStruct(w: *Writer, value: anytype) Error!void {
758 // Only extern and packed structs have defined in-memory layout.
759 comptime assert(@typeInfo(@TypeOf(value)).@"struct".layout != .auto);
760 return w.writeAll(std.mem.asBytes(&value));
761}
762
763/// The function is inline to avoid the dead code in case `endian` is
764/// comptime-known and matches host endianness.
765/// TODO: make sure this value is not a reference type
766pub inline fn writeStructEndian(w: *Writer, value: anytype, endian: std.builtin.Endian) Error!void {
767 switch (@typeInfo(@TypeOf(value))) {
768 .@"struct" => |info| switch (info.layout) {
769 .auto => @compileError("ill-defined memory layout"),
770 .@"extern" => {
771 if (native_endian == endian) {
772 return w.writeStruct(value);
773 } else {
774 var copy = value;
775 std.mem.byteSwapAllFields(@TypeOf(value), &copy);
776 return w.writeStruct(copy);
777 }
778 },
779 .@"packed" => {
780 return writeInt(w, info.backing_integer.?, @bitCast(value), endian);
781 },
782 },
783 else => @compileError("not a struct"),
784 }
785}
786
787pub inline fn writeSliceEndian(
788 w: *Writer,
789 Elem: type,
790 slice: []const Elem,
791 endian: std.builtin.Endian,
792) Error!void {
793 if (native_endian == endian) {
794 return writeAll(w, @ptrCast(slice));
795 } else {
796 return w.writeArraySwap(w, Elem, slice);
797 }
798}
799
800/// Unlike `writeSplat` and `writeVec`, this function will call into `VTable`
801/// even if there is enough buffer capacity for the file contents.
802///
803/// Although it would be possible to eliminate `error.Unimplemented` from the
804/// error set by reading directly into the buffer in such case, this is not
805/// done because it is more efficient to do it higher up the call stack so that
806/// the error does not occur with each write.
807///
808/// See `sendFileReading` for an alternative that does not have
809/// `error.Unimplemented` in the error set.
810pub fn sendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize {
811 return w.vtable.sendFile(w, file_reader, limit);
812}
813
814/// Returns how many bytes from `header` and `file_reader` were consumed.
815pub fn sendFileHeader(
816 w: *Writer,
817 header: []const u8,
818 file_reader: *File.Reader,
819 limit: Limit,
820) FileError!usize {
821 const new_end = w.end + header.len;
822 if (new_end <= w.buffer.len) {
823 @memcpy(w.buffer[w.end..][0..header.len], header);
824 w.end = new_end;
825 return header.len + try w.vtable.sendFile(w, file_reader, limit);
826 }
827 const buffered_contents = limit.slice(file_reader.interface.buffered());
828 const n = try w.vtable.drain(w, &.{ header, buffered_contents }, 1);
829 file_reader.interface.toss(n - header.len);
830 return n;
831}
832
833/// Asserts nonzero buffer capacity.
834pub fn sendFileReading(w: *Writer, file_reader: *File.Reader, limit: Limit) FileReadingError!usize {
835 const dest = limit.slice(try w.writableSliceGreedy(1));
836 const n = try file_reader.read(dest);
837 w.advance(n);
838 return n;
839}
840
841/// Number of bytes logically written is returned. This excludes bytes from
842/// `buffer` because they have already been logically written.
843pub fn sendFileAll(w: *Writer, file_reader: *File.Reader, limit: Limit) FileAllError!usize {
844 var remaining = @intFromEnum(limit);
845 while (remaining > 0) {
846 const n = sendFile(w, file_reader, .limited(remaining)) catch |err| switch (err) {
847 error.EndOfStream => break,
848 error.Unimplemented => {
849 file_reader.mode = file_reader.mode.toReading();
850 remaining -= try w.sendFileReadingAll(file_reader, .limited(remaining));
851 break;
852 },
853 else => |e| return e,
854 };
855 remaining -= n;
856 }
857 return @intFromEnum(limit) - remaining;
858}
859
860/// Equivalent to `sendFileAll` but uses direct `pread` and `read` calls on
861/// `file` rather than `sendFile`. This is generally used as a fallback when
862/// the underlying implementation returns `error.Unimplemented`, which is why
863/// that error code does not appear in this function's error set.
864///
865/// Asserts nonzero buffer capacity.
866pub fn sendFileReadingAll(w: *Writer, file_reader: *File.Reader, limit: Limit) FileAllError!usize {
867 var remaining = @intFromEnum(limit);
868 while (remaining > 0) {
869 remaining -= sendFileReading(w, file_reader, .limited(remaining)) catch |err| switch (err) {
870 error.EndOfStream => break,
871 else => |e| return e,
872 };
873 }
874 return @intFromEnum(limit) - remaining;
875}
876
877pub fn alignBuffer(
878 w: *Writer,
879 buffer: []const u8,
880 width: usize,
881 alignment: std.fmt.Alignment,
882 fill: u8,
883) Error!void {
884 const padding = if (buffer.len < width) width - buffer.len else 0;
885 if (padding == 0) {
886 @branchHint(.likely);
887 return w.writeAll(buffer);
888 }
889 switch (alignment) {
890 .left => {
891 try w.writeAll(buffer);
892 try w.splatByteAll(fill, padding);
893 },
894 .center => {
895 const left_padding = padding / 2;
896 const right_padding = (padding + 1) / 2;
897 try w.splatByteAll(fill, left_padding);
898 try w.writeAll(buffer);
899 try w.splatByteAll(fill, right_padding);
900 },
901 .right => {
902 try w.splatByteAll(fill, padding);
903 try w.writeAll(buffer);
904 },
905 }
906}
907
908pub fn alignBufferOptions(w: *Writer, buffer: []const u8, options: std.fmt.Options) Error!void {
909 return w.alignBuffer(buffer, options.width orelse buffer.len, options.alignment, options.fill);
910}
911
912pub fn printAddress(w: *Writer, value: anytype) Error!void {
913 const T = @TypeOf(value);
914 switch (@typeInfo(T)) {
915 .pointer => |info| {
916 try w.writeAll(@typeName(info.child) ++ "@");
917 const int = if (info.size == .slice) @intFromPtr(value.ptr) else @intFromPtr(value);
918 return w.printInt(int, 16, .lower, .{});
919 },
920 .optional => |info| {
921 if (@typeInfo(info.child) == .pointer) {
922 try w.writeAll(@typeName(info.child) ++ "@");
923 try w.printInt(@intFromPtr(value), 16, .lower, .{});
924 return;
925 }
926 },
927 else => {},
928 }
929
930 @compileError("cannot format non-pointer type " ++ @typeName(T) ++ " with * specifier");
931}
932
933pub fn printValue(
934 w: *Writer,
935 comptime fmt: []const u8,
936 options: std.fmt.Options,
937 value: anytype,
938 max_depth: usize,
939) Error!void {
940 const T = @TypeOf(value);
941
942 switch (fmt.len) {
943 1 => switch (fmt[0]) {
944 '*' => return w.printAddress(value),
945 'f' => return value.format(w),
946 'd' => switch (@typeInfo(T)) {
947 .float, .comptime_float => return printFloat(w, value, options.toNumber(.decimal, .lower)),
948 .int, .comptime_int => return printInt(w, value, 10, .lower, options),
949 .@"struct" => return value.formatNumber(w, options.toNumber(.decimal, .lower)),
950 .@"enum" => return printInt(w, @intFromEnum(value), 10, .lower, options),
951 .vector => return printVector(w, fmt, options, value, max_depth),
952 else => invalidFmtError(fmt, value),
953 },
954 'c' => return w.printAsciiChar(value, options),
955 'u' => return w.printUnicodeCodepoint(value),
956 'b' => switch (@typeInfo(T)) {
957 .int, .comptime_int => return printInt(w, value, 2, .lower, options),
958 .@"enum" => return printInt(w, @intFromEnum(value), 2, .lower, options),
959 .@"struct" => return value.formatNumber(w, options.toNumber(.binary, .lower)),
960 .vector => return printVector(w, fmt, options, value, max_depth),
961 else => invalidFmtError(fmt, value),
962 },
963 'o' => switch (@typeInfo(T)) {
964 .int, .comptime_int => return printInt(w, value, 8, .lower, options),
965 .@"enum" => return printInt(w, @intFromEnum(value), 8, .lower, options),
966 .@"struct" => return value.formatNumber(w, options.toNumber(.octal, .lower)),
967 .vector => return printVector(w, fmt, options, value, max_depth),
968 else => invalidFmtError(fmt, value),
969 },
970 'x' => switch (@typeInfo(T)) {
971 .float, .comptime_float => return printFloatHexOptions(w, value, options.toNumber(.hex, .lower)),
972 .int, .comptime_int => return printInt(w, value, 16, .lower, options),
973 .@"enum" => return printInt(w, @intFromEnum(value), 16, .lower, options),
974 .@"struct" => return value.formatNumber(w, options.toNumber(.hex, .lower)),
975 .pointer => |info| switch (info.size) {
976 .one, .slice => {
977 const slice: []const u8 = value;
978 optionsForbidden(options);
979 return printHex(w, slice, .lower);
980 },
981 .many, .c => {
982 const slice: [:0]const u8 = std.mem.span(value);
983 optionsForbidden(options);
984 return printHex(w, slice, .lower);
985 },
986 },
987 .array => {
988 const slice: []const u8 = &value;
989 optionsForbidden(options);
990 return printHex(w, slice, .lower);
991 },
992 .vector => return printVector(w, fmt, options, value, max_depth),
993 else => invalidFmtError(fmt, value),
994 },
995 'X' => switch (@typeInfo(T)) {
996 .float, .comptime_float => return printFloatHexOptions(w, value, options.toNumber(.hex, .lower)),
997 .int, .comptime_int => return printInt(w, value, 16, .upper, options),
998 .@"enum" => return printInt(w, @intFromEnum(value), 16, .upper, options),
999 .@"struct" => return value.formatNumber(w, options.toNumber(.hex, .upper)),
1000 .pointer => |info| switch (info.size) {
1001 .one, .slice => {
1002 const slice: []const u8 = value;
1003 optionsForbidden(options);
1004 return printHex(w, slice, .upper);
1005 },
1006 .many, .c => {
1007 const slice: [:0]const u8 = std.mem.span(value);
1008 optionsForbidden(options);
1009 return printHex(w, slice, .upper);
1010 },
1011 },
1012 .array => {
1013 const slice: []const u8 = &value;
1014 optionsForbidden(options);
1015 return printHex(w, slice, .upper);
1016 },
1017 .vector => return printVector(w, fmt, options, value, max_depth),
1018 else => invalidFmtError(fmt, value),
1019 },
1020 's' => switch (@typeInfo(T)) {
1021 .pointer => |info| switch (info.size) {
1022 .one, .slice => {
1023 const slice: []const u8 = value;
1024 return w.alignBufferOptions(slice, options);
1025 },
1026 .many, .c => {
1027 const slice: [:0]const u8 = std.mem.span(value);
1028 return w.alignBufferOptions(slice, options);
1029 },
1030 },
1031 .array => {
1032 const slice: []const u8 = &value;
1033 return w.alignBufferOptions(slice, options);
1034 },
1035 else => invalidFmtError(fmt, value),
1036 },
1037 'B' => switch (@typeInfo(T)) {
1038 .int, .comptime_int => return w.printByteSize(value, .decimal, options),
1039 .@"struct" => return value.formatByteSize(w, .decimal),
1040 else => invalidFmtError(fmt, value),
1041 },
1042 'D' => switch (@typeInfo(T)) {
1043 .int, .comptime_int => return w.printDuration(value, options),
1044 .@"struct" => return value.formatDuration(w),
1045 else => invalidFmtError(fmt, value),
1046 },
1047 'e' => switch (@typeInfo(T)) {
1048 .float, .comptime_float => return printFloat(w, value, options.toNumber(.scientific, .lower)),
1049 .@"struct" => return value.formatNumber(w, options.toNumber(.scientific, .lower)),
1050 else => invalidFmtError(fmt, value),
1051 },
1052 'E' => switch (@typeInfo(T)) {
1053 .float, .comptime_float => return printFloat(w, value, options.toNumber(.scientific, .upper)),
1054 .@"struct" => return value.formatNumber(w, options.toNumber(.scientific, .upper)),
1055 else => invalidFmtError(fmt, value),
1056 },
1057 't' => switch (@typeInfo(T)) {
1058 .error_set => return w.writeAll(@errorName(value)),
1059 .@"enum", .@"union" => return w.writeAll(@tagName(value)),
1060 else => invalidFmtError(fmt, value),
1061 },
1062 else => {},
1063 },
1064 2 => switch (fmt[0]) {
1065 'B' => switch (fmt[1]) {
1066 'i' => switch (@typeInfo(T)) {
1067 .int, .comptime_int => return w.printByteSize(value, .binary, options),
1068 .@"struct" => return value.formatByteSize(w, .binary),
1069 else => invalidFmtError(fmt, value),
1070 },
1071 else => {},
1072 },
1073 else => {},
1074 },
1075 3 => if (fmt[0] == 'b' and fmt[1] == '6' and fmt[2] == '4') switch (@typeInfo(T)) {
1076 .pointer => |info| switch (info.size) {
1077 .one, .slice => {
1078 const slice: []const u8 = value;
1079 optionsForbidden(options);
1080 return w.printBase64(slice);
1081 },
1082 .many, .c => {
1083 const slice: [:0]const u8 = std.mem.span(value);
1084 optionsForbidden(options);
1085 return w.printBase64(slice);
1086 },
1087 },
1088 .array => {
1089 const slice: []const u8 = &value;
1090 optionsForbidden(options);
1091 return w.printBase64(slice);
1092 },
1093 else => invalidFmtError(fmt, value),
1094 },
1095 else => {},
1096 }
1097
1098 const is_any = comptime std.mem.eql(u8, fmt, ANY);
1099 if (!is_any and std.meta.hasMethod(T, "format") and fmt.len == 0) {
1100 // after 0.15.0 is tagged, delete this compile error and its condition
1101 @compileError("ambiguous format string; specify {f} to call format method, or {any} to skip it");
1102 }
1103
1104 switch (@typeInfo(T)) {
1105 .float, .comptime_float => {
1106 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1107 return printFloat(w, value, options.toNumber(.decimal, .lower));
1108 },
1109 .int, .comptime_int => {
1110 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1111 return printInt(w, value, 10, .lower, options);
1112 },
1113 .bool => {
1114 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1115 const string: []const u8 = if (value) "true" else "false";
1116 return w.alignBufferOptions(string, options);
1117 },
1118 .void => {
1119 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1120 return w.alignBufferOptions("void", options);
1121 },
1122 .optional => {
1123 const remaining_fmt = comptime if (fmt.len > 0 and fmt[0] == '?')
1124 stripOptionalOrErrorUnionSpec(fmt)
1125 else if (is_any)
1126 ANY
1127 else
1128 @compileError("cannot print optional without a specifier (i.e. {?} or {any})");
1129 if (value) |payload| {
1130 return w.printValue(remaining_fmt, options, payload, max_depth);
1131 } else {
1132 return w.alignBufferOptions("null", options);
1133 }
1134 },
1135 .error_union => {
1136 const remaining_fmt = comptime if (fmt.len > 0 and fmt[0] == '!')
1137 stripOptionalOrErrorUnionSpec(fmt)
1138 else if (is_any)
1139 ANY
1140 else
1141 @compileError("cannot print error union without a specifier (i.e. {!} or {any})");
1142 if (value) |payload| {
1143 return w.printValue(remaining_fmt, options, payload, max_depth);
1144 } else |err| {
1145 return w.printValue("", options, err, max_depth);
1146 }
1147 },
1148 .error_set => {
1149 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1150 optionsForbidden(options);
1151 return printErrorSet(w, value);
1152 },
1153 .@"enum" => |info| {
1154 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1155 optionsForbidden(options);
1156 if (info.is_exhaustive) {
1157 return printEnumExhaustive(w, value);
1158 } else {
1159 return printEnumNonexhaustive(w, value);
1160 }
1161 },
1162 .@"union" => |info| {
1163 if (!is_any) {
1164 if (fmt.len != 0) invalidFmtError(fmt, value);
1165 return printValue(w, ANY, options, value, max_depth);
1166 }
1167 if (max_depth == 0) {
1168 try w.writeAll(".{ ... }");
1169 return;
1170 }
1171 if (info.tag_type) |UnionTagType| {
1172 try w.writeAll(".{ .");
1173 try w.writeAll(@tagName(@as(UnionTagType, value)));
1174 try w.writeAll(" = ");
1175 inline for (info.fields) |u_field| {
1176 if (value == @field(UnionTagType, u_field.name)) {
1177 try w.printValue(ANY, options, @field(value, u_field.name), max_depth - 1);
1178 }
1179 }
1180 try w.writeAll(" }");
1181 } else switch (info.layout) {
1182 .auto => {
1183 return w.writeAll(".{ ... }");
1184 },
1185 .@"extern", .@"packed" => {
1186 if (info.fields.len == 0) return w.writeAll(".{}");
1187 try w.writeAll(".{ ");
1188 inline for (info.fields) |field| {
1189 try w.writeByte('.');
1190 try w.writeAll(field.name);
1191 try w.writeAll(" = ");
1192 try w.printValue(ANY, options, @field(value, field.name), max_depth - 1);
1193 (try w.writableArray(2)).* = ", ".*;
1194 }
1195 w.buffer[w.end - 2 ..][0..2].* = " }".*;
1196 },
1197 }
1198 },
1199 .@"struct" => |info| {
1200 if (!is_any) {
1201 if (fmt.len != 0) invalidFmtError(fmt, value);
1202 return printValue(w, ANY, options, value, max_depth);
1203 }
1204 if (info.is_tuple) {
1205 // Skip the type and field names when formatting tuples.
1206 if (max_depth == 0) {
1207 try w.writeAll(".{ ... }");
1208 return;
1209 }
1210 try w.writeAll(".{");
1211 inline for (info.fields, 0..) |f, i| {
1212 if (i == 0) {
1213 try w.writeAll(" ");
1214 } else {
1215 try w.writeAll(", ");
1216 }
1217 try w.printValue(ANY, options, @field(value, f.name), max_depth - 1);
1218 }
1219 try w.writeAll(" }");
1220 return;
1221 }
1222 if (max_depth == 0) {
1223 try w.writeAll(".{ ... }");
1224 return;
1225 }
1226 try w.writeAll(".{");
1227 inline for (info.fields, 0..) |f, i| {
1228 if (i == 0) {
1229 try w.writeAll(" .");
1230 } else {
1231 try w.writeAll(", .");
1232 }
1233 try w.writeAll(f.name);
1234 try w.writeAll(" = ");
1235 try w.printValue(ANY, options, @field(value, f.name), max_depth - 1);
1236 }
1237 try w.writeAll(" }");
1238 },
1239 .pointer => |ptr_info| switch (ptr_info.size) {
1240 .one => switch (@typeInfo(ptr_info.child)) {
1241 .array => |array_info| return w.printValue(fmt, options, @as([]const array_info.child, value), max_depth),
1242 .@"enum", .@"union", .@"struct" => return w.printValue(fmt, options, value.*, max_depth),
1243 else => {
1244 var buffers: [2][]const u8 = .{ @typeName(ptr_info.child), "@" };
1245 try w.writeVecAll(&buffers);
1246 try w.printInt(@intFromPtr(value), 16, .lower, options);
1247 return;
1248 },
1249 },
1250 .many, .c => {
1251 if (!is_any) @compileError("cannot format pointer without a specifier (i.e. {s} or {*})");
1252 optionsForbidden(options);
1253 try w.printAddress(value);
1254 },
1255 .slice => {
1256 if (!is_any)
1257 @compileError("cannot format slice without a specifier (i.e. {s}, {x}, {b64}, or {any})");
1258 if (max_depth == 0) return w.writeAll("{ ... }");
1259 try w.writeAll("{ ");
1260 for (value, 0..) |elem, i| {
1261 try w.printValue(fmt, options, elem, max_depth - 1);
1262 if (i != value.len - 1) {
1263 try w.writeAll(", ");
1264 }
1265 }
1266 try w.writeAll(" }");
1267 },
1268 },
1269 .array => {
1270 if (!is_any) @compileError("cannot format array without a specifier (i.e. {s} or {any})");
1271 if (max_depth == 0) return w.writeAll("{ ... }");
1272 try w.writeAll("{ ");
1273 for (value, 0..) |elem, i| {
1274 try w.printValue(fmt, options, elem, max_depth - 1);
1275 if (i < value.len - 1) {
1276 try w.writeAll(", ");
1277 }
1278 }
1279 try w.writeAll(" }");
1280 },
1281 .vector => {
1282 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1283 return printVector(w, fmt, options, value, max_depth);
1284 },
1285 .@"fn" => @compileError("unable to format function body type, use '*const " ++ @typeName(T) ++ "' for a function pointer type"),
1286 .type => {
1287 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1288 return w.alignBufferOptions(@typeName(value), options);
1289 },
1290 .enum_literal => {
1291 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1292 optionsForbidden(options);
1293 var vecs: [2][]const u8 = .{ ".", @tagName(value) };
1294 return w.writeVecAll(&vecs);
1295 },
1296 .null => {
1297 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1298 return w.alignBufferOptions("null", options);
1299 },
1300 else => @compileError("unable to format type '" ++ @typeName(T) ++ "'"),
1301 }
1302}
1303
1304fn optionsForbidden(options: std.fmt.Options) void {
1305 assert(options.precision == null);
1306 assert(options.width == null);
1307}
1308
1309fn printErrorSet(w: *Writer, error_set: anyerror) Error!void {
1310 var vecs: [2][]const u8 = .{ "error.", @errorName(error_set) };
1311 try w.writeVecAll(&vecs);
1312}
1313
1314fn printEnumExhaustive(w: *Writer, value: anytype) Error!void {
1315 var vecs: [2][]const u8 = .{ ".", @tagName(value) };
1316 try w.writeVecAll(&vecs);
1317}
1318
1319fn printEnumNonexhaustive(w: *Writer, value: anytype) Error!void {
1320 if (std.enums.tagName(@TypeOf(value), value)) |tag_name| {
1321 var vecs: [2][]const u8 = .{ ".", tag_name };
1322 try w.writeVecAll(&vecs);
1323 return;
1324 }
1325 try w.writeAll("@enumFromInt(");
1326 try w.printInt(@intFromEnum(value), 10, .lower, .{});
1327 try w.writeByte(')');
1328}
1329
1330pub fn printVector(
1331 w: *Writer,
1332 comptime fmt: []const u8,
1333 options: std.fmt.Options,
1334 value: anytype,
1335 max_depth: usize,
1336) Error!void {
1337 const len = @typeInfo(@TypeOf(value)).vector.len;
1338 if (max_depth == 0) return w.writeAll("{ ... }");
1339 try w.writeAll("{ ");
1340 inline for (0..len) |i| {
1341 try w.printValue(fmt, options, value[i], max_depth - 1);
1342 if (i < len - 1) try w.writeAll(", ");
1343 }
1344 try w.writeAll(" }");
1345}
1346
1347// A wrapper around `printIntAny` to avoid the generic explosion of this
1348// function by funneling smaller integer types through `isize` and `usize`.
1349pub inline fn printInt(
1350 w: *Writer,
1351 value: anytype,
1352 base: u8,
1353 case: std.fmt.Case,
1354 options: std.fmt.Options,
1355) Error!void {
1356 switch (@TypeOf(value)) {
1357 isize, usize => {},
1358 comptime_int => {
1359 if (comptime std.math.cast(usize, value)) |x| return printIntAny(w, x, base, case, options);
1360 if (comptime std.math.cast(isize, value)) |x| return printIntAny(w, x, base, case, options);
1361 const Int = std.math.IntFittingRange(value, value);
1362 return printIntAny(w, @as(Int, value), base, case, options);
1363 },
1364 else => switch (@typeInfo(@TypeOf(value)).int.signedness) {
1365 .signed => if (std.math.cast(isize, value)) |x| return printIntAny(w, x, base, case, options),
1366 .unsigned => if (std.math.cast(usize, value)) |x| return printIntAny(w, x, base, case, options),
1367 },
1368 }
1369 return printIntAny(w, value, base, case, options);
1370}
1371
1372/// In general, prefer `printInt` to avoid generic explosion. However this
1373/// function may be used when optimal codegen for a particular integer type is
1374/// desired.
1375pub fn printIntAny(
1376 w: *Writer,
1377 value: anytype,
1378 base: u8,
1379 case: std.fmt.Case,
1380 options: std.fmt.Options,
1381) Error!void {
1382 assert(base >= 2);
1383 const value_info = @typeInfo(@TypeOf(value)).int;
1384
1385 // The type must have the same size as `base` or be wider in order for the
1386 // division to work
1387 const min_int_bits = comptime @max(value_info.bits, 8);
1388 const MinInt = std.meta.Int(.unsigned, min_int_bits);
1389
1390 const abs_value = @abs(value);
1391 // The worst case in terms of space needed is base 2, plus 1 for the sign
1392 var buf: [1 + @max(@as(comptime_int, value_info.bits), 1)]u8 = undefined;
1393
1394 var a: MinInt = abs_value;
1395 var index: usize = buf.len;
1396
1397 if (base == 10) {
1398 while (a >= 100) : (a = @divTrunc(a, 100)) {
1399 index -= 2;
1400 buf[index..][0..2].* = std.fmt.digits2(@intCast(a % 100));
1401 }
1402
1403 if (a < 10) {
1404 index -= 1;
1405 buf[index] = '0' + @as(u8, @intCast(a));
1406 } else {
1407 index -= 2;
1408 buf[index..][0..2].* = std.fmt.digits2(@intCast(a));
1409 }
1410 } else {
1411 while (true) {
1412 const digit = a % base;
1413 index -= 1;
1414 buf[index] = std.fmt.digitToChar(@intCast(digit), case);
1415 a /= base;
1416 if (a == 0) break;
1417 }
1418 }
1419
1420 if (value_info.signedness == .signed) {
1421 if (value < 0) {
1422 // Negative integer
1423 index -= 1;
1424 buf[index] = '-';
1425 } else if (options.width == null or options.width.? == 0) {
1426 // Positive integer, omit the plus sign
1427 } else {
1428 // Positive integer
1429 index -= 1;
1430 buf[index] = '+';
1431 }
1432 }
1433
1434 return w.alignBufferOptions(buf[index..], options);
1435}
1436
1437pub fn printAsciiChar(w: *Writer, c: u8, options: std.fmt.Options) Error!void {
1438 return w.alignBufferOptions(@as(*const [1]u8, &c), options);
1439}
1440
1441pub fn printAscii(w: *Writer, bytes: []const u8, options: std.fmt.Options) Error!void {
1442 return w.alignBufferOptions(bytes, options);
1443}
1444
1445pub fn printUnicodeCodepoint(w: *Writer, c: u21) Error!void {
1446 var buf: [4]u8 = undefined;
1447 const len = std.unicode.utf8Encode(c, &buf) catch |err| switch (err) {
1448 error.Utf8CannotEncodeSurrogateHalf, error.CodepointTooLarge => l: {
1449 buf[0..3].* = std.unicode.replacement_character_utf8;
1450 break :l 3;
1451 },
1452 };
1453 return w.writeAll(buf[0..len]);
1454}
1455
1456/// Uses a larger stack buffer; asserts mode is decimal or scientific.
1457pub fn printFloat(w: *Writer, value: anytype, options: std.fmt.Number) Error!void {
1458 const mode: std.fmt.float.Mode = switch (options.mode) {
1459 .decimal => .decimal,
1460 .scientific => .scientific,
1461 .binary, .octal, .hex => unreachable,
1462 };
1463 var buf: [std.fmt.float.bufferSize(.decimal, f64)]u8 = undefined;
1464 const s = std.fmt.float.render(&buf, value, .{
1465 .mode = mode,
1466 .precision = options.precision,
1467 }) catch |err| switch (err) {
1468 error.BufferTooSmall => "(float)",
1469 };
1470 return w.alignBuffer(s, options.width orelse s.len, options.alignment, options.fill);
1471}
1472
1473/// Uses a smaller stack buffer; asserts mode is not decimal or scientific.
1474pub fn printFloatHexOptions(w: *Writer, value: anytype, options: std.fmt.Number) Error!void {
1475 var buf: [50]u8 = undefined; // for aligning
1476 var sub_writer: Writer = .fixed(&buf);
1477 switch (options.mode) {
1478 .decimal => unreachable,
1479 .scientific => unreachable,
1480 .binary => @panic("TODO"),
1481 .octal => @panic("TODO"),
1482 .hex => {},
1483 }
1484 printFloatHex(&sub_writer, value, options.case, options.precision) catch unreachable; // buf is large enough
1485
1486 const printed = sub_writer.buffered();
1487 return w.alignBuffer(printed, options.width orelse printed.len, options.alignment, options.fill);
1488}
1489
1490pub fn printFloatHex(w: *Writer, value: anytype, case: std.fmt.Case, opt_precision: ?usize) Error!void {
1491 if (std.math.signbit(value)) try w.writeByte('-');
1492 if (std.math.isNan(value)) return w.writeAll(switch (case) {
1493 .lower => "nan",
1494 .upper => "NAN",
1495 });
1496 if (std.math.isInf(value)) return w.writeAll(switch (case) {
1497 .lower => "inf",
1498 .upper => "INF",
1499 });
1500
1501 const T = @TypeOf(value);
1502 const TU = std.meta.Int(.unsigned, @bitSizeOf(T));
1503
1504 const mantissa_bits = std.math.floatMantissaBits(T);
1505 const fractional_bits = std.math.floatFractionalBits(T);
1506 const exponent_bits = std.math.floatExponentBits(T);
1507 const mantissa_mask = (1 << mantissa_bits) - 1;
1508 const exponent_mask = (1 << exponent_bits) - 1;
1509 const exponent_bias = (1 << (exponent_bits - 1)) - 1;
1510
1511 const as_bits: TU = @bitCast(value);
1512 var mantissa = as_bits & mantissa_mask;
1513 var exponent: i32 = @as(u16, @truncate((as_bits >> mantissa_bits) & exponent_mask));
1514
1515 const is_denormal = exponent == 0 and mantissa != 0;
1516 const is_zero = exponent == 0 and mantissa == 0;
1517
1518 if (is_zero) {
1519 // Handle this case here to simplify the logic below.
1520 try w.writeAll("0x0");
1521 if (opt_precision) |precision| {
1522 if (precision > 0) {
1523 try w.writeAll(".");
1524 try w.splatByteAll('0', precision);
1525 }
1526 } else {
1527 try w.writeAll(".0");
1528 }
1529 try w.writeAll("p0");
1530 return;
1531 }
1532
1533 if (is_denormal) {
1534 // Adjust the exponent for printing.
1535 exponent += 1;
1536 } else {
1537 if (fractional_bits == mantissa_bits)
1538 mantissa |= 1 << fractional_bits; // Add the implicit integer bit.
1539 }
1540
1541 const mantissa_digits = (fractional_bits + 3) / 4;
1542 // Fill in zeroes to round the fraction width to a multiple of 4.
1543 mantissa <<= mantissa_digits * 4 - fractional_bits;
1544
1545 if (opt_precision) |precision| {
1546 // Round if needed.
1547 if (precision < mantissa_digits) {
1548 // We always have at least 4 extra bits.
1549 var extra_bits = (mantissa_digits - precision) * 4;
1550 // The result LSB is the Guard bit, we need two more (Round and
1551 // Sticky) to round the value.
1552 while (extra_bits > 2) {
1553 mantissa = (mantissa >> 1) | (mantissa & 1);
1554 extra_bits -= 1;
1555 }
1556 // Round to nearest, tie to even.
1557 mantissa |= @intFromBool(mantissa & 0b100 != 0);
1558 mantissa += 1;
1559 // Drop the excess bits.
1560 mantissa >>= 2;
1561 // Restore the alignment.
1562 mantissa <<= @as(std.math.Log2Int(TU), @intCast((mantissa_digits - precision) * 4));
1563
1564 const overflow = mantissa & (1 << 1 + mantissa_digits * 4) != 0;
1565 // Prefer a normalized result in case of overflow.
1566 if (overflow) {
1567 mantissa >>= 1;
1568 exponent += 1;
1569 }
1570 }
1571 }
1572
1573 // +1 for the decimal part.
1574 var buf: [1 + mantissa_digits]u8 = undefined;
1575 assert(std.fmt.printInt(&buf, mantissa, 16, case, .{ .fill = '0', .width = 1 + mantissa_digits }) == buf.len);
1576
1577 try w.writeAll("0x");
1578 try w.writeByte(buf[0]);
1579 const trimmed = std.mem.trimRight(u8, buf[1..], "0");
1580 if (opt_precision) |precision| {
1581 if (precision > 0) try w.writeAll(".");
1582 } else if (trimmed.len > 0) {
1583 try w.writeAll(".");
1584 }
1585 try w.writeAll(trimmed);
1586 // Add trailing zeros if explicitly requested.
1587 if (opt_precision) |precision| if (precision > 0) {
1588 if (precision > trimmed.len)
1589 try w.splatByteAll('0', precision - trimmed.len);
1590 };
1591 try w.writeAll("p");
1592 try w.printInt(exponent - exponent_bias, 10, case, .{});
1593}
1594
1595pub const ByteSizeUnits = enum {
1596 /// This formatter represents the number as multiple of 1000 and uses the SI
1597 /// measurement units (kB, MB, GB, ...).
1598 decimal,
1599 /// This formatter represents the number as multiple of 1024 and uses the IEC
1600 /// measurement units (KiB, MiB, GiB, ...).
1601 binary,
1602};
1603
1604/// Format option `precision` is ignored when `value` is less than 1kB
1605pub fn printByteSize(
1606 w: *std.io.Writer,
1607 value: u64,
1608 comptime units: ByteSizeUnits,
1609 options: std.fmt.Options,
1610) Error!void {
1611 if (value == 0) return w.alignBufferOptions("0B", options);
1612 // The worst case in terms of space needed is 32 bytes + 3 for the suffix.
1613 var buf: [std.fmt.float.min_buffer_size + 3]u8 = undefined;
1614
1615 const mags_si = " kMGTPEZY";
1616 const mags_iec = " KMGTPEZY";
1617
1618 const log2 = std.math.log2(value);
1619 const base = switch (units) {
1620 .decimal => 1000,
1621 .binary => 1024,
1622 };
1623 const magnitude = switch (units) {
1624 .decimal => @min(log2 / comptime std.math.log2(1000), mags_si.len - 1),
1625 .binary => @min(log2 / 10, mags_iec.len - 1),
1626 };
1627 const new_value = std.math.lossyCast(f64, value) / std.math.pow(f64, std.math.lossyCast(f64, base), std.math.lossyCast(f64, magnitude));
1628 const suffix = switch (units) {
1629 .decimal => mags_si[magnitude],
1630 .binary => mags_iec[magnitude],
1631 };
1632
1633 const s = switch (magnitude) {
1634 0 => buf[0..std.fmt.printInt(&buf, value, 10, .lower, .{})],
1635 else => std.fmt.float.render(&buf, new_value, .{ .mode = .decimal, .precision = options.precision }) catch |err| switch (err) {
1636 error.BufferTooSmall => unreachable,
1637 },
1638 };
1639
1640 var i: usize = s.len;
1641 if (suffix == ' ') {
1642 buf[i] = 'B';
1643 i += 1;
1644 } else switch (units) {
1645 .decimal => {
1646 buf[i..][0..2].* = [_]u8{ suffix, 'B' };
1647 i += 2;
1648 },
1649 .binary => {
1650 buf[i..][0..3].* = [_]u8{ suffix, 'i', 'B' };
1651 i += 3;
1652 },
1653 }
1654
1655 return w.alignBufferOptions(buf[0..i], options);
1656}
1657
1658// This ANY const is a workaround for: https://github.com/ziglang/zig/issues/7948
1659const ANY = "any";
1660
1661fn stripOptionalOrErrorUnionSpec(comptime fmt: []const u8) []const u8 {
1662 return if (std.mem.eql(u8, fmt[1..], ANY))
1663 ANY
1664 else
1665 fmt[1..];
1666}
1667
1668pub fn invalidFmtError(comptime fmt: []const u8, value: anytype) noreturn {
1669 @compileError("invalid format string '" ++ fmt ++ "' for type '" ++ @typeName(@TypeOf(value)) ++ "'");
1670}
1671
1672pub fn printDurationSigned(w: *Writer, ns: i64) Error!void {
1673 if (ns < 0) try w.writeByte('-');
1674 return w.printDurationUnsigned(@abs(ns));
1675}
1676
1677pub fn printDurationUnsigned(w: *Writer, ns: u64) Error!void {
1678 var ns_remaining = ns;
1679 inline for (.{
1680 .{ .ns = 365 * std.time.ns_per_day, .sep = 'y' },
1681 .{ .ns = std.time.ns_per_week, .sep = 'w' },
1682 .{ .ns = std.time.ns_per_day, .sep = 'd' },
1683 .{ .ns = std.time.ns_per_hour, .sep = 'h' },
1684 .{ .ns = std.time.ns_per_min, .sep = 'm' },
1685 }) |unit| {
1686 if (ns_remaining >= unit.ns) {
1687 const units = ns_remaining / unit.ns;
1688 try w.printInt(units, 10, .lower, .{});
1689 try w.writeByte(unit.sep);
1690 ns_remaining -= units * unit.ns;
1691 if (ns_remaining == 0) return;
1692 }
1693 }
1694
1695 inline for (.{
1696 .{ .ns = std.time.ns_per_s, .sep = "s" },
1697 .{ .ns = std.time.ns_per_ms, .sep = "ms" },
1698 .{ .ns = std.time.ns_per_us, .sep = "us" },
1699 }) |unit| {
1700 const kunits = ns_remaining * 1000 / unit.ns;
1701 if (kunits >= 1000) {
1702 try w.printInt(kunits / 1000, 10, .lower, .{});
1703 const frac = kunits % 1000;
1704 if (frac > 0) {
1705 // Write up to 3 decimal places
1706 var decimal_buf = [_]u8{ '.', 0, 0, 0 };
1707 var inner: Writer = .fixed(decimal_buf[1..]);
1708 inner.printInt(frac, 10, .lower, .{ .fill = '0', .width = 3 }) catch unreachable;
1709 var end: usize = 4;
1710 while (end > 1) : (end -= 1) {
1711 if (decimal_buf[end - 1] != '0') break;
1712 }
1713 try w.writeAll(decimal_buf[0..end]);
1714 }
1715 return w.writeAll(unit.sep);
1716 }
1717 }
1718
1719 try w.printInt(ns_remaining, 10, .lower, .{});
1720 try w.writeAll("ns");
1721}
1722
1723/// Writes number of nanoseconds according to its signed magnitude:
1724/// `[#y][#w][#d][#h][#m]#[.###][n|u|m]s`
1725/// `nanoseconds` must be an integer that coerces into `u64` or `i64`.
1726pub fn printDuration(w: *Writer, nanoseconds: anytype, options: std.fmt.Options) Error!void {
1727 // worst case: "-XXXyXXwXXdXXhXXmXX.XXXs".len = 24
1728 var buf: [24]u8 = undefined;
1729 var sub_writer: Writer = .fixed(&buf);
1730 if (@TypeOf(nanoseconds) == comptime_int) {
1731 if (nanoseconds >= 0) {
1732 sub_writer.printDurationUnsigned(nanoseconds) catch unreachable;
1733 } else {
1734 sub_writer.printDurationSigned(nanoseconds) catch unreachable;
1735 }
1736 } else switch (@typeInfo(@TypeOf(nanoseconds)).int.signedness) {
1737 .signed => sub_writer.printDurationSigned(nanoseconds) catch unreachable,
1738 .unsigned => sub_writer.printDurationUnsigned(nanoseconds) catch unreachable,
1739 }
1740 return w.alignBufferOptions(sub_writer.buffered(), options);
1741}
1742
1743pub fn printHex(w: *Writer, bytes: []const u8, case: std.fmt.Case) Error!void {
1744 const charset = switch (case) {
1745 .upper => "0123456789ABCDEF",
1746 .lower => "0123456789abcdef",
1747 };
1748 for (bytes) |c| {
1749 try w.writeByte(charset[c >> 4]);
1750 try w.writeByte(charset[c & 15]);
1751 }
1752}
1753
1754pub fn printBase64(w: *Writer, bytes: []const u8) Error!void {
1755 var chunker = std.mem.window(u8, bytes, 3, 3);
1756 var temp: [5]u8 = undefined;
1757 while (chunker.next()) |chunk| {
1758 try w.writeAll(std.base64.standard.Encoder.encode(&temp, chunk));
1759 }
1760}
1761
1762/// Write a single unsigned integer as LEB128 to the given writer.
1763pub fn writeUleb128(w: *Writer, value: anytype) Error!void {
1764 try w.writeLeb128(switch (@typeInfo(@TypeOf(value))) {
1765 .comptime_int => @as(std.math.IntFittingRange(0, @abs(value)), value),
1766 .int => |value_info| switch (value_info.signedness) {
1767 .signed => @as(@Type(.{ .int = .{ .signedness = .unsigned, .bits = value_info.bits -| 1 } }), @intCast(value)),
1768 .unsigned => value,
1769 },
1770 else => comptime unreachable,
1771 });
1772}
1773
1774/// Write a single signed integer as LEB128 to the given writer.
1775pub fn writeSleb128(w: *Writer, value: anytype) Error!void {
1776 try w.writeLeb128(switch (@typeInfo(@TypeOf(value))) {
1777 .comptime_int => @as(std.math.IntFittingRange(@min(value, -1), @max(0, value)), value),
1778 .int => |value_info| switch (value_info.signedness) {
1779 .signed => value,
1780 .unsigned => @as(@Type(.{ .int = .{ .signedness = .signed, .bits = value_info.bits + 1 } }), value),
1781 },
1782 else => comptime unreachable,
1783 });
1784}
1785
1786/// Write a single integer as LEB128 to the given writer.
1787pub fn writeLeb128(w: *Writer, value: anytype) Error!void {
1788 const value_info = @typeInfo(@TypeOf(value)).int;
1789 try w.writeMultipleOf7Leb128(@as(@Type(.{ .int = .{
1790 .signedness = value_info.signedness,
1791 .bits = std.mem.alignForwardAnyAlign(u16, value_info.bits, 7),
1792 } }), value));
1793}
1794
1795fn writeMultipleOf7Leb128(w: *Writer, value: anytype) Error!void {
1796 const value_info = @typeInfo(@TypeOf(value)).int;
1797 comptime assert(value_info.bits % 7 == 0);
1798 var remaining = value;
1799 while (true) {
1800 const buffer: []packed struct(u8) { bits: u7, more: bool } = @ptrCast(try w.writableSliceGreedy(1));
1801 for (buffer, 1..) |*byte, len| {
1802 const more = switch (value_info.signedness) {
1803 .signed => remaining >> 6 != remaining >> (value_info.bits - 1),
1804 .unsigned => remaining > std.math.maxInt(u7),
1805 };
1806 byte.* = if (@inComptime()) @typeInfo(@TypeOf(buffer)).pointer.child{
1807 .bits = @bitCast(@as(@Type(.{ .int = .{
1808 .signedness = value_info.signedness,
1809 .bits = 7,
1810 } }), @truncate(remaining))),
1811 .more = more,
1812 } else .{
1813 .bits = @bitCast(@as(@Type(.{ .int = .{
1814 .signedness = value_info.signedness,
1815 .bits = 7,
1816 } }), @truncate(remaining))),
1817 .more = more,
1818 };
1819 if (value_info.bits > 7) remaining >>= 7;
1820 if (!more) return w.advance(len);
1821 }
1822 w.advance(buffer.len);
1823 }
1824}
1825
1826test "printValue max_depth" {
1827 const Vec2 = struct {
1828 const SelfType = @This();
1829 x: f32,
1830 y: f32,
1831
1832 pub fn format(self: SelfType, w: *Writer) Error!void {
1833 return w.print("({d:.3},{d:.3})", .{ self.x, self.y });
1834 }
1835 };
1836 const E = enum {
1837 One,
1838 Two,
1839 Three,
1840 };
1841 const TU = union(enum) {
1842 const SelfType = @This();
1843 float: f32,
1844 int: u32,
1845 ptr: ?*SelfType,
1846 };
1847 const S = struct {
1848 const SelfType = @This();
1849 a: ?*SelfType,
1850 tu: TU,
1851 e: E,
1852 vec: Vec2,
1853 };
1854
1855 var inst = S{
1856 .a = null,
1857 .tu = TU{ .ptr = null },
1858 .e = E.Two,
1859 .vec = Vec2{ .x = 10.2, .y = 2.22 },
1860 };
1861 inst.a = &inst;
1862 inst.tu.ptr = &inst.tu;
1863
1864 var buf: [1000]u8 = undefined;
1865 var w: Writer = .fixed(&buf);
1866 try w.printValue("", .{}, inst, 0);
1867 try testing.expectEqualStrings(".{ ... }", w.buffered());
1868
1869 w = .fixed(&buf);
1870 try w.printValue("", .{}, inst, 1);
1871 try testing.expectEqualStrings(".{ .a = .{ ... }, .tu = .{ ... }, .e = .Two, .vec = .{ ... } }", w.buffered());
1872
1873 w = .fixed(&buf);
1874 try w.printValue("", .{}, inst, 2);
1875 try testing.expectEqualStrings(".{ .a = .{ .a = .{ ... }, .tu = .{ ... }, .e = .Two, .vec = .{ ... } }, .tu = .{ .ptr = .{ ... } }, .e = .Two, .vec = .{ .x = 10.2, .y = 2.22 } }", w.buffered());
1876
1877 w = .fixed(&buf);
1878 try w.printValue("", .{}, inst, 3);
1879 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());
1880
1881 const vec: @Vector(4, i32) = .{ 1, 2, 3, 4 };
1882 w = .fixed(&buf);
1883 try w.printValue("", .{}, vec, 0);
1884 try testing.expectEqualStrings("{ ... }", w.buffered());
1885
1886 w = .fixed(&buf);
1887 try w.printValue("", .{}, vec, 1);
1888 try testing.expectEqualStrings("{ 1, 2, 3, 4 }", w.buffered());
1889}
1890
1891test printDuration {
1892 try testDurationCase("0ns", 0);
1893 try testDurationCase("1ns", 1);
1894 try testDurationCase("999ns", std.time.ns_per_us - 1);
1895 try testDurationCase("1us", std.time.ns_per_us);
1896 try testDurationCase("1.45us", 1450);
1897 try testDurationCase("1.5us", 3 * std.time.ns_per_us / 2);
1898 try testDurationCase("14.5us", 14500);
1899 try testDurationCase("145us", 145000);
1900 try testDurationCase("999.999us", std.time.ns_per_ms - 1);
1901 try testDurationCase("1ms", std.time.ns_per_ms + 1);
1902 try testDurationCase("1.5ms", 3 * std.time.ns_per_ms / 2);
1903 try testDurationCase("1.11ms", 1110000);
1904 try testDurationCase("1.111ms", 1111000);
1905 try testDurationCase("1.111ms", 1111100);
1906 try testDurationCase("999.999ms", std.time.ns_per_s - 1);
1907 try testDurationCase("1s", std.time.ns_per_s);
1908 try testDurationCase("59.999s", std.time.ns_per_min - 1);
1909 try testDurationCase("1m", std.time.ns_per_min);
1910 try testDurationCase("1h", std.time.ns_per_hour);
1911 try testDurationCase("1d", std.time.ns_per_day);
1912 try testDurationCase("1w", std.time.ns_per_week);
1913 try testDurationCase("1y", 365 * std.time.ns_per_day);
1914 try testDurationCase("1y52w23h59m59.999s", 730 * std.time.ns_per_day - 1); // 365d = 52w1
1915 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);
1916 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);
1917 try testDurationCase("1y1h999.999us", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms - 1);
1918 try testDurationCase("1y1h1ms", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms);
1919 try testDurationCase("1y1h1ms", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms + 1);
1920 try testDurationCase("1y1m999ns", 365 * std.time.ns_per_day + std.time.ns_per_min + 999);
1921 try testDurationCase("584y49w23h34m33.709s", std.math.maxInt(u64));
1922
1923 try testing.expectFmt("=======0ns", "{D:=>10}", .{0});
1924 try testing.expectFmt("1ns=======", "{D:=<10}", .{1});
1925 try testing.expectFmt(" 999ns ", "{D:^10}", .{std.time.ns_per_us - 1});
1926}
1927
1928test printDurationSigned {
1929 try testDurationCaseSigned("0ns", 0);
1930 try testDurationCaseSigned("1ns", 1);
1931 try testDurationCaseSigned("-1ns", -(1));
1932 try testDurationCaseSigned("999ns", std.time.ns_per_us - 1);
1933 try testDurationCaseSigned("-999ns", -(std.time.ns_per_us - 1));
1934 try testDurationCaseSigned("1us", std.time.ns_per_us);
1935 try testDurationCaseSigned("-1us", -(std.time.ns_per_us));
1936 try testDurationCaseSigned("1.45us", 1450);
1937 try testDurationCaseSigned("-1.45us", -(1450));
1938 try testDurationCaseSigned("1.5us", 3 * std.time.ns_per_us / 2);
1939 try testDurationCaseSigned("-1.5us", -(3 * std.time.ns_per_us / 2));
1940 try testDurationCaseSigned("14.5us", 14500);
1941 try testDurationCaseSigned("-14.5us", -(14500));
1942 try testDurationCaseSigned("145us", 145000);
1943 try testDurationCaseSigned("-145us", -(145000));
1944 try testDurationCaseSigned("999.999us", std.time.ns_per_ms - 1);
1945 try testDurationCaseSigned("-999.999us", -(std.time.ns_per_ms - 1));
1946 try testDurationCaseSigned("1ms", std.time.ns_per_ms + 1);
1947 try testDurationCaseSigned("-1ms", -(std.time.ns_per_ms + 1));
1948 try testDurationCaseSigned("1.5ms", 3 * std.time.ns_per_ms / 2);
1949 try testDurationCaseSigned("-1.5ms", -(3 * std.time.ns_per_ms / 2));
1950 try testDurationCaseSigned("1.11ms", 1110000);
1951 try testDurationCaseSigned("-1.11ms", -(1110000));
1952 try testDurationCaseSigned("1.111ms", 1111000);
1953 try testDurationCaseSigned("-1.111ms", -(1111000));
1954 try testDurationCaseSigned("1.111ms", 1111100);
1955 try testDurationCaseSigned("-1.111ms", -(1111100));
1956 try testDurationCaseSigned("999.999ms", std.time.ns_per_s - 1);
1957 try testDurationCaseSigned("-999.999ms", -(std.time.ns_per_s - 1));
1958 try testDurationCaseSigned("1s", std.time.ns_per_s);
1959 try testDurationCaseSigned("-1s", -(std.time.ns_per_s));
1960 try testDurationCaseSigned("59.999s", std.time.ns_per_min - 1);
1961 try testDurationCaseSigned("-59.999s", -(std.time.ns_per_min - 1));
1962 try testDurationCaseSigned("1m", std.time.ns_per_min);
1963 try testDurationCaseSigned("-1m", -(std.time.ns_per_min));
1964 try testDurationCaseSigned("1h", std.time.ns_per_hour);
1965 try testDurationCaseSigned("-1h", -(std.time.ns_per_hour));
1966 try testDurationCaseSigned("1d", std.time.ns_per_day);
1967 try testDurationCaseSigned("-1d", -(std.time.ns_per_day));
1968 try testDurationCaseSigned("1w", std.time.ns_per_week);
1969 try testDurationCaseSigned("-1w", -(std.time.ns_per_week));
1970 try testDurationCaseSigned("1y", 365 * std.time.ns_per_day);
1971 try testDurationCaseSigned("-1y", -(365 * std.time.ns_per_day));
1972 try testDurationCaseSigned("1y52w23h59m59.999s", 730 * std.time.ns_per_day - 1); // 365d = 52w1d
1973 try testDurationCaseSigned("-1y52w23h59m59.999s", -(730 * std.time.ns_per_day - 1)); // 365d = 52w1d
1974 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);
1975 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));
1976 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);
1977 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));
1978 try testDurationCaseSigned("1y1h999.999us", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms - 1);
1979 try testDurationCaseSigned("-1y1h999.999us", -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms - 1));
1980 try testDurationCaseSigned("1y1h1ms", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms);
1981 try testDurationCaseSigned("-1y1h1ms", -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms));
1982 try testDurationCaseSigned("1y1h1ms", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms + 1);
1983 try testDurationCaseSigned("-1y1h1ms", -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms + 1));
1984 try testDurationCaseSigned("1y1m999ns", 365 * std.time.ns_per_day + std.time.ns_per_min + 999);
1985 try testDurationCaseSigned("-1y1m999ns", -(365 * std.time.ns_per_day + std.time.ns_per_min + 999));
1986 try testDurationCaseSigned("292y24w3d23h47m16.854s", std.math.maxInt(i64));
1987 try testDurationCaseSigned("-292y24w3d23h47m16.854s", std.math.minInt(i64) + 1);
1988 try testDurationCaseSigned("-292y24w3d23h47m16.854s", std.math.minInt(i64));
1989
1990 try testing.expectFmt("=======0ns", "{D:=>10}", .{0});
1991 try testing.expectFmt("1ns=======", "{D:=<10}", .{1});
1992 try testing.expectFmt("-1ns======", "{D:=<10}", .{-(1)});
1993 try testing.expectFmt(" -999ns ", "{D:^10}", .{-(std.time.ns_per_us - 1)});
1994}
1995
1996fn testDurationCase(expected: []const u8, input: u64) !void {
1997 var buf: [24]u8 = undefined;
1998 var w: Writer = .fixed(&buf);
1999 try w.printDurationUnsigned(input);
2000 try testing.expectEqualStrings(expected, w.buffered());
2001}
2002
2003fn testDurationCaseSigned(expected: []const u8, input: i64) !void {
2004 var buf: [24]u8 = undefined;
2005 var w: Writer = .fixed(&buf);
2006 try w.printDurationSigned(input);
2007 try testing.expectEqualStrings(expected, w.buffered());
2008}
2009
2010test printInt {
2011 try testPrintIntCase("-1", @as(i1, -1), 10, .lower, .{});
2012
2013 try testPrintIntCase("-101111000110000101001110", @as(i32, -12345678), 2, .lower, .{});
2014 try testPrintIntCase("-12345678", @as(i32, -12345678), 10, .lower, .{});
2015 try testPrintIntCase("-bc614e", @as(i32, -12345678), 16, .lower, .{});
2016 try testPrintIntCase("-BC614E", @as(i32, -12345678), 16, .upper, .{});
2017
2018 try testPrintIntCase("12345678", @as(u32, 12345678), 10, .upper, .{});
2019
2020 try testPrintIntCase(" 666", @as(u32, 666), 10, .lower, .{ .width = 6 });
2021 try testPrintIntCase(" 1234", @as(u32, 0x1234), 16, .lower, .{ .width = 6 });
2022 try testPrintIntCase("1234", @as(u32, 0x1234), 16, .lower, .{ .width = 1 });
2023
2024 try testPrintIntCase("+42", @as(i32, 42), 10, .lower, .{ .width = 3 });
2025 try testPrintIntCase("-42", @as(i32, -42), 10, .lower, .{ .width = 3 });
2026
2027 try testPrintIntCase("123456789123456789", @as(comptime_int, 123456789123456789), 10, .lower, .{});
2028}
2029
2030test "printFloat with comptime_float" {
2031 var buf: [20]u8 = undefined;
2032 var w: Writer = .fixed(&buf);
2033 try w.printFloat(@as(comptime_float, 1.0), std.fmt.Options.toNumber(.{}, .scientific, .lower));
2034 try testing.expectEqualStrings(w.buffered(), "1e0");
2035 try testing.expectFmt("1", "{}", .{1.0});
2036}
2037
2038fn testPrintIntCase(expected: []const u8, value: anytype, base: u8, case: std.fmt.Case, options: std.fmt.Options) !void {
2039 var buffer: [100]u8 = undefined;
2040 var w: Writer = .fixed(&buffer);
2041 try w.printInt(value, base, case, options);
2042 try testing.expectEqualStrings(expected, w.buffered());
2043}
2044
2045test printByteSize {
2046 try testing.expectFmt("file size: 42B\n", "file size: {B}\n", .{42});
2047 try testing.expectFmt("file size: 42B\n", "file size: {Bi}\n", .{42});
2048 try testing.expectFmt("file size: 63MB\n", "file size: {B}\n", .{63 * 1000 * 1000});
2049 try testing.expectFmt("file size: 63MiB\n", "file size: {Bi}\n", .{63 * 1024 * 1024});
2050 try testing.expectFmt("file size: 42B\n", "file size: {B:.2}\n", .{42});
2051 try testing.expectFmt("file size: 42B\n", "file size: {B:>9.2}\n", .{42});
2052 try testing.expectFmt("file size: 66.06MB\n", "file size: {B:.2}\n", .{63 * 1024 * 1024});
2053 try testing.expectFmt("file size: 60.08MiB\n", "file size: {Bi:.2}\n", .{63 * 1000 * 1000});
2054 try testing.expectFmt("file size: =66.06MB=\n", "file size: {B:=^9.2}\n", .{63 * 1024 * 1024});
2055 try testing.expectFmt("file size: 66.06MB\n", "file size: {B: >9.2}\n", .{63 * 1024 * 1024});
2056 try testing.expectFmt("file size: 66.06MB \n", "file size: {B: <9.2}\n", .{63 * 1024 * 1024});
2057 try testing.expectFmt("file size: 0.01844674407370955ZB\n", "file size: {B}\n", .{std.math.maxInt(u64)});
2058}
2059
2060test "bytes.hex" {
2061 const some_bytes = "\xCA\xFE\xBA\xBE";
2062 try testing.expectFmt("lowercase: cafebabe\n", "lowercase: {x}\n", .{some_bytes});
2063 try testing.expectFmt("uppercase: CAFEBABE\n", "uppercase: {X}\n", .{some_bytes});
2064 try testing.expectFmt("uppercase: CAFE\n", "uppercase: {X}\n", .{some_bytes[0..2]});
2065 try testing.expectFmt("lowercase: babe\n", "lowercase: {x}\n", .{some_bytes[2..]});
2066 const bytes_with_zeros = "\x00\x0E\xBA\xBE";
2067 try testing.expectFmt("lowercase: 000ebabe\n", "lowercase: {x}\n", .{bytes_with_zeros});
2068}
2069
2070test fixed {
2071 {
2072 var buf: [255]u8 = undefined;
2073 var w: Writer = .fixed(&buf);
2074 try w.print("{s}{s}!", .{ "Hello", "World" });
2075 try testing.expectEqualStrings("HelloWorld!", w.buffered());
2076 }
2077
2078 comptime {
2079 var buf: [255]u8 = undefined;
2080 var w: Writer = .fixed(&buf);
2081 try w.print("{s}{s}!", .{ "Hello", "World" });
2082 try testing.expectEqualStrings("HelloWorld!", w.buffered());
2083 }
2084}
2085
2086test "fixed output" {
2087 var buffer: [10]u8 = undefined;
2088 var w: Writer = .fixed(&buffer);
2089
2090 try w.writeAll("Hello");
2091 try testing.expect(std.mem.eql(u8, w.buffered(), "Hello"));
2092
2093 try w.writeAll("world");
2094 try testing.expect(std.mem.eql(u8, w.buffered(), "Helloworld"));
2095
2096 try testing.expectError(error.WriteFailed, w.writeAll("!"));
2097 try testing.expect(std.mem.eql(u8, w.buffered(), "Helloworld"));
2098
2099 w = .fixed(&buffer);
2100
2101 try testing.expect(w.buffered().len == 0);
2102
2103 try testing.expectError(error.WriteFailed, w.writeAll("Hello world!"));
2104 try testing.expect(std.mem.eql(u8, w.buffered(), "Hello worl"));
2105}
2106
2107test "writeSplat 0 len splat larger than capacity" {
2108 var buf: [8]u8 = undefined;
2109 var w: std.io.Writer = .fixed(&buf);
2110 const n = try w.writeSplat(&.{"something that overflows buf"}, 0);
2111 try testing.expectEqual(0, n);
2112}
2113
2114pub fn failingDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
2115 _ = w;
2116 _ = data;
2117 _ = splat;
2118 return error.WriteFailed;
2119}
2120
2121pub fn failingSendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize {
2122 _ = w;
2123 _ = file_reader;
2124 _ = limit;
2125 return error.WriteFailed;
2126}
2127
2128pub const Discarding = struct {
2129 count: u64,
2130 writer: Writer,
2131
2132 pub fn init(buffer: []u8) Discarding {
2133 return .{
2134 .count = 0,
2135 .writer = .{
2136 .vtable = &.{
2137 .drain = Discarding.drain,
2138 .sendFile = Discarding.sendFile,
2139 },
2140 .buffer = buffer,
2141 },
2142 };
2143 }
2144
2145 pub fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
2146 const d: *Discarding = @alignCast(@fieldParentPtr("writer", w));
2147 const slice = data[0 .. data.len - 1];
2148 const pattern = data[slice.len..];
2149 var written: usize = pattern.len * splat;
2150 for (slice) |bytes| written += bytes.len;
2151 d.count += w.end + written;
2152 w.end = 0;
2153 return written;
2154 }
2155
2156 pub fn sendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize {
2157 if (File.Handle == void) return error.Unimplemented;
2158 const d: *Discarding = @alignCast(@fieldParentPtr("writer", w));
2159 d.count += w.end;
2160 w.end = 0;
2161 if (file_reader.getSize()) |size| {
2162 const n = limit.minInt64(size - file_reader.pos);
2163 file_reader.seekBy(@intCast(n)) catch return error.Unimplemented;
2164 w.end = 0;
2165 d.count += n;
2166 return n;
2167 } else |_| {
2168 // Error is observable on `file_reader` instance, and it is better to
2169 // treat the file as a pipe.
2170 return error.Unimplemented;
2171 }
2172 }
2173};
2174
2175/// Removes the first `n` bytes from `buffer` by shifting buffer contents,
2176/// returning how many bytes are left after consuming the entire buffer, or
2177/// zero if the entire buffer was not consumed.
2178///
2179/// Useful for `VTable.drain` function implementations to implement partial
2180/// drains.
2181pub fn consume(w: *Writer, n: usize) usize {
2182 if (n < w.end) {
2183 const remaining = w.buffer[n..w.end];
2184 @memmove(w.buffer[0..remaining.len], remaining);
2185 w.end = remaining.len;
2186 return 0;
2187 }
2188 defer w.end = 0;
2189 return n - w.end;
2190}
2191
2192/// Shortcut for setting `end` to zero and returning zero. Equivalent to
2193/// calling `consume` with `end`.
2194pub fn consumeAll(w: *Writer) usize {
2195 w.end = 0;
2196 return 0;
2197}
2198
2199/// For use when the `Writer` implementation can cannot offer a more efficient
2200/// implementation than a basic read/write loop on the file.
2201pub fn unimplementedSendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize {
2202 _ = w;
2203 _ = file_reader;
2204 _ = limit;
2205 return error.Unimplemented;
2206}
2207
2208/// When this function is called it usually means the buffer got full, so it's
2209/// time to return an error. However, we still need to make sure all of the
2210/// available buffer has been filled. Also, it may be called from `flush` in
2211/// which case it should return successfully.
2212pub fn fixedDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
2213 if (data.len == 0) return 0;
2214 for (data[0 .. data.len - 1]) |bytes| {
2215 const dest = w.buffer[w.end..];
2216 const len = @min(bytes.len, dest.len);
2217 @memcpy(dest[0..len], bytes[0..len]);
2218 w.end += len;
2219 if (bytes.len > dest.len) return error.WriteFailed;
2220 }
2221 const pattern = data[data.len - 1];
2222 const dest = w.buffer[w.end..];
2223 switch (pattern.len) {
2224 0 => return w.end,
2225 1 => {
2226 assert(splat >= dest.len);
2227 @memset(dest, pattern[0]);
2228 w.end += dest.len;
2229 return error.WriteFailed;
2230 },
2231 else => {
2232 for (0..splat) |i| {
2233 const remaining = dest[i * pattern.len ..];
2234 const len = @min(pattern.len, remaining.len);
2235 @memcpy(remaining[0..len], pattern[0..len]);
2236 w.end += len;
2237 if (pattern.len > remaining.len) return error.WriteFailed;
2238 }
2239 unreachable;
2240 },
2241 }
2242}
2243
2244/// Provides a `Writer` implementation based on calling `Hasher.update`, sending
2245/// all data also to an underlying `Writer`.
2246///
2247/// When using this, the underlying writer is best unbuffered because all
2248/// writes are passed on directly to it.
2249///
2250/// This implementation makes suboptimal buffering decisions due to being
2251/// generic. A better solution will involve creating a writer for each hash
2252/// function, where the splat buffer can be tailored to the hash implementation
2253/// details.
2254pub fn Hashed(comptime Hasher: type) type {
2255 return struct {
2256 out: *Writer,
2257 hasher: Hasher,
2258 writer: Writer,
2259
2260 pub fn init(out: *Writer, buffer: []u8) @This() {
2261 return .initHasher(out, .{}, buffer);
2262 }
2263
2264 pub fn initHasher(out: *Writer, hasher: Hasher, buffer: []u8) @This() {
2265 return .{
2266 .out = out,
2267 .hasher = hasher,
2268 .writer = .{
2269 .buffer = buffer,
2270 .vtable = &.{ .drain = @This().drain },
2271 },
2272 };
2273 }
2274
2275 fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
2276 const this: *@This() = @alignCast(@fieldParentPtr("writer", w));
2277 const aux = w.buffered();
2278 const aux_n = try this.out.writeSplatHeader(aux, data, splat);
2279 if (aux_n < w.end) {
2280 this.hasher.update(w.buffer[0..aux_n]);
2281 const remaining = w.buffer[aux_n..w.end];
2282 @memmove(w.buffer[0..remaining.len], remaining);
2283 w.end = remaining.len;
2284 return 0;
2285 }
2286 this.hasher.update(aux);
2287 const n = aux_n - w.end;
2288 w.end = 0;
2289 var remaining: usize = n;
2290 for (data[0 .. data.len - 1]) |slice| {
2291 if (remaining <= slice.len) {
2292 this.hasher.update(slice[0..remaining]);
2293 return n;
2294 }
2295 remaining -= slice.len;
2296 this.hasher.update(slice);
2297 }
2298 const pattern = data[data.len - 1];
2299 assert(remaining == splat * pattern.len);
2300 switch (pattern.len) {
2301 0 => {
2302 assert(remaining == 0);
2303 },
2304 1 => {
2305 var buffer: [64]u8 = undefined;
2306 @memset(&buffer, pattern[0]);
2307 while (remaining > 0) {
2308 const update_len = @min(remaining, buffer.len);
2309 this.hasher.update(buffer[0..update_len]);
2310 remaining -= update_len;
2311 }
2312 },
2313 else => {
2314 while (remaining > 0) {
2315 const update_len = @min(remaining, pattern.len);
2316 this.hasher.update(pattern[0..update_len]);
2317 remaining -= update_len;
2318 }
2319 },
2320 }
2321 return n;
2322 }
2323 };
2324}
2325
2326/// Maintains `Writer` state such that it writes to the unused capacity of an
2327/// array list, filling it up completely before making a call through the
2328/// vtable, causing a resize. Consequently, the same, optimized, non-generic
2329/// machine code that uses `std.io.Reader`, such as formatted printing, takes
2330/// the hot paths when using this API.
2331///
2332/// When using this API, it is not necessary to call `flush`.
2333pub const Allocating = struct {
2334 allocator: Allocator,
2335 writer: Writer,
2336
2337 pub fn init(allocator: Allocator) Allocating {
2338 return .{
2339 .allocator = allocator,
2340 .writer = .{
2341 .buffer = &.{},
2342 .vtable = &vtable,
2343 },
2344 };
2345 }
2346
2347 pub fn initCapacity(allocator: Allocator, capacity: usize) error{OutOfMemory}!Allocating {
2348 return .{
2349 .allocator = allocator,
2350 .writer = .{
2351 .buffer = try allocator.alloc(u8, capacity),
2352 .vtable = &vtable,
2353 },
2354 };
2355 }
2356
2357 pub fn initOwnedSlice(allocator: Allocator, slice: []u8) Allocating {
2358 return .{
2359 .allocator = allocator,
2360 .writer = .{
2361 .buffer = slice,
2362 .vtable = &vtable,
2363 },
2364 };
2365 }
2366
2367 /// Replaces `array_list` with empty, taking ownership of the memory.
2368 pub fn fromArrayList(allocator: Allocator, array_list: *std.ArrayListUnmanaged(u8)) Allocating {
2369 defer array_list.* = .empty;
2370 return .{
2371 .allocator = allocator,
2372 .writer = .{
2373 .vtable = &vtable,
2374 .buffer = array_list.allocatedSlice(),
2375 .end = array_list.items.len,
2376 },
2377 };
2378 }
2379
2380 const vtable: VTable = .{
2381 .drain = Allocating.drain,
2382 .sendFile = Allocating.sendFile,
2383 .flush = noopFlush,
2384 };
2385
2386 pub fn deinit(a: *Allocating) void {
2387 a.allocator.free(a.writer.buffer);
2388 a.* = undefined;
2389 }
2390
2391 /// Returns an array list that takes ownership of the allocated memory.
2392 /// Resets the `Allocating` to an empty state.
2393 pub fn toArrayList(a: *Allocating) std.ArrayListUnmanaged(u8) {
2394 const w = &a.writer;
2395 const result: std.ArrayListUnmanaged(u8) = .{
2396 .items = w.buffer[0..w.end],
2397 .capacity = w.buffer.len,
2398 };
2399 w.buffer = &.{};
2400 w.end = 0;
2401 return result;
2402 }
2403
2404 pub fn toOwnedSlice(a: *Allocating) error{OutOfMemory}![]u8 {
2405 var list = a.toArrayList();
2406 return list.toOwnedSlice(a.allocator);
2407 }
2408
2409 pub fn toOwnedSliceSentinel(a: *Allocating, comptime sentinel: u8) error{OutOfMemory}![:sentinel]u8 {
2410 const gpa = a.allocator;
2411 var list = toArrayList(a);
2412 return list.toOwnedSliceSentinel(gpa, sentinel);
2413 }
2414
2415 pub fn getWritten(a: *Allocating) []u8 {
2416 return a.writer.buffered();
2417 }
2418
2419 pub fn shrinkRetainingCapacity(a: *Allocating, new_len: usize) void {
2420 a.writer.end = new_len;
2421 }
2422
2423 pub fn clearRetainingCapacity(a: *Allocating) void {
2424 a.shrinkRetainingCapacity(0);
2425 }
2426
2427 fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
2428 const a: *Allocating = @fieldParentPtr("writer", w);
2429 const gpa = a.allocator;
2430 const pattern = data[data.len - 1];
2431 const splat_len = pattern.len * splat;
2432 var list = a.toArrayList();
2433 defer setArrayList(a, list);
2434 const start_len = list.items.len;
2435 // Even if we append no data, this function needs to ensure there is more
2436 // capacity in the buffer to avoid infinite loop, hence the +1 in this loop.
2437 assert(data.len != 0);
2438 for (data) |bytes| {
2439 list.ensureUnusedCapacity(gpa, bytes.len + splat_len + 1) catch return error.WriteFailed;
2440 list.appendSliceAssumeCapacity(bytes);
2441 }
2442 if (splat == 0) {
2443 list.items.len -= pattern.len;
2444 } else switch (pattern.len) {
2445 0 => {},
2446 1 => list.appendNTimesAssumeCapacity(pattern[0], splat - 1),
2447 else => for (0..splat - 1) |_| list.appendSliceAssumeCapacity(pattern),
2448 }
2449 return list.items.len - start_len;
2450 }
2451
2452 fn sendFile(w: *Writer, file_reader: *File.Reader, limit: std.io.Limit) FileError!usize {
2453 if (File.Handle == void) return error.Unimplemented;
2454 const a: *Allocating = @fieldParentPtr("writer", w);
2455 const gpa = a.allocator;
2456 var list = a.toArrayList();
2457 defer setArrayList(a, list);
2458 const pos = file_reader.pos;
2459 const additional = if (file_reader.getSize()) |size| size - pos else |_| std.atomic.cache_line;
2460 list.ensureUnusedCapacity(gpa, limit.minInt64(additional)) catch return error.WriteFailed;
2461 const dest = limit.slice(list.unusedCapacitySlice());
2462 const n = file_reader.read(dest) catch |err| switch (err) {
2463 error.ReadFailed => return error.ReadFailed,
2464 error.EndOfStream => 0,
2465 };
2466 list.items.len += n;
2467 return n;
2468 }
2469
2470 fn setArrayList(a: *Allocating, list: std.ArrayListUnmanaged(u8)) void {
2471 a.writer.buffer = list.allocatedSlice();
2472 a.writer.end = list.items.len;
2473 }
2474
2475 test Allocating {
2476 var a: Allocating = .init(testing.allocator);
2477 defer a.deinit();
2478 const w = &a.writer;
2479
2480 const x: i32 = 42;
2481 const y: i32 = 1234;
2482 try w.print("x: {}\ny: {}\n", .{ x, y });
2483
2484 try testing.expectEqualSlices(u8, "x: 42\ny: 1234\n", a.getWritten());
2485 }
2486};
lib/std/Io/bit_reader.zig created+238
...@@ -0,0 +1,238 @@
1const std = @import("../std.zig");
2
3//General note on endianess:
4//Big endian is packed starting in the most significant part of the byte and subsequent
5// bytes contain less significant bits. Thus we always take bits from the high
6// end and place them below existing bits in our output.
7//Little endian is packed starting in the least significant part of the byte and
8// subsequent bytes contain more significant bits. Thus we always take bits from
9// the low end and place them above existing bits in our output.
10//Regardless of endianess, within any given byte the bits are always in most
11// to least significant order.
12//Also regardless of endianess, the buffer always aligns bits to the low end
13// of the byte.
14
15/// Creates a bit reader which allows for reading bits from an underlying standard reader
16pub fn BitReader(comptime endian: std.builtin.Endian, comptime Reader: type) type {
17 return struct {
18 reader: Reader,
19 bits: u8 = 0,
20 count: u4 = 0,
21
22 const low_bit_mask = [9]u8{
23 0b00000000,
24 0b00000001,
25 0b00000011,
26 0b00000111,
27 0b00001111,
28 0b00011111,
29 0b00111111,
30 0b01111111,
31 0b11111111,
32 };
33
34 fn Bits(comptime T: type) type {
35 return struct {
36 T,
37 u16,
38 };
39 }
40
41 fn initBits(comptime T: type, out: anytype, num: u16) Bits(T) {
42 const UT = std.meta.Int(.unsigned, @bitSizeOf(T));
43 return .{
44 @bitCast(@as(UT, @intCast(out))),
45 num,
46 };
47 }
48
49 /// Reads `bits` bits from the reader and returns a specified type
50 /// containing them in the least significant end, returning an error if the
51 /// specified number of bits could not be read.
52 pub fn readBitsNoEof(self: *@This(), comptime T: type, num: u16) !T {
53 const b, const c = try self.readBitsTuple(T, num);
54 if (c < num) return error.EndOfStream;
55 return b;
56 }
57
58 /// Reads `bits` bits from the reader and returns a specified type
59 /// containing them in the least significant end. The number of bits successfully
60 /// read is placed in `out_bits`, as reaching the end of the stream is not an error.
61 pub fn readBits(self: *@This(), comptime T: type, num: u16, out_bits: *u16) !T {
62 const b, const c = try self.readBitsTuple(T, num);
63 out_bits.* = c;
64 return b;
65 }
66
67 /// Reads `bits` bits from the reader and returns a tuple of the specified type
68 /// containing them in the least significant end, and the number of bits successfully
69 /// read. Reaching the end of the stream is not an error.
70 pub fn readBitsTuple(self: *@This(), comptime T: type, num: u16) !Bits(T) {
71 const UT = std.meta.Int(.unsigned, @bitSizeOf(T));
72 const U = if (@bitSizeOf(T) < 8) u8 else UT; //it is a pain to work with <u8
73
74 //dump any bits in our buffer first
75 if (num <= self.count) return initBits(T, self.removeBits(@intCast(num)), num);
76
77 var out_count: u16 = self.count;
78 var out: U = self.removeBits(self.count);
79
80 //grab all the full bytes we need and put their
81 //bits where they belong
82 const full_bytes_left = (num - out_count) / 8;
83
84 for (0..full_bytes_left) |_| {
85 const byte = self.reader.readByte() catch |err| switch (err) {
86 error.EndOfStream => return initBits(T, out, out_count),
87 else => |e| return e,
88 };
89
90 switch (endian) {
91 .big => {
92 if (U == u8) out = 0 else out <<= 8; //shifting u8 by 8 is illegal in Zig
93 out |= byte;
94 },
95 .little => {
96 const pos = @as(U, byte) << @intCast(out_count);
97 out |= pos;
98 },
99 }
100 out_count += 8;
101 }
102
103 const bits_left = num - out_count;
104 const keep = 8 - bits_left;
105
106 if (bits_left == 0) return initBits(T, out, out_count);
107
108 const final_byte = self.reader.readByte() catch |err| switch (err) {
109 error.EndOfStream => return initBits(T, out, out_count),
110 else => |e| return e,
111 };
112
113 switch (endian) {
114 .big => {
115 out <<= @intCast(bits_left);
116 out |= final_byte >> @intCast(keep);
117 self.bits = final_byte & low_bit_mask[keep];
118 },
119 .little => {
120 const pos = @as(U, final_byte & low_bit_mask[bits_left]) << @intCast(out_count);
121 out |= pos;
122 self.bits = final_byte >> @intCast(bits_left);
123 },
124 }
125
126 self.count = @intCast(keep);
127 return initBits(T, out, num);
128 }
129
130 //convenience function for removing bits from
131 //the appropriate part of the buffer based on
132 //endianess.
133 fn removeBits(self: *@This(), num: u4) u8 {
134 if (num == 8) {
135 self.count = 0;
136 return self.bits;
137 }
138
139 const keep = self.count - num;
140 const bits = switch (endian) {
141 .big => self.bits >> @intCast(keep),
142 .little => self.bits & low_bit_mask[num],
143 };
144 switch (endian) {
145 .big => self.bits &= low_bit_mask[keep],
146 .little => self.bits >>= @intCast(num),
147 }
148
149 self.count = keep;
150 return bits;
151 }
152
153 pub fn alignToByte(self: *@This()) void {
154 self.bits = 0;
155 self.count = 0;
156 }
157 };
158}
159
160pub fn bitReader(comptime endian: std.builtin.Endian, reader: anytype) BitReader(endian, @TypeOf(reader)) {
161 return .{ .reader = reader };
162}
163
164///////////////////////////////
165
166test "api coverage" {
167 const mem_be = [_]u8{ 0b11001101, 0b00001011 };
168 const mem_le = [_]u8{ 0b00011101, 0b10010101 };
169
170 var mem_in_be = std.io.fixedBufferStream(&mem_be);
171 var bit_stream_be = bitReader(.big, mem_in_be.reader());
172
173 var out_bits: u16 = undefined;
174
175 const expect = std.testing.expect;
176 const expectError = std.testing.expectError;
177
178 try expect(1 == try bit_stream_be.readBits(u2, 1, &out_bits));
179 try expect(out_bits == 1);
180 try expect(2 == try bit_stream_be.readBits(u5, 2, &out_bits));
181 try expect(out_bits == 2);
182 try expect(3 == try bit_stream_be.readBits(u128, 3, &out_bits));
183 try expect(out_bits == 3);
184 try expect(4 == try bit_stream_be.readBits(u8, 4, &out_bits));
185 try expect(out_bits == 4);
186 try expect(5 == try bit_stream_be.readBits(u9, 5, &out_bits));
187 try expect(out_bits == 5);
188 try expect(1 == try bit_stream_be.readBits(u1, 1, &out_bits));
189 try expect(out_bits == 1);
190
191 mem_in_be.pos = 0;
192 bit_stream_be.count = 0;
193 try expect(0b110011010000101 == try bit_stream_be.readBits(u15, 15, &out_bits));
194 try expect(out_bits == 15);
195
196 mem_in_be.pos = 0;
197 bit_stream_be.count = 0;
198 try expect(0b1100110100001011 == try bit_stream_be.readBits(u16, 16, &out_bits));
199 try expect(out_bits == 16);
200
201 _ = try bit_stream_be.readBits(u0, 0, &out_bits);
202
203 try expect(0 == try bit_stream_be.readBits(u1, 1, &out_bits));
204 try expect(out_bits == 0);
205 try expectError(error.EndOfStream, bit_stream_be.readBitsNoEof(u1, 1));
206
207 var mem_in_le = std.io.fixedBufferStream(&mem_le);
208 var bit_stream_le = bitReader(.little, mem_in_le.reader());
209
210 try expect(1 == try bit_stream_le.readBits(u2, 1, &out_bits));
211 try expect(out_bits == 1);
212 try expect(2 == try bit_stream_le.readBits(u5, 2, &out_bits));
213 try expect(out_bits == 2);
214 try expect(3 == try bit_stream_le.readBits(u128, 3, &out_bits));
215 try expect(out_bits == 3);
216 try expect(4 == try bit_stream_le.readBits(u8, 4, &out_bits));
217 try expect(out_bits == 4);
218 try expect(5 == try bit_stream_le.readBits(u9, 5, &out_bits));
219 try expect(out_bits == 5);
220 try expect(1 == try bit_stream_le.readBits(u1, 1, &out_bits));
221 try expect(out_bits == 1);
222
223 mem_in_le.pos = 0;
224 bit_stream_le.count = 0;
225 try expect(0b001010100011101 == try bit_stream_le.readBits(u15, 15, &out_bits));
226 try expect(out_bits == 15);
227
228 mem_in_le.pos = 0;
229 bit_stream_le.count = 0;
230 try expect(0b1001010100011101 == try bit_stream_le.readBits(u16, 16, &out_bits));
231 try expect(out_bits == 16);
232
233 _ = try bit_stream_le.readBits(u0, 0, &out_bits);
234
235 try expect(0 == try bit_stream_le.readBits(u1, 1, &out_bits));
236 try expect(out_bits == 0);
237 try expectError(error.EndOfStream, bit_stream_le.readBitsNoEof(u1, 1));
238}
lib/std/Io/bit_writer.zig created+179
...@@ -0,0 +1,179 @@
1const std = @import("../std.zig");
2
3//General note on endianess:
4//Big endian is packed starting in the most significant part of the byte and subsequent
5// bytes contain less significant bits. Thus we write out bits from the high end
6// of our input first.
7//Little endian is packed starting in the least significant part of the byte and
8// subsequent bytes contain more significant bits. Thus we write out bits from
9// the low end of our input first.
10//Regardless of endianess, within any given byte the bits are always in most
11// to least significant order.
12//Also regardless of endianess, the buffer always aligns bits to the low end
13// of the byte.
14
15/// Creates a bit writer which allows for writing bits to an underlying standard writer
16pub fn BitWriter(comptime endian: std.builtin.Endian, comptime Writer: type) type {
17 return struct {
18 writer: Writer,
19 bits: u8 = 0,
20 count: u4 = 0,
21
22 const low_bit_mask = [9]u8{
23 0b00000000,
24 0b00000001,
25 0b00000011,
26 0b00000111,
27 0b00001111,
28 0b00011111,
29 0b00111111,
30 0b01111111,
31 0b11111111,
32 };
33
34 /// Write the specified number of bits to the writer from the least significant bits of
35 /// the specified value. Bits will only be written to the writer when there
36 /// are enough to fill a byte.
37 pub fn writeBits(self: *@This(), value: anytype, num: u16) !void {
38 const T = @TypeOf(value);
39 const UT = std.meta.Int(.unsigned, @bitSizeOf(T));
40 const U = if (@bitSizeOf(T) < 8) u8 else UT; //<u8 is a pain to work with
41
42 var in: U = @as(UT, @bitCast(value));
43 var in_count: u16 = num;
44
45 if (self.count > 0) {
46 //if we can't fill the buffer, add what we have
47 const bits_free = 8 - self.count;
48 if (num < bits_free) {
49 self.addBits(@truncate(in), @intCast(num));
50 return;
51 }
52
53 //finish filling the buffer and flush it
54 if (num == bits_free) {
55 self.addBits(@truncate(in), @intCast(num));
56 return self.flushBits();
57 }
58
59 switch (endian) {
60 .big => {
61 const bits = in >> @intCast(in_count - bits_free);
62 self.addBits(@truncate(bits), bits_free);
63 },
64 .little => {
65 self.addBits(@truncate(in), bits_free);
66 in >>= @intCast(bits_free);
67 },
68 }
69 in_count -= bits_free;
70 try self.flushBits();
71 }
72
73 //write full bytes while we can
74 const full_bytes_left = in_count / 8;
75 for (0..full_bytes_left) |_| {
76 switch (endian) {
77 .big => {
78 const bits = in >> @intCast(in_count - 8);
79 try self.writer.writeByte(@truncate(bits));
80 },
81 .little => {
82 try self.writer.writeByte(@truncate(in));
83 if (U == u8) in = 0 else in >>= 8;
84 },
85 }
86 in_count -= 8;
87 }
88
89 //save the remaining bits in the buffer
90 self.addBits(@truncate(in), @intCast(in_count));
91 }
92
93 //convenience funciton for adding bits to the buffer
94 //in the appropriate position based on endianess
95 fn addBits(self: *@This(), bits: u8, num: u4) void {
96 if (num == 8) self.bits = bits else switch (endian) {
97 .big => {
98 self.bits <<= @intCast(num);
99 self.bits |= bits & low_bit_mask[num];
100 },
101 .little => {
102 const pos = bits << @intCast(self.count);
103 self.bits |= pos;
104 },
105 }
106 self.count += num;
107 }
108
109 /// Flush any remaining bits to the writer, filling
110 /// unused bits with 0s.
111 pub fn flushBits(self: *@This()) !void {
112 if (self.count == 0) return;
113 if (endian == .big) self.bits <<= @intCast(8 - self.count);
114 try self.writer.writeByte(self.bits);
115 self.bits = 0;
116 self.count = 0;
117 }
118 };
119}
120
121pub fn bitWriter(comptime endian: std.builtin.Endian, writer: anytype) BitWriter(endian, @TypeOf(writer)) {
122 return .{ .writer = writer };
123}
124
125///////////////////////////////
126
127test "api coverage" {
128 var mem_be = [_]u8{0} ** 2;
129 var mem_le = [_]u8{0} ** 2;
130
131 var mem_out_be = std.io.fixedBufferStream(&mem_be);
132 var bit_stream_be = bitWriter(.big, mem_out_be.writer());
133
134 const testing = std.testing;
135
136 try bit_stream_be.writeBits(@as(u2, 1), 1);
137 try bit_stream_be.writeBits(@as(u5, 2), 2);
138 try bit_stream_be.writeBits(@as(u128, 3), 3);
139 try bit_stream_be.writeBits(@as(u8, 4), 4);
140 try bit_stream_be.writeBits(@as(u9, 5), 5);
141 try bit_stream_be.writeBits(@as(u1, 1), 1);
142
143 try testing.expect(mem_be[0] == 0b11001101 and mem_be[1] == 0b00001011);
144
145 mem_out_be.pos = 0;
146
147 try bit_stream_be.writeBits(@as(u15, 0b110011010000101), 15);
148 try bit_stream_be.flushBits();
149 try testing.expect(mem_be[0] == 0b11001101 and mem_be[1] == 0b00001010);
150
151 mem_out_be.pos = 0;
152 try bit_stream_be.writeBits(@as(u32, 0b110011010000101), 16);
153 try testing.expect(mem_be[0] == 0b01100110 and mem_be[1] == 0b10000101);
154
155 try bit_stream_be.writeBits(@as(u0, 0), 0);
156
157 var mem_out_le = std.io.fixedBufferStream(&mem_le);
158 var bit_stream_le = bitWriter(.little, mem_out_le.writer());
159
160 try bit_stream_le.writeBits(@as(u2, 1), 1);
161 try bit_stream_le.writeBits(@as(u5, 2), 2);
162 try bit_stream_le.writeBits(@as(u128, 3), 3);
163 try bit_stream_le.writeBits(@as(u8, 4), 4);
164 try bit_stream_le.writeBits(@as(u9, 5), 5);
165 try bit_stream_le.writeBits(@as(u1, 1), 1);
166
167 try testing.expect(mem_le[0] == 0b00011101 and mem_le[1] == 0b10010101);
168
169 mem_out_le.pos = 0;
170 try bit_stream_le.writeBits(@as(u15, 0b110011010000101), 15);
171 try bit_stream_le.flushBits();
172 try testing.expect(mem_le[0] == 0b10000101 and mem_le[1] == 0b01100110);
173
174 mem_out_le.pos = 0;
175 try bit_stream_le.writeBits(@as(u32, 0b1100110100001011), 16);
176 try testing.expect(mem_le[0] == 0b00001011 and mem_le[1] == 0b11001101);
177
178 try bit_stream_le.writeBits(@as(u0, 0), 0);
179}
lib/std/Io/buffered_atomic_file.zig created+55
...@@ -0,0 +1,55 @@
1const std = @import("../std.zig");
2const mem = std.mem;
3const fs = std.fs;
4const File = std.fs.File;
5
6pub const BufferedAtomicFile = struct {
7 atomic_file: fs.AtomicFile,
8 file_writer: File.Writer,
9 buffered_writer: BufferedWriter,
10 allocator: mem.Allocator,
11
12 pub const buffer_size = 4096;
13 pub const BufferedWriter = std.io.BufferedWriter(buffer_size, File.Writer);
14 pub const Writer = std.io.GenericWriter(*BufferedWriter, BufferedWriter.Error, BufferedWriter.write);
15
16 /// TODO when https://github.com/ziglang/zig/issues/2761 is solved
17 /// this API will not need an allocator
18 pub fn create(
19 allocator: mem.Allocator,
20 dir: fs.Dir,
21 dest_path: []const u8,
22 atomic_file_options: fs.Dir.AtomicFileOptions,
23 ) !*BufferedAtomicFile {
24 var self = try allocator.create(BufferedAtomicFile);
25 self.* = BufferedAtomicFile{
26 .atomic_file = undefined,
27 .file_writer = undefined,
28 .buffered_writer = undefined,
29 .allocator = allocator,
30 };
31 errdefer allocator.destroy(self);
32
33 self.atomic_file = try dir.atomicFile(dest_path, atomic_file_options);
34 errdefer self.atomic_file.deinit();
35
36 self.file_writer = self.atomic_file.file.deprecatedWriter();
37 self.buffered_writer = .{ .unbuffered_writer = self.file_writer };
38 return self;
39 }
40
41 /// always call destroy, even after successful finish()
42 pub fn destroy(self: *BufferedAtomicFile) void {
43 self.atomic_file.deinit();
44 self.allocator.destroy(self);
45 }
46
47 pub fn finish(self: *BufferedAtomicFile) !void {
48 try self.buffered_writer.flush();
49 try self.atomic_file.finish();
50 }
51
52 pub fn writer(self: *BufferedAtomicFile) Writer {
53 return .{ .context = &self.buffered_writer };
54 }
55};
lib/std/Io/buffered_reader.zig created+201
...@@ -0,0 +1,201 @@
1const std = @import("../std.zig");
2const io = std.io;
3const mem = std.mem;
4const assert = std.debug.assert;
5const testing = std.testing;
6
7pub fn BufferedReader(comptime buffer_size: usize, comptime ReaderType: type) type {
8 return struct {
9 unbuffered_reader: ReaderType,
10 buf: [buffer_size]u8 = undefined,
11 start: usize = 0,
12 end: usize = 0,
13
14 pub const Error = ReaderType.Error;
15 pub const Reader = io.GenericReader(*Self, Error, read);
16
17 const Self = @This();
18
19 pub fn read(self: *Self, dest: []u8) Error!usize {
20 // First try reading from the already buffered data onto the destination.
21 const current = self.buf[self.start..self.end];
22 if (current.len != 0) {
23 const to_transfer = @min(current.len, dest.len);
24 @memcpy(dest[0..to_transfer], current[0..to_transfer]);
25 self.start += to_transfer;
26 return to_transfer;
27 }
28
29 // If dest is large, read from the unbuffered reader directly into the destination.
30 if (dest.len >= buffer_size) {
31 return self.unbuffered_reader.read(dest);
32 }
33
34 // If dest is small, read from the unbuffered reader into our own internal buffer,
35 // and then transfer to destination.
36 self.end = try self.unbuffered_reader.read(&self.buf);
37 const to_transfer = @min(self.end, dest.len);
38 @memcpy(dest[0..to_transfer], self.buf[0..to_transfer]);
39 self.start = to_transfer;
40 return to_transfer;
41 }
42
43 pub fn reader(self: *Self) Reader {
44 return .{ .context = self };
45 }
46 };
47}
48
49pub fn bufferedReader(reader: anytype) BufferedReader(4096, @TypeOf(reader)) {
50 return .{ .unbuffered_reader = reader };
51}
52
53pub fn bufferedReaderSize(comptime size: usize, reader: anytype) BufferedReader(size, @TypeOf(reader)) {
54 return .{ .unbuffered_reader = reader };
55}
56
57test "OneByte" {
58 const OneByteReadReader = struct {
59 str: []const u8,
60 curr: usize,
61
62 const Error = error{NoError};
63 const Self = @This();
64 const Reader = io.GenericReader(*Self, Error, read);
65
66 fn init(str: []const u8) Self {
67 return Self{
68 .str = str,
69 .curr = 0,
70 };
71 }
72
73 fn read(self: *Self, dest: []u8) Error!usize {
74 if (self.str.len <= self.curr or dest.len == 0)
75 return 0;
76
77 dest[0] = self.str[self.curr];
78 self.curr += 1;
79 return 1;
80 }
81
82 fn reader(self: *Self) Reader {
83 return .{ .context = self };
84 }
85 };
86
87 const str = "This is a test";
88 var one_byte_stream = OneByteReadReader.init(str);
89 var buf_reader = bufferedReader(one_byte_stream.reader());
90 const stream = buf_reader.reader();
91
92 const res = try stream.readAllAlloc(testing.allocator, str.len + 1);
93 defer testing.allocator.free(res);
94 try testing.expectEqualSlices(u8, str, res);
95}
96
97fn smallBufferedReader(underlying_stream: anytype) BufferedReader(8, @TypeOf(underlying_stream)) {
98 return .{ .unbuffered_reader = underlying_stream };
99}
100test "Block" {
101 const BlockReader = struct {
102 block: []const u8,
103 reads_allowed: usize,
104 curr_read: usize,
105
106 const Error = error{NoError};
107 const Self = @This();
108 const Reader = io.GenericReader(*Self, Error, read);
109
110 fn init(block: []const u8, reads_allowed: usize) Self {
111 return Self{
112 .block = block,
113 .reads_allowed = reads_allowed,
114 .curr_read = 0,
115 };
116 }
117
118 fn read(self: *Self, dest: []u8) Error!usize {
119 if (self.curr_read >= self.reads_allowed) return 0;
120 @memcpy(dest[0..self.block.len], self.block);
121
122 self.curr_read += 1;
123 return self.block.len;
124 }
125
126 fn reader(self: *Self) Reader {
127 return .{ .context = self };
128 }
129 };
130
131 const block = "0123";
132
133 // len out == block
134 {
135 var test_buf_reader: BufferedReader(4, BlockReader) = .{
136 .unbuffered_reader = BlockReader.init(block, 2),
137 };
138 const reader = test_buf_reader.reader();
139 var out_buf: [4]u8 = undefined;
140 _ = try reader.readAll(&out_buf);
141 try testing.expectEqualSlices(u8, &out_buf, block);
142 _ = try reader.readAll(&out_buf);
143 try testing.expectEqualSlices(u8, &out_buf, block);
144 try testing.expectEqual(try reader.readAll(&out_buf), 0);
145 }
146
147 // len out < block
148 {
149 var test_buf_reader: BufferedReader(4, BlockReader) = .{
150 .unbuffered_reader = BlockReader.init(block, 2),
151 };
152 const reader = test_buf_reader.reader();
153 var out_buf: [3]u8 = undefined;
154 _ = try reader.readAll(&out_buf);
155 try testing.expectEqualSlices(u8, &out_buf, "012");
156 _ = try reader.readAll(&out_buf);
157 try testing.expectEqualSlices(u8, &out_buf, "301");
158 const n = try reader.readAll(&out_buf);
159 try testing.expectEqualSlices(u8, out_buf[0..n], "23");
160 try testing.expectEqual(try reader.readAll(&out_buf), 0);
161 }
162
163 // len out > block
164 {
165 var test_buf_reader: BufferedReader(4, BlockReader) = .{
166 .unbuffered_reader = BlockReader.init(block, 2),
167 };
168 const reader = test_buf_reader.reader();
169 var out_buf: [5]u8 = undefined;
170 _ = try reader.readAll(&out_buf);
171 try testing.expectEqualSlices(u8, &out_buf, "01230");
172 const n = try reader.readAll(&out_buf);
173 try testing.expectEqualSlices(u8, out_buf[0..n], "123");
174 try testing.expectEqual(try reader.readAll(&out_buf), 0);
175 }
176
177 // len out == 0
178 {
179 var test_buf_reader: BufferedReader(4, BlockReader) = .{
180 .unbuffered_reader = BlockReader.init(block, 2),
181 };
182 const reader = test_buf_reader.reader();
183 var out_buf: [0]u8 = undefined;
184 _ = try reader.readAll(&out_buf);
185 try testing.expectEqualSlices(u8, &out_buf, "");
186 }
187
188 // len bufreader buf > block
189 {
190 var test_buf_reader: BufferedReader(5, BlockReader) = .{
191 .unbuffered_reader = BlockReader.init(block, 2),
192 };
193 const reader = test_buf_reader.reader();
194 var out_buf: [4]u8 = undefined;
195 _ = try reader.readAll(&out_buf);
196 try testing.expectEqualSlices(u8, &out_buf, block);
197 _ = try reader.readAll(&out_buf);
198 try testing.expectEqualSlices(u8, &out_buf, block);
199 try testing.expectEqual(try reader.readAll(&out_buf), 0);
200 }
201}
lib/std/Io/buffered_writer.zig created+43
...@@ -0,0 +1,43 @@
1const std = @import("../std.zig");
2
3const io = std.io;
4const mem = std.mem;
5
6pub fn BufferedWriter(comptime buffer_size: usize, comptime WriterType: type) type {
7 return struct {
8 unbuffered_writer: WriterType,
9 buf: [buffer_size]u8 = undefined,
10 end: usize = 0,
11
12 pub const Error = WriterType.Error;
13 pub const Writer = io.GenericWriter(*Self, Error, write);
14
15 const Self = @This();
16
17 pub fn flush(self: *Self) !void {
18 try self.unbuffered_writer.writeAll(self.buf[0..self.end]);
19 self.end = 0;
20 }
21
22 pub fn writer(self: *Self) Writer {
23 return .{ .context = self };
24 }
25
26 pub fn write(self: *Self, bytes: []const u8) Error!usize {
27 if (self.end + bytes.len > self.buf.len) {
28 try self.flush();
29 if (bytes.len > self.buf.len)
30 return self.unbuffered_writer.write(bytes);
31 }
32
33 const new_end = self.end + bytes.len;
34 @memcpy(self.buf[self.end..new_end], bytes);
35 self.end = new_end;
36 return bytes.len;
37 }
38 };
39}
40
41pub fn bufferedWriter(underlying_stream: anytype) BufferedWriter(4096, @TypeOf(underlying_stream)) {
42 return .{ .unbuffered_writer = underlying_stream };
43}
lib/std/Io/c_writer.zig created+44
...@@ -0,0 +1,44 @@
1const std = @import("../std.zig");
2const builtin = @import("builtin");
3const io = std.io;
4const testing = std.testing;
5
6pub const CWriter = io.GenericWriter(*std.c.FILE, std.fs.File.WriteError, cWriterWrite);
7
8pub fn cWriter(c_file: *std.c.FILE) CWriter {
9 return .{ .context = c_file };
10}
11
12fn cWriterWrite(c_file: *std.c.FILE, bytes: []const u8) std.fs.File.WriteError!usize {
13 const amt_written = std.c.fwrite(bytes.ptr, 1, bytes.len, c_file);
14 if (amt_written >= 0) return amt_written;
15 switch (@as(std.c.E, @enumFromInt(std.c._errno().*))) {
16 .SUCCESS => unreachable,
17 .INVAL => unreachable,
18 .FAULT => unreachable,
19 .AGAIN => unreachable, // this is a blocking API
20 .BADF => unreachable, // always a race condition
21 .DESTADDRREQ => unreachable, // connect was never called
22 .DQUOT => return error.DiskQuota,
23 .FBIG => return error.FileTooBig,
24 .IO => return error.InputOutput,
25 .NOSPC => return error.NoSpaceLeft,
26 .PERM => return error.PermissionDenied,
27 .PIPE => return error.BrokenPipe,
28 else => |err| return std.posix.unexpectedErrno(err),
29 }
30}
31
32test cWriter {
33 if (!builtin.link_libc or builtin.os.tag == .wasi) return error.SkipZigTest;
34
35 const filename = "tmp_io_test_file.txt";
36 const out_file = std.c.fopen(filename, "w") orelse return error.UnableToOpenTestFile;
37 defer {
38 _ = std.c.fclose(out_file);
39 std.fs.cwd().deleteFileZ(filename) catch {};
40 }
41
42 const writer = cWriter(out_file);
43 try writer.print("hi: {}\n", .{@as(i32, 123)});
44}
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/counting_reader.zig created+43
...@@ -0,0 +1,43 @@
1const std = @import("../std.zig");
2const io = std.io;
3const testing = std.testing;
4
5/// A Reader that counts how many bytes has been read from it.
6pub fn CountingReader(comptime ReaderType: anytype) type {
7 return struct {
8 child_reader: ReaderType,
9 bytes_read: u64 = 0,
10
11 pub const Error = ReaderType.Error;
12 pub const Reader = io.GenericReader(*@This(), Error, read);
13
14 pub fn read(self: *@This(), buf: []u8) Error!usize {
15 const amt = try self.child_reader.read(buf);
16 self.bytes_read += amt;
17 return amt;
18 }
19
20 pub fn reader(self: *@This()) Reader {
21 return .{ .context = self };
22 }
23 };
24}
25
26pub fn countingReader(reader: anytype) CountingReader(@TypeOf(reader)) {
27 return .{ .child_reader = reader };
28}
29
30test CountingReader {
31 const bytes = "yay" ** 100;
32 var fbs = io.fixedBufferStream(bytes);
33
34 var counting_stream = countingReader(fbs.reader());
35 const stream = counting_stream.reader();
36
37 //read and discard all bytes
38 while (stream.readByte()) |_| {} else |err| {
39 try testing.expect(err == error.EndOfStream);
40 }
41
42 try testing.expect(counting_stream.bytes_read == bytes.len);
43}
lib/std/Io/counting_writer.zig created+39
...@@ -0,0 +1,39 @@
1const std = @import("../std.zig");
2const io = std.io;
3const testing = std.testing;
4
5/// A Writer that counts how many bytes has been written to it.
6pub fn CountingWriter(comptime WriterType: type) type {
7 return struct {
8 bytes_written: u64,
9 child_stream: WriterType,
10
11 pub const Error = WriterType.Error;
12 pub const Writer = io.GenericWriter(*Self, Error, write);
13
14 const Self = @This();
15
16 pub fn write(self: *Self, bytes: []const u8) Error!usize {
17 const amt = try self.child_stream.write(bytes);
18 self.bytes_written += amt;
19 return amt;
20 }
21
22 pub fn writer(self: *Self) Writer {
23 return .{ .context = self };
24 }
25 };
26}
27
28pub fn countingWriter(child_stream: anytype) CountingWriter(@TypeOf(child_stream)) {
29 return .{ .bytes_written = 0, .child_stream = child_stream };
30}
31
32test CountingWriter {
33 var counting_stream = countingWriter(std.io.null_writer);
34 const stream = counting_stream.writer();
35
36 const bytes = "yay" ** 100;
37 stream.writeAll(bytes) catch unreachable;
38 try testing.expect(counting_stream.bytes_written == bytes.len);
39}
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/fixed_buffer_stream.zig created+198
...@@ -0,0 +1,198 @@
1const std = @import("../std.zig");
2const io = std.io;
3const testing = std.testing;
4const mem = std.mem;
5const assert = std.debug.assert;
6
7/// This turns a byte buffer into an `io.GenericWriter`, `io.GenericReader`, or `io.SeekableStream`.
8/// If the supplied byte buffer is const, then `io.GenericWriter` is not available.
9pub fn FixedBufferStream(comptime Buffer: type) type {
10 return struct {
11 /// `Buffer` is either a `[]u8` or `[]const u8`.
12 buffer: Buffer,
13 pos: usize,
14
15 pub const ReadError = error{};
16 pub const WriteError = error{NoSpaceLeft};
17 pub const SeekError = error{};
18 pub const GetSeekPosError = error{};
19
20 pub const Reader = io.GenericReader(*Self, ReadError, read);
21 pub const Writer = io.GenericWriter(*Self, WriteError, write);
22
23 pub const SeekableStream = io.SeekableStream(
24 *Self,
25 SeekError,
26 GetSeekPosError,
27 seekTo,
28 seekBy,
29 getPos,
30 getEndPos,
31 );
32
33 const Self = @This();
34
35 pub fn reader(self: *Self) Reader {
36 return .{ .context = self };
37 }
38
39 pub fn writer(self: *Self) Writer {
40 return .{ .context = self };
41 }
42
43 pub fn seekableStream(self: *Self) SeekableStream {
44 return .{ .context = self };
45 }
46
47 pub fn read(self: *Self, dest: []u8) ReadError!usize {
48 const size = @min(dest.len, self.buffer.len - self.pos);
49 const end = self.pos + size;
50
51 @memcpy(dest[0..size], self.buffer[self.pos..end]);
52 self.pos = end;
53
54 return size;
55 }
56
57 /// If the returned number of bytes written is less than requested, the
58 /// buffer is full. Returns `error.NoSpaceLeft` when no bytes would be written.
59 /// Note: `error.NoSpaceLeft` matches the corresponding error from
60 /// `std.fs.File.WriteError`.
61 pub fn write(self: *Self, bytes: []const u8) WriteError!usize {
62 if (bytes.len == 0) return 0;
63 if (self.pos >= self.buffer.len) return error.NoSpaceLeft;
64
65 const n = @min(self.buffer.len - self.pos, bytes.len);
66 @memcpy(self.buffer[self.pos..][0..n], bytes[0..n]);
67 self.pos += n;
68
69 if (n == 0) return error.NoSpaceLeft;
70
71 return n;
72 }
73
74 pub fn seekTo(self: *Self, pos: u64) SeekError!void {
75 self.pos = @min(std.math.lossyCast(usize, pos), self.buffer.len);
76 }
77
78 pub fn seekBy(self: *Self, amt: i64) SeekError!void {
79 if (amt < 0) {
80 const abs_amt = @abs(amt);
81 const abs_amt_usize = std.math.cast(usize, abs_amt) orelse std.math.maxInt(usize);
82 if (abs_amt_usize > self.pos) {
83 self.pos = 0;
84 } else {
85 self.pos -= abs_amt_usize;
86 }
87 } else {
88 const amt_usize = std.math.cast(usize, amt) orelse std.math.maxInt(usize);
89 const new_pos = std.math.add(usize, self.pos, amt_usize) catch std.math.maxInt(usize);
90 self.pos = @min(self.buffer.len, new_pos);
91 }
92 }
93
94 pub fn getEndPos(self: *Self) GetSeekPosError!u64 {
95 return self.buffer.len;
96 }
97
98 pub fn getPos(self: *Self) GetSeekPosError!u64 {
99 return self.pos;
100 }
101
102 pub fn getWritten(self: Self) Buffer {
103 return self.buffer[0..self.pos];
104 }
105
106 pub fn reset(self: *Self) void {
107 self.pos = 0;
108 }
109 };
110}
111
112pub fn fixedBufferStream(buffer: anytype) FixedBufferStream(Slice(@TypeOf(buffer))) {
113 return .{ .buffer = buffer, .pos = 0 };
114}
115
116fn Slice(comptime T: type) type {
117 switch (@typeInfo(T)) {
118 .pointer => |ptr_info| {
119 var new_ptr_info = ptr_info;
120 switch (ptr_info.size) {
121 .slice => {},
122 .one => switch (@typeInfo(ptr_info.child)) {
123 .array => |info| new_ptr_info.child = info.child,
124 else => @compileError("invalid type given to fixedBufferStream"),
125 },
126 else => @compileError("invalid type given to fixedBufferStream"),
127 }
128 new_ptr_info.size = .slice;
129 return @Type(.{ .pointer = new_ptr_info });
130 },
131 else => @compileError("invalid type given to fixedBufferStream"),
132 }
133}
134
135test "output" {
136 var buf: [255]u8 = undefined;
137 var fbs = fixedBufferStream(&buf);
138 const stream = fbs.writer();
139
140 try stream.print("{s}{s}!", .{ "Hello", "World" });
141 try testing.expectEqualSlices(u8, "HelloWorld!", fbs.getWritten());
142}
143
144test "output at comptime" {
145 comptime {
146 var buf: [255]u8 = undefined;
147 var fbs = fixedBufferStream(&buf);
148 const stream = fbs.writer();
149
150 try stream.print("{s}{s}!", .{ "Hello", "World" });
151 try testing.expectEqualSlices(u8, "HelloWorld!", fbs.getWritten());
152 }
153}
154
155test "output 2" {
156 var buffer: [10]u8 = undefined;
157 var fbs = fixedBufferStream(&buffer);
158
159 try fbs.writer().writeAll("Hello");
160 try testing.expect(mem.eql(u8, fbs.getWritten(), "Hello"));
161
162 try fbs.writer().writeAll("world");
163 try testing.expect(mem.eql(u8, fbs.getWritten(), "Helloworld"));
164
165 try testing.expectError(error.NoSpaceLeft, fbs.writer().writeAll("!"));
166 try testing.expect(mem.eql(u8, fbs.getWritten(), "Helloworld"));
167
168 fbs.reset();
169 try testing.expect(fbs.getWritten().len == 0);
170
171 try testing.expectError(error.NoSpaceLeft, fbs.writer().writeAll("Hello world!"));
172 try testing.expect(mem.eql(u8, fbs.getWritten(), "Hello worl"));
173
174 try fbs.seekTo((try fbs.getEndPos()) + 1);
175 try testing.expectError(error.NoSpaceLeft, fbs.writer().writeAll("H"));
176}
177
178test "input" {
179 const bytes = [_]u8{ 1, 2, 3, 4, 5, 6, 7 };
180 var fbs = fixedBufferStream(&bytes);
181
182 var dest: [4]u8 = undefined;
183
184 var read = try fbs.reader().read(&dest);
185 try testing.expect(read == 4);
186 try testing.expect(mem.eql(u8, dest[0..4], bytes[0..4]));
187
188 read = try fbs.reader().read(&dest);
189 try testing.expect(read == 3);
190 try testing.expect(mem.eql(u8, dest[0..3], bytes[4..7]));
191
192 read = try fbs.reader().read(&dest);
193 try testing.expect(read == 0);
194
195 try fbs.seekTo((try fbs.getEndPos()) + 1);
196 read = try fbs.reader().read(&dest);
197 try testing.expect(read == 0);
198}
lib/std/Io/limited_reader.zig created+45
...@@ -0,0 +1,45 @@
1const std = @import("../std.zig");
2const io = std.io;
3const assert = std.debug.assert;
4const testing = std.testing;
5
6pub fn LimitedReader(comptime ReaderType: type) type {
7 return struct {
8 inner_reader: ReaderType,
9 bytes_left: u64,
10
11 pub const Error = ReaderType.Error;
12 pub const Reader = io.GenericReader(*Self, Error, read);
13
14 const Self = @This();
15
16 pub fn read(self: *Self, dest: []u8) Error!usize {
17 const max_read = @min(self.bytes_left, dest.len);
18 const n = try self.inner_reader.read(dest[0..max_read]);
19 self.bytes_left -= n;
20 return n;
21 }
22
23 pub fn reader(self: *Self) Reader {
24 return .{ .context = self };
25 }
26 };
27}
28
29/// Returns an initialised `LimitedReader`.
30/// `bytes_left` is a `u64` to be able to take 64 bit file offsets
31pub fn limitedReader(inner_reader: anytype, bytes_left: u64) LimitedReader(@TypeOf(inner_reader)) {
32 return .{ .inner_reader = inner_reader, .bytes_left = bytes_left };
33}
34
35test "basic usage" {
36 const data = "hello world";
37 var fbs = std.io.fixedBufferStream(data);
38 var early_stream = limitedReader(fbs.reader(), 3);
39
40 var buf: [5]u8 = undefined;
41 try testing.expectEqual(@as(usize, 3), try early_stream.reader().read(&buf));
42 try testing.expectEqualSlices(u8, data[0..3], buf[0..3]);
43 try testing.expectEqual(@as(usize, 0), try early_stream.reader().read(&buf));
44 try testing.expectError(error.EndOfStream, early_stream.reader().skipBytes(10, .{}));
45}
lib/std/Io/multi_writer.zig created+53
...@@ -0,0 +1,53 @@
1const std = @import("../std.zig");
2const io = std.io;
3
4/// Takes a tuple of streams, and constructs a new stream that writes to all of them
5pub fn MultiWriter(comptime Writers: type) type {
6 comptime var ErrSet = error{};
7 inline for (@typeInfo(Writers).@"struct".fields) |field| {
8 const StreamType = field.type;
9 ErrSet = ErrSet || StreamType.Error;
10 }
11
12 return struct {
13 const Self = @This();
14
15 streams: Writers,
16
17 pub const Error = ErrSet;
18 pub const Writer = io.GenericWriter(*Self, Error, write);
19
20 pub fn writer(self: *Self) Writer {
21 return .{ .context = self };
22 }
23
24 pub fn write(self: *Self, bytes: []const u8) Error!usize {
25 inline for (self.streams) |stream|
26 try stream.writeAll(bytes);
27 return bytes.len;
28 }
29 };
30}
31
32pub fn multiWriter(streams: anytype) MultiWriter(@TypeOf(streams)) {
33 return .{ .streams = streams };
34}
35
36const testing = std.testing;
37
38test "MultiWriter" {
39 var tmp = testing.tmpDir(.{});
40 defer tmp.cleanup();
41 var f = try tmp.dir.createFile("t.txt", .{});
42
43 var buf1: [255]u8 = undefined;
44 var fbs1 = io.fixedBufferStream(&buf1);
45 var buf2: [255]u8 = undefined;
46 var stream = multiWriter(.{ fbs1.writer(), f.writer() });
47
48 try stream.writer().print("HI", .{});
49 f.close();
50
51 try testing.expectEqualSlices(u8, "HI", fbs1.getWritten());
52 try testing.expectEqualSlices(u8, "HI", try tmp.dir.readFile("t.txt", &buf2));
53}
lib/std/Io/seekable_stream.zig created+35
...@@ -0,0 +1,35 @@
1const std = @import("../std.zig");
2
3pub fn SeekableStream(
4 comptime Context: type,
5 comptime SeekErrorType: type,
6 comptime GetSeekPosErrorType: type,
7 comptime seekToFn: fn (context: Context, pos: u64) SeekErrorType!void,
8 comptime seekByFn: fn (context: Context, pos: i64) SeekErrorType!void,
9 comptime getPosFn: fn (context: Context) GetSeekPosErrorType!u64,
10 comptime getEndPosFn: fn (context: Context) GetSeekPosErrorType!u64,
11) type {
12 return struct {
13 context: Context,
14
15 const Self = @This();
16 pub const SeekError = SeekErrorType;
17 pub const GetSeekPosError = GetSeekPosErrorType;
18
19 pub fn seekTo(self: Self, pos: u64) SeekError!void {
20 return seekToFn(self.context, pos);
21 }
22
23 pub fn seekBy(self: Self, amt: i64) SeekError!void {
24 return seekByFn(self.context, amt);
25 }
26
27 pub fn getEndPos(self: Self) GetSeekPosError!u64 {
28 return getEndPosFn(self.context);
29 }
30
31 pub fn getPos(self: Self) GetSeekPosError!u64 {
32 return getPosFn(self.context);
33 }
34 };
35}
lib/std/Io/stream_source.zig created+127
...@@ -0,0 +1,127 @@
1const std = @import("../std.zig");
2const builtin = @import("builtin");
3const io = std.io;
4
5/// Provides `io.GenericReader`, `io.GenericWriter`, and `io.SeekableStream` for in-memory buffers as
6/// well as files.
7/// For memory sources, if the supplied byte buffer is const, then `io.GenericWriter` is not available.
8/// The error set of the stream functions is the error set of the corresponding file functions.
9pub const StreamSource = union(enum) {
10 // TODO: expose UEFI files to std.os in a way that allows this to be true
11 const has_file = (builtin.os.tag != .freestanding and builtin.os.tag != .uefi);
12
13 /// The stream access is redirected to this buffer.
14 buffer: io.FixedBufferStream([]u8),
15
16 /// The stream access is redirected to this buffer.
17 /// Writing to the source will always yield `error.AccessDenied`.
18 const_buffer: io.FixedBufferStream([]const u8),
19
20 /// The stream access is redirected to this file.
21 /// On freestanding, this must never be initialized!
22 file: if (has_file) std.fs.File else void,
23
24 pub const ReadError = io.FixedBufferStream([]u8).ReadError || (if (has_file) std.fs.File.ReadError else error{});
25 pub const WriteError = error{AccessDenied} || io.FixedBufferStream([]u8).WriteError || (if (has_file) std.fs.File.WriteError else error{});
26 pub const SeekError = io.FixedBufferStream([]u8).SeekError || (if (has_file) std.fs.File.SeekError else error{});
27 pub const GetSeekPosError = io.FixedBufferStream([]u8).GetSeekPosError || (if (has_file) std.fs.File.GetSeekPosError else error{});
28
29 pub const Reader = io.GenericReader(*StreamSource, ReadError, read);
30 pub const Writer = io.GenericWriter(*StreamSource, WriteError, write);
31 pub const SeekableStream = io.SeekableStream(
32 *StreamSource,
33 SeekError,
34 GetSeekPosError,
35 seekTo,
36 seekBy,
37 getPos,
38 getEndPos,
39 );
40
41 pub fn read(self: *StreamSource, dest: []u8) ReadError!usize {
42 switch (self.*) {
43 .buffer => |*x| return x.read(dest),
44 .const_buffer => |*x| return x.read(dest),
45 .file => |x| if (!has_file) unreachable else return x.read(dest),
46 }
47 }
48
49 pub fn write(self: *StreamSource, bytes: []const u8) WriteError!usize {
50 switch (self.*) {
51 .buffer => |*x| return x.write(bytes),
52 .const_buffer => return error.AccessDenied,
53 .file => |x| if (!has_file) unreachable else return x.write(bytes),
54 }
55 }
56
57 pub fn seekTo(self: *StreamSource, pos: u64) SeekError!void {
58 switch (self.*) {
59 .buffer => |*x| return x.seekTo(pos),
60 .const_buffer => |*x| return x.seekTo(pos),
61 .file => |x| if (!has_file) unreachable else return x.seekTo(pos),
62 }
63 }
64
65 pub fn seekBy(self: *StreamSource, amt: i64) SeekError!void {
66 switch (self.*) {
67 .buffer => |*x| return x.seekBy(amt),
68 .const_buffer => |*x| return x.seekBy(amt),
69 .file => |x| if (!has_file) unreachable else return x.seekBy(amt),
70 }
71 }
72
73 pub fn getEndPos(self: *StreamSource) GetSeekPosError!u64 {
74 switch (self.*) {
75 .buffer => |*x| return x.getEndPos(),
76 .const_buffer => |*x| return x.getEndPos(),
77 .file => |x| if (!has_file) unreachable else return x.getEndPos(),
78 }
79 }
80
81 pub fn getPos(self: *StreamSource) GetSeekPosError!u64 {
82 switch (self.*) {
83 .buffer => |*x| return x.getPos(),
84 .const_buffer => |*x| return x.getPos(),
85 .file => |x| if (!has_file) unreachable else return x.getPos(),
86 }
87 }
88
89 pub fn reader(self: *StreamSource) Reader {
90 return .{ .context = self };
91 }
92
93 pub fn writer(self: *StreamSource) Writer {
94 return .{ .context = self };
95 }
96
97 pub fn seekableStream(self: *StreamSource) SeekableStream {
98 return .{ .context = self };
99 }
100};
101
102test "refs" {
103 std.testing.refAllDecls(StreamSource);
104}
105
106test "mutable buffer" {
107 var buffer: [64]u8 = undefined;
108 var source = StreamSource{ .buffer = std.io.fixedBufferStream(&buffer) };
109
110 var writer = source.writer();
111
112 try writer.writeAll("Hello, World!");
113
114 try std.testing.expectEqualStrings("Hello, World!", source.buffer.getWritten());
115}
116
117test "const buffer" {
118 const buffer: [64]u8 = "Hello, World!".* ++ ([1]u8{0xAA} ** 51);
119 var source = StreamSource{ .const_buffer = std.io.fixedBufferStream(&buffer) };
120
121 var reader = source.reader();
122
123 var dst_buffer: [13]u8 = undefined;
124 try reader.readNoEof(&dst_buffer);
125
126 try std.testing.expectEqualStrings("Hello, World!", &dst_buffer);
127}
lib/std/Io/test.zig created+182
...@@ -0,0 +1,182 @@
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}
170
171test "GenericReader methods can return error.EndOfStream" {
172 // https://github.com/ziglang/zig/issues/17733
173 var fbs = std.io.fixedBufferStream("");
174 try std.testing.expectError(
175 error.EndOfStream,
176 fbs.reader().readEnum(enum(u8) { a, b }, .little),
177 );
178 try std.testing.expectError(
179 error.EndOfStream,
180 fbs.reader().isBytes("foo"),
181 );
182}
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/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-884
...@@ -1,884 +0,0 @@
1const std = @import("std.zig");
2const builtin = @import("builtin");
3const root = @import("root");
4const c = std.c;
5const is_windows = builtin.os.tag == .windows;
6const windows = std.os.windows;
7const posix = std.posix;
8const math = std.math;
9const assert = std.debug.assert;
10const fs = std.fs;
11const mem = std.mem;
12const meta = std.meta;
13const File = std.fs.File;
14const Allocator = std.mem.Allocator;
15const Alignment = std.mem.Alignment;
16
17pub const Limit = enum(usize) {
18 nothing = 0,
19 unlimited = std.math.maxInt(usize),
20 _,
21
22 /// `std.math.maxInt(usize)` is interpreted to mean `.unlimited`.
23 pub fn limited(n: usize) Limit {
24 return @enumFromInt(n);
25 }
26
27 /// Any value grater than `std.math.maxInt(usize)` is interpreted to mean
28 /// `.unlimited`.
29 pub fn limited64(n: u64) Limit {
30 return @enumFromInt(@min(n, std.math.maxInt(usize)));
31 }
32
33 pub fn countVec(data: []const []const u8) Limit {
34 var total: usize = 0;
35 for (data) |d| total += d.len;
36 return .limited(total);
37 }
38
39 pub fn min(a: Limit, b: Limit) Limit {
40 return @enumFromInt(@min(@intFromEnum(a), @intFromEnum(b)));
41 }
42
43 pub fn minInt(l: Limit, n: usize) usize {
44 return @min(n, @intFromEnum(l));
45 }
46
47 pub fn minInt64(l: Limit, n: u64) usize {
48 return @min(n, @intFromEnum(l));
49 }
50
51 pub fn slice(l: Limit, s: []u8) []u8 {
52 return s[0..l.minInt(s.len)];
53 }
54
55 pub fn sliceConst(l: Limit, s: []const u8) []const u8 {
56 return s[0..l.minInt(s.len)];
57 }
58
59 pub fn toInt(l: Limit) ?usize {
60 return switch (l) {
61 else => @intFromEnum(l),
62 .unlimited => null,
63 };
64 }
65
66 /// Reduces a slice to account for the limit, leaving room for one extra
67 /// byte above the limit, allowing for the use case of differentiating
68 /// between end-of-stream and reaching the limit.
69 pub fn slice1(l: Limit, non_empty_buffer: []u8) []u8 {
70 assert(non_empty_buffer.len >= 1);
71 return non_empty_buffer[0..@min(@intFromEnum(l) +| 1, non_empty_buffer.len)];
72 }
73
74 pub fn nonzero(l: Limit) bool {
75 return @intFromEnum(l) > 0;
76 }
77
78 /// Return a new limit reduced by `amount` or return `null` indicating
79 /// limit would be exceeded.
80 pub fn subtract(l: Limit, amount: usize) ?Limit {
81 if (l == .unlimited) return .unlimited;
82 if (amount > @intFromEnum(l)) return null;
83 return @enumFromInt(@intFromEnum(l) - amount);
84 }
85};
86
87pub const Reader = @import("io/Reader.zig");
88pub const Writer = @import("io/Writer.zig");
89
90/// Deprecated in favor of `Reader`.
91pub fn GenericReader(
92 comptime Context: type,
93 comptime ReadError: type,
94 /// Returns the number of bytes read. It may be less than buffer.len.
95 /// If the number of bytes read is 0, it means end of stream.
96 /// End of stream is not an error condition.
97 comptime readFn: fn (context: Context, buffer: []u8) ReadError!usize,
98) type {
99 return struct {
100 context: Context,
101
102 pub const Error = ReadError;
103 pub const NoEofError = ReadError || error{
104 EndOfStream,
105 };
106
107 pub inline fn read(self: Self, buffer: []u8) Error!usize {
108 return readFn(self.context, buffer);
109 }
110
111 pub inline fn readAll(self: Self, buffer: []u8) Error!usize {
112 return @errorCast(self.any().readAll(buffer));
113 }
114
115 pub inline fn readAtLeast(self: Self, buffer: []u8, len: usize) Error!usize {
116 return @errorCast(self.any().readAtLeast(buffer, len));
117 }
118
119 pub inline fn readNoEof(self: Self, buf: []u8) NoEofError!void {
120 return @errorCast(self.any().readNoEof(buf));
121 }
122
123 pub inline fn readAllArrayList(
124 self: Self,
125 array_list: *std.ArrayList(u8),
126 max_append_size: usize,
127 ) (error{StreamTooLong} || Allocator.Error || Error)!void {
128 return @errorCast(self.any().readAllArrayList(array_list, max_append_size));
129 }
130
131 pub inline fn readAllArrayListAligned(
132 self: Self,
133 comptime alignment: ?Alignment,
134 array_list: *std.ArrayListAligned(u8, alignment),
135 max_append_size: usize,
136 ) (error{StreamTooLong} || Allocator.Error || Error)!void {
137 return @errorCast(self.any().readAllArrayListAligned(
138 alignment,
139 array_list,
140 max_append_size,
141 ));
142 }
143
144 pub inline fn readAllAlloc(
145 self: Self,
146 allocator: Allocator,
147 max_size: usize,
148 ) (Error || Allocator.Error || error{StreamTooLong})![]u8 {
149 return @errorCast(self.any().readAllAlloc(allocator, max_size));
150 }
151
152 pub inline fn readUntilDelimiterArrayList(
153 self: Self,
154 array_list: *std.ArrayList(u8),
155 delimiter: u8,
156 max_size: usize,
157 ) (NoEofError || Allocator.Error || error{StreamTooLong})!void {
158 return @errorCast(self.any().readUntilDelimiterArrayList(
159 array_list,
160 delimiter,
161 max_size,
162 ));
163 }
164
165 pub inline fn readUntilDelimiterAlloc(
166 self: Self,
167 allocator: Allocator,
168 delimiter: u8,
169 max_size: usize,
170 ) (NoEofError || Allocator.Error || error{StreamTooLong})![]u8 {
171 return @errorCast(self.any().readUntilDelimiterAlloc(
172 allocator,
173 delimiter,
174 max_size,
175 ));
176 }
177
178 pub inline fn readUntilDelimiter(
179 self: Self,
180 buf: []u8,
181 delimiter: u8,
182 ) (NoEofError || error{StreamTooLong})![]u8 {
183 return @errorCast(self.any().readUntilDelimiter(buf, delimiter));
184 }
185
186 pub inline fn readUntilDelimiterOrEofAlloc(
187 self: Self,
188 allocator: Allocator,
189 delimiter: u8,
190 max_size: usize,
191 ) (Error || Allocator.Error || error{StreamTooLong})!?[]u8 {
192 return @errorCast(self.any().readUntilDelimiterOrEofAlloc(
193 allocator,
194 delimiter,
195 max_size,
196 ));
197 }
198
199 pub inline fn readUntilDelimiterOrEof(
200 self: Self,
201 buf: []u8,
202 delimiter: u8,
203 ) (Error || error{StreamTooLong})!?[]u8 {
204 return @errorCast(self.any().readUntilDelimiterOrEof(buf, delimiter));
205 }
206
207 pub inline fn streamUntilDelimiter(
208 self: Self,
209 writer: anytype,
210 delimiter: u8,
211 optional_max_size: ?usize,
212 ) (NoEofError || error{StreamTooLong} || @TypeOf(writer).Error)!void {
213 return @errorCast(self.any().streamUntilDelimiter(
214 writer,
215 delimiter,
216 optional_max_size,
217 ));
218 }
219
220 pub inline fn skipUntilDelimiterOrEof(self: Self, delimiter: u8) Error!void {
221 return @errorCast(self.any().skipUntilDelimiterOrEof(delimiter));
222 }
223
224 pub inline fn readByte(self: Self) NoEofError!u8 {
225 return @errorCast(self.any().readByte());
226 }
227
228 pub inline fn readByteSigned(self: Self) NoEofError!i8 {
229 return @errorCast(self.any().readByteSigned());
230 }
231
232 pub inline fn readBytesNoEof(
233 self: Self,
234 comptime num_bytes: usize,
235 ) NoEofError![num_bytes]u8 {
236 return @errorCast(self.any().readBytesNoEof(num_bytes));
237 }
238
239 pub inline fn readIntoBoundedBytes(
240 self: Self,
241 comptime num_bytes: usize,
242 bounded: *std.BoundedArray(u8, num_bytes),
243 ) Error!void {
244 return @errorCast(self.any().readIntoBoundedBytes(num_bytes, bounded));
245 }
246
247 pub inline fn readBoundedBytes(
248 self: Self,
249 comptime num_bytes: usize,
250 ) Error!std.BoundedArray(u8, num_bytes) {
251 return @errorCast(self.any().readBoundedBytes(num_bytes));
252 }
253
254 pub inline fn readInt(self: Self, comptime T: type, endian: std.builtin.Endian) NoEofError!T {
255 return @errorCast(self.any().readInt(T, endian));
256 }
257
258 pub inline fn readVarInt(
259 self: Self,
260 comptime ReturnType: type,
261 endian: std.builtin.Endian,
262 size: usize,
263 ) NoEofError!ReturnType {
264 return @errorCast(self.any().readVarInt(ReturnType, endian, size));
265 }
266
267 pub const SkipBytesOptions = AnyReader.SkipBytesOptions;
268
269 pub inline fn skipBytes(
270 self: Self,
271 num_bytes: u64,
272 comptime options: SkipBytesOptions,
273 ) NoEofError!void {
274 return @errorCast(self.any().skipBytes(num_bytes, options));
275 }
276
277 pub inline fn isBytes(self: Self, slice: []const u8) NoEofError!bool {
278 return @errorCast(self.any().isBytes(slice));
279 }
280
281 pub inline fn readStruct(self: Self, comptime T: type) NoEofError!T {
282 return @errorCast(self.any().readStruct(T));
283 }
284
285 pub inline fn readStructEndian(self: Self, comptime T: type, endian: std.builtin.Endian) NoEofError!T {
286 return @errorCast(self.any().readStructEndian(T, endian));
287 }
288
289 pub const ReadEnumError = NoEofError || error{
290 /// An integer was read, but it did not match any of the tags in the supplied enum.
291 InvalidValue,
292 };
293
294 pub inline fn readEnum(
295 self: Self,
296 comptime Enum: type,
297 endian: std.builtin.Endian,
298 ) ReadEnumError!Enum {
299 return @errorCast(self.any().readEnum(Enum, endian));
300 }
301
302 pub inline fn any(self: *const Self) AnyReader {
303 return .{
304 .context = @ptrCast(&self.context),
305 .readFn = typeErasedReadFn,
306 };
307 }
308
309 const Self = @This();
310
311 fn typeErasedReadFn(context: *const anyopaque, buffer: []u8) anyerror!usize {
312 const ptr: *const Context = @alignCast(@ptrCast(context));
313 return readFn(ptr.*, buffer);
314 }
315 };
316}
317
318/// Deprecated in favor of `Writer`.
319pub fn GenericWriter(
320 comptime Context: type,
321 comptime WriteError: type,
322 comptime writeFn: fn (context: Context, bytes: []const u8) WriteError!usize,
323) type {
324 return struct {
325 context: Context,
326
327 const Self = @This();
328 pub const Error = WriteError;
329
330 pub inline fn write(self: Self, bytes: []const u8) Error!usize {
331 return writeFn(self.context, bytes);
332 }
333
334 pub inline fn writeAll(self: Self, bytes: []const u8) Error!void {
335 return @errorCast(self.any().writeAll(bytes));
336 }
337
338 pub inline fn print(self: Self, comptime format: []const u8, args: anytype) Error!void {
339 return @errorCast(self.any().print(format, args));
340 }
341
342 pub inline fn writeByte(self: Self, byte: u8) Error!void {
343 return @errorCast(self.any().writeByte(byte));
344 }
345
346 pub inline fn writeByteNTimes(self: Self, byte: u8, n: usize) Error!void {
347 return @errorCast(self.any().writeByteNTimes(byte, n));
348 }
349
350 pub inline fn writeBytesNTimes(self: Self, bytes: []const u8, n: usize) Error!void {
351 return @errorCast(self.any().writeBytesNTimes(bytes, n));
352 }
353
354 pub inline fn writeInt(self: Self, comptime T: type, value: T, endian: std.builtin.Endian) Error!void {
355 return @errorCast(self.any().writeInt(T, value, endian));
356 }
357
358 pub inline fn writeStruct(self: Self, value: anytype) Error!void {
359 return @errorCast(self.any().writeStruct(value));
360 }
361
362 pub inline fn writeStructEndian(self: Self, value: anytype, endian: std.builtin.Endian) Error!void {
363 return @errorCast(self.any().writeStructEndian(value, endian));
364 }
365
366 pub inline fn any(self: *const Self) AnyWriter {
367 return .{
368 .context = @ptrCast(&self.context),
369 .writeFn = typeErasedWriteFn,
370 };
371 }
372
373 fn typeErasedWriteFn(context: *const anyopaque, bytes: []const u8) anyerror!usize {
374 const ptr: *const Context = @alignCast(@ptrCast(context));
375 return writeFn(ptr.*, bytes);
376 }
377
378 /// Helper for bridging to the new `Writer` API while upgrading.
379 pub fn adaptToNewApi(self: *const Self) Adapter {
380 return .{
381 .derp_writer = self.*,
382 .new_interface = .{
383 .buffer = &.{},
384 .vtable = &.{ .drain = Adapter.drain },
385 },
386 };
387 }
388
389 pub const Adapter = struct {
390 derp_writer: Self,
391 new_interface: Writer,
392 err: ?Error = null,
393
394 fn drain(w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize {
395 _ = splat;
396 const a: *@This() = @fieldParentPtr("new_interface", w);
397 return a.derp_writer.write(data[0]) catch |err| {
398 a.err = err;
399 return error.WriteFailed;
400 };
401 }
402 };
403 };
404}
405
406/// Deprecated in favor of `Reader`.
407pub const AnyReader = @import("io/DeprecatedReader.zig");
408/// Deprecated in favor of `Writer`.
409pub const AnyWriter = @import("io/DeprecatedWriter.zig");
410
411pub const SeekableStream = @import("io/seekable_stream.zig").SeekableStream;
412
413pub const BufferedWriter = @import("io/buffered_writer.zig").BufferedWriter;
414pub const bufferedWriter = @import("io/buffered_writer.zig").bufferedWriter;
415
416pub const BufferedReader = @import("io/buffered_reader.zig").BufferedReader;
417pub const bufferedReader = @import("io/buffered_reader.zig").bufferedReader;
418pub const bufferedReaderSize = @import("io/buffered_reader.zig").bufferedReaderSize;
419
420pub const FixedBufferStream = @import("io/fixed_buffer_stream.zig").FixedBufferStream;
421pub const fixedBufferStream = @import("io/fixed_buffer_stream.zig").fixedBufferStream;
422
423pub const CWriter = @import("io/c_writer.zig").CWriter;
424pub const cWriter = @import("io/c_writer.zig").cWriter;
425
426pub const LimitedReader = @import("io/limited_reader.zig").LimitedReader;
427pub const limitedReader = @import("io/limited_reader.zig").limitedReader;
428
429pub const CountingWriter = @import("io/counting_writer.zig").CountingWriter;
430pub const countingWriter = @import("io/counting_writer.zig").countingWriter;
431pub const CountingReader = @import("io/counting_reader.zig").CountingReader;
432pub const countingReader = @import("io/counting_reader.zig").countingReader;
433
434pub const MultiWriter = @import("io/multi_writer.zig").MultiWriter;
435pub const multiWriter = @import("io/multi_writer.zig").multiWriter;
436
437pub const BitReader = @import("io/bit_reader.zig").BitReader;
438pub const bitReader = @import("io/bit_reader.zig").bitReader;
439
440pub const BitWriter = @import("io/bit_writer.zig").BitWriter;
441pub const bitWriter = @import("io/bit_writer.zig").bitWriter;
442
443pub const ChangeDetectionStream = @import("io/change_detection_stream.zig").ChangeDetectionStream;
444pub const changeDetectionStream = @import("io/change_detection_stream.zig").changeDetectionStream;
445
446pub const FindByteWriter = @import("io/find_byte_writer.zig").FindByteWriter;
447pub const findByteWriter = @import("io/find_byte_writer.zig").findByteWriter;
448
449pub const BufferedAtomicFile = @import("io/buffered_atomic_file.zig").BufferedAtomicFile;
450
451pub const StreamSource = @import("io/stream_source.zig").StreamSource;
452
453pub const tty = @import("io/tty.zig");
454
455/// A Writer that doesn't write to anything.
456pub const null_writer: NullWriter = .{ .context = {} };
457
458pub const NullWriter = GenericWriter(void, error{}, dummyWrite);
459fn dummyWrite(context: void, data: []const u8) error{}!usize {
460 _ = context;
461 return data.len;
462}
463
464test null_writer {
465 null_writer.writeAll("yay" ** 10) catch |err| switch (err) {};
466}
467
468pub fn poll(
469 allocator: Allocator,
470 comptime StreamEnum: type,
471 files: PollFiles(StreamEnum),
472) Poller(StreamEnum) {
473 const enum_fields = @typeInfo(StreamEnum).@"enum".fields;
474 var result: Poller(StreamEnum) = undefined;
475
476 if (is_windows) result.windows = .{
477 .first_read_done = false,
478 .overlapped = [1]windows.OVERLAPPED{
479 mem.zeroes(windows.OVERLAPPED),
480 } ** enum_fields.len,
481 .small_bufs = undefined,
482 .active = .{
483 .count = 0,
484 .handles_buf = undefined,
485 .stream_map = undefined,
486 },
487 };
488
489 inline for (0..enum_fields.len) |i| {
490 result.fifos[i] = .{
491 .allocator = allocator,
492 .buf = &.{},
493 .head = 0,
494 .count = 0,
495 };
496 if (is_windows) {
497 result.windows.active.handles_buf[i] = @field(files, enum_fields[i].name).handle;
498 } else {
499 result.poll_fds[i] = .{
500 .fd = @field(files, enum_fields[i].name).handle,
501 .events = posix.POLL.IN,
502 .revents = undefined,
503 };
504 }
505 }
506 return result;
507}
508
509pub const PollFifo = std.fifo.LinearFifo(u8, .Dynamic);
510
511pub fn Poller(comptime StreamEnum: type) type {
512 return struct {
513 const enum_fields = @typeInfo(StreamEnum).@"enum".fields;
514 const PollFd = if (is_windows) void else posix.pollfd;
515
516 fifos: [enum_fields.len]PollFifo,
517 poll_fds: [enum_fields.len]PollFd,
518 windows: if (is_windows) struct {
519 first_read_done: bool,
520 overlapped: [enum_fields.len]windows.OVERLAPPED,
521 small_bufs: [enum_fields.len][128]u8,
522 active: struct {
523 count: math.IntFittingRange(0, enum_fields.len),
524 handles_buf: [enum_fields.len]windows.HANDLE,
525 stream_map: [enum_fields.len]StreamEnum,
526
527 pub fn removeAt(self: *@This(), index: u32) void {
528 std.debug.assert(index < self.count);
529 for (index + 1..self.count) |i| {
530 self.handles_buf[i - 1] = self.handles_buf[i];
531 self.stream_map[i - 1] = self.stream_map[i];
532 }
533 self.count -= 1;
534 }
535 },
536 } else void,
537
538 const Self = @This();
539
540 pub fn deinit(self: *Self) void {
541 if (is_windows) {
542 // cancel any pending IO to prevent clobbering OVERLAPPED value
543 for (self.windows.active.handles_buf[0..self.windows.active.count]) |h| {
544 _ = windows.kernel32.CancelIo(h);
545 }
546 }
547 inline for (&self.fifos) |*q| q.deinit();
548 self.* = undefined;
549 }
550
551 pub fn poll(self: *Self) !bool {
552 if (is_windows) {
553 return pollWindows(self, null);
554 } else {
555 return pollPosix(self, null);
556 }
557 }
558
559 pub fn pollTimeout(self: *Self, nanoseconds: u64) !bool {
560 if (is_windows) {
561 return pollWindows(self, nanoseconds);
562 } else {
563 return pollPosix(self, nanoseconds);
564 }
565 }
566
567 pub inline fn fifo(self: *Self, comptime which: StreamEnum) *PollFifo {
568 return &self.fifos[@intFromEnum(which)];
569 }
570
571 fn pollWindows(self: *Self, nanoseconds: ?u64) !bool {
572 const bump_amt = 512;
573
574 if (!self.windows.first_read_done) {
575 var already_read_data = false;
576 for (0..enum_fields.len) |i| {
577 const handle = self.windows.active.handles_buf[i];
578 switch (try windowsAsyncReadToFifoAndQueueSmallRead(
579 handle,
580 &self.windows.overlapped[i],
581 &self.fifos[i],
582 &self.windows.small_bufs[i],
583 bump_amt,
584 )) {
585 .populated, .empty => |state| {
586 if (state == .populated) already_read_data = true;
587 self.windows.active.handles_buf[self.windows.active.count] = handle;
588 self.windows.active.stream_map[self.windows.active.count] = @as(StreamEnum, @enumFromInt(i));
589 self.windows.active.count += 1;
590 },
591 .closed => {}, // don't add to the wait_objects list
592 .closed_populated => {
593 // don't add to the wait_objects list, but we did already get data
594 already_read_data = true;
595 },
596 }
597 }
598 self.windows.first_read_done = true;
599 if (already_read_data) return true;
600 }
601
602 while (true) {
603 if (self.windows.active.count == 0) return false;
604
605 const status = windows.kernel32.WaitForMultipleObjects(
606 self.windows.active.count,
607 &self.windows.active.handles_buf,
608 0,
609 if (nanoseconds) |ns|
610 @min(std.math.cast(u32, ns / std.time.ns_per_ms) orelse (windows.INFINITE - 1), windows.INFINITE - 1)
611 else
612 windows.INFINITE,
613 );
614 if (status == windows.WAIT_FAILED)
615 return windows.unexpectedError(windows.GetLastError());
616 if (status == windows.WAIT_TIMEOUT)
617 return true;
618
619 if (status < windows.WAIT_OBJECT_0 or status > windows.WAIT_OBJECT_0 + enum_fields.len - 1)
620 unreachable;
621
622 const active_idx = status - windows.WAIT_OBJECT_0;
623
624 const stream_idx = @intFromEnum(self.windows.active.stream_map[active_idx]);
625 const handle = self.windows.active.handles_buf[active_idx];
626
627 const overlapped = &self.windows.overlapped[stream_idx];
628 const stream_fifo = &self.fifos[stream_idx];
629 const small_buf = &self.windows.small_bufs[stream_idx];
630
631 const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) {
632 .success => |n| n,
633 .closed => {
634 self.windows.active.removeAt(active_idx);
635 continue;
636 },
637 .aborted => unreachable,
638 };
639 try stream_fifo.write(small_buf[0..num_bytes_read]);
640
641 switch (try windowsAsyncReadToFifoAndQueueSmallRead(
642 handle,
643 overlapped,
644 stream_fifo,
645 small_buf,
646 bump_amt,
647 )) {
648 .empty => {}, // irrelevant, we already got data from the small buffer
649 .populated => {},
650 .closed,
651 .closed_populated, // identical, since we already got data from the small buffer
652 => self.windows.active.removeAt(active_idx),
653 }
654 return true;
655 }
656 }
657
658 fn pollPosix(self: *Self, nanoseconds: ?u64) !bool {
659 // We ask for ensureUnusedCapacity with this much extra space. This
660 // has more of an effect on small reads because once the reads
661 // start to get larger the amount of space an ArrayList will
662 // allocate grows exponentially.
663 const bump_amt = 512;
664
665 const err_mask = posix.POLL.ERR | posix.POLL.NVAL | posix.POLL.HUP;
666
667 const events_len = try posix.poll(&self.poll_fds, if (nanoseconds) |ns|
668 std.math.cast(i32, ns / std.time.ns_per_ms) orelse std.math.maxInt(i32)
669 else
670 -1);
671 if (events_len == 0) {
672 for (self.poll_fds) |poll_fd| {
673 if (poll_fd.fd != -1) return true;
674 } else return false;
675 }
676
677 var keep_polling = false;
678 inline for (&self.poll_fds, &self.fifos) |*poll_fd, *q| {
679 // Try reading whatever is available before checking the error
680 // conditions.
681 // It's still possible to read after a POLL.HUP is received,
682 // always check if there's some data waiting to be read first.
683 if (poll_fd.revents & posix.POLL.IN != 0) {
684 const buf = try q.writableWithSize(bump_amt);
685 const amt = posix.read(poll_fd.fd, buf) catch |err| switch (err) {
686 error.BrokenPipe => 0, // Handle the same as EOF.
687 else => |e| return e,
688 };
689 q.update(amt);
690 if (amt == 0) {
691 // Remove the fd when the EOF condition is met.
692 poll_fd.fd = -1;
693 } else {
694 keep_polling = true;
695 }
696 } else if (poll_fd.revents & err_mask != 0) {
697 // Exclude the fds that signaled an error.
698 poll_fd.fd = -1;
699 } else if (poll_fd.fd != -1) {
700 keep_polling = true;
701 }
702 }
703 return keep_polling;
704 }
705 };
706}
707
708/// The `ReadFile` docuementation states that `lpNumberOfBytesRead` does not have a meaningful
709/// result when using overlapped I/O, but also that it cannot be `null` on Windows 7. For
710/// compatibility, we point it to this dummy variables, which we never otherwise access.
711/// See: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile
712var win_dummy_bytes_read: u32 = undefined;
713
714/// Read as much data as possible from `handle` with `overlapped`, and write it to the FIFO. Before
715/// returning, queue a read into `small_buf` so that `WaitForMultipleObjects` returns when more data
716/// is available. `handle` must have no pending asynchronous operation.
717fn windowsAsyncReadToFifoAndQueueSmallRead(
718 handle: windows.HANDLE,
719 overlapped: *windows.OVERLAPPED,
720 fifo: *PollFifo,
721 small_buf: *[128]u8,
722 bump_amt: usize,
723) !enum { empty, populated, closed_populated, closed } {
724 var read_any_data = false;
725 while (true) {
726 const fifo_read_pending = while (true) {
727 const buf = try fifo.writableWithSize(bump_amt);
728 const buf_len = math.cast(u32, buf.len) orelse math.maxInt(u32);
729
730 if (0 == windows.kernel32.ReadFile(
731 handle,
732 buf.ptr,
733 buf_len,
734 &win_dummy_bytes_read,
735 overlapped,
736 )) switch (windows.GetLastError()) {
737 .IO_PENDING => break true,
738 .BROKEN_PIPE => return if (read_any_data) .closed_populated else .closed,
739 else => |err| return windows.unexpectedError(err),
740 };
741
742 const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) {
743 .success => |n| n,
744 .closed => return if (read_any_data) .closed_populated else .closed,
745 .aborted => unreachable,
746 };
747
748 read_any_data = true;
749 fifo.update(num_bytes_read);
750
751 if (num_bytes_read == buf_len) {
752 // We filled the buffer, so there's probably more data available.
753 continue;
754 } else {
755 // We didn't fill the buffer, so assume we're out of data.
756 // There is no pending read.
757 break false;
758 }
759 };
760
761 if (fifo_read_pending) cancel_read: {
762 // Cancel the pending read into the FIFO.
763 _ = windows.kernel32.CancelIo(handle);
764
765 // We have to wait for the handle to be signalled, i.e. for the cancellation to complete.
766 switch (windows.kernel32.WaitForSingleObject(handle, windows.INFINITE)) {
767 windows.WAIT_OBJECT_0 => {},
768 windows.WAIT_FAILED => return windows.unexpectedError(windows.GetLastError()),
769 else => unreachable,
770 }
771
772 // If it completed before we canceled, make sure to tell the FIFO!
773 const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, true)) {
774 .success => |n| n,
775 .closed => return if (read_any_data) .closed_populated else .closed,
776 .aborted => break :cancel_read,
777 };
778 read_any_data = true;
779 fifo.update(num_bytes_read);
780 }
781
782 // Try to queue the 1-byte read.
783 if (0 == windows.kernel32.ReadFile(
784 handle,
785 small_buf,
786 small_buf.len,
787 &win_dummy_bytes_read,
788 overlapped,
789 )) switch (windows.GetLastError()) {
790 .IO_PENDING => {
791 // 1-byte read pending as intended
792 return if (read_any_data) .populated else .empty;
793 },
794 .BROKEN_PIPE => return if (read_any_data) .closed_populated else .closed,
795 else => |err| return windows.unexpectedError(err),
796 };
797
798 // We got data back this time. Write it to the FIFO and run the main loop again.
799 const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) {
800 .success => |n| n,
801 .closed => return if (read_any_data) .closed_populated else .closed,
802 .aborted => unreachable,
803 };
804 try fifo.write(small_buf[0..num_bytes_read]);
805 read_any_data = true;
806 }
807}
808
809/// Simple wrapper around `GetOverlappedResult` to determine the result of a `ReadFile` operation.
810/// If `!allow_aborted`, then `aborted` is never returned (`OPERATION_ABORTED` is considered unexpected).
811///
812/// The `ReadFile` documentation states that the number of bytes read by an overlapped `ReadFile` must be determined using `GetOverlappedResult`, even if the
813/// operation immediately returns data:
814/// "Use NULL for [lpNumberOfBytesRead] if this is an asynchronous operation to avoid potentially
815/// erroneous results."
816/// "If `hFile` was opened with `FILE_FLAG_OVERLAPPED`, the following conditions are in effect: [...]
817/// The lpNumberOfBytesRead parameter should be set to NULL. Use the GetOverlappedResult function to
818/// get the actual number of bytes read."
819/// See: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile
820fn windowsGetReadResult(
821 handle: windows.HANDLE,
822 overlapped: *windows.OVERLAPPED,
823 allow_aborted: bool,
824) !union(enum) {
825 success: u32,
826 closed,
827 aborted,
828} {
829 var num_bytes_read: u32 = undefined;
830 if (0 == windows.kernel32.GetOverlappedResult(
831 handle,
832 overlapped,
833 &num_bytes_read,
834 0,
835 )) switch (windows.GetLastError()) {
836 .BROKEN_PIPE => return .closed,
837 .OPERATION_ABORTED => |err| if (allow_aborted) {
838 return .aborted;
839 } else {
840 return windows.unexpectedError(err);
841 },
842 else => |err| return windows.unexpectedError(err),
843 };
844 return .{ .success = num_bytes_read };
845}
846
847/// Given an enum, returns a struct with fields of that enum, each field
848/// representing an I/O stream for polling.
849pub fn PollFiles(comptime StreamEnum: type) type {
850 const enum_fields = @typeInfo(StreamEnum).@"enum".fields;
851 var struct_fields: [enum_fields.len]std.builtin.Type.StructField = undefined;
852 for (&struct_fields, enum_fields) |*struct_field, enum_field| {
853 struct_field.* = .{
854 .name = enum_field.name,
855 .type = fs.File,
856 .default_value_ptr = null,
857 .is_comptime = false,
858 .alignment = @alignOf(fs.File),
859 };
860 }
861 return @Type(.{ .@"struct" = .{
862 .layout = .auto,
863 .fields = &struct_fields,
864 .decls = &.{},
865 .is_tuple = false,
866 } });
867}
868
869test {
870 _ = Reader;
871 _ = Writer;
872 _ = @import("io/bit_reader.zig");
873 _ = @import("io/bit_writer.zig");
874 _ = @import("io/buffered_atomic_file.zig");
875 _ = @import("io/buffered_reader.zig");
876 _ = @import("io/buffered_writer.zig");
877 _ = @import("io/c_writer.zig");
878 _ = @import("io/counting_writer.zig");
879 _ = @import("io/counting_reader.zig");
880 _ = @import("io/fixed_buffer_stream.zig");
881 _ = @import("io/seekable_stream.zig");
882 _ = @import("io/stream_source.zig");
883 _ = @import("io/test.zig");
884}
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-1731
...@@ -1,1731 +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/// See also:
844/// * `streamDelimiterEnding`
845/// * `streamDelimiterLimit`
846pub fn streamDelimiter(r: *Reader, w: *Writer, delimiter: u8) StreamError!usize {
847 const n = streamDelimiterLimit(r, w, delimiter, .unlimited) catch |err| switch (err) {
848 error.StreamTooLong => unreachable, // unlimited is passed
849 else => |e| return e,
850 };
851 if (r.seek == r.end) return error.EndOfStream;
852 return n;
853}
854
855/// Appends to `w` contents by reading from the stream until `delimiter` is found.
856/// Does not write the delimiter itself.
857///
858/// Returns number of bytes streamed, which may be zero. End of stream can be
859/// detected by checking if the next byte in the stream is the delimiter.
860///
861/// See also:
862/// * `streamDelimiter`
863/// * `streamDelimiterLimit`
864pub fn streamDelimiterEnding(
865 r: *Reader,
866 w: *Writer,
867 delimiter: u8,
868) StreamRemainingError!usize {
869 return streamDelimiterLimit(r, w, delimiter, .unlimited) catch |err| switch (err) {
870 error.StreamTooLong => unreachable, // unlimited is passed
871 else => |e| return e,
872 };
873}
874
875pub const StreamDelimiterLimitError = error{
876 ReadFailed,
877 WriteFailed,
878 /// The delimiter was not found within the limit.
879 StreamTooLong,
880};
881
882/// Appends to `w` contents by reading from the stream until `delimiter` is found.
883/// Does not write the delimiter itself.
884///
885/// Returns number of bytes streamed, which may be zero. End of stream can be
886/// detected by checking if the next byte in the stream is the delimiter.
887pub fn streamDelimiterLimit(
888 r: *Reader,
889 w: *Writer,
890 delimiter: u8,
891 limit: Limit,
892) StreamDelimiterLimitError!usize {
893 var remaining = @intFromEnum(limit);
894 while (remaining != 0) {
895 const available = Limit.limited(remaining).slice(r.peekGreedy(1) catch |err| switch (err) {
896 error.ReadFailed => return error.ReadFailed,
897 error.EndOfStream => return @intFromEnum(limit) - remaining,
898 });
899 if (std.mem.indexOfScalar(u8, available, delimiter)) |delimiter_index| {
900 try w.writeAll(available[0..delimiter_index]);
901 r.toss(delimiter_index);
902 remaining -= delimiter_index;
903 return @intFromEnum(limit) - remaining;
904 }
905 try w.writeAll(available);
906 r.toss(available.len);
907 remaining -= available.len;
908 }
909 return error.StreamTooLong;
910}
911
912/// Reads from the stream until specified byte is found, discarding all data,
913/// including the delimiter.
914///
915/// Returns number of bytes discarded, or `error.EndOfStream` if the delimiter
916/// is not found.
917///
918/// See also:
919/// * `discardDelimiterExclusive`
920/// * `discardDelimiterLimit`
921pub fn discardDelimiterInclusive(r: *Reader, delimiter: u8) Error!usize {
922 const n = discardDelimiterLimit(r, delimiter, .unlimited) catch |err| switch (err) {
923 error.StreamTooLong => unreachable, // unlimited is passed
924 else => |e| return e,
925 };
926 if (r.seek == r.end) return error.EndOfStream;
927 assert(r.buffer[r.seek] == delimiter);
928 toss(r, 1);
929 return n + 1;
930}
931
932/// Reads from the stream until specified byte is found, discarding all data,
933/// excluding the delimiter.
934///
935/// Returns the number of bytes discarded.
936///
937/// Succeeds if stream ends before delimiter found. End of stream can be
938/// detected by checking if the delimiter is buffered.
939///
940/// See also:
941/// * `discardDelimiterInclusive`
942/// * `discardDelimiterLimit`
943pub fn discardDelimiterExclusive(r: *Reader, delimiter: u8) ShortError!usize {
944 return discardDelimiterLimit(r, delimiter, .unlimited) catch |err| switch (err) {
945 error.StreamTooLong => unreachable, // unlimited is passed
946 else => |e| return e,
947 };
948}
949
950pub const DiscardDelimiterLimitError = error{
951 ReadFailed,
952 /// The delimiter was not found within the limit.
953 StreamTooLong,
954};
955
956/// Reads from the stream until specified byte is found, discarding all data,
957/// excluding the delimiter.
958///
959/// Returns the number of bytes discarded.
960///
961/// Succeeds if stream ends before delimiter found. End of stream can be
962/// detected by checking if the delimiter is buffered.
963pub fn discardDelimiterLimit(r: *Reader, delimiter: u8, limit: Limit) DiscardDelimiterLimitError!usize {
964 var remaining = @intFromEnum(limit);
965 while (remaining != 0) {
966 const available = Limit.limited(remaining).slice(r.peekGreedy(1) catch |err| switch (err) {
967 error.ReadFailed => return error.ReadFailed,
968 error.EndOfStream => return @intFromEnum(limit) - remaining,
969 });
970 if (std.mem.indexOfScalar(u8, available, delimiter)) |delimiter_index| {
971 r.toss(delimiter_index);
972 remaining -= delimiter_index;
973 return @intFromEnum(limit) - remaining;
974 }
975 r.toss(available.len);
976 remaining -= available.len;
977 }
978 return error.StreamTooLong;
979}
980
981/// Fills the buffer such that it contains at least `n` bytes, without
982/// advancing the seek position.
983///
984/// Returns `error.EndOfStream` if and only if there are fewer than `n` bytes
985/// remaining.
986///
987/// Asserts buffer capacity is at least `n`.
988pub fn fill(r: *Reader, n: usize) Error!void {
989 assert(n <= r.buffer.len);
990 if (r.seek + n <= r.end) {
991 @branchHint(.likely);
992 return;
993 }
994 if (r.seek + n <= r.buffer.len) while (true) {
995 const end_cap = r.buffer[r.end..];
996 var writer: Writer = .fixed(end_cap);
997 r.end += r.vtable.stream(r, &writer, .limited(end_cap.len)) catch |err| switch (err) {
998 error.WriteFailed => unreachable,
999 else => |e| return e,
1000 };
1001 if (r.seek + n <= r.end) return;
1002 };
1003 if (r.vtable.stream == &endingStream) {
1004 // Protect the `@constCast` of `fixed`.
1005 return error.EndOfStream;
1006 }
1007 rebaseCapacity(r, n);
1008 var writer: Writer = .{
1009 .buffer = r.buffer,
1010 .vtable = &.{ .drain = Writer.fixedDrain },
1011 };
1012 while (r.end < r.seek + n) {
1013 writer.end = r.end;
1014 r.end += r.vtable.stream(r, &writer, .limited(r.buffer.len - r.end)) catch |err| switch (err) {
1015 error.WriteFailed => unreachable,
1016 error.ReadFailed, error.EndOfStream => |e| return e,
1017 };
1018 }
1019}
1020
1021/// Without advancing the seek position, does exactly one underlying read, filling the buffer as
1022/// much as possible. This may result in zero bytes added to the buffer, which is not an end of
1023/// stream condition. End of stream is communicated via returning `error.EndOfStream`.
1024///
1025/// Asserts buffer capacity is at least 1.
1026pub fn fillMore(r: *Reader) Error!void {
1027 rebaseCapacity(r, 1);
1028 var writer: Writer = .{
1029 .buffer = r.buffer,
1030 .end = r.end,
1031 .vtable = &.{ .drain = Writer.fixedDrain },
1032 };
1033 r.end += r.vtable.stream(r, &writer, .limited(r.buffer.len - r.end)) catch |err| switch (err) {
1034 error.WriteFailed => unreachable,
1035 else => |e| return e,
1036 };
1037}
1038
1039/// Returns the next byte from the stream or returns `error.EndOfStream`.
1040///
1041/// Does not advance the seek position.
1042///
1043/// Asserts the buffer capacity is nonzero.
1044pub fn peekByte(r: *Reader) Error!u8 {
1045 const buffer = r.buffer[0..r.end];
1046 const seek = r.seek;
1047 if (seek < buffer.len) {
1048 @branchHint(.likely);
1049 return buffer[seek];
1050 }
1051 try fill(r, 1);
1052 return r.buffer[r.seek];
1053}
1054
1055/// Reads 1 byte from the stream or returns `error.EndOfStream`.
1056///
1057/// Asserts the buffer capacity is nonzero.
1058pub fn takeByte(r: *Reader) Error!u8 {
1059 const result = try peekByte(r);
1060 r.seek += 1;
1061 return result;
1062}
1063
1064/// Same as `takeByte` except the returned byte is signed.
1065pub fn takeByteSigned(r: *Reader) Error!i8 {
1066 return @bitCast(try r.takeByte());
1067}
1068
1069/// Asserts the buffer was initialized with a capacity at least `@bitSizeOf(T) / 8`.
1070pub inline fn takeInt(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T {
1071 const n = @divExact(@typeInfo(T).int.bits, 8);
1072 return std.mem.readInt(T, try r.takeArray(n), endian);
1073}
1074
1075/// Asserts the buffer was initialized with a capacity at least `n`.
1076pub fn takeVarInt(r: *Reader, comptime Int: type, endian: std.builtin.Endian, n: usize) Error!Int {
1077 assert(n <= @sizeOf(Int));
1078 return std.mem.readVarInt(Int, try r.take(n), endian);
1079}
1080
1081/// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`.
1082///
1083/// Advances the seek position.
1084///
1085/// See also:
1086/// * `peekStruct`
1087/// * `takeStructEndian`
1088pub fn takeStruct(r: *Reader, comptime T: type) Error!*align(1) T {
1089 // Only extern and packed structs have defined in-memory layout.
1090 comptime assert(@typeInfo(T).@"struct".layout != .auto);
1091 return @ptrCast(try r.takeArray(@sizeOf(T)));
1092}
1093
1094/// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`.
1095///
1096/// Does not advance the seek position.
1097///
1098/// See also:
1099/// * `takeStruct`
1100/// * `peekStructEndian`
1101pub fn peekStruct(r: *Reader, comptime T: type) Error!*align(1) T {
1102 // Only extern and packed structs have defined in-memory layout.
1103 comptime assert(@typeInfo(T).@"struct".layout != .auto);
1104 return @ptrCast(try r.peekArray(@sizeOf(T)));
1105}
1106
1107/// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`.
1108///
1109/// This function is inline to avoid referencing `std.mem.byteSwapAllFields`
1110/// when `endian` is comptime-known and matches the host endianness.
1111///
1112/// See also:
1113/// * `takeStruct`
1114/// * `peekStructEndian`
1115pub inline fn takeStructEndian(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T {
1116 var res = (try r.takeStruct(T)).*;
1117 if (native_endian != endian) std.mem.byteSwapAllFields(T, &res);
1118 return res;
1119}
1120
1121/// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`.
1122///
1123/// This function is inline to avoid referencing `std.mem.byteSwapAllFields`
1124/// when `endian` is comptime-known and matches the host endianness.
1125///
1126/// See also:
1127/// * `takeStructEndian`
1128/// * `peekStruct`
1129pub inline fn peekStructEndian(r: *Reader, comptime T: type, endian: std.builtin.Endian) Error!T {
1130 var res = (try r.peekStruct(T)).*;
1131 if (native_endian != endian) std.mem.byteSwapAllFields(T, &res);
1132 return res;
1133}
1134
1135pub const TakeEnumError = Error || error{InvalidEnumTag};
1136
1137/// Reads an integer with the same size as the given enum's tag type. If the
1138/// integer matches an enum tag, casts the integer to the enum tag and returns
1139/// it. Otherwise, returns `error.InvalidEnumTag`.
1140///
1141/// Asserts the buffer was initialized with a capacity at least `@sizeOf(Enum)`.
1142pub fn takeEnum(r: *Reader, comptime Enum: type, endian: std.builtin.Endian) TakeEnumError!Enum {
1143 const Tag = @typeInfo(Enum).@"enum".tag_type;
1144 const int = try r.takeInt(Tag, endian);
1145 return std.meta.intToEnum(Enum, int);
1146}
1147
1148/// Reads an integer with the same size as the given nonexhaustive enum's tag type.
1149///
1150/// Asserts the buffer was initialized with a capacity at least `@sizeOf(Enum)`.
1151pub fn takeEnumNonexhaustive(r: *Reader, comptime Enum: type, endian: std.builtin.Endian) Error!Enum {
1152 const info = @typeInfo(Enum).@"enum";
1153 comptime assert(!info.is_exhaustive);
1154 comptime assert(@bitSizeOf(info.tag_type) == @sizeOf(info.tag_type) * 8);
1155 return takeEnum(r, Enum, endian) catch |err| switch (err) {
1156 error.InvalidEnumTag => unreachable,
1157 else => |e| return e,
1158 };
1159}
1160
1161pub const TakeLeb128Error = Error || error{Overflow};
1162
1163/// Read a single LEB128 value as type T, or `error.Overflow` if the value cannot fit.
1164pub fn takeLeb128(r: *Reader, comptime Result: type) TakeLeb128Error!Result {
1165 const result_info = @typeInfo(Result).int;
1166 return std.math.cast(Result, try r.takeMultipleOf7Leb128(@Type(.{ .int = .{
1167 .signedness = result_info.signedness,
1168 .bits = std.mem.alignForwardAnyAlign(u16, result_info.bits, 7),
1169 } }))) orelse error.Overflow;
1170}
1171
1172pub fn expandTotalCapacity(r: *Reader, allocator: Allocator, n: usize) Allocator.Error!void {
1173 if (n <= r.buffer.len) return;
1174 if (r.seek > 0) rebase(r);
1175 var list: ArrayList(u8) = .{
1176 .items = r.buffer[0..r.end],
1177 .capacity = r.buffer.len,
1178 };
1179 defer r.buffer = list.allocatedSlice();
1180 try list.ensureTotalCapacity(allocator, n);
1181}
1182
1183pub const FillAllocError = Error || Allocator.Error;
1184
1185pub fn fillAlloc(r: *Reader, allocator: Allocator, n: usize) FillAllocError!void {
1186 try expandTotalCapacity(r, allocator, n);
1187 return fill(r, n);
1188}
1189
1190/// Returns a slice into the unused capacity of `buffer` with at least
1191/// `min_len` bytes, extending `buffer` by resizing it with `gpa` as necessary.
1192///
1193/// After calling this function, typically the caller will follow up with a
1194/// call to `advanceBufferEnd` to report the actual number of bytes buffered.
1195pub fn writableSliceGreedyAlloc(r: *Reader, allocator: Allocator, min_len: usize) Allocator.Error![]u8 {
1196 {
1197 const unused = r.buffer[r.end..];
1198 if (unused.len >= min_len) return unused;
1199 }
1200 if (r.seek > 0) rebase(r);
1201 {
1202 var list: ArrayList(u8) = .{
1203 .items = r.buffer[0..r.end],
1204 .capacity = r.buffer.len,
1205 };
1206 defer r.buffer = list.allocatedSlice();
1207 try list.ensureUnusedCapacity(allocator, min_len);
1208 }
1209 const unused = r.buffer[r.end..];
1210 assert(unused.len >= min_len);
1211 return unused;
1212}
1213
1214/// After writing directly into the unused capacity of `buffer`, this function
1215/// updates `end` so that users of `Reader` can receive the data.
1216pub fn advanceBufferEnd(r: *Reader, n: usize) void {
1217 assert(n <= r.buffer.len - r.end);
1218 r.end += n;
1219}
1220
1221fn takeMultipleOf7Leb128(r: *Reader, comptime Result: type) TakeLeb128Error!Result {
1222 const result_info = @typeInfo(Result).int;
1223 comptime assert(result_info.bits % 7 == 0);
1224 var remaining_bits: std.math.Log2IntCeil(Result) = result_info.bits;
1225 const UnsignedResult = @Type(.{ .int = .{
1226 .signedness = .unsigned,
1227 .bits = result_info.bits,
1228 } });
1229 var result: UnsignedResult = 0;
1230 var fits = true;
1231 while (true) {
1232 const buffer: []const packed struct(u8) { bits: u7, more: bool } = @ptrCast(try r.peekGreedy(1));
1233 for (buffer, 1..) |byte, len| {
1234 if (remaining_bits > 0) {
1235 result = @shlExact(@as(UnsignedResult, byte.bits), result_info.bits - 7) |
1236 if (result_info.bits > 7) @shrExact(result, 7) else 0;
1237 remaining_bits -= 7;
1238 } else if (fits) fits = switch (result_info.signedness) {
1239 .signed => @as(i7, @bitCast(byte.bits)) ==
1240 @as(i7, @truncate(@as(Result, @bitCast(result)) >> (result_info.bits - 1))),
1241 .unsigned => byte.bits == 0,
1242 };
1243 if (byte.more) continue;
1244 r.toss(len);
1245 return if (fits) @as(Result, @bitCast(result)) >> remaining_bits else error.Overflow;
1246 }
1247 r.toss(buffer.len);
1248 }
1249}
1250
1251/// Left-aligns data such that `r.seek` becomes zero.
1252pub fn rebase(r: *Reader) void {
1253 if (r.seek == 0) return;
1254 const data = r.buffer[r.seek..r.end];
1255 @memmove(r.buffer[0..data.len], data);
1256 r.seek = 0;
1257 r.end = data.len;
1258}
1259
1260/// Ensures `capacity` more data can be buffered without rebasing, by rebasing
1261/// if necessary.
1262///
1263/// Asserts `capacity` is within the buffer capacity.
1264pub fn rebaseCapacity(r: *Reader, capacity: usize) void {
1265 if (r.end > r.buffer.len - capacity) rebase(r);
1266}
1267
1268/// Advances the stream and decreases the size of the storage buffer by `n`,
1269/// returning the range of bytes no longer accessible by `r`.
1270///
1271/// This action can be undone by `restitute`.
1272///
1273/// Asserts there are at least `n` buffered bytes already.
1274///
1275/// Asserts that `r.seek` is zero, i.e. the buffer is in a rebased state.
1276pub fn steal(r: *Reader, n: usize) []u8 {
1277 assert(r.seek == 0);
1278 assert(n <= r.end);
1279 const stolen = r.buffer[0..n];
1280 r.buffer = r.buffer[n..];
1281 r.end -= n;
1282 return stolen;
1283}
1284
1285/// Expands the storage buffer, undoing the effects of `steal`
1286/// Assumes that `n` does not exceed the total number of stolen bytes.
1287pub fn restitute(r: *Reader, n: usize) void {
1288 r.buffer = (r.buffer.ptr - n)[0 .. r.buffer.len + n];
1289 r.end += n;
1290 r.seek += n;
1291}
1292
1293test fixed {
1294 var r: Reader = .fixed("a\x02");
1295 try testing.expect((try r.takeByte()) == 'a');
1296 try testing.expect((try r.takeEnum(enum(u8) {
1297 a = 0,
1298 b = 99,
1299 c = 2,
1300 d = 3,
1301 }, builtin.cpu.arch.endian())) == .c);
1302 try testing.expectError(error.EndOfStream, r.takeByte());
1303}
1304
1305test peek {
1306 var r: Reader = .fixed("abc");
1307 try testing.expectEqualStrings("ab", try r.peek(2));
1308 try testing.expectEqualStrings("a", try r.peek(1));
1309}
1310
1311test peekGreedy {
1312 var r: Reader = .fixed("abc");
1313 try testing.expectEqualStrings("abc", try r.peekGreedy(1));
1314}
1315
1316test toss {
1317 var r: Reader = .fixed("abc");
1318 r.toss(1);
1319 try testing.expectEqualStrings("bc", r.buffered());
1320}
1321
1322test take {
1323 var r: Reader = .fixed("abc");
1324 try testing.expectEqualStrings("ab", try r.take(2));
1325 try testing.expectEqualStrings("c", try r.take(1));
1326}
1327
1328test takeArray {
1329 var r: Reader = .fixed("abc");
1330 try testing.expectEqualStrings("ab", try r.takeArray(2));
1331 try testing.expectEqualStrings("c", try r.takeArray(1));
1332}
1333
1334test peekArray {
1335 var r: Reader = .fixed("abc");
1336 try testing.expectEqualStrings("ab", try r.peekArray(2));
1337 try testing.expectEqualStrings("a", try r.peekArray(1));
1338}
1339
1340test discardAll {
1341 var r: Reader = .fixed("foobar");
1342 try r.discardAll(3);
1343 try testing.expectEqualStrings("bar", try r.take(3));
1344 try r.discardAll(0);
1345 try testing.expectError(error.EndOfStream, r.discardAll(1));
1346}
1347
1348test discardRemaining {
1349 var r: Reader = .fixed("foobar");
1350 r.toss(1);
1351 try testing.expectEqual(5, try r.discardRemaining());
1352 try testing.expectEqual(0, try r.discardRemaining());
1353}
1354
1355test stream {
1356 var out_buffer: [10]u8 = undefined;
1357 var r: Reader = .fixed("foobar");
1358 var w: Writer = .fixed(&out_buffer);
1359 // Short streams are possible with this function but not with fixed.
1360 try testing.expectEqual(2, try r.stream(&w, .limited(2)));
1361 try testing.expectEqualStrings("fo", w.buffered());
1362 try testing.expectEqual(4, try r.stream(&w, .unlimited));
1363 try testing.expectEqualStrings("foobar", w.buffered());
1364}
1365
1366test takeSentinel {
1367 var r: Reader = .fixed("ab\nc");
1368 try testing.expectEqualStrings("ab", try r.takeSentinel('\n'));
1369 try testing.expectError(error.EndOfStream, r.takeSentinel('\n'));
1370 try testing.expectEqualStrings("c", try r.peek(1));
1371}
1372
1373test peekSentinel {
1374 var r: Reader = .fixed("ab\nc");
1375 try testing.expectEqualStrings("ab", try r.peekSentinel('\n'));
1376 try testing.expectEqualStrings("ab", try r.peekSentinel('\n'));
1377}
1378
1379test takeDelimiterInclusive {
1380 var r: Reader = .fixed("ab\nc");
1381 try testing.expectEqualStrings("ab\n", try r.takeDelimiterInclusive('\n'));
1382 try testing.expectError(error.EndOfStream, r.takeDelimiterInclusive('\n'));
1383}
1384
1385test peekDelimiterInclusive {
1386 var r: Reader = .fixed("ab\nc");
1387 try testing.expectEqualStrings("ab\n", try r.peekDelimiterInclusive('\n'));
1388 try testing.expectEqualStrings("ab\n", try r.peekDelimiterInclusive('\n'));
1389 r.toss(3);
1390 try testing.expectError(error.EndOfStream, r.peekDelimiterInclusive('\n'));
1391}
1392
1393test takeDelimiterExclusive {
1394 var r: Reader = .fixed("ab\nc");
1395 try testing.expectEqualStrings("ab", try r.takeDelimiterExclusive('\n'));
1396 try testing.expectEqualStrings("c", try r.takeDelimiterExclusive('\n'));
1397 try testing.expectError(error.EndOfStream, r.takeDelimiterExclusive('\n'));
1398}
1399
1400test peekDelimiterExclusive {
1401 var r: Reader = .fixed("ab\nc");
1402 try testing.expectEqualStrings("ab", try r.peekDelimiterExclusive('\n'));
1403 try testing.expectEqualStrings("ab", try r.peekDelimiterExclusive('\n'));
1404 r.toss(3);
1405 try testing.expectEqualStrings("c", try r.peekDelimiterExclusive('\n'));
1406}
1407
1408test streamDelimiter {
1409 var out_buffer: [10]u8 = undefined;
1410 var r: Reader = .fixed("foo\nbars");
1411 var w: Writer = .fixed(&out_buffer);
1412 try testing.expectEqual(3, try r.streamDelimiter(&w, '\n'));
1413 try testing.expectEqualStrings("foo", w.buffered());
1414 try testing.expectEqual(0, try r.streamDelimiter(&w, '\n'));
1415 r.toss(1);
1416 try testing.expectError(error.EndOfStream, r.streamDelimiter(&w, '\n'));
1417}
1418
1419test streamDelimiterEnding {
1420 var out_buffer: [10]u8 = undefined;
1421 var r: Reader = .fixed("foo\nbars");
1422 var w: Writer = .fixed(&out_buffer);
1423 try testing.expectEqual(3, try r.streamDelimiterEnding(&w, '\n'));
1424 try testing.expectEqualStrings("foo", w.buffered());
1425 r.toss(1);
1426 try testing.expectEqual(4, try r.streamDelimiterEnding(&w, '\n'));
1427 try testing.expectEqualStrings("foobars", w.buffered());
1428 try testing.expectEqual(0, try r.streamDelimiterEnding(&w, '\n'));
1429 try testing.expectEqual(0, try r.streamDelimiterEnding(&w, '\n'));
1430}
1431
1432test streamDelimiterLimit {
1433 var out_buffer: [10]u8 = undefined;
1434 var r: Reader = .fixed("foo\nbars");
1435 var w: Writer = .fixed(&out_buffer);
1436 try testing.expectError(error.StreamTooLong, r.streamDelimiterLimit(&w, '\n', .limited(2)));
1437 try testing.expectEqual(1, try r.streamDelimiterLimit(&w, '\n', .limited(3)));
1438 try testing.expectEqualStrings("\n", try r.take(1));
1439 try testing.expectEqual(4, try r.streamDelimiterLimit(&w, '\n', .unlimited));
1440 try testing.expectEqualStrings("foobars", w.buffered());
1441}
1442
1443test discardDelimiterExclusive {
1444 var r: Reader = .fixed("foob\nar");
1445 try testing.expectEqual(4, try r.discardDelimiterExclusive('\n'));
1446 try testing.expectEqualStrings("\n", try r.take(1));
1447 try testing.expectEqual(2, try r.discardDelimiterExclusive('\n'));
1448 try testing.expectEqual(0, try r.discardDelimiterExclusive('\n'));
1449}
1450
1451test discardDelimiterInclusive {
1452 var r: Reader = .fixed("foob\nar");
1453 try testing.expectEqual(5, try r.discardDelimiterInclusive('\n'));
1454 try testing.expectError(error.EndOfStream, r.discardDelimiterInclusive('\n'));
1455}
1456
1457test discardDelimiterLimit {
1458 var r: Reader = .fixed("foob\nar");
1459 try testing.expectError(error.StreamTooLong, r.discardDelimiterLimit('\n', .limited(4)));
1460 try testing.expectEqual(0, try r.discardDelimiterLimit('\n', .limited(2)));
1461 try testing.expectEqualStrings("\n", try r.take(1));
1462 try testing.expectEqual(2, try r.discardDelimiterLimit('\n', .unlimited));
1463 try testing.expectEqual(0, try r.discardDelimiterLimit('\n', .unlimited));
1464}
1465
1466test fill {
1467 var r: Reader = .fixed("abc");
1468 try r.fill(1);
1469 try r.fill(3);
1470}
1471
1472test takeByte {
1473 var r: Reader = .fixed("ab");
1474 try testing.expectEqual('a', try r.takeByte());
1475 try testing.expectEqual('b', try r.takeByte());
1476 try testing.expectError(error.EndOfStream, r.takeByte());
1477}
1478
1479test takeByteSigned {
1480 var r: Reader = .fixed(&.{ 255, 5 });
1481 try testing.expectEqual(-1, try r.takeByteSigned());
1482 try testing.expectEqual(5, try r.takeByteSigned());
1483 try testing.expectError(error.EndOfStream, r.takeByteSigned());
1484}
1485
1486test takeInt {
1487 var r: Reader = .fixed(&.{ 0x12, 0x34, 0x56 });
1488 try testing.expectEqual(0x1234, try r.takeInt(u16, .big));
1489 try testing.expectError(error.EndOfStream, r.takeInt(u16, .little));
1490}
1491
1492test takeVarInt {
1493 var r: Reader = .fixed(&.{ 0x12, 0x34, 0x56 });
1494 try testing.expectEqual(0x123456, try r.takeVarInt(u64, .big, 3));
1495 try testing.expectError(error.EndOfStream, r.takeVarInt(u16, .little, 1));
1496}
1497
1498test takeStruct {
1499 var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 });
1500 const S = extern struct { a: u8, b: u16 };
1501 switch (native_endian) {
1502 .little => try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.takeStruct(S)).*),
1503 .big => try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.takeStruct(S)).*),
1504 }
1505 try testing.expectError(error.EndOfStream, r.takeStruct(S));
1506}
1507
1508test peekStruct {
1509 var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 });
1510 const S = extern struct { a: u8, b: u16 };
1511 switch (native_endian) {
1512 .little => {
1513 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.peekStruct(S)).*);
1514 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), (try r.peekStruct(S)).*);
1515 },
1516 .big => {
1517 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.peekStruct(S)).*);
1518 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), (try r.peekStruct(S)).*);
1519 },
1520 }
1521}
1522
1523test takeStructEndian {
1524 var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 });
1525 const S = extern struct { a: u8, b: u16 };
1526 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), try r.takeStructEndian(S, .big));
1527 try testing.expectError(error.EndOfStream, r.takeStructEndian(S, .little));
1528}
1529
1530test peekStructEndian {
1531 var r: Reader = .fixed(&.{ 0x12, 0x00, 0x34, 0x56 });
1532 const S = extern struct { a: u8, b: u16 };
1533 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x3456 }), try r.peekStructEndian(S, .big));
1534 try testing.expectEqual(@as(S, .{ .a = 0x12, .b = 0x5634 }), try r.peekStructEndian(S, .little));
1535}
1536
1537test takeEnum {
1538 var r: Reader = .fixed(&.{ 2, 0, 1 });
1539 const E1 = enum(u8) { a, b, c };
1540 const E2 = enum(u16) { _ };
1541 try testing.expectEqual(E1.c, try r.takeEnum(E1, .little));
1542 try testing.expectEqual(@as(E2, @enumFromInt(0x0001)), try r.takeEnum(E2, .big));
1543}
1544
1545test takeLeb128 {
1546 var r: Reader = .fixed("\xc7\x9f\x7f\x80");
1547 try testing.expectEqual(-12345, try r.takeLeb128(i64));
1548 try testing.expectEqual(0x80, try r.peekByte());
1549 try testing.expectError(error.EndOfStream, r.takeLeb128(i64));
1550}
1551
1552test readSliceShort {
1553 var r: Reader = .fixed("HelloFren");
1554 var buf: [5]u8 = undefined;
1555 try testing.expectEqual(5, try r.readSliceShort(&buf));
1556 try testing.expectEqualStrings("Hello", buf[0..5]);
1557 try testing.expectEqual(4, try r.readSliceShort(&buf));
1558 try testing.expectEqualStrings("Fren", buf[0..4]);
1559 try testing.expectEqual(0, try r.readSliceShort(&buf));
1560}
1561
1562test readVec {
1563 var r: Reader = .fixed(std.ascii.letters);
1564 var flat_buffer: [52]u8 = undefined;
1565 var bufs: [2][]u8 = .{
1566 flat_buffer[0..26],
1567 flat_buffer[26..],
1568 };
1569 // Short reads are possible with this function but not with fixed.
1570 try testing.expectEqual(26 * 2, try r.readVec(&bufs));
1571 try testing.expectEqualStrings(std.ascii.letters[0..26], bufs[0]);
1572 try testing.expectEqualStrings(std.ascii.letters[26..], bufs[1]);
1573}
1574
1575test readVecLimit {
1576 var r: Reader = .fixed(std.ascii.letters);
1577 var flat_buffer: [52]u8 = undefined;
1578 var bufs: [2][]u8 = .{
1579 flat_buffer[0..26],
1580 flat_buffer[26..],
1581 };
1582 // Short reads are possible with this function but not with fixed.
1583 try testing.expectEqual(50, try r.readVecLimit(&bufs, .limited(50)));
1584 try testing.expectEqualStrings(std.ascii.letters[0..26], bufs[0]);
1585 try testing.expectEqualStrings(std.ascii.letters[26..50], bufs[1][0..24]);
1586}
1587
1588test "expected error.EndOfStream" {
1589 // Unit test inspired by https://github.com/ziglang/zig/issues/17733
1590 var buffer: [3]u8 = undefined;
1591 var r: std.io.Reader = .fixed(&buffer);
1592 r.end = 0; // capacity 3, but empty
1593 try std.testing.expectError(error.EndOfStream, r.takeEnum(enum(u8) { a, b }, .little));
1594 try std.testing.expectError(error.EndOfStream, r.take(3));
1595}
1596
1597fn endingStream(r: *Reader, w: *Writer, limit: Limit) StreamError!usize {
1598 _ = r;
1599 _ = w;
1600 _ = limit;
1601 return error.EndOfStream;
1602}
1603
1604fn endingDiscard(r: *Reader, limit: Limit) Error!usize {
1605 _ = r;
1606 _ = limit;
1607 return error.EndOfStream;
1608}
1609
1610fn failingStream(r: *Reader, w: *Writer, limit: Limit) StreamError!usize {
1611 _ = r;
1612 _ = w;
1613 _ = limit;
1614 return error.ReadFailed;
1615}
1616
1617fn failingDiscard(r: *Reader, limit: Limit) Error!usize {
1618 _ = r;
1619 _ = limit;
1620 return error.ReadFailed;
1621}
1622
1623test "readAlloc when the backing reader provides one byte at a time" {
1624 const OneByteReader = struct {
1625 str: []const u8,
1626 i: usize,
1627 reader: Reader,
1628
1629 fn stream(r: *Reader, w: *Writer, limit: Limit) StreamError!usize {
1630 assert(@intFromEnum(limit) >= 1);
1631 const self: *@This() = @fieldParentPtr("reader", r);
1632 if (self.str.len - self.i == 0) return error.EndOfStream;
1633 try w.writeByte(self.str[self.i]);
1634 self.i += 1;
1635 return 1;
1636 }
1637 };
1638 const str = "This is a test";
1639 var one_byte_stream: OneByteReader = .{
1640 .str = str,
1641 .i = 0,
1642 .reader = .{
1643 .buffer = &.{},
1644 .vtable = &.{ .stream = OneByteReader.stream },
1645 .seek = 0,
1646 .end = 0,
1647 },
1648 };
1649 const res = try one_byte_stream.reader.allocRemaining(std.testing.allocator, .unlimited);
1650 defer std.testing.allocator.free(res);
1651 try std.testing.expectEqualStrings(str, res);
1652}
1653
1654test "takeDelimiterInclusive when it rebases" {
1655 const written_line = "ABCDEFGHIJKLMNOPQRSTUVWXYZ\n";
1656 var buffer: [128]u8 = undefined;
1657 var tr: std.testing.Reader = .init(&buffer, &.{
1658 .{ .buffer = written_line },
1659 .{ .buffer = written_line },
1660 .{ .buffer = written_line },
1661 .{ .buffer = written_line },
1662 .{ .buffer = written_line },
1663 .{ .buffer = written_line },
1664 });
1665 const r = &tr.interface;
1666 for (0..6) |_| {
1667 try std.testing.expectEqualStrings(written_line, try r.takeDelimiterInclusive('\n'));
1668 }
1669}
1670
1671/// Provides a `Reader` implementation by passing data from an underlying
1672/// reader through `Hasher.update`.
1673///
1674/// The underlying reader is best unbuffered.
1675///
1676/// This implementation makes suboptimal buffering decisions due to being
1677/// generic. A better solution will involve creating a reader for each hash
1678/// function, where the discard buffer can be tailored to the hash
1679/// implementation details.
1680pub fn Hashed(comptime Hasher: type) type {
1681 return struct {
1682 in: *Reader,
1683 hasher: Hasher,
1684 interface: Reader,
1685
1686 pub fn init(in: *Reader, hasher: Hasher, buffer: []u8) @This() {
1687 return .{
1688 .in = in,
1689 .hasher = hasher,
1690 .interface = .{
1691 .vtable = &.{
1692 .read = @This().read,
1693 .discard = @This().discard,
1694 },
1695 .buffer = buffer,
1696 .end = 0,
1697 .seek = 0,
1698 },
1699 };
1700 }
1701
1702 fn read(r: *Reader, w: *Writer, limit: Limit) StreamError!usize {
1703 const this: *@This() = @alignCast(@fieldParentPtr("interface", r));
1704 const data = w.writableVector(limit);
1705 const n = try this.in.readVec(data);
1706 const result = w.advanceVector(n);
1707 var remaining: usize = n;
1708 for (data) |slice| {
1709 if (remaining < slice.len) {
1710 this.hasher.update(slice[0..remaining]);
1711 return result;
1712 } else {
1713 remaining -= slice.len;
1714 this.hasher.update(slice);
1715 }
1716 }
1717 assert(remaining == 0);
1718 return result;
1719 }
1720
1721 fn discard(r: *Reader, limit: Limit) Error!usize {
1722 const this: *@This() = @alignCast(@fieldParentPtr("interface", r));
1723 var w = this.hasher.writer(&.{});
1724 const n = this.in.stream(&w, limit) catch |err| switch (err) {
1725 error.WriteFailed => unreachable,
1726 else => |e| return e,
1727 };
1728 return n;
1729 }
1730 };
1731}
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/Reader/test.zig deleted-372
...@@ -1,372 +0,0 @@
1const builtin = @import("builtin");
2const std = @import("../../std.zig");
3const testing = std.testing;
4
5test "Reader" {
6 var buf = "a\x02".*;
7 var fis = std.io.fixedBufferStream(&buf);
8 const reader = fis.reader();
9 try testing.expect((try reader.readByte()) == 'a');
10 try testing.expect((try reader.readEnum(enum(u8) {
11 a = 0,
12 b = 99,
13 c = 2,
14 d = 3,
15 }, builtin.cpu.arch.endian())) == .c);
16 try testing.expectError(error.EndOfStream, reader.readByte());
17}
18
19test "isBytes" {
20 var fis = std.io.fixedBufferStream("foobar");
21 const reader = fis.reader();
22 try testing.expectEqual(true, try reader.isBytes("foo"));
23 try testing.expectEqual(false, try reader.isBytes("qux"));
24}
25
26test "skipBytes" {
27 var fis = std.io.fixedBufferStream("foobar");
28 const reader = fis.reader();
29 try reader.skipBytes(3, .{});
30 try testing.expect(try reader.isBytes("bar"));
31 try reader.skipBytes(0, .{});
32 try testing.expectError(error.EndOfStream, reader.skipBytes(1, .{}));
33}
34
35test "readUntilDelimiterArrayList returns ArrayLists with bytes read until the delimiter, then EndOfStream" {
36 const a = std.testing.allocator;
37 var list = std.ArrayList(u8).init(a);
38 defer list.deinit();
39
40 var fis = std.io.fixedBufferStream("0000\n1234\n");
41 const reader = fis.reader();
42
43 try reader.readUntilDelimiterArrayList(&list, '\n', 5);
44 try std.testing.expectEqualStrings("0000", list.items);
45 try reader.readUntilDelimiterArrayList(&list, '\n', 5);
46 try std.testing.expectEqualStrings("1234", list.items);
47 try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiterArrayList(&list, '\n', 5));
48}
49
50test "readUntilDelimiterArrayList returns an empty ArrayList" {
51 const a = std.testing.allocator;
52 var list = std.ArrayList(u8).init(a);
53 defer list.deinit();
54
55 var fis = std.io.fixedBufferStream("\n");
56 const reader = fis.reader();
57
58 try reader.readUntilDelimiterArrayList(&list, '\n', 5);
59 try std.testing.expectEqualStrings("", list.items);
60}
61
62test "readUntilDelimiterArrayList returns StreamTooLong, then an ArrayList with bytes read until the delimiter" {
63 const a = std.testing.allocator;
64 var list = std.ArrayList(u8).init(a);
65 defer list.deinit();
66
67 var fis = std.io.fixedBufferStream("1234567\n");
68 const reader = fis.reader();
69
70 try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterArrayList(&list, '\n', 5));
71 try std.testing.expectEqualStrings("12345", list.items);
72 try reader.readUntilDelimiterArrayList(&list, '\n', 5);
73 try std.testing.expectEqualStrings("67", list.items);
74}
75
76test "readUntilDelimiterArrayList returns EndOfStream" {
77 const a = std.testing.allocator;
78 var list = std.ArrayList(u8).init(a);
79 defer list.deinit();
80
81 var fis = std.io.fixedBufferStream("1234");
82 const reader = fis.reader();
83
84 try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiterArrayList(&list, '\n', 5));
85 try std.testing.expectEqualStrings("1234", list.items);
86}
87
88test "readUntilDelimiterAlloc returns ArrayLists with bytes read until the delimiter, then EndOfStream" {
89 const a = std.testing.allocator;
90
91 var fis = std.io.fixedBufferStream("0000\n1234\n");
92 const reader = fis.reader();
93
94 {
95 const result = try reader.readUntilDelimiterAlloc(a, '\n', 5);
96 defer a.free(result);
97 try std.testing.expectEqualStrings("0000", result);
98 }
99
100 {
101 const result = try reader.readUntilDelimiterAlloc(a, '\n', 5);
102 defer a.free(result);
103 try std.testing.expectEqualStrings("1234", result);
104 }
105
106 try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiterAlloc(a, '\n', 5));
107}
108
109test "readUntilDelimiterAlloc returns an empty ArrayList" {
110 const a = std.testing.allocator;
111
112 var fis = std.io.fixedBufferStream("\n");
113 const reader = fis.reader();
114
115 {
116 const result = try reader.readUntilDelimiterAlloc(a, '\n', 5);
117 defer a.free(result);
118 try std.testing.expectEqualStrings("", result);
119 }
120}
121
122test "readUntilDelimiterAlloc returns StreamTooLong, then an ArrayList with bytes read until the delimiter" {
123 const a = std.testing.allocator;
124
125 var fis = std.io.fixedBufferStream("1234567\n");
126 const reader = fis.reader();
127
128 try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterAlloc(a, '\n', 5));
129
130 const result = try reader.readUntilDelimiterAlloc(a, '\n', 5);
131 defer a.free(result);
132 try std.testing.expectEqualStrings("67", result);
133}
134
135test "readUntilDelimiterAlloc returns EndOfStream" {
136 const a = std.testing.allocator;
137
138 var fis = std.io.fixedBufferStream("1234");
139 const reader = fis.reader();
140
141 try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiterAlloc(a, '\n', 5));
142}
143
144test "readUntilDelimiter returns bytes read until the delimiter" {
145 var buf: [5]u8 = undefined;
146 var fis = std.io.fixedBufferStream("0000\n1234\n");
147 const reader = fis.reader();
148 try std.testing.expectEqualStrings("0000", try reader.readUntilDelimiter(&buf, '\n'));
149 try std.testing.expectEqualStrings("1234", try reader.readUntilDelimiter(&buf, '\n'));
150}
151
152test "readUntilDelimiter returns an empty string" {
153 var buf: [5]u8 = undefined;
154 var fis = std.io.fixedBufferStream("\n");
155 const reader = fis.reader();
156 try std.testing.expectEqualStrings("", try reader.readUntilDelimiter(&buf, '\n'));
157}
158
159test "readUntilDelimiter returns StreamTooLong, then an empty string" {
160 var buf: [5]u8 = undefined;
161 var fis = std.io.fixedBufferStream("12345\n");
162 const reader = fis.reader();
163 try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiter(&buf, '\n'));
164 try std.testing.expectEqualStrings("", try reader.readUntilDelimiter(&buf, '\n'));
165}
166
167test "readUntilDelimiter returns StreamTooLong, then bytes read until the delimiter" {
168 var buf: [5]u8 = undefined;
169 var fis = std.io.fixedBufferStream("1234567\n");
170 const reader = fis.reader();
171 try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiter(&buf, '\n'));
172 try std.testing.expectEqualStrings("67", try reader.readUntilDelimiter(&buf, '\n'));
173}
174
175test "readUntilDelimiter returns EndOfStream" {
176 {
177 var buf: [5]u8 = undefined;
178 var fis = std.io.fixedBufferStream("");
179 const reader = fis.reader();
180 try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiter(&buf, '\n'));
181 }
182 {
183 var buf: [5]u8 = undefined;
184 var fis = std.io.fixedBufferStream("1234");
185 const reader = fis.reader();
186 try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiter(&buf, '\n'));
187 }
188}
189
190test "readUntilDelimiter returns bytes read until delimiter, then EndOfStream" {
191 var buf: [5]u8 = undefined;
192 var fis = std.io.fixedBufferStream("1234\n");
193 const reader = fis.reader();
194 try std.testing.expectEqualStrings("1234", try reader.readUntilDelimiter(&buf, '\n'));
195 try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiter(&buf, '\n'));
196}
197
198test "readUntilDelimiter returns StreamTooLong, then EndOfStream" {
199 var buf: [5]u8 = undefined;
200 var fis = std.io.fixedBufferStream("12345");
201 const reader = fis.reader();
202 try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiter(&buf, '\n'));
203 try std.testing.expectError(error.EndOfStream, reader.readUntilDelimiter(&buf, '\n'));
204}
205
206test "readUntilDelimiter writes all bytes read to the output buffer" {
207 var buf: [5]u8 = undefined;
208 var fis = std.io.fixedBufferStream("0000\n12345");
209 const reader = fis.reader();
210 _ = try reader.readUntilDelimiter(&buf, '\n');
211 try std.testing.expectEqualStrings("0000\n", &buf);
212 try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiter(&buf, '\n'));
213 try std.testing.expectEqualStrings("12345", &buf);
214}
215
216test "readUntilDelimiterOrEofAlloc returns ArrayLists with bytes read until the delimiter, then EndOfStream" {
217 const a = std.testing.allocator;
218
219 var fis = std.io.fixedBufferStream("0000\n1234\n");
220 const reader = fis.reader();
221
222 {
223 const result = (try reader.readUntilDelimiterOrEofAlloc(a, '\n', 5)).?;
224 defer a.free(result);
225 try std.testing.expectEqualStrings("0000", result);
226 }
227
228 {
229 const result = (try reader.readUntilDelimiterOrEofAlloc(a, '\n', 5)).?;
230 defer a.free(result);
231 try std.testing.expectEqualStrings("1234", result);
232 }
233
234 try std.testing.expect((try reader.readUntilDelimiterOrEofAlloc(a, '\n', 5)) == null);
235}
236
237test "readUntilDelimiterOrEofAlloc returns an empty ArrayList" {
238 const a = std.testing.allocator;
239
240 var fis = std.io.fixedBufferStream("\n");
241 const reader = fis.reader();
242
243 {
244 const result = (try reader.readUntilDelimiterOrEofAlloc(a, '\n', 5)).?;
245 defer a.free(result);
246 try std.testing.expectEqualStrings("", result);
247 }
248}
249
250test "readUntilDelimiterOrEofAlloc returns StreamTooLong, then an ArrayList with bytes read until the delimiter" {
251 const a = std.testing.allocator;
252
253 var fis = std.io.fixedBufferStream("1234567\n");
254 const reader = fis.reader();
255
256 try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterOrEofAlloc(a, '\n', 5));
257
258 const result = (try reader.readUntilDelimiterOrEofAlloc(a, '\n', 5)).?;
259 defer a.free(result);
260 try std.testing.expectEqualStrings("67", result);
261}
262
263test "readUntilDelimiterOrEof returns bytes read until the delimiter" {
264 var buf: [5]u8 = undefined;
265 var fis = std.io.fixedBufferStream("0000\n1234\n");
266 const reader = fis.reader();
267 try std.testing.expectEqualStrings("0000", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?);
268 try std.testing.expectEqualStrings("1234", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?);
269}
270
271test "readUntilDelimiterOrEof returns an empty string" {
272 var buf: [5]u8 = undefined;
273 var fis = std.io.fixedBufferStream("\n");
274 const reader = fis.reader();
275 try std.testing.expectEqualStrings("", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?);
276}
277
278test "readUntilDelimiterOrEof returns StreamTooLong, then an empty string" {
279 var buf: [5]u8 = undefined;
280 var fis = std.io.fixedBufferStream("12345\n");
281 const reader = fis.reader();
282 try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterOrEof(&buf, '\n'));
283 try std.testing.expectEqualStrings("", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?);
284}
285
286test "readUntilDelimiterOrEof returns StreamTooLong, then bytes read until the delimiter" {
287 var buf: [5]u8 = undefined;
288 var fis = std.io.fixedBufferStream("1234567\n");
289 const reader = fis.reader();
290 try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterOrEof(&buf, '\n'));
291 try std.testing.expectEqualStrings("67", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?);
292}
293
294test "readUntilDelimiterOrEof returns null" {
295 var buf: [5]u8 = undefined;
296 var fis = std.io.fixedBufferStream("");
297 const reader = fis.reader();
298 try std.testing.expect((try reader.readUntilDelimiterOrEof(&buf, '\n')) == null);
299}
300
301test "readUntilDelimiterOrEof returns bytes read until delimiter, then null" {
302 var buf: [5]u8 = undefined;
303 var fis = std.io.fixedBufferStream("1234\n");
304 const reader = fis.reader();
305 try std.testing.expectEqualStrings("1234", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?);
306 try std.testing.expect((try reader.readUntilDelimiterOrEof(&buf, '\n')) == null);
307}
308
309test "readUntilDelimiterOrEof returns bytes read until end-of-stream" {
310 var buf: [5]u8 = undefined;
311 var fis = std.io.fixedBufferStream("1234");
312 const reader = fis.reader();
313 try std.testing.expectEqualStrings("1234", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?);
314}
315
316test "readUntilDelimiterOrEof returns StreamTooLong, then bytes read until end-of-stream" {
317 var buf: [5]u8 = undefined;
318 var fis = std.io.fixedBufferStream("1234567");
319 const reader = fis.reader();
320 try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterOrEof(&buf, '\n'));
321 try std.testing.expectEqualStrings("67", (try reader.readUntilDelimiterOrEof(&buf, '\n')).?);
322}
323
324test "readUntilDelimiterOrEof writes all bytes read to the output buffer" {
325 var buf: [5]u8 = undefined;
326 var fis = std.io.fixedBufferStream("0000\n12345");
327 const reader = fis.reader();
328 _ = try reader.readUntilDelimiterOrEof(&buf, '\n');
329 try std.testing.expectEqualStrings("0000\n", &buf);
330 try std.testing.expectError(error.StreamTooLong, reader.readUntilDelimiterOrEof(&buf, '\n'));
331 try std.testing.expectEqualStrings("12345", &buf);
332}
333
334test "streamUntilDelimiter writes all bytes without delimiter to the output" {
335 const input_string = "some_string_with_delimiter!";
336 var input_fbs = std.io.fixedBufferStream(input_string);
337 const reader = input_fbs.reader();
338
339 var output: [input_string.len]u8 = undefined;
340 var output_fbs = std.io.fixedBufferStream(&output);
341 const writer = output_fbs.writer();
342
343 try reader.streamUntilDelimiter(writer, '!', input_fbs.buffer.len);
344 try std.testing.expectEqualStrings("some_string_with_delimiter", output_fbs.getWritten());
345 try std.testing.expectError(error.EndOfStream, reader.streamUntilDelimiter(writer, '!', input_fbs.buffer.len));
346
347 input_fbs.reset();
348 output_fbs.reset();
349
350 try std.testing.expectError(error.StreamTooLong, reader.streamUntilDelimiter(writer, '!', 5));
351}
352
353test "readBoundedBytes correctly reads into a new bounded array" {
354 const test_string = "abcdefg";
355 var fis = std.io.fixedBufferStream(test_string);
356 const reader = fis.reader();
357
358 var array = try reader.readBoundedBytes(10000);
359 try testing.expectEqualStrings(array.slice(), test_string);
360}
361
362test "readIntoBoundedBytes correctly reads into a provided bounded array" {
363 const test_string = "abcdefg";
364 var fis = std.io.fixedBufferStream(test_string);
365 const reader = fis.reader();
366
367 var bounded_array = std.BoundedArray(u8, 10000){};
368
369 // compile time error if the size is not the same at the provided `bounded.capacity()`
370 try reader.readIntoBoundedBytes(10000, &bounded_array);
371 try testing.expectEqualStrings(bounded_array.slice(), test_string);
372}
lib/std/io/Writer.zig deleted-2486
...@@ -1,2486 +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 `}}`.
563pub fn print(w: *Writer, comptime fmt: []const u8, args: anytype) Error!void {
564 const ArgsType = @TypeOf(args);
565 const args_type_info = @typeInfo(ArgsType);
566 if (args_type_info != .@"struct") {
567 @compileError("expected tuple or struct argument, found " ++ @typeName(ArgsType));
568 }
569
570 const fields_info = args_type_info.@"struct".fields;
571 const max_format_args = @typeInfo(std.fmt.ArgSetType).int.bits;
572 if (fields_info.len > max_format_args) {
573 @compileError("32 arguments max are supported per format call");
574 }
575
576 @setEvalBranchQuota(fmt.len * 1000);
577 comptime var arg_state: std.fmt.ArgState = .{ .args_len = fields_info.len };
578 comptime var i = 0;
579 comptime var literal: []const u8 = "";
580 inline while (true) {
581 const start_index = i;
582
583 inline while (i < fmt.len) : (i += 1) {
584 switch (fmt[i]) {
585 '{', '}' => break,
586 else => {},
587 }
588 }
589
590 comptime var end_index = i;
591 comptime var unescape_brace = false;
592
593 // Handle {{ and }}, those are un-escaped as single braces
594 if (i + 1 < fmt.len and fmt[i + 1] == fmt[i]) {
595 unescape_brace = true;
596 // Make the first brace part of the literal...
597 end_index += 1;
598 // ...and skip both
599 i += 2;
600 }
601
602 literal = literal ++ fmt[start_index..end_index];
603
604 // We've already skipped the other brace, restart the loop
605 if (unescape_brace) continue;
606
607 // Write out the literal
608 if (literal.len != 0) {
609 try w.writeAll(literal);
610 literal = "";
611 }
612
613 if (i >= fmt.len) break;
614
615 if (fmt[i] == '}') {
616 @compileError("missing opening {");
617 }
618
619 // Get past the {
620 comptime assert(fmt[i] == '{');
621 i += 1;
622
623 const fmt_begin = i;
624 // Find the closing brace
625 inline while (i < fmt.len and fmt[i] != '}') : (i += 1) {}
626 const fmt_end = i;
627
628 if (i >= fmt.len) {
629 @compileError("missing closing }");
630 }
631
632 // Get past the }
633 comptime assert(fmt[i] == '}');
634 i += 1;
635
636 const placeholder_array = fmt[fmt_begin..fmt_end].*;
637 const placeholder = comptime std.fmt.Placeholder.parse(&placeholder_array);
638 const arg_pos = comptime switch (placeholder.arg) {
639 .none => null,
640 .number => |pos| pos,
641 .named => |arg_name| std.meta.fieldIndex(ArgsType, arg_name) orelse
642 @compileError("no argument with name '" ++ arg_name ++ "'"),
643 };
644
645 const width = switch (placeholder.width) {
646 .none => null,
647 .number => |v| v,
648 .named => |arg_name| blk: {
649 const arg_i = comptime std.meta.fieldIndex(ArgsType, arg_name) orelse
650 @compileError("no argument with name '" ++ arg_name ++ "'");
651 _ = comptime arg_state.nextArg(arg_i) orelse @compileError("too few arguments");
652 break :blk @field(args, arg_name);
653 },
654 };
655
656 const precision = switch (placeholder.precision) {
657 .none => null,
658 .number => |v| v,
659 .named => |arg_name| blk: {
660 const arg_i = comptime std.meta.fieldIndex(ArgsType, arg_name) orelse
661 @compileError("no argument with name '" ++ arg_name ++ "'");
662 _ = comptime arg_state.nextArg(arg_i) orelse @compileError("too few arguments");
663 break :blk @field(args, arg_name);
664 },
665 };
666
667 const arg_to_print = comptime arg_state.nextArg(arg_pos) orelse
668 @compileError("too few arguments");
669
670 try w.printValue(
671 placeholder.specifier_arg,
672 .{
673 .fill = placeholder.fill,
674 .alignment = placeholder.alignment,
675 .width = width,
676 .precision = precision,
677 },
678 @field(args, fields_info[arg_to_print].name),
679 std.options.fmt_max_depth,
680 );
681 }
682
683 if (comptime arg_state.hasUnusedArgs()) {
684 const missing_count = arg_state.args_len - @popCount(arg_state.used_args);
685 switch (missing_count) {
686 0 => unreachable,
687 1 => @compileError("unused argument in '" ++ fmt ++ "'"),
688 else => @compileError(std.fmt.comptimePrint("{d}", .{missing_count}) ++ " unused arguments in '" ++ fmt ++ "'"),
689 }
690 }
691}
692
693/// Calls `drain` as many times as necessary such that `byte` is transferred.
694pub fn writeByte(w: *Writer, byte: u8) Error!void {
695 while (w.buffer.len - w.end == 0) {
696 const n = try w.vtable.drain(w, &.{&.{byte}}, 1);
697 if (n > 0) return;
698 } else {
699 @branchHint(.likely);
700 w.buffer[w.end] = byte;
701 w.end += 1;
702 }
703}
704
705/// When draining the buffer, ensures that at least `preserve_length` bytes
706/// remain buffered.
707pub fn writeBytePreserve(w: *Writer, preserve_length: usize, byte: u8) Error!void {
708 while (w.buffer.len - w.end == 0) {
709 try drainPreserve(w, preserve_length);
710 } else {
711 @branchHint(.likely);
712 w.buffer[w.end] = byte;
713 w.end += 1;
714 }
715}
716
717/// Writes the same byte many times, performing the underlying write call as
718/// many times as necessary.
719pub fn splatByteAll(w: *Writer, byte: u8, n: usize) Error!void {
720 var remaining: usize = n;
721 while (remaining > 0) remaining -= try w.splatByte(byte, remaining);
722}
723
724/// Writes the same byte many times, allowing short writes.
725///
726/// Does maximum of one underlying `VTable.drain`.
727pub fn splatByte(w: *Writer, byte: u8, n: usize) Error!usize {
728 return writeSplat(w, &.{&.{byte}}, n);
729}
730
731/// Writes the same slice many times, performing the underlying write call as
732/// many times as necessary.
733pub fn splatBytesAll(w: *Writer, bytes: []const u8, splat: usize) Error!void {
734 var remaining_bytes: usize = bytes.len * splat;
735 remaining_bytes -= try w.splatBytes(bytes, splat);
736 while (remaining_bytes > 0) {
737 const leftover = remaining_bytes % bytes.len;
738 const buffers: [2][]const u8 = .{ bytes[bytes.len - leftover ..], bytes };
739 remaining_bytes -= try w.splatBytes(&buffers, splat);
740 }
741}
742
743/// Writes the same slice many times, allowing short writes.
744///
745/// Does maximum of one underlying `VTable.writeSplat`.
746pub fn splatBytes(w: *Writer, bytes: []const u8, n: usize) Error!usize {
747 return writeSplat(w, &.{bytes}, n);
748}
749
750/// Asserts the `buffer` was initialized with a capacity of at least `@sizeOf(T)` bytes.
751pub inline fn writeInt(w: *Writer, comptime T: type, value: T, endian: std.builtin.Endian) Error!void {
752 var bytes: [@divExact(@typeInfo(T).int.bits, 8)]u8 = undefined;
753 std.mem.writeInt(std.math.ByteAlignedInt(@TypeOf(value)), &bytes, value, endian);
754 return w.writeAll(&bytes);
755}
756
757pub fn writeStruct(w: *Writer, value: anytype) Error!void {
758 // Only extern and packed structs have defined in-memory layout.
759 comptime assert(@typeInfo(@TypeOf(value)).@"struct".layout != .auto);
760 return w.writeAll(std.mem.asBytes(&value));
761}
762
763/// The function is inline to avoid the dead code in case `endian` is
764/// comptime-known and matches host endianness.
765/// TODO: make sure this value is not a reference type
766pub inline fn writeStructEndian(w: *Writer, value: anytype, endian: std.builtin.Endian) Error!void {
767 switch (@typeInfo(@TypeOf(value))) {
768 .@"struct" => |info| switch (info.layout) {
769 .auto => @compileError("ill-defined memory layout"),
770 .@"extern" => {
771 if (native_endian == endian) {
772 return w.writeStruct(value);
773 } else {
774 var copy = value;
775 std.mem.byteSwapAllFields(@TypeOf(value), &copy);
776 return w.writeStruct(copy);
777 }
778 },
779 .@"packed" => {
780 return writeInt(w, info.backing_integer.?, @bitCast(value), endian);
781 },
782 },
783 else => @compileError("not a struct"),
784 }
785}
786
787pub inline fn writeSliceEndian(
788 w: *Writer,
789 Elem: type,
790 slice: []const Elem,
791 endian: std.builtin.Endian,
792) Error!void {
793 if (native_endian == endian) {
794 return writeAll(w, @ptrCast(slice));
795 } else {
796 return w.writeArraySwap(w, Elem, slice);
797 }
798}
799
800/// Unlike `writeSplat` and `writeVec`, this function will call into `VTable`
801/// even if there is enough buffer capacity for the file contents.
802///
803/// Although it would be possible to eliminate `error.Unimplemented` from the
804/// error set by reading directly into the buffer in such case, this is not
805/// done because it is more efficient to do it higher up the call stack so that
806/// the error does not occur with each write.
807///
808/// See `sendFileReading` for an alternative that does not have
809/// `error.Unimplemented` in the error set.
810pub fn sendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize {
811 return w.vtable.sendFile(w, file_reader, limit);
812}
813
814/// Returns how many bytes from `header` and `file_reader` were consumed.
815pub fn sendFileHeader(
816 w: *Writer,
817 header: []const u8,
818 file_reader: *File.Reader,
819 limit: Limit,
820) FileError!usize {
821 const new_end = w.end + header.len;
822 if (new_end <= w.buffer.len) {
823 @memcpy(w.buffer[w.end..][0..header.len], header);
824 w.end = new_end;
825 return header.len + try w.vtable.sendFile(w, file_reader, limit);
826 }
827 const buffered_contents = limit.slice(file_reader.interface.buffered());
828 const n = try w.vtable.drain(w, &.{ header, buffered_contents }, 1);
829 file_reader.interface.toss(n - header.len);
830 return n;
831}
832
833/// Asserts nonzero buffer capacity.
834pub fn sendFileReading(w: *Writer, file_reader: *File.Reader, limit: Limit) FileReadingError!usize {
835 const dest = limit.slice(try w.writableSliceGreedy(1));
836 const n = try file_reader.read(dest);
837 w.advance(n);
838 return n;
839}
840
841/// Number of bytes logically written is returned. This excludes bytes from
842/// `buffer` because they have already been logically written.
843pub fn sendFileAll(w: *Writer, file_reader: *File.Reader, limit: Limit) FileAllError!usize {
844 var remaining = @intFromEnum(limit);
845 while (remaining > 0) {
846 const n = sendFile(w, file_reader, .limited(remaining)) catch |err| switch (err) {
847 error.EndOfStream => break,
848 error.Unimplemented => {
849 file_reader.mode = file_reader.mode.toReading();
850 remaining -= try w.sendFileReadingAll(file_reader, .limited(remaining));
851 break;
852 },
853 else => |e| return e,
854 };
855 remaining -= n;
856 }
857 return @intFromEnum(limit) - remaining;
858}
859
860/// Equivalent to `sendFileAll` but uses direct `pread` and `read` calls on
861/// `file` rather than `sendFile`. This is generally used as a fallback when
862/// the underlying implementation returns `error.Unimplemented`, which is why
863/// that error code does not appear in this function's error set.
864///
865/// Asserts nonzero buffer capacity.
866pub fn sendFileReadingAll(w: *Writer, file_reader: *File.Reader, limit: Limit) FileAllError!usize {
867 var remaining = @intFromEnum(limit);
868 while (remaining > 0) {
869 remaining -= sendFileReading(w, file_reader, .limited(remaining)) catch |err| switch (err) {
870 error.EndOfStream => break,
871 else => |e| return e,
872 };
873 }
874 return @intFromEnum(limit) - remaining;
875}
876
877pub fn alignBuffer(
878 w: *Writer,
879 buffer: []const u8,
880 width: usize,
881 alignment: std.fmt.Alignment,
882 fill: u8,
883) Error!void {
884 const padding = if (buffer.len < width) width - buffer.len else 0;
885 if (padding == 0) {
886 @branchHint(.likely);
887 return w.writeAll(buffer);
888 }
889 switch (alignment) {
890 .left => {
891 try w.writeAll(buffer);
892 try w.splatByteAll(fill, padding);
893 },
894 .center => {
895 const left_padding = padding / 2;
896 const right_padding = (padding + 1) / 2;
897 try w.splatByteAll(fill, left_padding);
898 try w.writeAll(buffer);
899 try w.splatByteAll(fill, right_padding);
900 },
901 .right => {
902 try w.splatByteAll(fill, padding);
903 try w.writeAll(buffer);
904 },
905 }
906}
907
908pub fn alignBufferOptions(w: *Writer, buffer: []const u8, options: std.fmt.Options) Error!void {
909 return w.alignBuffer(buffer, options.width orelse buffer.len, options.alignment, options.fill);
910}
911
912pub fn printAddress(w: *Writer, value: anytype) Error!void {
913 const T = @TypeOf(value);
914 switch (@typeInfo(T)) {
915 .pointer => |info| {
916 try w.writeAll(@typeName(info.child) ++ "@");
917 const int = if (info.size == .slice) @intFromPtr(value.ptr) else @intFromPtr(value);
918 return w.printInt(int, 16, .lower, .{});
919 },
920 .optional => |info| {
921 if (@typeInfo(info.child) == .pointer) {
922 try w.writeAll(@typeName(info.child) ++ "@");
923 try w.printInt(@intFromPtr(value), 16, .lower, .{});
924 return;
925 }
926 },
927 else => {},
928 }
929
930 @compileError("cannot format non-pointer type " ++ @typeName(T) ++ " with * specifier");
931}
932
933pub fn printValue(
934 w: *Writer,
935 comptime fmt: []const u8,
936 options: std.fmt.Options,
937 value: anytype,
938 max_depth: usize,
939) Error!void {
940 const T = @TypeOf(value);
941
942 switch (fmt.len) {
943 1 => switch (fmt[0]) {
944 '*' => return w.printAddress(value),
945 'f' => return value.format(w),
946 'd' => switch (@typeInfo(T)) {
947 .float, .comptime_float => return printFloat(w, value, options.toNumber(.decimal, .lower)),
948 .int, .comptime_int => return printInt(w, value, 10, .lower, options),
949 .@"struct" => return value.formatNumber(w, options.toNumber(.decimal, .lower)),
950 .@"enum" => return printInt(w, @intFromEnum(value), 10, .lower, options),
951 .vector => return printVector(w, fmt, options, value, max_depth),
952 else => invalidFmtError(fmt, value),
953 },
954 'c' => return w.printAsciiChar(value, options),
955 'u' => return w.printUnicodeCodepoint(value),
956 'b' => switch (@typeInfo(T)) {
957 .int, .comptime_int => return printInt(w, value, 2, .lower, options),
958 .@"enum" => return printInt(w, @intFromEnum(value), 2, .lower, options),
959 .@"struct" => return value.formatNumber(w, options.toNumber(.binary, .lower)),
960 .vector => return printVector(w, fmt, options, value, max_depth),
961 else => invalidFmtError(fmt, value),
962 },
963 'o' => switch (@typeInfo(T)) {
964 .int, .comptime_int => return printInt(w, value, 8, .lower, options),
965 .@"enum" => return printInt(w, @intFromEnum(value), 8, .lower, options),
966 .@"struct" => return value.formatNumber(w, options.toNumber(.octal, .lower)),
967 .vector => return printVector(w, fmt, options, value, max_depth),
968 else => invalidFmtError(fmt, value),
969 },
970 'x' => switch (@typeInfo(T)) {
971 .float, .comptime_float => return printFloatHexOptions(w, value, options.toNumber(.hex, .lower)),
972 .int, .comptime_int => return printInt(w, value, 16, .lower, options),
973 .@"enum" => return printInt(w, @intFromEnum(value), 16, .lower, options),
974 .@"struct" => return value.formatNumber(w, options.toNumber(.hex, .lower)),
975 .pointer => |info| switch (info.size) {
976 .one, .slice => {
977 const slice: []const u8 = value;
978 optionsForbidden(options);
979 return printHex(w, slice, .lower);
980 },
981 .many, .c => {
982 const slice: [:0]const u8 = std.mem.span(value);
983 optionsForbidden(options);
984 return printHex(w, slice, .lower);
985 },
986 },
987 .array => {
988 const slice: []const u8 = &value;
989 optionsForbidden(options);
990 return printHex(w, slice, .lower);
991 },
992 .vector => return printVector(w, fmt, options, value, max_depth),
993 else => invalidFmtError(fmt, value),
994 },
995 'X' => switch (@typeInfo(T)) {
996 .float, .comptime_float => return printFloatHexOptions(w, value, options.toNumber(.hex, .lower)),
997 .int, .comptime_int => return printInt(w, value, 16, .upper, options),
998 .@"enum" => return printInt(w, @intFromEnum(value), 16, .upper, options),
999 .@"struct" => return value.formatNumber(w, options.toNumber(.hex, .upper)),
1000 .pointer => |info| switch (info.size) {
1001 .one, .slice => {
1002 const slice: []const u8 = value;
1003 optionsForbidden(options);
1004 return printHex(w, slice, .upper);
1005 },
1006 .many, .c => {
1007 const slice: [:0]const u8 = std.mem.span(value);
1008 optionsForbidden(options);
1009 return printHex(w, slice, .upper);
1010 },
1011 },
1012 .array => {
1013 const slice: []const u8 = &value;
1014 optionsForbidden(options);
1015 return printHex(w, slice, .upper);
1016 },
1017 .vector => return printVector(w, fmt, options, value, max_depth),
1018 else => invalidFmtError(fmt, value),
1019 },
1020 's' => switch (@typeInfo(T)) {
1021 .pointer => |info| switch (info.size) {
1022 .one, .slice => {
1023 const slice: []const u8 = value;
1024 return w.alignBufferOptions(slice, options);
1025 },
1026 .many, .c => {
1027 const slice: [:0]const u8 = std.mem.span(value);
1028 return w.alignBufferOptions(slice, options);
1029 },
1030 },
1031 .array => {
1032 const slice: []const u8 = &value;
1033 return w.alignBufferOptions(slice, options);
1034 },
1035 else => invalidFmtError(fmt, value),
1036 },
1037 'B' => switch (@typeInfo(T)) {
1038 .int, .comptime_int => return w.printByteSize(value, .decimal, options),
1039 .@"struct" => return value.formatByteSize(w, .decimal),
1040 else => invalidFmtError(fmt, value),
1041 },
1042 'D' => switch (@typeInfo(T)) {
1043 .int, .comptime_int => return w.printDuration(value, options),
1044 .@"struct" => return value.formatDuration(w),
1045 else => invalidFmtError(fmt, value),
1046 },
1047 'e' => switch (@typeInfo(T)) {
1048 .float, .comptime_float => return printFloat(w, value, options.toNumber(.scientific, .lower)),
1049 .@"struct" => return value.formatNumber(w, options.toNumber(.scientific, .lower)),
1050 else => invalidFmtError(fmt, value),
1051 },
1052 'E' => switch (@typeInfo(T)) {
1053 .float, .comptime_float => return printFloat(w, value, options.toNumber(.scientific, .upper)),
1054 .@"struct" => return value.formatNumber(w, options.toNumber(.scientific, .upper)),
1055 else => invalidFmtError(fmt, value),
1056 },
1057 't' => switch (@typeInfo(T)) {
1058 .error_set => return w.writeAll(@errorName(value)),
1059 .@"enum", .@"union" => return w.writeAll(@tagName(value)),
1060 else => invalidFmtError(fmt, value),
1061 },
1062 else => {},
1063 },
1064 2 => switch (fmt[0]) {
1065 'B' => switch (fmt[1]) {
1066 'i' => switch (@typeInfo(T)) {
1067 .int, .comptime_int => return w.printByteSize(value, .binary, options),
1068 .@"struct" => return value.formatByteSize(w, .binary),
1069 else => invalidFmtError(fmt, value),
1070 },
1071 else => {},
1072 },
1073 else => {},
1074 },
1075 3 => if (fmt[0] == 'b' and fmt[1] == '6' and fmt[2] == '4') switch (@typeInfo(T)) {
1076 .pointer => |info| switch (info.size) {
1077 .one, .slice => {
1078 const slice: []const u8 = value;
1079 optionsForbidden(options);
1080 return w.printBase64(slice);
1081 },
1082 .many, .c => {
1083 const slice: [:0]const u8 = std.mem.span(value);
1084 optionsForbidden(options);
1085 return w.printBase64(slice);
1086 },
1087 },
1088 .array => {
1089 const slice: []const u8 = &value;
1090 optionsForbidden(options);
1091 return w.printBase64(slice);
1092 },
1093 else => invalidFmtError(fmt, value),
1094 },
1095 else => {},
1096 }
1097
1098 const is_any = comptime std.mem.eql(u8, fmt, ANY);
1099 if (!is_any and std.meta.hasMethod(T, "format") and fmt.len == 0) {
1100 // after 0.15.0 is tagged, delete this compile error and its condition
1101 @compileError("ambiguous format string; specify {f} to call format method, or {any} to skip it");
1102 }
1103
1104 switch (@typeInfo(T)) {
1105 .float, .comptime_float => {
1106 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1107 return printFloat(w, value, options.toNumber(.decimal, .lower));
1108 },
1109 .int, .comptime_int => {
1110 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1111 return printInt(w, value, 10, .lower, options);
1112 },
1113 .bool => {
1114 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1115 const string: []const u8 = if (value) "true" else "false";
1116 return w.alignBufferOptions(string, options);
1117 },
1118 .void => {
1119 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1120 return w.alignBufferOptions("void", options);
1121 },
1122 .optional => {
1123 const remaining_fmt = comptime if (fmt.len > 0 and fmt[0] == '?')
1124 stripOptionalOrErrorUnionSpec(fmt)
1125 else if (is_any)
1126 ANY
1127 else
1128 @compileError("cannot print optional without a specifier (i.e. {?} or {any})");
1129 if (value) |payload| {
1130 return w.printValue(remaining_fmt, options, payload, max_depth);
1131 } else {
1132 return w.alignBufferOptions("null", options);
1133 }
1134 },
1135 .error_union => {
1136 const remaining_fmt = comptime if (fmt.len > 0 and fmt[0] == '!')
1137 stripOptionalOrErrorUnionSpec(fmt)
1138 else if (is_any)
1139 ANY
1140 else
1141 @compileError("cannot print error union without a specifier (i.e. {!} or {any})");
1142 if (value) |payload| {
1143 return w.printValue(remaining_fmt, options, payload, max_depth);
1144 } else |err| {
1145 return w.printValue("", options, err, max_depth);
1146 }
1147 },
1148 .error_set => {
1149 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1150 optionsForbidden(options);
1151 return printErrorSet(w, value);
1152 },
1153 .@"enum" => |info| {
1154 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1155 optionsForbidden(options);
1156 if (info.is_exhaustive) {
1157 return printEnumExhaustive(w, value);
1158 } else {
1159 return printEnumNonexhaustive(w, value);
1160 }
1161 },
1162 .@"union" => |info| {
1163 if (!is_any) {
1164 if (fmt.len != 0) invalidFmtError(fmt, value);
1165 return printValue(w, ANY, options, value, max_depth);
1166 }
1167 if (max_depth == 0) {
1168 try w.writeAll(".{ ... }");
1169 return;
1170 }
1171 if (info.tag_type) |UnionTagType| {
1172 try w.writeAll(".{ .");
1173 try w.writeAll(@tagName(@as(UnionTagType, value)));
1174 try w.writeAll(" = ");
1175 inline for (info.fields) |u_field| {
1176 if (value == @field(UnionTagType, u_field.name)) {
1177 try w.printValue(ANY, options, @field(value, u_field.name), max_depth - 1);
1178 }
1179 }
1180 try w.writeAll(" }");
1181 } else switch (info.layout) {
1182 .auto => {
1183 return w.writeAll(".{ ... }");
1184 },
1185 .@"extern", .@"packed" => {
1186 if (info.fields.len == 0) return w.writeAll(".{}");
1187 try w.writeAll(".{ ");
1188 inline for (info.fields) |field| {
1189 try w.writeByte('.');
1190 try w.writeAll(field.name);
1191 try w.writeAll(" = ");
1192 try w.printValue(ANY, options, @field(value, field.name), max_depth - 1);
1193 (try w.writableArray(2)).* = ", ".*;
1194 }
1195 w.buffer[w.end - 2 ..][0..2].* = " }".*;
1196 },
1197 }
1198 },
1199 .@"struct" => |info| {
1200 if (!is_any) {
1201 if (fmt.len != 0) invalidFmtError(fmt, value);
1202 return printValue(w, ANY, options, value, max_depth);
1203 }
1204 if (info.is_tuple) {
1205 // Skip the type and field names when formatting tuples.
1206 if (max_depth == 0) {
1207 try w.writeAll(".{ ... }");
1208 return;
1209 }
1210 try w.writeAll(".{");
1211 inline for (info.fields, 0..) |f, i| {
1212 if (i == 0) {
1213 try w.writeAll(" ");
1214 } else {
1215 try w.writeAll(", ");
1216 }
1217 try w.printValue(ANY, options, @field(value, f.name), max_depth - 1);
1218 }
1219 try w.writeAll(" }");
1220 return;
1221 }
1222 if (max_depth == 0) {
1223 try w.writeAll(".{ ... }");
1224 return;
1225 }
1226 try w.writeAll(".{");
1227 inline for (info.fields, 0..) |f, i| {
1228 if (i == 0) {
1229 try w.writeAll(" .");
1230 } else {
1231 try w.writeAll(", .");
1232 }
1233 try w.writeAll(f.name);
1234 try w.writeAll(" = ");
1235 try w.printValue(ANY, options, @field(value, f.name), max_depth - 1);
1236 }
1237 try w.writeAll(" }");
1238 },
1239 .pointer => |ptr_info| switch (ptr_info.size) {
1240 .one => switch (@typeInfo(ptr_info.child)) {
1241 .array => |array_info| return w.printValue(fmt, options, @as([]const array_info.child, value), max_depth),
1242 .@"enum", .@"union", .@"struct" => return w.printValue(fmt, options, value.*, max_depth),
1243 else => {
1244 var buffers: [2][]const u8 = .{ @typeName(ptr_info.child), "@" };
1245 try w.writeVecAll(&buffers);
1246 try w.printInt(@intFromPtr(value), 16, .lower, options);
1247 return;
1248 },
1249 },
1250 .many, .c => {
1251 if (!is_any) @compileError("cannot format pointer without a specifier (i.e. {s} or {*})");
1252 optionsForbidden(options);
1253 try w.printAddress(value);
1254 },
1255 .slice => {
1256 if (!is_any)
1257 @compileError("cannot format slice without a specifier (i.e. {s}, {x}, {b64}, or {any})");
1258 if (max_depth == 0) return w.writeAll("{ ... }");
1259 try w.writeAll("{ ");
1260 for (value, 0..) |elem, i| {
1261 try w.printValue(fmt, options, elem, max_depth - 1);
1262 if (i != value.len - 1) {
1263 try w.writeAll(", ");
1264 }
1265 }
1266 try w.writeAll(" }");
1267 },
1268 },
1269 .array => {
1270 if (!is_any) @compileError("cannot format array without a specifier (i.e. {s} or {any})");
1271 if (max_depth == 0) return w.writeAll("{ ... }");
1272 try w.writeAll("{ ");
1273 for (value, 0..) |elem, i| {
1274 try w.printValue(fmt, options, elem, max_depth - 1);
1275 if (i < value.len - 1) {
1276 try w.writeAll(", ");
1277 }
1278 }
1279 try w.writeAll(" }");
1280 },
1281 .vector => {
1282 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1283 return printVector(w, fmt, options, value, max_depth);
1284 },
1285 .@"fn" => @compileError("unable to format function body type, use '*const " ++ @typeName(T) ++ "' for a function pointer type"),
1286 .type => {
1287 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1288 return w.alignBufferOptions(@typeName(value), options);
1289 },
1290 .enum_literal => {
1291 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1292 optionsForbidden(options);
1293 var vecs: [2][]const u8 = .{ ".", @tagName(value) };
1294 return w.writeVecAll(&vecs);
1295 },
1296 .null => {
1297 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1298 return w.alignBufferOptions("null", options);
1299 },
1300 else => @compileError("unable to format type '" ++ @typeName(T) ++ "'"),
1301 }
1302}
1303
1304fn optionsForbidden(options: std.fmt.Options) void {
1305 assert(options.precision == null);
1306 assert(options.width == null);
1307}
1308
1309fn printErrorSet(w: *Writer, error_set: anyerror) Error!void {
1310 var vecs: [2][]const u8 = .{ "error.", @errorName(error_set) };
1311 try w.writeVecAll(&vecs);
1312}
1313
1314fn printEnumExhaustive(w: *Writer, value: anytype) Error!void {
1315 var vecs: [2][]const u8 = .{ ".", @tagName(value) };
1316 try w.writeVecAll(&vecs);
1317}
1318
1319fn printEnumNonexhaustive(w: *Writer, value: anytype) Error!void {
1320 if (std.enums.tagName(@TypeOf(value), value)) |tag_name| {
1321 var vecs: [2][]const u8 = .{ ".", tag_name };
1322 try w.writeVecAll(&vecs);
1323 return;
1324 }
1325 try w.writeAll("@enumFromInt(");
1326 try w.printInt(@intFromEnum(value), 10, .lower, .{});
1327 try w.writeByte(')');
1328}
1329
1330pub fn printVector(
1331 w: *Writer,
1332 comptime fmt: []const u8,
1333 options: std.fmt.Options,
1334 value: anytype,
1335 max_depth: usize,
1336) Error!void {
1337 const len = @typeInfo(@TypeOf(value)).vector.len;
1338 if (max_depth == 0) return w.writeAll("{ ... }");
1339 try w.writeAll("{ ");
1340 inline for (0..len) |i| {
1341 try w.printValue(fmt, options, value[i], max_depth - 1);
1342 if (i < len - 1) try w.writeAll(", ");
1343 }
1344 try w.writeAll(" }");
1345}
1346
1347// A wrapper around `printIntAny` to avoid the generic explosion of this
1348// function by funneling smaller integer types through `isize` and `usize`.
1349pub inline fn printInt(
1350 w: *Writer,
1351 value: anytype,
1352 base: u8,
1353 case: std.fmt.Case,
1354 options: std.fmt.Options,
1355) Error!void {
1356 switch (@TypeOf(value)) {
1357 isize, usize => {},
1358 comptime_int => {
1359 if (comptime std.math.cast(usize, value)) |x| return printIntAny(w, x, base, case, options);
1360 if (comptime std.math.cast(isize, value)) |x| return printIntAny(w, x, base, case, options);
1361 const Int = std.math.IntFittingRange(value, value);
1362 return printIntAny(w, @as(Int, value), base, case, options);
1363 },
1364 else => switch (@typeInfo(@TypeOf(value)).int.signedness) {
1365 .signed => if (std.math.cast(isize, value)) |x| return printIntAny(w, x, base, case, options),
1366 .unsigned => if (std.math.cast(usize, value)) |x| return printIntAny(w, x, base, case, options),
1367 },
1368 }
1369 return printIntAny(w, value, base, case, options);
1370}
1371
1372/// In general, prefer `printInt` to avoid generic explosion. However this
1373/// function may be used when optimal codegen for a particular integer type is
1374/// desired.
1375pub fn printIntAny(
1376 w: *Writer,
1377 value: anytype,
1378 base: u8,
1379 case: std.fmt.Case,
1380 options: std.fmt.Options,
1381) Error!void {
1382 assert(base >= 2);
1383 const value_info = @typeInfo(@TypeOf(value)).int;
1384
1385 // The type must have the same size as `base` or be wider in order for the
1386 // division to work
1387 const min_int_bits = comptime @max(value_info.bits, 8);
1388 const MinInt = std.meta.Int(.unsigned, min_int_bits);
1389
1390 const abs_value = @abs(value);
1391 // The worst case in terms of space needed is base 2, plus 1 for the sign
1392 var buf: [1 + @max(@as(comptime_int, value_info.bits), 1)]u8 = undefined;
1393
1394 var a: MinInt = abs_value;
1395 var index: usize = buf.len;
1396
1397 if (base == 10) {
1398 while (a >= 100) : (a = @divTrunc(a, 100)) {
1399 index -= 2;
1400 buf[index..][0..2].* = std.fmt.digits2(@intCast(a % 100));
1401 }
1402
1403 if (a < 10) {
1404 index -= 1;
1405 buf[index] = '0' + @as(u8, @intCast(a));
1406 } else {
1407 index -= 2;
1408 buf[index..][0..2].* = std.fmt.digits2(@intCast(a));
1409 }
1410 } else {
1411 while (true) {
1412 const digit = a % base;
1413 index -= 1;
1414 buf[index] = std.fmt.digitToChar(@intCast(digit), case);
1415 a /= base;
1416 if (a == 0) break;
1417 }
1418 }
1419
1420 if (value_info.signedness == .signed) {
1421 if (value < 0) {
1422 // Negative integer
1423 index -= 1;
1424 buf[index] = '-';
1425 } else if (options.width == null or options.width.? == 0) {
1426 // Positive integer, omit the plus sign
1427 } else {
1428 // Positive integer
1429 index -= 1;
1430 buf[index] = '+';
1431 }
1432 }
1433
1434 return w.alignBufferOptions(buf[index..], options);
1435}
1436
1437pub fn printAsciiChar(w: *Writer, c: u8, options: std.fmt.Options) Error!void {
1438 return w.alignBufferOptions(@as(*const [1]u8, &c), options);
1439}
1440
1441pub fn printAscii(w: *Writer, bytes: []const u8, options: std.fmt.Options) Error!void {
1442 return w.alignBufferOptions(bytes, options);
1443}
1444
1445pub fn printUnicodeCodepoint(w: *Writer, c: u21) Error!void {
1446 var buf: [4]u8 = undefined;
1447 const len = std.unicode.utf8Encode(c, &buf) catch |err| switch (err) {
1448 error.Utf8CannotEncodeSurrogateHalf, error.CodepointTooLarge => l: {
1449 buf[0..3].* = std.unicode.replacement_character_utf8;
1450 break :l 3;
1451 },
1452 };
1453 return w.writeAll(buf[0..len]);
1454}
1455
1456/// Uses a larger stack buffer; asserts mode is decimal or scientific.
1457pub fn printFloat(w: *Writer, value: anytype, options: std.fmt.Number) Error!void {
1458 const mode: std.fmt.float.Mode = switch (options.mode) {
1459 .decimal => .decimal,
1460 .scientific => .scientific,
1461 .binary, .octal, .hex => unreachable,
1462 };
1463 var buf: [std.fmt.float.bufferSize(.decimal, f64)]u8 = undefined;
1464 const s = std.fmt.float.render(&buf, value, .{
1465 .mode = mode,
1466 .precision = options.precision,
1467 }) catch |err| switch (err) {
1468 error.BufferTooSmall => "(float)",
1469 };
1470 return w.alignBuffer(s, options.width orelse s.len, options.alignment, options.fill);
1471}
1472
1473/// Uses a smaller stack buffer; asserts mode is not decimal or scientific.
1474pub fn printFloatHexOptions(w: *Writer, value: anytype, options: std.fmt.Number) Error!void {
1475 var buf: [50]u8 = undefined; // for aligning
1476 var sub_writer: Writer = .fixed(&buf);
1477 switch (options.mode) {
1478 .decimal => unreachable,
1479 .scientific => unreachable,
1480 .binary => @panic("TODO"),
1481 .octal => @panic("TODO"),
1482 .hex => {},
1483 }
1484 printFloatHex(&sub_writer, value, options.case, options.precision) catch unreachable; // buf is large enough
1485
1486 const printed = sub_writer.buffered();
1487 return w.alignBuffer(printed, options.width orelse printed.len, options.alignment, options.fill);
1488}
1489
1490pub fn printFloatHex(w: *Writer, value: anytype, case: std.fmt.Case, opt_precision: ?usize) Error!void {
1491 if (std.math.signbit(value)) try w.writeByte('-');
1492 if (std.math.isNan(value)) return w.writeAll(switch (case) {
1493 .lower => "nan",
1494 .upper => "NAN",
1495 });
1496 if (std.math.isInf(value)) return w.writeAll(switch (case) {
1497 .lower => "inf",
1498 .upper => "INF",
1499 });
1500
1501 const T = @TypeOf(value);
1502 const TU = std.meta.Int(.unsigned, @bitSizeOf(T));
1503
1504 const mantissa_bits = std.math.floatMantissaBits(T);
1505 const fractional_bits = std.math.floatFractionalBits(T);
1506 const exponent_bits = std.math.floatExponentBits(T);
1507 const mantissa_mask = (1 << mantissa_bits) - 1;
1508 const exponent_mask = (1 << exponent_bits) - 1;
1509 const exponent_bias = (1 << (exponent_bits - 1)) - 1;
1510
1511 const as_bits: TU = @bitCast(value);
1512 var mantissa = as_bits & mantissa_mask;
1513 var exponent: i32 = @as(u16, @truncate((as_bits >> mantissa_bits) & exponent_mask));
1514
1515 const is_denormal = exponent == 0 and mantissa != 0;
1516 const is_zero = exponent == 0 and mantissa == 0;
1517
1518 if (is_zero) {
1519 // Handle this case here to simplify the logic below.
1520 try w.writeAll("0x0");
1521 if (opt_precision) |precision| {
1522 if (precision > 0) {
1523 try w.writeAll(".");
1524 try w.splatByteAll('0', precision);
1525 }
1526 } else {
1527 try w.writeAll(".0");
1528 }
1529 try w.writeAll("p0");
1530 return;
1531 }
1532
1533 if (is_denormal) {
1534 // Adjust the exponent for printing.
1535 exponent += 1;
1536 } else {
1537 if (fractional_bits == mantissa_bits)
1538 mantissa |= 1 << fractional_bits; // Add the implicit integer bit.
1539 }
1540
1541 const mantissa_digits = (fractional_bits + 3) / 4;
1542 // Fill in zeroes to round the fraction width to a multiple of 4.
1543 mantissa <<= mantissa_digits * 4 - fractional_bits;
1544
1545 if (opt_precision) |precision| {
1546 // Round if needed.
1547 if (precision < mantissa_digits) {
1548 // We always have at least 4 extra bits.
1549 var extra_bits = (mantissa_digits - precision) * 4;
1550 // The result LSB is the Guard bit, we need two more (Round and
1551 // Sticky) to round the value.
1552 while (extra_bits > 2) {
1553 mantissa = (mantissa >> 1) | (mantissa & 1);
1554 extra_bits -= 1;
1555 }
1556 // Round to nearest, tie to even.
1557 mantissa |= @intFromBool(mantissa & 0b100 != 0);
1558 mantissa += 1;
1559 // Drop the excess bits.
1560 mantissa >>= 2;
1561 // Restore the alignment.
1562 mantissa <<= @as(std.math.Log2Int(TU), @intCast((mantissa_digits - precision) * 4));
1563
1564 const overflow = mantissa & (1 << 1 + mantissa_digits * 4) != 0;
1565 // Prefer a normalized result in case of overflow.
1566 if (overflow) {
1567 mantissa >>= 1;
1568 exponent += 1;
1569 }
1570 }
1571 }
1572
1573 // +1 for the decimal part.
1574 var buf: [1 + mantissa_digits]u8 = undefined;
1575 assert(std.fmt.printInt(&buf, mantissa, 16, case, .{ .fill = '0', .width = 1 + mantissa_digits }) == buf.len);
1576
1577 try w.writeAll("0x");
1578 try w.writeByte(buf[0]);
1579 const trimmed = std.mem.trimRight(u8, buf[1..], "0");
1580 if (opt_precision) |precision| {
1581 if (precision > 0) try w.writeAll(".");
1582 } else if (trimmed.len > 0) {
1583 try w.writeAll(".");
1584 }
1585 try w.writeAll(trimmed);
1586 // Add trailing zeros if explicitly requested.
1587 if (opt_precision) |precision| if (precision > 0) {
1588 if (precision > trimmed.len)
1589 try w.splatByteAll('0', precision - trimmed.len);
1590 };
1591 try w.writeAll("p");
1592 try w.printInt(exponent - exponent_bias, 10, case, .{});
1593}
1594
1595pub const ByteSizeUnits = enum {
1596 /// This formatter represents the number as multiple of 1000 and uses the SI
1597 /// measurement units (kB, MB, GB, ...).
1598 decimal,
1599 /// This formatter represents the number as multiple of 1024 and uses the IEC
1600 /// measurement units (KiB, MiB, GiB, ...).
1601 binary,
1602};
1603
1604/// Format option `precision` is ignored when `value` is less than 1kB
1605pub fn printByteSize(
1606 w: *std.io.Writer,
1607 value: u64,
1608 comptime units: ByteSizeUnits,
1609 options: std.fmt.Options,
1610) Error!void {
1611 if (value == 0) return w.alignBufferOptions("0B", options);
1612 // The worst case in terms of space needed is 32 bytes + 3 for the suffix.
1613 var buf: [std.fmt.float.min_buffer_size + 3]u8 = undefined;
1614
1615 const mags_si = " kMGTPEZY";
1616 const mags_iec = " KMGTPEZY";
1617
1618 const log2 = std.math.log2(value);
1619 const base = switch (units) {
1620 .decimal => 1000,
1621 .binary => 1024,
1622 };
1623 const magnitude = switch (units) {
1624 .decimal => @min(log2 / comptime std.math.log2(1000), mags_si.len - 1),
1625 .binary => @min(log2 / 10, mags_iec.len - 1),
1626 };
1627 const new_value = std.math.lossyCast(f64, value) / std.math.pow(f64, std.math.lossyCast(f64, base), std.math.lossyCast(f64, magnitude));
1628 const suffix = switch (units) {
1629 .decimal => mags_si[magnitude],
1630 .binary => mags_iec[magnitude],
1631 };
1632
1633 const s = switch (magnitude) {
1634 0 => buf[0..std.fmt.printInt(&buf, value, 10, .lower, .{})],
1635 else => std.fmt.float.render(&buf, new_value, .{ .mode = .decimal, .precision = options.precision }) catch |err| switch (err) {
1636 error.BufferTooSmall => unreachable,
1637 },
1638 };
1639
1640 var i: usize = s.len;
1641 if (suffix == ' ') {
1642 buf[i] = 'B';
1643 i += 1;
1644 } else switch (units) {
1645 .decimal => {
1646 buf[i..][0..2].* = [_]u8{ suffix, 'B' };
1647 i += 2;
1648 },
1649 .binary => {
1650 buf[i..][0..3].* = [_]u8{ suffix, 'i', 'B' };
1651 i += 3;
1652 },
1653 }
1654
1655 return w.alignBufferOptions(buf[0..i], options);
1656}
1657
1658// This ANY const is a workaround for: https://github.com/ziglang/zig/issues/7948
1659const ANY = "any";
1660
1661fn stripOptionalOrErrorUnionSpec(comptime fmt: []const u8) []const u8 {
1662 return if (std.mem.eql(u8, fmt[1..], ANY))
1663 ANY
1664 else
1665 fmt[1..];
1666}
1667
1668pub fn invalidFmtError(comptime fmt: []const u8, value: anytype) noreturn {
1669 @compileError("invalid format string '" ++ fmt ++ "' for type '" ++ @typeName(@TypeOf(value)) ++ "'");
1670}
1671
1672pub fn printDurationSigned(w: *Writer, ns: i64) Error!void {
1673 if (ns < 0) try w.writeByte('-');
1674 return w.printDurationUnsigned(@abs(ns));
1675}
1676
1677pub fn printDurationUnsigned(w: *Writer, ns: u64) Error!void {
1678 var ns_remaining = ns;
1679 inline for (.{
1680 .{ .ns = 365 * std.time.ns_per_day, .sep = 'y' },
1681 .{ .ns = std.time.ns_per_week, .sep = 'w' },
1682 .{ .ns = std.time.ns_per_day, .sep = 'd' },
1683 .{ .ns = std.time.ns_per_hour, .sep = 'h' },
1684 .{ .ns = std.time.ns_per_min, .sep = 'm' },
1685 }) |unit| {
1686 if (ns_remaining >= unit.ns) {
1687 const units = ns_remaining / unit.ns;
1688 try w.printInt(units, 10, .lower, .{});
1689 try w.writeByte(unit.sep);
1690 ns_remaining -= units * unit.ns;
1691 if (ns_remaining == 0) return;
1692 }
1693 }
1694
1695 inline for (.{
1696 .{ .ns = std.time.ns_per_s, .sep = "s" },
1697 .{ .ns = std.time.ns_per_ms, .sep = "ms" },
1698 .{ .ns = std.time.ns_per_us, .sep = "us" },
1699 }) |unit| {
1700 const kunits = ns_remaining * 1000 / unit.ns;
1701 if (kunits >= 1000) {
1702 try w.printInt(kunits / 1000, 10, .lower, .{});
1703 const frac = kunits % 1000;
1704 if (frac > 0) {
1705 // Write up to 3 decimal places
1706 var decimal_buf = [_]u8{ '.', 0, 0, 0 };
1707 var inner: Writer = .fixed(decimal_buf[1..]);
1708 inner.printInt(frac, 10, .lower, .{ .fill = '0', .width = 3 }) catch unreachable;
1709 var end: usize = 4;
1710 while (end > 1) : (end -= 1) {
1711 if (decimal_buf[end - 1] != '0') break;
1712 }
1713 try w.writeAll(decimal_buf[0..end]);
1714 }
1715 return w.writeAll(unit.sep);
1716 }
1717 }
1718
1719 try w.printInt(ns_remaining, 10, .lower, .{});
1720 try w.writeAll("ns");
1721}
1722
1723/// Writes number of nanoseconds according to its signed magnitude:
1724/// `[#y][#w][#d][#h][#m]#[.###][n|u|m]s`
1725/// `nanoseconds` must be an integer that coerces into `u64` or `i64`.
1726pub fn printDuration(w: *Writer, nanoseconds: anytype, options: std.fmt.Options) Error!void {
1727 // worst case: "-XXXyXXwXXdXXhXXmXX.XXXs".len = 24
1728 var buf: [24]u8 = undefined;
1729 var sub_writer: Writer = .fixed(&buf);
1730 if (@TypeOf(nanoseconds) == comptime_int) {
1731 if (nanoseconds >= 0) {
1732 sub_writer.printDurationUnsigned(nanoseconds) catch unreachable;
1733 } else {
1734 sub_writer.printDurationSigned(nanoseconds) catch unreachable;
1735 }
1736 } else switch (@typeInfo(@TypeOf(nanoseconds)).int.signedness) {
1737 .signed => sub_writer.printDurationSigned(nanoseconds) catch unreachable,
1738 .unsigned => sub_writer.printDurationUnsigned(nanoseconds) catch unreachable,
1739 }
1740 return w.alignBufferOptions(sub_writer.buffered(), options);
1741}
1742
1743pub fn printHex(w: *Writer, bytes: []const u8, case: std.fmt.Case) Error!void {
1744 const charset = switch (case) {
1745 .upper => "0123456789ABCDEF",
1746 .lower => "0123456789abcdef",
1747 };
1748 for (bytes) |c| {
1749 try w.writeByte(charset[c >> 4]);
1750 try w.writeByte(charset[c & 15]);
1751 }
1752}
1753
1754pub fn printBase64(w: *Writer, bytes: []const u8) Error!void {
1755 var chunker = std.mem.window(u8, bytes, 3, 3);
1756 var temp: [5]u8 = undefined;
1757 while (chunker.next()) |chunk| {
1758 try w.writeAll(std.base64.standard.Encoder.encode(&temp, chunk));
1759 }
1760}
1761
1762/// Write a single unsigned integer as LEB128 to the given writer.
1763pub fn writeUleb128(w: *Writer, value: anytype) Error!void {
1764 try w.writeLeb128(switch (@typeInfo(@TypeOf(value))) {
1765 .comptime_int => @as(std.math.IntFittingRange(0, @abs(value)), value),
1766 .int => |value_info| switch (value_info.signedness) {
1767 .signed => @as(@Type(.{ .int = .{ .signedness = .unsigned, .bits = value_info.bits -| 1 } }), @intCast(value)),
1768 .unsigned => value,
1769 },
1770 else => comptime unreachable,
1771 });
1772}
1773
1774/// Write a single signed integer as LEB128 to the given writer.
1775pub fn writeSleb128(w: *Writer, value: anytype) Error!void {
1776 try w.writeLeb128(switch (@typeInfo(@TypeOf(value))) {
1777 .comptime_int => @as(std.math.IntFittingRange(@min(value, -1), @max(0, value)), value),
1778 .int => |value_info| switch (value_info.signedness) {
1779 .signed => value,
1780 .unsigned => @as(@Type(.{ .int = .{ .signedness = .signed, .bits = value_info.bits + 1 } }), value),
1781 },
1782 else => comptime unreachable,
1783 });
1784}
1785
1786/// Write a single integer as LEB128 to the given writer.
1787pub fn writeLeb128(w: *Writer, value: anytype) Error!void {
1788 const value_info = @typeInfo(@TypeOf(value)).int;
1789 try w.writeMultipleOf7Leb128(@as(@Type(.{ .int = .{
1790 .signedness = value_info.signedness,
1791 .bits = std.mem.alignForwardAnyAlign(u16, value_info.bits, 7),
1792 } }), value));
1793}
1794
1795fn writeMultipleOf7Leb128(w: *Writer, value: anytype) Error!void {
1796 const value_info = @typeInfo(@TypeOf(value)).int;
1797 comptime assert(value_info.bits % 7 == 0);
1798 var remaining = value;
1799 while (true) {
1800 const buffer: []packed struct(u8) { bits: u7, more: bool } = @ptrCast(try w.writableSliceGreedy(1));
1801 for (buffer, 1..) |*byte, len| {
1802 const more = switch (value_info.signedness) {
1803 .signed => remaining >> 6 != remaining >> (value_info.bits - 1),
1804 .unsigned => remaining > std.math.maxInt(u7),
1805 };
1806 byte.* = if (@inComptime()) @typeInfo(@TypeOf(buffer)).pointer.child{
1807 .bits = @bitCast(@as(@Type(.{ .int = .{
1808 .signedness = value_info.signedness,
1809 .bits = 7,
1810 } }), @truncate(remaining))),
1811 .more = more,
1812 } else .{
1813 .bits = @bitCast(@as(@Type(.{ .int = .{
1814 .signedness = value_info.signedness,
1815 .bits = 7,
1816 } }), @truncate(remaining))),
1817 .more = more,
1818 };
1819 if (value_info.bits > 7) remaining >>= 7;
1820 if (!more) return w.advance(len);
1821 }
1822 w.advance(buffer.len);
1823 }
1824}
1825
1826test "printValue max_depth" {
1827 const Vec2 = struct {
1828 const SelfType = @This();
1829 x: f32,
1830 y: f32,
1831
1832 pub fn format(self: SelfType, w: *Writer) Error!void {
1833 return w.print("({d:.3},{d:.3})", .{ self.x, self.y });
1834 }
1835 };
1836 const E = enum {
1837 One,
1838 Two,
1839 Three,
1840 };
1841 const TU = union(enum) {
1842 const SelfType = @This();
1843 float: f32,
1844 int: u32,
1845 ptr: ?*SelfType,
1846 };
1847 const S = struct {
1848 const SelfType = @This();
1849 a: ?*SelfType,
1850 tu: TU,
1851 e: E,
1852 vec: Vec2,
1853 };
1854
1855 var inst = S{
1856 .a = null,
1857 .tu = TU{ .ptr = null },
1858 .e = E.Two,
1859 .vec = Vec2{ .x = 10.2, .y = 2.22 },
1860 };
1861 inst.a = &inst;
1862 inst.tu.ptr = &inst.tu;
1863
1864 var buf: [1000]u8 = undefined;
1865 var w: Writer = .fixed(&buf);
1866 try w.printValue("", .{}, inst, 0);
1867 try testing.expectEqualStrings(".{ ... }", w.buffered());
1868
1869 w = .fixed(&buf);
1870 try w.printValue("", .{}, inst, 1);
1871 try testing.expectEqualStrings(".{ .a = .{ ... }, .tu = .{ ... }, .e = .Two, .vec = .{ ... } }", w.buffered());
1872
1873 w = .fixed(&buf);
1874 try w.printValue("", .{}, inst, 2);
1875 try testing.expectEqualStrings(".{ .a = .{ .a = .{ ... }, .tu = .{ ... }, .e = .Two, .vec = .{ ... } }, .tu = .{ .ptr = .{ ... } }, .e = .Two, .vec = .{ .x = 10.2, .y = 2.22 } }", w.buffered());
1876
1877 w = .fixed(&buf);
1878 try w.printValue("", .{}, inst, 3);
1879 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());
1880
1881 const vec: @Vector(4, i32) = .{ 1, 2, 3, 4 };
1882 w = .fixed(&buf);
1883 try w.printValue("", .{}, vec, 0);
1884 try testing.expectEqualStrings("{ ... }", w.buffered());
1885
1886 w = .fixed(&buf);
1887 try w.printValue("", .{}, vec, 1);
1888 try testing.expectEqualStrings("{ 1, 2, 3, 4 }", w.buffered());
1889}
1890
1891test printDuration {
1892 try testDurationCase("0ns", 0);
1893 try testDurationCase("1ns", 1);
1894 try testDurationCase("999ns", std.time.ns_per_us - 1);
1895 try testDurationCase("1us", std.time.ns_per_us);
1896 try testDurationCase("1.45us", 1450);
1897 try testDurationCase("1.5us", 3 * std.time.ns_per_us / 2);
1898 try testDurationCase("14.5us", 14500);
1899 try testDurationCase("145us", 145000);
1900 try testDurationCase("999.999us", std.time.ns_per_ms - 1);
1901 try testDurationCase("1ms", std.time.ns_per_ms + 1);
1902 try testDurationCase("1.5ms", 3 * std.time.ns_per_ms / 2);
1903 try testDurationCase("1.11ms", 1110000);
1904 try testDurationCase("1.111ms", 1111000);
1905 try testDurationCase("1.111ms", 1111100);
1906 try testDurationCase("999.999ms", std.time.ns_per_s - 1);
1907 try testDurationCase("1s", std.time.ns_per_s);
1908 try testDurationCase("59.999s", std.time.ns_per_min - 1);
1909 try testDurationCase("1m", std.time.ns_per_min);
1910 try testDurationCase("1h", std.time.ns_per_hour);
1911 try testDurationCase("1d", std.time.ns_per_day);
1912 try testDurationCase("1w", std.time.ns_per_week);
1913 try testDurationCase("1y", 365 * std.time.ns_per_day);
1914 try testDurationCase("1y52w23h59m59.999s", 730 * std.time.ns_per_day - 1); // 365d = 52w1
1915 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);
1916 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);
1917 try testDurationCase("1y1h999.999us", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms - 1);
1918 try testDurationCase("1y1h1ms", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms);
1919 try testDurationCase("1y1h1ms", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms + 1);
1920 try testDurationCase("1y1m999ns", 365 * std.time.ns_per_day + std.time.ns_per_min + 999);
1921 try testDurationCase("584y49w23h34m33.709s", std.math.maxInt(u64));
1922
1923 try testing.expectFmt("=======0ns", "{D:=>10}", .{0});
1924 try testing.expectFmt("1ns=======", "{D:=<10}", .{1});
1925 try testing.expectFmt(" 999ns ", "{D:^10}", .{std.time.ns_per_us - 1});
1926}
1927
1928test printDurationSigned {
1929 try testDurationCaseSigned("0ns", 0);
1930 try testDurationCaseSigned("1ns", 1);
1931 try testDurationCaseSigned("-1ns", -(1));
1932 try testDurationCaseSigned("999ns", std.time.ns_per_us - 1);
1933 try testDurationCaseSigned("-999ns", -(std.time.ns_per_us - 1));
1934 try testDurationCaseSigned("1us", std.time.ns_per_us);
1935 try testDurationCaseSigned("-1us", -(std.time.ns_per_us));
1936 try testDurationCaseSigned("1.45us", 1450);
1937 try testDurationCaseSigned("-1.45us", -(1450));
1938 try testDurationCaseSigned("1.5us", 3 * std.time.ns_per_us / 2);
1939 try testDurationCaseSigned("-1.5us", -(3 * std.time.ns_per_us / 2));
1940 try testDurationCaseSigned("14.5us", 14500);
1941 try testDurationCaseSigned("-14.5us", -(14500));
1942 try testDurationCaseSigned("145us", 145000);
1943 try testDurationCaseSigned("-145us", -(145000));
1944 try testDurationCaseSigned("999.999us", std.time.ns_per_ms - 1);
1945 try testDurationCaseSigned("-999.999us", -(std.time.ns_per_ms - 1));
1946 try testDurationCaseSigned("1ms", std.time.ns_per_ms + 1);
1947 try testDurationCaseSigned("-1ms", -(std.time.ns_per_ms + 1));
1948 try testDurationCaseSigned("1.5ms", 3 * std.time.ns_per_ms / 2);
1949 try testDurationCaseSigned("-1.5ms", -(3 * std.time.ns_per_ms / 2));
1950 try testDurationCaseSigned("1.11ms", 1110000);
1951 try testDurationCaseSigned("-1.11ms", -(1110000));
1952 try testDurationCaseSigned("1.111ms", 1111000);
1953 try testDurationCaseSigned("-1.111ms", -(1111000));
1954 try testDurationCaseSigned("1.111ms", 1111100);
1955 try testDurationCaseSigned("-1.111ms", -(1111100));
1956 try testDurationCaseSigned("999.999ms", std.time.ns_per_s - 1);
1957 try testDurationCaseSigned("-999.999ms", -(std.time.ns_per_s - 1));
1958 try testDurationCaseSigned("1s", std.time.ns_per_s);
1959 try testDurationCaseSigned("-1s", -(std.time.ns_per_s));
1960 try testDurationCaseSigned("59.999s", std.time.ns_per_min - 1);
1961 try testDurationCaseSigned("-59.999s", -(std.time.ns_per_min - 1));
1962 try testDurationCaseSigned("1m", std.time.ns_per_min);
1963 try testDurationCaseSigned("-1m", -(std.time.ns_per_min));
1964 try testDurationCaseSigned("1h", std.time.ns_per_hour);
1965 try testDurationCaseSigned("-1h", -(std.time.ns_per_hour));
1966 try testDurationCaseSigned("1d", std.time.ns_per_day);
1967 try testDurationCaseSigned("-1d", -(std.time.ns_per_day));
1968 try testDurationCaseSigned("1w", std.time.ns_per_week);
1969 try testDurationCaseSigned("-1w", -(std.time.ns_per_week));
1970 try testDurationCaseSigned("1y", 365 * std.time.ns_per_day);
1971 try testDurationCaseSigned("-1y", -(365 * std.time.ns_per_day));
1972 try testDurationCaseSigned("1y52w23h59m59.999s", 730 * std.time.ns_per_day - 1); // 365d = 52w1d
1973 try testDurationCaseSigned("-1y52w23h59m59.999s", -(730 * std.time.ns_per_day - 1)); // 365d = 52w1d
1974 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);
1975 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));
1976 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);
1977 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));
1978 try testDurationCaseSigned("1y1h999.999us", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms - 1);
1979 try testDurationCaseSigned("-1y1h999.999us", -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms - 1));
1980 try testDurationCaseSigned("1y1h1ms", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms);
1981 try testDurationCaseSigned("-1y1h1ms", -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms));
1982 try testDurationCaseSigned("1y1h1ms", 365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms + 1);
1983 try testDurationCaseSigned("-1y1h1ms", -(365 * std.time.ns_per_day + std.time.ns_per_hour + std.time.ns_per_ms + 1));
1984 try testDurationCaseSigned("1y1m999ns", 365 * std.time.ns_per_day + std.time.ns_per_min + 999);
1985 try testDurationCaseSigned("-1y1m999ns", -(365 * std.time.ns_per_day + std.time.ns_per_min + 999));
1986 try testDurationCaseSigned("292y24w3d23h47m16.854s", std.math.maxInt(i64));
1987 try testDurationCaseSigned("-292y24w3d23h47m16.854s", std.math.minInt(i64) + 1);
1988 try testDurationCaseSigned("-292y24w3d23h47m16.854s", std.math.minInt(i64));
1989
1990 try testing.expectFmt("=======0ns", "{D:=>10}", .{0});
1991 try testing.expectFmt("1ns=======", "{D:=<10}", .{1});
1992 try testing.expectFmt("-1ns======", "{D:=<10}", .{-(1)});
1993 try testing.expectFmt(" -999ns ", "{D:^10}", .{-(std.time.ns_per_us - 1)});
1994}
1995
1996fn testDurationCase(expected: []const u8, input: u64) !void {
1997 var buf: [24]u8 = undefined;
1998 var w: Writer = .fixed(&buf);
1999 try w.printDurationUnsigned(input);
2000 try testing.expectEqualStrings(expected, w.buffered());
2001}
2002
2003fn testDurationCaseSigned(expected: []const u8, input: i64) !void {
2004 var buf: [24]u8 = undefined;
2005 var w: Writer = .fixed(&buf);
2006 try w.printDurationSigned(input);
2007 try testing.expectEqualStrings(expected, w.buffered());
2008}
2009
2010test printInt {
2011 try testPrintIntCase("-1", @as(i1, -1), 10, .lower, .{});
2012
2013 try testPrintIntCase("-101111000110000101001110", @as(i32, -12345678), 2, .lower, .{});
2014 try testPrintIntCase("-12345678", @as(i32, -12345678), 10, .lower, .{});
2015 try testPrintIntCase("-bc614e", @as(i32, -12345678), 16, .lower, .{});
2016 try testPrintIntCase("-BC614E", @as(i32, -12345678), 16, .upper, .{});
2017
2018 try testPrintIntCase("12345678", @as(u32, 12345678), 10, .upper, .{});
2019
2020 try testPrintIntCase(" 666", @as(u32, 666), 10, .lower, .{ .width = 6 });
2021 try testPrintIntCase(" 1234", @as(u32, 0x1234), 16, .lower, .{ .width = 6 });
2022 try testPrintIntCase("1234", @as(u32, 0x1234), 16, .lower, .{ .width = 1 });
2023
2024 try testPrintIntCase("+42", @as(i32, 42), 10, .lower, .{ .width = 3 });
2025 try testPrintIntCase("-42", @as(i32, -42), 10, .lower, .{ .width = 3 });
2026
2027 try testPrintIntCase("123456789123456789", @as(comptime_int, 123456789123456789), 10, .lower, .{});
2028}
2029
2030test "printFloat with comptime_float" {
2031 var buf: [20]u8 = undefined;
2032 var w: Writer = .fixed(&buf);
2033 try w.printFloat(@as(comptime_float, 1.0), std.fmt.Options.toNumber(.{}, .scientific, .lower));
2034 try testing.expectEqualStrings(w.buffered(), "1e0");
2035 try testing.expectFmt("1", "{}", .{1.0});
2036}
2037
2038fn testPrintIntCase(expected: []const u8, value: anytype, base: u8, case: std.fmt.Case, options: std.fmt.Options) !void {
2039 var buffer: [100]u8 = undefined;
2040 var w: Writer = .fixed(&buffer);
2041 try w.printInt(value, base, case, options);
2042 try testing.expectEqualStrings(expected, w.buffered());
2043}
2044
2045test printByteSize {
2046 try testing.expectFmt("file size: 42B\n", "file size: {B}\n", .{42});
2047 try testing.expectFmt("file size: 42B\n", "file size: {Bi}\n", .{42});
2048 try testing.expectFmt("file size: 63MB\n", "file size: {B}\n", .{63 * 1000 * 1000});
2049 try testing.expectFmt("file size: 63MiB\n", "file size: {Bi}\n", .{63 * 1024 * 1024});
2050 try testing.expectFmt("file size: 42B\n", "file size: {B:.2}\n", .{42});
2051 try testing.expectFmt("file size: 42B\n", "file size: {B:>9.2}\n", .{42});
2052 try testing.expectFmt("file size: 66.06MB\n", "file size: {B:.2}\n", .{63 * 1024 * 1024});
2053 try testing.expectFmt("file size: 60.08MiB\n", "file size: {Bi:.2}\n", .{63 * 1000 * 1000});
2054 try testing.expectFmt("file size: =66.06MB=\n", "file size: {B:=^9.2}\n", .{63 * 1024 * 1024});
2055 try testing.expectFmt("file size: 66.06MB\n", "file size: {B: >9.2}\n", .{63 * 1024 * 1024});
2056 try testing.expectFmt("file size: 66.06MB \n", "file size: {B: <9.2}\n", .{63 * 1024 * 1024});
2057 try testing.expectFmt("file size: 0.01844674407370955ZB\n", "file size: {B}\n", .{std.math.maxInt(u64)});
2058}
2059
2060test "bytes.hex" {
2061 const some_bytes = "\xCA\xFE\xBA\xBE";
2062 try testing.expectFmt("lowercase: cafebabe\n", "lowercase: {x}\n", .{some_bytes});
2063 try testing.expectFmt("uppercase: CAFEBABE\n", "uppercase: {X}\n", .{some_bytes});
2064 try testing.expectFmt("uppercase: CAFE\n", "uppercase: {X}\n", .{some_bytes[0..2]});
2065 try testing.expectFmt("lowercase: babe\n", "lowercase: {x}\n", .{some_bytes[2..]});
2066 const bytes_with_zeros = "\x00\x0E\xBA\xBE";
2067 try testing.expectFmt("lowercase: 000ebabe\n", "lowercase: {x}\n", .{bytes_with_zeros});
2068}
2069
2070test fixed {
2071 {
2072 var buf: [255]u8 = undefined;
2073 var w: Writer = .fixed(&buf);
2074 try w.print("{s}{s}!", .{ "Hello", "World" });
2075 try testing.expectEqualStrings("HelloWorld!", w.buffered());
2076 }
2077
2078 comptime {
2079 var buf: [255]u8 = undefined;
2080 var w: Writer = .fixed(&buf);
2081 try w.print("{s}{s}!", .{ "Hello", "World" });
2082 try testing.expectEqualStrings("HelloWorld!", w.buffered());
2083 }
2084}
2085
2086test "fixed output" {
2087 var buffer: [10]u8 = undefined;
2088 var w: Writer = .fixed(&buffer);
2089
2090 try w.writeAll("Hello");
2091 try testing.expect(std.mem.eql(u8, w.buffered(), "Hello"));
2092
2093 try w.writeAll("world");
2094 try testing.expect(std.mem.eql(u8, w.buffered(), "Helloworld"));
2095
2096 try testing.expectError(error.WriteFailed, w.writeAll("!"));
2097 try testing.expect(std.mem.eql(u8, w.buffered(), "Helloworld"));
2098
2099 w = .fixed(&buffer);
2100
2101 try testing.expect(w.buffered().len == 0);
2102
2103 try testing.expectError(error.WriteFailed, w.writeAll("Hello world!"));
2104 try testing.expect(std.mem.eql(u8, w.buffered(), "Hello worl"));
2105}
2106
2107test "writeSplat 0 len splat larger than capacity" {
2108 var buf: [8]u8 = undefined;
2109 var w: std.io.Writer = .fixed(&buf);
2110 const n = try w.writeSplat(&.{"something that overflows buf"}, 0);
2111 try testing.expectEqual(0, n);
2112}
2113
2114pub fn failingDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
2115 _ = w;
2116 _ = data;
2117 _ = splat;
2118 return error.WriteFailed;
2119}
2120
2121pub fn failingSendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize {
2122 _ = w;
2123 _ = file_reader;
2124 _ = limit;
2125 return error.WriteFailed;
2126}
2127
2128pub const Discarding = struct {
2129 count: u64,
2130 writer: Writer,
2131
2132 pub fn init(buffer: []u8) Discarding {
2133 return .{
2134 .count = 0,
2135 .writer = .{
2136 .vtable = &.{
2137 .drain = Discarding.drain,
2138 .sendFile = Discarding.sendFile,
2139 },
2140 .buffer = buffer,
2141 },
2142 };
2143 }
2144
2145 pub fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
2146 const d: *Discarding = @alignCast(@fieldParentPtr("writer", w));
2147 const slice = data[0 .. data.len - 1];
2148 const pattern = data[slice.len..];
2149 var written: usize = pattern.len * splat;
2150 for (slice) |bytes| written += bytes.len;
2151 d.count += w.end + written;
2152 w.end = 0;
2153 return written;
2154 }
2155
2156 pub fn sendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize {
2157 if (File.Handle == void) return error.Unimplemented;
2158 const d: *Discarding = @alignCast(@fieldParentPtr("writer", w));
2159 d.count += w.end;
2160 w.end = 0;
2161 if (file_reader.getSize()) |size| {
2162 const n = limit.minInt64(size - file_reader.pos);
2163 file_reader.seekBy(@intCast(n)) catch return error.Unimplemented;
2164 w.end = 0;
2165 d.count += n;
2166 return n;
2167 } else |_| {
2168 // Error is observable on `file_reader` instance, and it is better to
2169 // treat the file as a pipe.
2170 return error.Unimplemented;
2171 }
2172 }
2173};
2174
2175/// Removes the first `n` bytes from `buffer` by shifting buffer contents,
2176/// returning how many bytes are left after consuming the entire buffer, or
2177/// zero if the entire buffer was not consumed.
2178///
2179/// Useful for `VTable.drain` function implementations to implement partial
2180/// drains.
2181pub fn consume(w: *Writer, n: usize) usize {
2182 if (n < w.end) {
2183 const remaining = w.buffer[n..w.end];
2184 @memmove(w.buffer[0..remaining.len], remaining);
2185 w.end = remaining.len;
2186 return 0;
2187 }
2188 defer w.end = 0;
2189 return n - w.end;
2190}
2191
2192/// Shortcut for setting `end` to zero and returning zero. Equivalent to
2193/// calling `consume` with `end`.
2194pub fn consumeAll(w: *Writer) usize {
2195 w.end = 0;
2196 return 0;
2197}
2198
2199/// For use when the `Writer` implementation can cannot offer a more efficient
2200/// implementation than a basic read/write loop on the file.
2201pub fn unimplementedSendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize {
2202 _ = w;
2203 _ = file_reader;
2204 _ = limit;
2205 return error.Unimplemented;
2206}
2207
2208/// When this function is called it usually means the buffer got full, so it's
2209/// time to return an error. However, we still need to make sure all of the
2210/// available buffer has been filled. Also, it may be called from `flush` in
2211/// which case it should return successfully.
2212pub fn fixedDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
2213 if (data.len == 0) return 0;
2214 for (data[0 .. data.len - 1]) |bytes| {
2215 const dest = w.buffer[w.end..];
2216 const len = @min(bytes.len, dest.len);
2217 @memcpy(dest[0..len], bytes[0..len]);
2218 w.end += len;
2219 if (bytes.len > dest.len) return error.WriteFailed;
2220 }
2221 const pattern = data[data.len - 1];
2222 const dest = w.buffer[w.end..];
2223 switch (pattern.len) {
2224 0 => return w.end,
2225 1 => {
2226 assert(splat >= dest.len);
2227 @memset(dest, pattern[0]);
2228 w.end += dest.len;
2229 return error.WriteFailed;
2230 },
2231 else => {
2232 for (0..splat) |i| {
2233 const remaining = dest[i * pattern.len ..];
2234 const len = @min(pattern.len, remaining.len);
2235 @memcpy(remaining[0..len], pattern[0..len]);
2236 w.end += len;
2237 if (pattern.len > remaining.len) return error.WriteFailed;
2238 }
2239 unreachable;
2240 },
2241 }
2242}
2243
2244/// Provides a `Writer` implementation based on calling `Hasher.update`, sending
2245/// all data also to an underlying `Writer`.
2246///
2247/// When using this, the underlying writer is best unbuffered because all
2248/// writes are passed on directly to it.
2249///
2250/// This implementation makes suboptimal buffering decisions due to being
2251/// generic. A better solution will involve creating a writer for each hash
2252/// function, where the splat buffer can be tailored to the hash implementation
2253/// details.
2254pub fn Hashed(comptime Hasher: type) type {
2255 return struct {
2256 out: *Writer,
2257 hasher: Hasher,
2258 writer: Writer,
2259
2260 pub fn init(out: *Writer, buffer: []u8) @This() {
2261 return .initHasher(out, .{}, buffer);
2262 }
2263
2264 pub fn initHasher(out: *Writer, hasher: Hasher, buffer: []u8) @This() {
2265 return .{
2266 .out = out,
2267 .hasher = hasher,
2268 .writer = .{
2269 .buffer = buffer,
2270 .vtable = &.{ .drain = @This().drain },
2271 },
2272 };
2273 }
2274
2275 fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
2276 const this: *@This() = @alignCast(@fieldParentPtr("writer", w));
2277 const aux = w.buffered();
2278 const aux_n = try this.out.writeSplatHeader(aux, data, splat);
2279 if (aux_n < w.end) {
2280 this.hasher.update(w.buffer[0..aux_n]);
2281 const remaining = w.buffer[aux_n..w.end];
2282 @memmove(w.buffer[0..remaining.len], remaining);
2283 w.end = remaining.len;
2284 return 0;
2285 }
2286 this.hasher.update(aux);
2287 const n = aux_n - w.end;
2288 w.end = 0;
2289 var remaining: usize = n;
2290 for (data[0 .. data.len - 1]) |slice| {
2291 if (remaining <= slice.len) {
2292 this.hasher.update(slice[0..remaining]);
2293 return n;
2294 }
2295 remaining -= slice.len;
2296 this.hasher.update(slice);
2297 }
2298 const pattern = data[data.len - 1];
2299 assert(remaining == splat * pattern.len);
2300 switch (pattern.len) {
2301 0 => {
2302 assert(remaining == 0);
2303 },
2304 1 => {
2305 var buffer: [64]u8 = undefined;
2306 @memset(&buffer, pattern[0]);
2307 while (remaining > 0) {
2308 const update_len = @min(remaining, buffer.len);
2309 this.hasher.update(buffer[0..update_len]);
2310 remaining -= update_len;
2311 }
2312 },
2313 else => {
2314 while (remaining > 0) {
2315 const update_len = @min(remaining, pattern.len);
2316 this.hasher.update(pattern[0..update_len]);
2317 remaining -= update_len;
2318 }
2319 },
2320 }
2321 return n;
2322 }
2323 };
2324}
2325
2326/// Maintains `Writer` state such that it writes to the unused capacity of an
2327/// array list, filling it up completely before making a call through the
2328/// vtable, causing a resize. Consequently, the same, optimized, non-generic
2329/// machine code that uses `std.io.Reader`, such as formatted printing, takes
2330/// the hot paths when using this API.
2331///
2332/// When using this API, it is not necessary to call `flush`.
2333pub const Allocating = struct {
2334 allocator: Allocator,
2335 writer: Writer,
2336
2337 pub fn init(allocator: Allocator) Allocating {
2338 return .{
2339 .allocator = allocator,
2340 .writer = .{
2341 .buffer = &.{},
2342 .vtable = &vtable,
2343 },
2344 };
2345 }
2346
2347 pub fn initCapacity(allocator: Allocator, capacity: usize) error{OutOfMemory}!Allocating {
2348 return .{
2349 .allocator = allocator,
2350 .writer = .{
2351 .buffer = try allocator.alloc(u8, capacity),
2352 .vtable = &vtable,
2353 },
2354 };
2355 }
2356
2357 pub fn initOwnedSlice(allocator: Allocator, slice: []u8) Allocating {
2358 return .{
2359 .allocator = allocator,
2360 .writer = .{
2361 .buffer = slice,
2362 .vtable = &vtable,
2363 },
2364 };
2365 }
2366
2367 /// Replaces `array_list` with empty, taking ownership of the memory.
2368 pub fn fromArrayList(allocator: Allocator, array_list: *std.ArrayListUnmanaged(u8)) Allocating {
2369 defer array_list.* = .empty;
2370 return .{
2371 .allocator = allocator,
2372 .writer = .{
2373 .vtable = &vtable,
2374 .buffer = array_list.allocatedSlice(),
2375 .end = array_list.items.len,
2376 },
2377 };
2378 }
2379
2380 const vtable: VTable = .{
2381 .drain = Allocating.drain,
2382 .sendFile = Allocating.sendFile,
2383 .flush = noopFlush,
2384 };
2385
2386 pub fn deinit(a: *Allocating) void {
2387 a.allocator.free(a.writer.buffer);
2388 a.* = undefined;
2389 }
2390
2391 /// Returns an array list that takes ownership of the allocated memory.
2392 /// Resets the `Allocating` to an empty state.
2393 pub fn toArrayList(a: *Allocating) std.ArrayListUnmanaged(u8) {
2394 const w = &a.writer;
2395 const result: std.ArrayListUnmanaged(u8) = .{
2396 .items = w.buffer[0..w.end],
2397 .capacity = w.buffer.len,
2398 };
2399 w.buffer = &.{};
2400 w.end = 0;
2401 return result;
2402 }
2403
2404 pub fn toOwnedSlice(a: *Allocating) error{OutOfMemory}![]u8 {
2405 var list = a.toArrayList();
2406 return list.toOwnedSlice(a.allocator);
2407 }
2408
2409 pub fn toOwnedSliceSentinel(a: *Allocating, comptime sentinel: u8) error{OutOfMemory}![:sentinel]u8 {
2410 const gpa = a.allocator;
2411 var list = toArrayList(a);
2412 return list.toOwnedSliceSentinel(gpa, sentinel);
2413 }
2414
2415 pub fn getWritten(a: *Allocating) []u8 {
2416 return a.writer.buffered();
2417 }
2418
2419 pub fn shrinkRetainingCapacity(a: *Allocating, new_len: usize) void {
2420 a.writer.end = new_len;
2421 }
2422
2423 pub fn clearRetainingCapacity(a: *Allocating) void {
2424 a.shrinkRetainingCapacity(0);
2425 }
2426
2427 fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
2428 const a: *Allocating = @fieldParentPtr("writer", w);
2429 const gpa = a.allocator;
2430 const pattern = data[data.len - 1];
2431 const splat_len = pattern.len * splat;
2432 var list = a.toArrayList();
2433 defer setArrayList(a, list);
2434 const start_len = list.items.len;
2435 // Even if we append no data, this function needs to ensure there is more
2436 // capacity in the buffer to avoid infinite loop, hence the +1 in this loop.
2437 assert(data.len != 0);
2438 for (data) |bytes| {
2439 list.ensureUnusedCapacity(gpa, bytes.len + splat_len + 1) catch return error.WriteFailed;
2440 list.appendSliceAssumeCapacity(bytes);
2441 }
2442 if (splat == 0) {
2443 list.items.len -= pattern.len;
2444 } else switch (pattern.len) {
2445 0 => {},
2446 1 => list.appendNTimesAssumeCapacity(pattern[0], splat - 1),
2447 else => for (0..splat - 1) |_| list.appendSliceAssumeCapacity(pattern),
2448 }
2449 return list.items.len - start_len;
2450 }
2451
2452 fn sendFile(w: *Writer, file_reader: *File.Reader, limit: std.io.Limit) FileError!usize {
2453 if (File.Handle == void) return error.Unimplemented;
2454 const a: *Allocating = @fieldParentPtr("writer", w);
2455 const gpa = a.allocator;
2456 var list = a.toArrayList();
2457 defer setArrayList(a, list);
2458 const pos = file_reader.pos;
2459 const additional = if (file_reader.getSize()) |size| size - pos else |_| std.atomic.cache_line;
2460 list.ensureUnusedCapacity(gpa, limit.minInt64(additional)) catch return error.WriteFailed;
2461 const dest = limit.slice(list.unusedCapacitySlice());
2462 const n = file_reader.read(dest) catch |err| switch (err) {
2463 error.ReadFailed => return error.ReadFailed,
2464 error.EndOfStream => 0,
2465 };
2466 list.items.len += n;
2467 return n;
2468 }
2469
2470 fn setArrayList(a: *Allocating, list: std.ArrayListUnmanaged(u8)) void {
2471 a.writer.buffer = list.allocatedSlice();
2472 a.writer.end = list.items.len;
2473 }
2474
2475 test Allocating {
2476 var a: Allocating = .init(testing.allocator);
2477 defer a.deinit();
2478 const w = &a.writer;
2479
2480 const x: i32 = 42;
2481 const y: i32 = 1234;
2482 try w.print("x: {}\ny: {}\n", .{ x, y });
2483
2484 try testing.expectEqualSlices(u8, "x: 42\ny: 1234\n", a.getWritten());
2485 }
2486};
lib/std/io/bit_reader.zig deleted-238
...@@ -1,238 +0,0 @@
1const std = @import("../std.zig");
2
3//General note on endianess:
4//Big endian is packed starting in the most significant part of the byte and subsequent
5// bytes contain less significant bits. Thus we always take bits from the high
6// end and place them below existing bits in our output.
7//Little endian is packed starting in the least significant part of the byte and
8// subsequent bytes contain more significant bits. Thus we always take bits from
9// the low end and place them above existing bits in our output.
10//Regardless of endianess, within any given byte the bits are always in most
11// to least significant order.
12//Also regardless of endianess, the buffer always aligns bits to the low end
13// of the byte.
14
15/// Creates a bit reader which allows for reading bits from an underlying standard reader
16pub fn BitReader(comptime endian: std.builtin.Endian, comptime Reader: type) type {
17 return struct {
18 reader: Reader,
19 bits: u8 = 0,
20 count: u4 = 0,
21
22 const low_bit_mask = [9]u8{
23 0b00000000,
24 0b00000001,
25 0b00000011,
26 0b00000111,
27 0b00001111,
28 0b00011111,
29 0b00111111,
30 0b01111111,
31 0b11111111,
32 };
33
34 fn Bits(comptime T: type) type {
35 return struct {
36 T,
37 u16,
38 };
39 }
40
41 fn initBits(comptime T: type, out: anytype, num: u16) Bits(T) {
42 const UT = std.meta.Int(.unsigned, @bitSizeOf(T));
43 return .{
44 @bitCast(@as(UT, @intCast(out))),
45 num,
46 };
47 }
48
49 /// Reads `bits` bits from the reader and returns a specified type
50 /// containing them in the least significant end, returning an error if the
51 /// specified number of bits could not be read.
52 pub fn readBitsNoEof(self: *@This(), comptime T: type, num: u16) !T {
53 const b, const c = try self.readBitsTuple(T, num);
54 if (c < num) return error.EndOfStream;
55 return b;
56 }
57
58 /// Reads `bits` bits from the reader and returns a specified type
59 /// containing them in the least significant end. The number of bits successfully
60 /// read is placed in `out_bits`, as reaching the end of the stream is not an error.
61 pub fn readBits(self: *@This(), comptime T: type, num: u16, out_bits: *u16) !T {
62 const b, const c = try self.readBitsTuple(T, num);
63 out_bits.* = c;
64 return b;
65 }
66
67 /// Reads `bits` bits from the reader and returns a tuple of the specified type
68 /// containing them in the least significant end, and the number of bits successfully
69 /// read. Reaching the end of the stream is not an error.
70 pub fn readBitsTuple(self: *@This(), comptime T: type, num: u16) !Bits(T) {
71 const UT = std.meta.Int(.unsigned, @bitSizeOf(T));
72 const U = if (@bitSizeOf(T) < 8) u8 else UT; //it is a pain to work with <u8
73
74 //dump any bits in our buffer first
75 if (num <= self.count) return initBits(T, self.removeBits(@intCast(num)), num);
76
77 var out_count: u16 = self.count;
78 var out: U = self.removeBits(self.count);
79
80 //grab all the full bytes we need and put their
81 //bits where they belong
82 const full_bytes_left = (num - out_count) / 8;
83
84 for (0..full_bytes_left) |_| {
85 const byte = self.reader.readByte() catch |err| switch (err) {
86 error.EndOfStream => return initBits(T, out, out_count),
87 else => |e| return e,
88 };
89
90 switch (endian) {
91 .big => {
92 if (U == u8) out = 0 else out <<= 8; //shifting u8 by 8 is illegal in Zig
93 out |= byte;
94 },
95 .little => {
96 const pos = @as(U, byte) << @intCast(out_count);
97 out |= pos;
98 },
99 }
100 out_count += 8;
101 }
102
103 const bits_left = num - out_count;
104 const keep = 8 - bits_left;
105
106 if (bits_left == 0) return initBits(T, out, out_count);
107
108 const final_byte = self.reader.readByte() catch |err| switch (err) {
109 error.EndOfStream => return initBits(T, out, out_count),
110 else => |e| return e,
111 };
112
113 switch (endian) {
114 .big => {
115 out <<= @intCast(bits_left);
116 out |= final_byte >> @intCast(keep);
117 self.bits = final_byte & low_bit_mask[keep];
118 },
119 .little => {
120 const pos = @as(U, final_byte & low_bit_mask[bits_left]) << @intCast(out_count);
121 out |= pos;
122 self.bits = final_byte >> @intCast(bits_left);
123 },
124 }
125
126 self.count = @intCast(keep);
127 return initBits(T, out, num);
128 }
129
130 //convenience function for removing bits from
131 //the appropriate part of the buffer based on
132 //endianess.
133 fn removeBits(self: *@This(), num: u4) u8 {
134 if (num == 8) {
135 self.count = 0;
136 return self.bits;
137 }
138
139 const keep = self.count - num;
140 const bits = switch (endian) {
141 .big => self.bits >> @intCast(keep),
142 .little => self.bits & low_bit_mask[num],
143 };
144 switch (endian) {
145 .big => self.bits &= low_bit_mask[keep],
146 .little => self.bits >>= @intCast(num),
147 }
148
149 self.count = keep;
150 return bits;
151 }
152
153 pub fn alignToByte(self: *@This()) void {
154 self.bits = 0;
155 self.count = 0;
156 }
157 };
158}
159
160pub fn bitReader(comptime endian: std.builtin.Endian, reader: anytype) BitReader(endian, @TypeOf(reader)) {
161 return .{ .reader = reader };
162}
163
164///////////////////////////////
165
166test "api coverage" {
167 const mem_be = [_]u8{ 0b11001101, 0b00001011 };
168 const mem_le = [_]u8{ 0b00011101, 0b10010101 };
169
170 var mem_in_be = std.io.fixedBufferStream(&mem_be);
171 var bit_stream_be = bitReader(.big, mem_in_be.reader());
172
173 var out_bits: u16 = undefined;
174
175 const expect = std.testing.expect;
176 const expectError = std.testing.expectError;
177
178 try expect(1 == try bit_stream_be.readBits(u2, 1, &out_bits));
179 try expect(out_bits == 1);
180 try expect(2 == try bit_stream_be.readBits(u5, 2, &out_bits));
181 try expect(out_bits == 2);
182 try expect(3 == try bit_stream_be.readBits(u128, 3, &out_bits));
183 try expect(out_bits == 3);
184 try expect(4 == try bit_stream_be.readBits(u8, 4, &out_bits));
185 try expect(out_bits == 4);
186 try expect(5 == try bit_stream_be.readBits(u9, 5, &out_bits));
187 try expect(out_bits == 5);
188 try expect(1 == try bit_stream_be.readBits(u1, 1, &out_bits));
189 try expect(out_bits == 1);
190
191 mem_in_be.pos = 0;
192 bit_stream_be.count = 0;
193 try expect(0b110011010000101 == try bit_stream_be.readBits(u15, 15, &out_bits));
194 try expect(out_bits == 15);
195
196 mem_in_be.pos = 0;
197 bit_stream_be.count = 0;
198 try expect(0b1100110100001011 == try bit_stream_be.readBits(u16, 16, &out_bits));
199 try expect(out_bits == 16);
200
201 _ = try bit_stream_be.readBits(u0, 0, &out_bits);
202
203 try expect(0 == try bit_stream_be.readBits(u1, 1, &out_bits));
204 try expect(out_bits == 0);
205 try expectError(error.EndOfStream, bit_stream_be.readBitsNoEof(u1, 1));
206
207 var mem_in_le = std.io.fixedBufferStream(&mem_le);
208 var bit_stream_le = bitReader(.little, mem_in_le.reader());
209
210 try expect(1 == try bit_stream_le.readBits(u2, 1, &out_bits));
211 try expect(out_bits == 1);
212 try expect(2 == try bit_stream_le.readBits(u5, 2, &out_bits));
213 try expect(out_bits == 2);
214 try expect(3 == try bit_stream_le.readBits(u128, 3, &out_bits));
215 try expect(out_bits == 3);
216 try expect(4 == try bit_stream_le.readBits(u8, 4, &out_bits));
217 try expect(out_bits == 4);
218 try expect(5 == try bit_stream_le.readBits(u9, 5, &out_bits));
219 try expect(out_bits == 5);
220 try expect(1 == try bit_stream_le.readBits(u1, 1, &out_bits));
221 try expect(out_bits == 1);
222
223 mem_in_le.pos = 0;
224 bit_stream_le.count = 0;
225 try expect(0b001010100011101 == try bit_stream_le.readBits(u15, 15, &out_bits));
226 try expect(out_bits == 15);
227
228 mem_in_le.pos = 0;
229 bit_stream_le.count = 0;
230 try expect(0b1001010100011101 == try bit_stream_le.readBits(u16, 16, &out_bits));
231 try expect(out_bits == 16);
232
233 _ = try bit_stream_le.readBits(u0, 0, &out_bits);
234
235 try expect(0 == try bit_stream_le.readBits(u1, 1, &out_bits));
236 try expect(out_bits == 0);
237 try expectError(error.EndOfStream, bit_stream_le.readBitsNoEof(u1, 1));
238}
lib/std/io/bit_writer.zig deleted-179
...@@ -1,179 +0,0 @@
1const std = @import("../std.zig");
2
3//General note on endianess:
4//Big endian is packed starting in the most significant part of the byte and subsequent
5// bytes contain less significant bits. Thus we write out bits from the high end
6// of our input first.
7//Little endian is packed starting in the least significant part of the byte and
8// subsequent bytes contain more significant bits. Thus we write out bits from
9// the low end of our input first.
10//Regardless of endianess, within any given byte the bits are always in most
11// to least significant order.
12//Also regardless of endianess, the buffer always aligns bits to the low end
13// of the byte.
14
15/// Creates a bit writer which allows for writing bits to an underlying standard writer
16pub fn BitWriter(comptime endian: std.builtin.Endian, comptime Writer: type) type {
17 return struct {
18 writer: Writer,
19 bits: u8 = 0,
20 count: u4 = 0,
21
22 const low_bit_mask = [9]u8{
23 0b00000000,
24 0b00000001,
25 0b00000011,
26 0b00000111,
27 0b00001111,
28 0b00011111,
29 0b00111111,
30 0b01111111,
31 0b11111111,
32 };
33
34 /// Write the specified number of bits to the writer from the least significant bits of
35 /// the specified value. Bits will only be written to the writer when there
36 /// are enough to fill a byte.
37 pub fn writeBits(self: *@This(), value: anytype, num: u16) !void {
38 const T = @TypeOf(value);
39 const UT = std.meta.Int(.unsigned, @bitSizeOf(T));
40 const U = if (@bitSizeOf(T) < 8) u8 else UT; //<u8 is a pain to work with
41
42 var in: U = @as(UT, @bitCast(value));
43 var in_count: u16 = num;
44
45 if (self.count > 0) {
46 //if we can't fill the buffer, add what we have
47 const bits_free = 8 - self.count;
48 if (num < bits_free) {
49 self.addBits(@truncate(in), @intCast(num));
50 return;
51 }
52
53 //finish filling the buffer and flush it
54 if (num == bits_free) {
55 self.addBits(@truncate(in), @intCast(num));
56 return self.flushBits();
57 }
58
59 switch (endian) {
60 .big => {
61 const bits = in >> @intCast(in_count - bits_free);
62 self.addBits(@truncate(bits), bits_free);
63 },
64 .little => {
65 self.addBits(@truncate(in), bits_free);
66 in >>= @intCast(bits_free);
67 },
68 }
69 in_count -= bits_free;
70 try self.flushBits();
71 }
72
73 //write full bytes while we can
74 const full_bytes_left = in_count / 8;
75 for (0..full_bytes_left) |_| {
76 switch (endian) {
77 .big => {
78 const bits = in >> @intCast(in_count - 8);
79 try self.writer.writeByte(@truncate(bits));
80 },
81 .little => {
82 try self.writer.writeByte(@truncate(in));
83 if (U == u8) in = 0 else in >>= 8;
84 },
85 }
86 in_count -= 8;
87 }
88
89 //save the remaining bits in the buffer
90 self.addBits(@truncate(in), @intCast(in_count));
91 }
92
93 //convenience funciton for adding bits to the buffer
94 //in the appropriate position based on endianess
95 fn addBits(self: *@This(), bits: u8, num: u4) void {
96 if (num == 8) self.bits = bits else switch (endian) {
97 .big => {
98 self.bits <<= @intCast(num);
99 self.bits |= bits & low_bit_mask[num];
100 },
101 .little => {
102 const pos = bits << @intCast(self.count);
103 self.bits |= pos;
104 },
105 }
106 self.count += num;
107 }
108
109 /// Flush any remaining bits to the writer, filling
110 /// unused bits with 0s.
111 pub fn flushBits(self: *@This()) !void {
112 if (self.count == 0) return;
113 if (endian == .big) self.bits <<= @intCast(8 - self.count);
114 try self.writer.writeByte(self.bits);
115 self.bits = 0;
116 self.count = 0;
117 }
118 };
119}
120
121pub fn bitWriter(comptime endian: std.builtin.Endian, writer: anytype) BitWriter(endian, @TypeOf(writer)) {
122 return .{ .writer = writer };
123}
124
125///////////////////////////////
126
127test "api coverage" {
128 var mem_be = [_]u8{0} ** 2;
129 var mem_le = [_]u8{0} ** 2;
130
131 var mem_out_be = std.io.fixedBufferStream(&mem_be);
132 var bit_stream_be = bitWriter(.big, mem_out_be.writer());
133
134 const testing = std.testing;
135
136 try bit_stream_be.writeBits(@as(u2, 1), 1);
137 try bit_stream_be.writeBits(@as(u5, 2), 2);
138 try bit_stream_be.writeBits(@as(u128, 3), 3);
139 try bit_stream_be.writeBits(@as(u8, 4), 4);
140 try bit_stream_be.writeBits(@as(u9, 5), 5);
141 try bit_stream_be.writeBits(@as(u1, 1), 1);
142
143 try testing.expect(mem_be[0] == 0b11001101 and mem_be[1] == 0b00001011);
144
145 mem_out_be.pos = 0;
146
147 try bit_stream_be.writeBits(@as(u15, 0b110011010000101), 15);
148 try bit_stream_be.flushBits();
149 try testing.expect(mem_be[0] == 0b11001101 and mem_be[1] == 0b00001010);
150
151 mem_out_be.pos = 0;
152 try bit_stream_be.writeBits(@as(u32, 0b110011010000101), 16);
153 try testing.expect(mem_be[0] == 0b01100110 and mem_be[1] == 0b10000101);
154
155 try bit_stream_be.writeBits(@as(u0, 0), 0);
156
157 var mem_out_le = std.io.fixedBufferStream(&mem_le);
158 var bit_stream_le = bitWriter(.little, mem_out_le.writer());
159
160 try bit_stream_le.writeBits(@as(u2, 1), 1);
161 try bit_stream_le.writeBits(@as(u5, 2), 2);
162 try bit_stream_le.writeBits(@as(u128, 3), 3);
163 try bit_stream_le.writeBits(@as(u8, 4), 4);
164 try bit_stream_le.writeBits(@as(u9, 5), 5);
165 try bit_stream_le.writeBits(@as(u1, 1), 1);
166
167 try testing.expect(mem_le[0] == 0b00011101 and mem_le[1] == 0b10010101);
168
169 mem_out_le.pos = 0;
170 try bit_stream_le.writeBits(@as(u15, 0b110011010000101), 15);
171 try bit_stream_le.flushBits();
172 try testing.expect(mem_le[0] == 0b10000101 and mem_le[1] == 0b01100110);
173
174 mem_out_le.pos = 0;
175 try bit_stream_le.writeBits(@as(u32, 0b1100110100001011), 16);
176 try testing.expect(mem_le[0] == 0b00001011 and mem_le[1] == 0b11001101);
177
178 try bit_stream_le.writeBits(@as(u0, 0), 0);
179}
lib/std/io/buffered_atomic_file.zig deleted-55
...@@ -1,55 +0,0 @@
1const std = @import("../std.zig");
2const mem = std.mem;
3const fs = std.fs;
4const File = std.fs.File;
5
6pub const BufferedAtomicFile = struct {
7 atomic_file: fs.AtomicFile,
8 file_writer: File.Writer,
9 buffered_writer: BufferedWriter,
10 allocator: mem.Allocator,
11
12 pub const buffer_size = 4096;
13 pub const BufferedWriter = std.io.BufferedWriter(buffer_size, File.Writer);
14 pub const Writer = std.io.GenericWriter(*BufferedWriter, BufferedWriter.Error, BufferedWriter.write);
15
16 /// TODO when https://github.com/ziglang/zig/issues/2761 is solved
17 /// this API will not need an allocator
18 pub fn create(
19 allocator: mem.Allocator,
20 dir: fs.Dir,
21 dest_path: []const u8,
22 atomic_file_options: fs.Dir.AtomicFileOptions,
23 ) !*BufferedAtomicFile {
24 var self = try allocator.create(BufferedAtomicFile);
25 self.* = BufferedAtomicFile{
26 .atomic_file = undefined,
27 .file_writer = undefined,
28 .buffered_writer = undefined,
29 .allocator = allocator,
30 };
31 errdefer allocator.destroy(self);
32
33 self.atomic_file = try dir.atomicFile(dest_path, atomic_file_options);
34 errdefer self.atomic_file.deinit();
35
36 self.file_writer = self.atomic_file.file.deprecatedWriter();
37 self.buffered_writer = .{ .unbuffered_writer = self.file_writer };
38 return self;
39 }
40
41 /// always call destroy, even after successful finish()
42 pub fn destroy(self: *BufferedAtomicFile) void {
43 self.atomic_file.deinit();
44 self.allocator.destroy(self);
45 }
46
47 pub fn finish(self: *BufferedAtomicFile) !void {
48 try self.buffered_writer.flush();
49 try self.atomic_file.finish();
50 }
51
52 pub fn writer(self: *BufferedAtomicFile) Writer {
53 return .{ .context = &self.buffered_writer };
54 }
55};
lib/std/io/buffered_reader.zig deleted-201
...@@ -1,201 +0,0 @@
1const std = @import("../std.zig");
2const io = std.io;
3const mem = std.mem;
4const assert = std.debug.assert;
5const testing = std.testing;
6
7pub fn BufferedReader(comptime buffer_size: usize, comptime ReaderType: type) type {
8 return struct {
9 unbuffered_reader: ReaderType,
10 buf: [buffer_size]u8 = undefined,
11 start: usize = 0,
12 end: usize = 0,
13
14 pub const Error = ReaderType.Error;
15 pub const Reader = io.GenericReader(*Self, Error, read);
16
17 const Self = @This();
18
19 pub fn read(self: *Self, dest: []u8) Error!usize {
20 // First try reading from the already buffered data onto the destination.
21 const current = self.buf[self.start..self.end];
22 if (current.len != 0) {
23 const to_transfer = @min(current.len, dest.len);
24 @memcpy(dest[0..to_transfer], current[0..to_transfer]);
25 self.start += to_transfer;
26 return to_transfer;
27 }
28
29 // If dest is large, read from the unbuffered reader directly into the destination.
30 if (dest.len >= buffer_size) {
31 return self.unbuffered_reader.read(dest);
32 }
33
34 // If dest is small, read from the unbuffered reader into our own internal buffer,
35 // and then transfer to destination.
36 self.end = try self.unbuffered_reader.read(&self.buf);
37 const to_transfer = @min(self.end, dest.len);
38 @memcpy(dest[0..to_transfer], self.buf[0..to_transfer]);
39 self.start = to_transfer;
40 return to_transfer;
41 }
42
43 pub fn reader(self: *Self) Reader {
44 return .{ .context = self };
45 }
46 };
47}
48
49pub fn bufferedReader(reader: anytype) BufferedReader(4096, @TypeOf(reader)) {
50 return .{ .unbuffered_reader = reader };
51}
52
53pub fn bufferedReaderSize(comptime size: usize, reader: anytype) BufferedReader(size, @TypeOf(reader)) {
54 return .{ .unbuffered_reader = reader };
55}
56
57test "OneByte" {
58 const OneByteReadReader = struct {
59 str: []const u8,
60 curr: usize,
61
62 const Error = error{NoError};
63 const Self = @This();
64 const Reader = io.GenericReader(*Self, Error, read);
65
66 fn init(str: []const u8) Self {
67 return Self{
68 .str = str,
69 .curr = 0,
70 };
71 }
72
73 fn read(self: *Self, dest: []u8) Error!usize {
74 if (self.str.len <= self.curr or dest.len == 0)
75 return 0;
76
77 dest[0] = self.str[self.curr];
78 self.curr += 1;
79 return 1;
80 }
81
82 fn reader(self: *Self) Reader {
83 return .{ .context = self };
84 }
85 };
86
87 const str = "This is a test";
88 var one_byte_stream = OneByteReadReader.init(str);
89 var buf_reader = bufferedReader(one_byte_stream.reader());
90 const stream = buf_reader.reader();
91
92 const res = try stream.readAllAlloc(testing.allocator, str.len + 1);
93 defer testing.allocator.free(res);
94 try testing.expectEqualSlices(u8, str, res);
95}
96
97fn smallBufferedReader(underlying_stream: anytype) BufferedReader(8, @TypeOf(underlying_stream)) {
98 return .{ .unbuffered_reader = underlying_stream };
99}
100test "Block" {
101 const BlockReader = struct {
102 block: []const u8,
103 reads_allowed: usize,
104 curr_read: usize,
105
106 const Error = error{NoError};
107 const Self = @This();
108 const Reader = io.GenericReader(*Self, Error, read);
109
110 fn init(block: []const u8, reads_allowed: usize) Self {
111 return Self{
112 .block = block,
113 .reads_allowed = reads_allowed,
114 .curr_read = 0,
115 };
116 }
117
118 fn read(self: *Self, dest: []u8) Error!usize {
119 if (self.curr_read >= self.reads_allowed) return 0;
120 @memcpy(dest[0..self.block.len], self.block);
121
122 self.curr_read += 1;
123 return self.block.len;
124 }
125
126 fn reader(self: *Self) Reader {
127 return .{ .context = self };
128 }
129 };
130
131 const block = "0123";
132
133 // len out == block
134 {
135 var test_buf_reader: BufferedReader(4, BlockReader) = .{
136 .unbuffered_reader = BlockReader.init(block, 2),
137 };
138 const reader = test_buf_reader.reader();
139 var out_buf: [4]u8 = undefined;
140 _ = try reader.readAll(&out_buf);
141 try testing.expectEqualSlices(u8, &out_buf, block);
142 _ = try reader.readAll(&out_buf);
143 try testing.expectEqualSlices(u8, &out_buf, block);
144 try testing.expectEqual(try reader.readAll(&out_buf), 0);
145 }
146
147 // len out < block
148 {
149 var test_buf_reader: BufferedReader(4, BlockReader) = .{
150 .unbuffered_reader = BlockReader.init(block, 2),
151 };
152 const reader = test_buf_reader.reader();
153 var out_buf: [3]u8 = undefined;
154 _ = try reader.readAll(&out_buf);
155 try testing.expectEqualSlices(u8, &out_buf, "012");
156 _ = try reader.readAll(&out_buf);
157 try testing.expectEqualSlices(u8, &out_buf, "301");
158 const n = try reader.readAll(&out_buf);
159 try testing.expectEqualSlices(u8, out_buf[0..n], "23");
160 try testing.expectEqual(try reader.readAll(&out_buf), 0);
161 }
162
163 // len out > block
164 {
165 var test_buf_reader: BufferedReader(4, BlockReader) = .{
166 .unbuffered_reader = BlockReader.init(block, 2),
167 };
168 const reader = test_buf_reader.reader();
169 var out_buf: [5]u8 = undefined;
170 _ = try reader.readAll(&out_buf);
171 try testing.expectEqualSlices(u8, &out_buf, "01230");
172 const n = try reader.readAll(&out_buf);
173 try testing.expectEqualSlices(u8, out_buf[0..n], "123");
174 try testing.expectEqual(try reader.readAll(&out_buf), 0);
175 }
176
177 // len out == 0
178 {
179 var test_buf_reader: BufferedReader(4, BlockReader) = .{
180 .unbuffered_reader = BlockReader.init(block, 2),
181 };
182 const reader = test_buf_reader.reader();
183 var out_buf: [0]u8 = undefined;
184 _ = try reader.readAll(&out_buf);
185 try testing.expectEqualSlices(u8, &out_buf, "");
186 }
187
188 // len bufreader buf > block
189 {
190 var test_buf_reader: BufferedReader(5, BlockReader) = .{
191 .unbuffered_reader = BlockReader.init(block, 2),
192 };
193 const reader = test_buf_reader.reader();
194 var out_buf: [4]u8 = undefined;
195 _ = try reader.readAll(&out_buf);
196 try testing.expectEqualSlices(u8, &out_buf, block);
197 _ = try reader.readAll(&out_buf);
198 try testing.expectEqualSlices(u8, &out_buf, block);
199 try testing.expectEqual(try reader.readAll(&out_buf), 0);
200 }
201}
lib/std/io/buffered_writer.zig deleted-43
...@@ -1,43 +0,0 @@
1const std = @import("../std.zig");
2
3const io = std.io;
4const mem = std.mem;
5
6pub fn BufferedWriter(comptime buffer_size: usize, comptime WriterType: type) type {
7 return struct {
8 unbuffered_writer: WriterType,
9 buf: [buffer_size]u8 = undefined,
10 end: usize = 0,
11
12 pub const Error = WriterType.Error;
13 pub const Writer = io.GenericWriter(*Self, Error, write);
14
15 const Self = @This();
16
17 pub fn flush(self: *Self) !void {
18 try self.unbuffered_writer.writeAll(self.buf[0..self.end]);
19 self.end = 0;
20 }
21
22 pub fn writer(self: *Self) Writer {
23 return .{ .context = self };
24 }
25
26 pub fn write(self: *Self, bytes: []const u8) Error!usize {
27 if (self.end + bytes.len > self.buf.len) {
28 try self.flush();
29 if (bytes.len > self.buf.len)
30 return self.unbuffered_writer.write(bytes);
31 }
32
33 const new_end = self.end + bytes.len;
34 @memcpy(self.buf[self.end..new_end], bytes);
35 self.end = new_end;
36 return bytes.len;
37 }
38 };
39}
40
41pub fn bufferedWriter(underlying_stream: anytype) BufferedWriter(4096, @TypeOf(underlying_stream)) {
42 return .{ .unbuffered_writer = underlying_stream };
43}
lib/std/io/c_writer.zig deleted-44
...@@ -1,44 +0,0 @@
1const std = @import("../std.zig");
2const builtin = @import("builtin");
3const io = std.io;
4const testing = std.testing;
5
6pub const CWriter = io.GenericWriter(*std.c.FILE, std.fs.File.WriteError, cWriterWrite);
7
8pub fn cWriter(c_file: *std.c.FILE) CWriter {
9 return .{ .context = c_file };
10}
11
12fn cWriterWrite(c_file: *std.c.FILE, bytes: []const u8) std.fs.File.WriteError!usize {
13 const amt_written = std.c.fwrite(bytes.ptr, 1, bytes.len, c_file);
14 if (amt_written >= 0) return amt_written;
15 switch (@as(std.c.E, @enumFromInt(std.c._errno().*))) {
16 .SUCCESS => unreachable,
17 .INVAL => unreachable,
18 .FAULT => unreachable,
19 .AGAIN => unreachable, // this is a blocking API
20 .BADF => unreachable, // always a race condition
21 .DESTADDRREQ => unreachable, // connect was never called
22 .DQUOT => return error.DiskQuota,
23 .FBIG => return error.FileTooBig,
24 .IO => return error.InputOutput,
25 .NOSPC => return error.NoSpaceLeft,
26 .PERM => return error.PermissionDenied,
27 .PIPE => return error.BrokenPipe,
28 else => |err| return std.posix.unexpectedErrno(err),
29 }
30}
31
32test cWriter {
33 if (!builtin.link_libc or builtin.os.tag == .wasi) return error.SkipZigTest;
34
35 const filename = "tmp_io_test_file.txt";
36 const out_file = std.c.fopen(filename, "w") orelse return error.UnableToOpenTestFile;
37 defer {
38 _ = std.c.fclose(out_file);
39 std.fs.cwd().deleteFileZ(filename) catch {};
40 }
41
42 const writer = cWriter(out_file);
43 try writer.print("hi: {}\n", .{@as(i32, 123)});
44}
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/counting_reader.zig deleted-43
...@@ -1,43 +0,0 @@
1const std = @import("../std.zig");
2const io = std.io;
3const testing = std.testing;
4
5/// A Reader that counts how many bytes has been read from it.
6pub fn CountingReader(comptime ReaderType: anytype) type {
7 return struct {
8 child_reader: ReaderType,
9 bytes_read: u64 = 0,
10
11 pub const Error = ReaderType.Error;
12 pub const Reader = io.GenericReader(*@This(), Error, read);
13
14 pub fn read(self: *@This(), buf: []u8) Error!usize {
15 const amt = try self.child_reader.read(buf);
16 self.bytes_read += amt;
17 return amt;
18 }
19
20 pub fn reader(self: *@This()) Reader {
21 return .{ .context = self };
22 }
23 };
24}
25
26pub fn countingReader(reader: anytype) CountingReader(@TypeOf(reader)) {
27 return .{ .child_reader = reader };
28}
29
30test CountingReader {
31 const bytes = "yay" ** 100;
32 var fbs = io.fixedBufferStream(bytes);
33
34 var counting_stream = countingReader(fbs.reader());
35 const stream = counting_stream.reader();
36
37 //read and discard all bytes
38 while (stream.readByte()) |_| {} else |err| {
39 try testing.expect(err == error.EndOfStream);
40 }
41
42 try testing.expect(counting_stream.bytes_read == bytes.len);
43}
lib/std/io/counting_writer.zig deleted-39
...@@ -1,39 +0,0 @@
1const std = @import("../std.zig");
2const io = std.io;
3const testing = std.testing;
4
5/// A Writer that counts how many bytes has been written to it.
6pub fn CountingWriter(comptime WriterType: type) type {
7 return struct {
8 bytes_written: u64,
9 child_stream: WriterType,
10
11 pub const Error = WriterType.Error;
12 pub const Writer = io.GenericWriter(*Self, Error, write);
13
14 const Self = @This();
15
16 pub fn write(self: *Self, bytes: []const u8) Error!usize {
17 const amt = try self.child_stream.write(bytes);
18 self.bytes_written += amt;
19 return amt;
20 }
21
22 pub fn writer(self: *Self) Writer {
23 return .{ .context = self };
24 }
25 };
26}
27
28pub fn countingWriter(child_stream: anytype) CountingWriter(@TypeOf(child_stream)) {
29 return .{ .bytes_written = 0, .child_stream = child_stream };
30}
31
32test CountingWriter {
33 var counting_stream = countingWriter(std.io.null_writer);
34 const stream = counting_stream.writer();
35
36 const bytes = "yay" ** 100;
37 stream.writeAll(bytes) catch unreachable;
38 try testing.expect(counting_stream.bytes_written == bytes.len);
39}
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/fixed_buffer_stream.zig deleted-198
...@@ -1,198 +0,0 @@
1const std = @import("../std.zig");
2const io = std.io;
3const testing = std.testing;
4const mem = std.mem;
5const assert = std.debug.assert;
6
7/// This turns a byte buffer into an `io.GenericWriter`, `io.GenericReader`, or `io.SeekableStream`.
8/// If the supplied byte buffer is const, then `io.GenericWriter` is not available.
9pub fn FixedBufferStream(comptime Buffer: type) type {
10 return struct {
11 /// `Buffer` is either a `[]u8` or `[]const u8`.
12 buffer: Buffer,
13 pos: usize,
14
15 pub const ReadError = error{};
16 pub const WriteError = error{NoSpaceLeft};
17 pub const SeekError = error{};
18 pub const GetSeekPosError = error{};
19
20 pub const Reader = io.GenericReader(*Self, ReadError, read);
21 pub const Writer = io.GenericWriter(*Self, WriteError, write);
22
23 pub const SeekableStream = io.SeekableStream(
24 *Self,
25 SeekError,
26 GetSeekPosError,
27 seekTo,
28 seekBy,
29 getPos,
30 getEndPos,
31 );
32
33 const Self = @This();
34
35 pub fn reader(self: *Self) Reader {
36 return .{ .context = self };
37 }
38
39 pub fn writer(self: *Self) Writer {
40 return .{ .context = self };
41 }
42
43 pub fn seekableStream(self: *Self) SeekableStream {
44 return .{ .context = self };
45 }
46
47 pub fn read(self: *Self, dest: []u8) ReadError!usize {
48 const size = @min(dest.len, self.buffer.len - self.pos);
49 const end = self.pos + size;
50
51 @memcpy(dest[0..size], self.buffer[self.pos..end]);
52 self.pos = end;
53
54 return size;
55 }
56
57 /// If the returned number of bytes written is less than requested, the
58 /// buffer is full. Returns `error.NoSpaceLeft` when no bytes would be written.
59 /// Note: `error.NoSpaceLeft` matches the corresponding error from
60 /// `std.fs.File.WriteError`.
61 pub fn write(self: *Self, bytes: []const u8) WriteError!usize {
62 if (bytes.len == 0) return 0;
63 if (self.pos >= self.buffer.len) return error.NoSpaceLeft;
64
65 const n = @min(self.buffer.len - self.pos, bytes.len);
66 @memcpy(self.buffer[self.pos..][0..n], bytes[0..n]);
67 self.pos += n;
68
69 if (n == 0) return error.NoSpaceLeft;
70
71 return n;
72 }
73
74 pub fn seekTo(self: *Self, pos: u64) SeekError!void {
75 self.pos = @min(std.math.lossyCast(usize, pos), self.buffer.len);
76 }
77
78 pub fn seekBy(self: *Self, amt: i64) SeekError!void {
79 if (amt < 0) {
80 const abs_amt = @abs(amt);
81 const abs_amt_usize = std.math.cast(usize, abs_amt) orelse std.math.maxInt(usize);
82 if (abs_amt_usize > self.pos) {
83 self.pos = 0;
84 } else {
85 self.pos -= abs_amt_usize;
86 }
87 } else {
88 const amt_usize = std.math.cast(usize, amt) orelse std.math.maxInt(usize);
89 const new_pos = std.math.add(usize, self.pos, amt_usize) catch std.math.maxInt(usize);
90 self.pos = @min(self.buffer.len, new_pos);
91 }
92 }
93
94 pub fn getEndPos(self: *Self) GetSeekPosError!u64 {
95 return self.buffer.len;
96 }
97
98 pub fn getPos(self: *Self) GetSeekPosError!u64 {
99 return self.pos;
100 }
101
102 pub fn getWritten(self: Self) Buffer {
103 return self.buffer[0..self.pos];
104 }
105
106 pub fn reset(self: *Self) void {
107 self.pos = 0;
108 }
109 };
110}
111
112pub fn fixedBufferStream(buffer: anytype) FixedBufferStream(Slice(@TypeOf(buffer))) {
113 return .{ .buffer = buffer, .pos = 0 };
114}
115
116fn Slice(comptime T: type) type {
117 switch (@typeInfo(T)) {
118 .pointer => |ptr_info| {
119 var new_ptr_info = ptr_info;
120 switch (ptr_info.size) {
121 .slice => {},
122 .one => switch (@typeInfo(ptr_info.child)) {
123 .array => |info| new_ptr_info.child = info.child,
124 else => @compileError("invalid type given to fixedBufferStream"),
125 },
126 else => @compileError("invalid type given to fixedBufferStream"),
127 }
128 new_ptr_info.size = .slice;
129 return @Type(.{ .pointer = new_ptr_info });
130 },
131 else => @compileError("invalid type given to fixedBufferStream"),
132 }
133}
134
135test "output" {
136 var buf: [255]u8 = undefined;
137 var fbs = fixedBufferStream(&buf);
138 const stream = fbs.writer();
139
140 try stream.print("{s}{s}!", .{ "Hello", "World" });
141 try testing.expectEqualSlices(u8, "HelloWorld!", fbs.getWritten());
142}
143
144test "output at comptime" {
145 comptime {
146 var buf: [255]u8 = undefined;
147 var fbs = fixedBufferStream(&buf);
148 const stream = fbs.writer();
149
150 try stream.print("{s}{s}!", .{ "Hello", "World" });
151 try testing.expectEqualSlices(u8, "HelloWorld!", fbs.getWritten());
152 }
153}
154
155test "output 2" {
156 var buffer: [10]u8 = undefined;
157 var fbs = fixedBufferStream(&buffer);
158
159 try fbs.writer().writeAll("Hello");
160 try testing.expect(mem.eql(u8, fbs.getWritten(), "Hello"));
161
162 try fbs.writer().writeAll("world");
163 try testing.expect(mem.eql(u8, fbs.getWritten(), "Helloworld"));
164
165 try testing.expectError(error.NoSpaceLeft, fbs.writer().writeAll("!"));
166 try testing.expect(mem.eql(u8, fbs.getWritten(), "Helloworld"));
167
168 fbs.reset();
169 try testing.expect(fbs.getWritten().len == 0);
170
171 try testing.expectError(error.NoSpaceLeft, fbs.writer().writeAll("Hello world!"));
172 try testing.expect(mem.eql(u8, fbs.getWritten(), "Hello worl"));
173
174 try fbs.seekTo((try fbs.getEndPos()) + 1);
175 try testing.expectError(error.NoSpaceLeft, fbs.writer().writeAll("H"));
176}
177
178test "input" {
179 const bytes = [_]u8{ 1, 2, 3, 4, 5, 6, 7 };
180 var fbs = fixedBufferStream(&bytes);
181
182 var dest: [4]u8 = undefined;
183
184 var read = try fbs.reader().read(&dest);
185 try testing.expect(read == 4);
186 try testing.expect(mem.eql(u8, dest[0..4], bytes[0..4]));
187
188 read = try fbs.reader().read(&dest);
189 try testing.expect(read == 3);
190 try testing.expect(mem.eql(u8, dest[0..3], bytes[4..7]));
191
192 read = try fbs.reader().read(&dest);
193 try testing.expect(read == 0);
194
195 try fbs.seekTo((try fbs.getEndPos()) + 1);
196 read = try fbs.reader().read(&dest);
197 try testing.expect(read == 0);
198}
lib/std/io/limited_reader.zig deleted-45
...@@ -1,45 +0,0 @@
1const std = @import("../std.zig");
2const io = std.io;
3const assert = std.debug.assert;
4const testing = std.testing;
5
6pub fn LimitedReader(comptime ReaderType: type) type {
7 return struct {
8 inner_reader: ReaderType,
9 bytes_left: u64,
10
11 pub const Error = ReaderType.Error;
12 pub const Reader = io.GenericReader(*Self, Error, read);
13
14 const Self = @This();
15
16 pub fn read(self: *Self, dest: []u8) Error!usize {
17 const max_read = @min(self.bytes_left, dest.len);
18 const n = try self.inner_reader.read(dest[0..max_read]);
19 self.bytes_left -= n;
20 return n;
21 }
22
23 pub fn reader(self: *Self) Reader {
24 return .{ .context = self };
25 }
26 };
27}
28
29/// Returns an initialised `LimitedReader`.
30/// `bytes_left` is a `u64` to be able to take 64 bit file offsets
31pub fn limitedReader(inner_reader: anytype, bytes_left: u64) LimitedReader(@TypeOf(inner_reader)) {
32 return .{ .inner_reader = inner_reader, .bytes_left = bytes_left };
33}
34
35test "basic usage" {
36 const data = "hello world";
37 var fbs = std.io.fixedBufferStream(data);
38 var early_stream = limitedReader(fbs.reader(), 3);
39
40 var buf: [5]u8 = undefined;
41 try testing.expectEqual(@as(usize, 3), try early_stream.reader().read(&buf));
42 try testing.expectEqualSlices(u8, data[0..3], buf[0..3]);
43 try testing.expectEqual(@as(usize, 0), try early_stream.reader().read(&buf));
44 try testing.expectError(error.EndOfStream, early_stream.reader().skipBytes(10, .{}));
45}
lib/std/io/multi_writer.zig deleted-53
...@@ -1,53 +0,0 @@
1const std = @import("../std.zig");
2const io = std.io;
3
4/// Takes a tuple of streams, and constructs a new stream that writes to all of them
5pub fn MultiWriter(comptime Writers: type) type {
6 comptime var ErrSet = error{};
7 inline for (@typeInfo(Writers).@"struct".fields) |field| {
8 const StreamType = field.type;
9 ErrSet = ErrSet || StreamType.Error;
10 }
11
12 return struct {
13 const Self = @This();
14
15 streams: Writers,
16
17 pub const Error = ErrSet;
18 pub const Writer = io.GenericWriter(*Self, Error, write);
19
20 pub fn writer(self: *Self) Writer {
21 return .{ .context = self };
22 }
23
24 pub fn write(self: *Self, bytes: []const u8) Error!usize {
25 inline for (self.streams) |stream|
26 try stream.writeAll(bytes);
27 return bytes.len;
28 }
29 };
30}
31
32pub fn multiWriter(streams: anytype) MultiWriter(@TypeOf(streams)) {
33 return .{ .streams = streams };
34}
35
36const testing = std.testing;
37
38test "MultiWriter" {
39 var tmp = testing.tmpDir(.{});
40 defer tmp.cleanup();
41 var f = try tmp.dir.createFile("t.txt", .{});
42
43 var buf1: [255]u8 = undefined;
44 var fbs1 = io.fixedBufferStream(&buf1);
45 var buf2: [255]u8 = undefined;
46 var stream = multiWriter(.{ fbs1.writer(), f.writer() });
47
48 try stream.writer().print("HI", .{});
49 f.close();
50
51 try testing.expectEqualSlices(u8, "HI", fbs1.getWritten());
52 try testing.expectEqualSlices(u8, "HI", try tmp.dir.readFile("t.txt", &buf2));
53}
lib/std/io/seekable_stream.zig deleted-35
...@@ -1,35 +0,0 @@
1const std = @import("../std.zig");
2
3pub fn SeekableStream(
4 comptime Context: type,
5 comptime SeekErrorType: type,
6 comptime GetSeekPosErrorType: type,
7 comptime seekToFn: fn (context: Context, pos: u64) SeekErrorType!void,
8 comptime seekByFn: fn (context: Context, pos: i64) SeekErrorType!void,
9 comptime getPosFn: fn (context: Context) GetSeekPosErrorType!u64,
10 comptime getEndPosFn: fn (context: Context) GetSeekPosErrorType!u64,
11) type {
12 return struct {
13 context: Context,
14
15 const Self = @This();
16 pub const SeekError = SeekErrorType;
17 pub const GetSeekPosError = GetSeekPosErrorType;
18
19 pub fn seekTo(self: Self, pos: u64) SeekError!void {
20 return seekToFn(self.context, pos);
21 }
22
23 pub fn seekBy(self: Self, amt: i64) SeekError!void {
24 return seekByFn(self.context, amt);
25 }
26
27 pub fn getEndPos(self: Self) GetSeekPosError!u64 {
28 return getEndPosFn(self.context);
29 }
30
31 pub fn getPos(self: Self) GetSeekPosError!u64 {
32 return getPosFn(self.context);
33 }
34 };
35}
lib/std/io/stream_source.zig deleted-127
...@@ -1,127 +0,0 @@
1const std = @import("../std.zig");
2const builtin = @import("builtin");
3const io = std.io;
4
5/// Provides `io.GenericReader`, `io.GenericWriter`, and `io.SeekableStream` for in-memory buffers as
6/// well as files.
7/// For memory sources, if the supplied byte buffer is const, then `io.GenericWriter` is not available.
8/// The error set of the stream functions is the error set of the corresponding file functions.
9pub const StreamSource = union(enum) {
10 // TODO: expose UEFI files to std.os in a way that allows this to be true
11 const has_file = (builtin.os.tag != .freestanding and builtin.os.tag != .uefi);
12
13 /// The stream access is redirected to this buffer.
14 buffer: io.FixedBufferStream([]u8),
15
16 /// The stream access is redirected to this buffer.
17 /// Writing to the source will always yield `error.AccessDenied`.
18 const_buffer: io.FixedBufferStream([]const u8),
19
20 /// The stream access is redirected to this file.
21 /// On freestanding, this must never be initialized!
22 file: if (has_file) std.fs.File else void,
23
24 pub const ReadError = io.FixedBufferStream([]u8).ReadError || (if (has_file) std.fs.File.ReadError else error{});
25 pub const WriteError = error{AccessDenied} || io.FixedBufferStream([]u8).WriteError || (if (has_file) std.fs.File.WriteError else error{});
26 pub const SeekError = io.FixedBufferStream([]u8).SeekError || (if (has_file) std.fs.File.SeekError else error{});
27 pub const GetSeekPosError = io.FixedBufferStream([]u8).GetSeekPosError || (if (has_file) std.fs.File.GetSeekPosError else error{});
28
29 pub const Reader = io.GenericReader(*StreamSource, ReadError, read);
30 pub const Writer = io.GenericWriter(*StreamSource, WriteError, write);
31 pub const SeekableStream = io.SeekableStream(
32 *StreamSource,
33 SeekError,
34 GetSeekPosError,
35 seekTo,
36 seekBy,
37 getPos,
38 getEndPos,
39 );
40
41 pub fn read(self: *StreamSource, dest: []u8) ReadError!usize {
42 switch (self.*) {
43 .buffer => |*x| return x.read(dest),
44 .const_buffer => |*x| return x.read(dest),
45 .file => |x| if (!has_file) unreachable else return x.read(dest),
46 }
47 }
48
49 pub fn write(self: *StreamSource, bytes: []const u8) WriteError!usize {
50 switch (self.*) {
51 .buffer => |*x| return x.write(bytes),
52 .const_buffer => return error.AccessDenied,
53 .file => |x| if (!has_file) unreachable else return x.write(bytes),
54 }
55 }
56
57 pub fn seekTo(self: *StreamSource, pos: u64) SeekError!void {
58 switch (self.*) {
59 .buffer => |*x| return x.seekTo(pos),
60 .const_buffer => |*x| return x.seekTo(pos),
61 .file => |x| if (!has_file) unreachable else return x.seekTo(pos),
62 }
63 }
64
65 pub fn seekBy(self: *StreamSource, amt: i64) SeekError!void {
66 switch (self.*) {
67 .buffer => |*x| return x.seekBy(amt),
68 .const_buffer => |*x| return x.seekBy(amt),
69 .file => |x| if (!has_file) unreachable else return x.seekBy(amt),
70 }
71 }
72
73 pub fn getEndPos(self: *StreamSource) GetSeekPosError!u64 {
74 switch (self.*) {
75 .buffer => |*x| return x.getEndPos(),
76 .const_buffer => |*x| return x.getEndPos(),
77 .file => |x| if (!has_file) unreachable else return x.getEndPos(),
78 }
79 }
80
81 pub fn getPos(self: *StreamSource) GetSeekPosError!u64 {
82 switch (self.*) {
83 .buffer => |*x| return x.getPos(),
84 .const_buffer => |*x| return x.getPos(),
85 .file => |x| if (!has_file) unreachable else return x.getPos(),
86 }
87 }
88
89 pub fn reader(self: *StreamSource) Reader {
90 return .{ .context = self };
91 }
92
93 pub fn writer(self: *StreamSource) Writer {
94 return .{ .context = self };
95 }
96
97 pub fn seekableStream(self: *StreamSource) SeekableStream {
98 return .{ .context = self };
99 }
100};
101
102test "refs" {
103 std.testing.refAllDecls(StreamSource);
104}
105
106test "mutable buffer" {
107 var buffer: [64]u8 = undefined;
108 var source = StreamSource{ .buffer = std.io.fixedBufferStream(&buffer) };
109
110 var writer = source.writer();
111
112 try writer.writeAll("Hello, World!");
113
114 try std.testing.expectEqualStrings("Hello, World!", source.buffer.getWritten());
115}
116
117test "const buffer" {
118 const buffer: [64]u8 = "Hello, World!".* ++ ([1]u8{0xAA} ** 51);
119 var source = StreamSource{ .const_buffer = std.io.fixedBufferStream(&buffer) };
120
121 var reader = source.reader();
122
123 var dst_buffer: [13]u8 = undefined;
124 try reader.readNoEof(&dst_buffer);
125
126 try std.testing.expectEqualStrings("Hello, World!", &dst_buffer);
127}
lib/std/io/test.zig deleted-182
...@@ -1,182 +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}
170
171test "GenericReader methods can return error.EndOfStream" {
172 // https://github.com/ziglang/zig/issues/17733
173 var fbs = std.io.fixedBufferStream("");
174 try std.testing.expectError(
175 error.EndOfStream,
176 fbs.reader().readEnum(enum(u8) { a, b }, .little),
177 );
178 try std.testing.expectError(
179 error.EndOfStream,
180 fbs.reader().isBytes("foo"),
181 );
182}
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/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;
...@@ -67,7 +68,8 @@ pub const hash = @import("hash.zig");...@@ -67,7 +68,8 @@ pub const hash = @import("hash.zig");
67pub const hash_map = @import("hash_map.zig");68pub const hash_map = @import("hash_map.zig");
68pub const heap = @import("heap.zig");69pub const heap = @import("heap.zig");
69pub const http = @import("http.zig");70pub const http = @import("http.zig");
70pub const io = @import("io.zig");71/// Deprecated
72pub const io = Io;
71pub const json = @import("json.zig");73pub const json = @import("json.zig");
72pub const leb = @import("leb128.zig");74pub const leb = @import("leb128.zig");
73pub const log = @import("log.zig");75pub const log = @import("log.zig");