1const Writer = @This();
2
3const builtin = @import("builtin");
4const native_endian = builtin.target.cpu.arch.endian();
5
6const std = @import("../std.zig");
7const assert = std.debug.assert;
8const Limit = std.Io.Limit;
9const File = std.Io.File;
10const testing = std.testing;
11const Allocator = std.mem.Allocator;
12const ArrayList = std.ArrayList;
13
14vtable: *const VTable,
15/// If this has length zero, the writer is unbuffered, and `flush` is a no-op.
16buffer: []u8,
17/// In `buffer` before this are buffered bytes, after this is `undefined`.
18end: usize = 0,
19
20pub const VTable = struct {
21 /// Sends bytes to the logical sink. A write will only be sent here if it
22 /// could not fit into `buffer`, or during a `flush` operation.
23 ///
24 /// `buffer[0..end]` is consumed first, followed by each slice of `data` in
25 /// order. Elements of `data` may alias each other but may not alias
26 /// `buffer`.
27 ///
28 /// This function modifies `Writer.end` and `Writer.buffer` in an
29 /// implementation-defined manner.
30 ///
31 /// `data.len` must be nonzero.
32 ///
33 /// The last element of `data` is repeated as necessary so that it is
34 /// written `splat` number of times, which may be zero.
35 ///
36 /// This function may not be called if the data to be written could have
37 /// been stored in `buffer` instead, including when the amount of data to
38 /// be written is zero and the buffer capacity is zero.
39 ///
40 /// Number of bytes consumed from `data` is returned, excluding bytes from
41 /// `buffer`.
42 ///
43 /// Number of bytes returned may be zero, which does not indicate stream
44 /// end. A subsequent call may return nonzero, or signal end of stream via
45 /// `error.WriteFailed`.
46 drain: *const fn (w: *Writer, data: []const []const u8, splat: usize) Error!usize,
47
48 /// Copies contents from an open file to the logical sink. `buffer[0..end]`
49 /// is consumed first, followed by `limit` bytes from `file_reader`.
50 ///
51 /// Number of bytes logically written is returned. This excludes bytes from
52 /// `buffer` because they have already been logically written. Number of
53 /// bytes consumed from `buffer` are tracked by modifying `end`.
54 ///
55 /// Number of bytes returned may be zero, which does not indicate stream
56 /// end. A subsequent call may return nonzero, or signal end of stream via
57 /// `error.WriteFailed`. Caller may check `file_reader` state
58 /// (`File.Reader.atEnd`) to disambiguate between a zero-length read or
59 /// write, and whether the file reached the end.
60 ///
61 /// `error.Unimplemented` indicates the callee cannot offer a more
62 /// efficient implementation than the caller performing its own reads.
63 sendFile: *const fn (
64 w: *Writer,
65 file_reader: *File.Reader,
66 /// Maximum amount of bytes to read from the file. Implementations may
67 /// assume that the file size does not exceed this amount. Data from
68 /// `buffer` does not count towards this limit.
69 limit: Limit,
70 ) FileError!usize = unimplementedSendFile,
71
72 /// Consumes all remaining buffer.
73 ///
74 /// The default flush implementation calls drain repeatedly until `end` is
75 /// zero, however it is legal for implementations to manage `end`
76 /// differently. For instance, `Allocating` flush is a no-op.
77 ///
78 /// There may be subsequent calls to `drain` and `sendFile` after a `flush`
79 /// operation.
80 flush: *const fn (w: *Writer) Error!void = defaultFlush,
81
82 /// Ensures `capacity` more bytes can be buffered without rebasing.
83 ///
84 /// The most recent `preserve` bytes must remain buffered.
85 ///
86 /// Only called when `capacity` bytes cannot fit into the unused capacity
87 /// of `buffer`.
88 rebase: *const fn (w: *Writer, preserve: usize, capacity: usize) Error!void = defaultRebase,
89};
90
91pub const Error = error{
92 /// See the `Writer` implementation for detailed diagnostics.
93 WriteFailed,
94};
95
96pub const FileAllError = error{
97 /// Detailed diagnostics are found on the `File.Reader` struct.
98 ReadFailed,
99 /// See the `Writer` implementation for detailed diagnostics.
100 WriteFailed,
101};
102
103pub const FileReadingError = error{
104 /// Detailed diagnostics are found on the `File.Reader` struct.
105 ReadFailed,
106 /// See the `Writer` implementation for detailed diagnostics.
107 WriteFailed,
108 /// Reached the end of the file being read.
109 EndOfStream,
110};
111
112pub const FileError = error{
113 /// Detailed diagnostics are found on the `File.Reader` struct.
114 ReadFailed,
115 /// See the `Writer` implementation for detailed diagnostics.
116 WriteFailed,
117 /// Reached the end of the file being read.
118 EndOfStream,
119 /// Indicates the caller should do its own file reading; the callee cannot
120 /// offer a more efficient implementation.
121 Unimplemented,
122};
123
124/// Writes to `buffer` and returns `error.WriteFailed` when it is full.
125pub fn fixed(buffer: []u8) Writer {
126 return .{
127 .vtable = &.{
128 .drain = fixedDrain,
129 .flush = noopFlush,
130 .rebase = failingRebase,
131 },
132 .buffer = buffer,
133 };
134}
135
136pub fn hashed(w: *Writer, hasher: anytype, buffer: []u8) Hashed(@TypeOf(hasher)) {
137 return .initHasher(w, hasher, buffer);
138}
139
140pub const failing: Writer = .{
141 .vtable = &.{
142 .drain = failingDrain,
143 .sendFile = failingSendFile,
144 .rebase = failingRebase,
145 },
146 .buffer = &.{},
147};
148
149test failing {
150 var fw: Writer = .failing;
151 try testing.expectError(error.WriteFailed, fw.writeAll("always fails"));
152}
153
154/// Returns the contents not yet drained.
155pub fn buffered(w: *const Writer) []u8 {
156 return w.buffer[0..w.end];
157}
158
159pub fn countSplat(data: []const []const u8, splat: usize) usize {
160 var total: usize = 0;
161 for (data[0 .. data.len - 1]) |buf| total += buf.len;
162 total += data[data.len - 1].len * splat;
163 return total;
164}
165
166pub fn countSendFileLowerBound(n: usize, file_reader: *File.Reader, limit: Limit) ?usize {
167 const total: u64 = @min(@backingInt(limit), file_reader.getSize() catch return null);
168 return std.math.lossyCast(usize, total + n);
169}
170
171/// If the total number of bytes of `data` fits inside `unusedCapacitySlice`,
172/// this function is guaranteed to not fail, not call into `VTable`, and return
173/// the total bytes inside `data`.
174pub fn writeVec(w: *Writer, data: []const []const u8) Error!usize {
175 return writeSplat(w, data, 1);
176}
177
178/// If the number of bytes to write based on `data` and `splat` fits inside
179/// `unusedCapacitySlice`, this function is guaranteed to not fail, not call
180/// into `VTable`, and return the full number of bytes.
181pub fn writeSplat(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
182 assert(data.len > 0);
183 const buffer = w.buffer;
184 const count = countSplat(data, splat);
185 if (w.end + count > buffer.len) return w.vtable.drain(w, data, splat);
186 for (data[0 .. data.len - 1]) |bytes| {
187 @memcpy(buffer[w.end..][0..bytes.len], bytes);
188 w.end += bytes.len;
189 }
190 const pattern = data[data.len - 1];
191 switch (pattern.len) {
192 0 => {},
193 1 => {
194 @memset(buffer[w.end..][0..splat], pattern[0]);
195 w.end += splat;
196 },
197 else => for (0..splat) |_| {
198 @memcpy(buffer[w.end..][0..pattern.len], pattern);
199 w.end += pattern.len;
200 },
201 }
202 return count;
203}
204
205/// Returns how many bytes were consumed from `header` and `data`.
206pub fn writeSplatHeader(
207 w: *Writer,
208 header: []const u8,
209 data: []const []const u8,
210 splat: usize,
211) Error!usize {
212 return writeSplatHeaderLimit(w, header, data, splat, .unlimited);
213}
214
215/// Equivalent to `writeSplatHeader` but writes at most `limit` bytes.
216pub fn writeSplatHeaderLimit(
217 w: *Writer,
218 header: []const u8,
219 data: []const []const u8,
220 splat: usize,
221 limit: Limit,
222) Error!usize {
223 var remaining = @backingInt(limit);
224 assert(data.len > 0);
225 {
226 const copy_len = @min(header.len, remaining);
227 if (w.buffer.len - w.end < copy_len) return try writeSplatHeaderLimitFinish(w, header, data, splat, remaining);
228 @memcpy(w.buffer[w.end..][0..copy_len], header[0..copy_len]);
229 w.end += copy_len;
230 remaining -= copy_len;
231 }
232
233 remaining_zero: {
234 if (remaining == 0) break :remaining_zero;
235 for (data[0 .. data.len - 1], 0..) |bytes, i| {
236 const copy_len = @min(bytes.len, remaining);
237 if (w.buffer.len - w.end < copy_len) {
238 const n = try writeSplatHeaderLimitFinish(w, &.{}, data[i..], splat, remaining);
239 return @backingInt(limit) - remaining + n;
240 }
241 @memcpy(w.buffer[w.end..][0..copy_len], bytes[0..copy_len]);
242 w.end += copy_len;
243 remaining -= copy_len;
244 }
245
246 if (remaining == 0) break :remaining_zero;
247 const pattern = data[data.len - 1];
248 for (0..splat) |i| {
249 const copy_len = @min(pattern.len, remaining);
250 if (w.buffer.len - w.end < copy_len) {
251 const remaining_splat = splat - i;
252 const n = try writeSplatHeaderLimitFinish(w, &.{}, data[data.len - 1 ..][0..1], remaining_splat, remaining);
253 return @backingInt(limit) - remaining + n;
254 }
255 @memcpy(w.buffer[w.end..][0..copy_len], pattern[0..copy_len]);
256 w.end += copy_len;
257 remaining -= copy_len;
258 }
259 }
260
261 return @backingInt(limit) - remaining;
262}
263
264fn writeSplatHeaderLimitFinish(
265 w: *Writer,
266 header: []const u8,
267 data: []const []const u8,
268 splat: usize,
269 limit: usize,
270) Error!usize {
271 var remaining = limit;
272 var total: usize = 0;
273 var vecs: [8][]const u8 = undefined;
274 var i: usize = 0;
275 if (header.len != 0) {
276 const copy_len = @min(header.len, remaining);
277 vecs[i] = header[0..copy_len];
278 i += 1;
279 remaining -= copy_len;
280 if (remaining == 0) {
281 return w.vtable.drain(w, (&vecs)[0..i], 1);
282 }
283 }
284 for (data[0 .. data.len - 1]) |buf| {
285 if (buf.len == 0) continue;
286 const copy_len = @min(buf.len, remaining);
287 vecs[i] = buf[0..copy_len];
288 i += 1;
289 remaining -= copy_len;
290 if (remaining == 0) {
291 return w.vtable.drain(w, (&vecs)[0..i], 1);
292 }
293 if (i == vecs.len) {
294 total += try w.vtable.drain(w, &vecs, 1);
295 i = 0;
296 }
297 }
298 const pattern = data[data.len - 1];
299 if (splat == 1 or remaining < pattern.len) {
300 vecs[i] = pattern[0..@min(remaining, pattern.len)];
301 i += 1;
302 total += try w.vtable.drain(w, (&vecs)[0..i], 1);
303 return total;
304 }
305 vecs[i] = pattern;
306 i += 1;
307 total += try w.vtable.drain(w, (&vecs)[0..i], @min(remaining / pattern.len, splat));
308 return total;
309}
310
311const SplatHeaderTestCase = struct {
312 writer_type: enum { fixed, allocating },
313 /// When writer_type is .fixed, determines the buffer size.
314 /// When writer_type is .allocating, determines the initial capacity.
315 buf_len: usize = 100,
316 header: []const u8,
317 data: []const []const u8,
318 splat: u8,
319 limit: u8,
320 expected_res: union(enum) { written: usize, write_failed },
321 expected_buf_content: []const u8,
322};
323
324fn testWriteSplatHeaderLimit(comptime test_case: SplatHeaderTestCase) !void {
325 var buf: [test_case.buf_len]u8 = @splat(0);
326 var aw: Allocating = if (test_case.writer_type == .allocating)
327 try Allocating.initCapacity(testing.allocator, test_case.buf_len)
328 else
329 undefined;
330 defer if (test_case.writer_type == .allocating) aw.deinit();
331 var fw: Writer = if (test_case.writer_type == .fixed) .fixed(&buf) else undefined;
332 var w: *Writer = switch (test_case.writer_type) {
333 .allocating => &aw.writer,
334 .fixed => &fw,
335 };
336 const n_or_error = w.writeSplatHeaderLimit(test_case.header, test_case.data, test_case.splat, .limited(test_case.limit));
337 switch (test_case.expected_res) {
338 .written => |expected_len| {
339 const n = try n_or_error;
340 try std.testing.expectEqual(expected_len, n);
341 },
342 .write_failed => {
343 try std.testing.expectError(error.WriteFailed, n_or_error);
344 },
345 }
346 try std.testing.expectEqualStrings(test_case.expected_buf_content, w.buffered());
347}
348
349test "fixed writer writeSplatHeaderLimit" {
350 // fixed writer with buffer larger than the full data size
351 try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "header is longer", .data = &.{""}, .splat = 1, .limit = 6, .expected_res = .{ .written = 6 }, .expected_buf_content = "header" });
352 try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "head", .data = &.{"123456"}, .splat = 1, .limit = 5, .expected_res = .{ .written = 5 }, .expected_buf_content = "head1" });
353 try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "head", .data = &.{"123"}, .splat = 1, .limit = 10, .expected_res = .{ .written = 7 }, .expected_buf_content = "head123" });
354 try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "head", .data = &.{ "1", "abcdefg" }, .splat = 1, .limit = 6, .expected_res = .{ .written = 6 }, .expected_buf_content = "head1a" });
355 try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "head", .data = &.{ "123", "abc" }, .splat = 2, .limit = 6, .expected_res = .{ .written = 6 }, .expected_buf_content = "head12" });
356 try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "head", .data = &.{ "123", "abc" }, .splat = 2, .limit = 11, .expected_res = .{ .written = 11 }, .expected_buf_content = "head123abca" });
357 try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "head", .data = &.{ "123", "a" }, .splat = 2, .limit = 10, .expected_res = .{ .written = 9 }, .expected_buf_content = "head123aa" });
358 try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "head", .data = &.{ "123", "abc" }, .splat = 2, .limit = 100, .expected_res = .{ .written = 13 }, .expected_buf_content = "head123abcabc" });
359
360 // fixed writer with buffer smaller than the full data size
361 try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "header is longer", .data = &.{""}, .splat = 1, .limit = 6, .expected_res = .write_failed, .expected_buf_content = "head", .buf_len = 4 });
362 try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "head", .data = &.{"123456"}, .splat = 1, .limit = 8, .expected_res = .write_failed, .expected_buf_content = "head1", .buf_len = 5 });
363 try testWriteSplatHeaderLimit(.{ .writer_type = .fixed, .header = "head", .data = &.{ "123", "ab" }, .splat = 2, .limit = 100, .expected_res = .write_failed, .expected_buf_content = "head123aba", .buf_len = 10 });
364
365 // allocating writer that needs to expand capacity during splat
366 try testWriteSplatHeaderLimit(.{ .writer_type = .allocating, .buf_len = 8, .header = "hhhh", .data = &.{"PP"}, .splat = 3, .limit = 100, .expected_res = .{ .written = 10 }, .expected_buf_content = "hhhhPPPPPP" });
367 try testWriteSplatHeaderLimit(.{ .writer_type = .allocating, .buf_len = 2, .header = "", .data = &.{ "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "X", "Y", "ZZ" }, .splat = 2, .limit = 100, .expected_res = .{ .written = 16 }, .expected_buf_content = "0123456789XYZZZZ" });
368 try testWriteSplatHeaderLimit(.{ .writer_type = .allocating, .buf_len = 2, .header = "", .data = &.{ "0", "1", "2", "", "", "3", "4" }, .splat = 2, .limit = 4, .expected_res = .{ .written = 4 }, .expected_buf_content = "0123" });
369}
370
371test "writeSplatHeader splatting avoids buffer aliasing temptation" {
372 const initial_buf = try testing.allocator.alloc(u8, 8);
373 var aw: Allocating = .initOwnedSlice(testing.allocator, initial_buf);
374 defer aw.deinit();
375 // This test assumes 8 vector buffer in this function.
376 const n = try aw.writer.writeSplatHeader("header which is longer than buf ", &.{
377 "1", "2", "3", "4", "5", "6", "foo", "bar", "foo",
378 }, 3);
379 try testing.expectEqual(53, n);
380 try testing.expectEqualStrings(
381 "header which is longer than buf 123456foobarfoofoofoo",
382 aw.writer.buffered(),
383 );
384}
385
386/// Drains all remaining buffered data.
387pub fn flush(w: *Writer) Error!void {
388 return w.vtable.flush(w);
389}
390
391/// Repeatedly calls `VTable.drain` until `end` is zero.
392pub fn defaultFlush(w: *Writer) Error!void {
393 const drainFn = w.vtable.drain;
394 while (w.end != 0) _ = try drainFn(w, &.{""}, 1);
395}
396
397/// Does nothing.
398pub fn noopFlush(w: *Writer) Error!void {
399 _ = w;
400}
401
402test "fixed buffer flush" {
403 var buffer: [1]u8 = undefined;
404 var writer: Writer = .fixed(&buffer);
405
406 try writer.writeByte(10);
407 try writer.flush();
408 try testing.expectEqual(10, buffer[0]);
409}
410
411pub fn rebase(w: *Writer, preserve: usize, unused_capacity_len: usize) Error!void {
412 if (w.buffer.len - w.end >= unused_capacity_len) {
413 @branchHint(.likely);
414 return;
415 }
416 return w.vtable.rebase(w, preserve, unused_capacity_len);
417}
418
419pub fn defaultRebase(w: *Writer, preserve: usize, minimum_len: usize) Error!void {
420 while (w.buffer.len - w.end < minimum_len) {
421 {
422 // TODO: instead of this logic that "hides" data from
423 // the implementation, introduce a seek index to Writer
424 const preserved_head = w.end -| preserve;
425 const preserved_tail = w.end;
426 const preserved_len = preserved_tail - preserved_head;
427 w.end = preserved_head;
428 defer w.end += preserved_len;
429 assert(0 == try w.vtable.drain(w, &.{""}, 1));
430 assert(w.end <= preserved_head + preserved_len);
431 @memmove(w.buffer[w.end..][0..preserved_len], w.buffer[preserved_head..preserved_tail]);
432 }
433
434 // If the loop condition was false this assertion would have passed
435 // anyway. Otherwise, give the implementation a chance to grow the
436 // buffer before asserting on the buffer length.
437 assert(w.buffer.len - preserve >= minimum_len);
438 }
439}
440
441pub fn unusedCapacitySlice(w: *const Writer) []u8 {
442 return w.buffer[w.end..];
443}
444
445pub fn unusedCapacityLen(w: *const Writer) usize {
446 return w.buffer.len - w.end;
447}
448
449/// Asserts the provided buffer has total capacity enough for `len`.
450///
451/// Advances the buffer end position by `len`.
452pub fn writableArray(w: *Writer, comptime len: usize) Error!*[len]u8 {
453 const big_slice = try w.writableSliceGreedy(len);
454 advance(w, len);
455 return big_slice[0..len];
456}
457
458/// Asserts the provided buffer has total capacity enough for `len`.
459///
460/// Advances the buffer end position by `len`.
461pub fn writableSlice(w: *Writer, len: usize) Error![]u8 {
462 const big_slice = try w.writableSliceGreedy(len);
463 advance(w, len);
464 return big_slice[0..len];
465}
466
467/// Asserts the provided buffer has total capacity enough for `minimum_len`.
468///
469/// Does not `advance` the buffer end position.
470///
471/// If `minimum_len` is zero, this is equivalent to `unusedCapacitySlice`.
472pub fn writableSliceGreedy(w: *Writer, minimum_len: usize) Error![]u8 {
473 return writableSliceGreedyPreserve(w, 0, minimum_len);
474}
475
476/// Asserts the provided buffer has total capacity enough for `minimum_len`
477/// and `preserve` combined.
478///
479/// Does not `advance` the buffer end position.
480///
481/// When draining the buffer, ensures that at least `preserve` bytes
482/// remain buffered.
483///
484/// If `preserve` is zero, this is equivalent to `writableSliceGreedy`.
485pub fn writableSliceGreedyPreserve(w: *Writer, preserve: usize, minimum_len: usize) Error![]u8 {
486 if (w.buffer.len - w.end >= minimum_len) {
487 @branchHint(.likely);
488 return w.buffer[w.end..];
489 }
490 try w.vtable.rebase(w, preserve, minimum_len);
491 assert(w.buffer.len >= preserve + minimum_len);
492 return w.buffer[w.end..];
493}
494
495/// Asserts the provided buffer has total capacity enough for `len`
496/// and `preserve` combined.
497///
498/// Advances the buffer end position by `len`.
499///
500/// When draining the buffer, ensures that at least `preserve` bytes
501/// remain buffered.
502///
503/// If `preserve` is zero, this is equivalent to `writableSlice`.
504pub fn writableSlicePreserve(w: *Writer, preserve: usize, len: usize) Error![]u8 {
505 const big_slice = try w.writableSliceGreedyPreserve(preserve, len);
506 advance(w, len);
507 return big_slice[0..len];
508}
509
510pub fn ensureUnusedCapacity(w: *Writer, n: usize) Error!void {
511 _ = try writableSliceGreedy(w, n);
512}
513
514pub fn undo(w: *Writer, n: usize) void {
515 w.end -= n;
516}
517
518/// After calling `writableSliceGreedy`, this function tracks how many bytes
519/// were written to it.
520///
521/// This is not needed when using `writableSlice` or `writableArray`.
522pub fn advance(w: *Writer, n: usize) void {
523 const new_end = w.end + n;
524 assert(new_end <= w.buffer.len);
525 w.end = new_end;
526}
527
528/// The `data` parameter is mutable because this function needs to mutate the
529/// fields in order to handle partial writes from `VTable.writeSplat`.
530pub fn writeVecAll(w: *Writer, data: [][]const u8) Error!void {
531 var index: usize = 0;
532 var truncate: usize = 0;
533 while (index < data.len) {
534 {
535 const untruncated = data[index];
536 data[index] = untruncated[truncate..];
537 defer data[index] = untruncated;
538 truncate += try w.writeVec(data[index..]);
539 }
540 while (index < data.len and truncate >= data[index].len) {
541 truncate -= data[index].len;
542 index += 1;
543 }
544 }
545}
546
547/// The `data` parameter is mutable because this function needs to mutate the
548/// fields in order to handle partial writes from `VTable.writeSplat`.
549/// `data` will be restored to its original state before returning.
550pub fn writeSplatAll(w: *Writer, data: [][]const u8, splat: usize) Error!void {
551 var index: usize = 0;
552 var truncate: usize = 0;
553 while (index + 1 < data.len) {
554 {
555 const untruncated = data[index];
556 data[index] = untruncated[truncate..];
557 defer data[index] = untruncated;
558 truncate += try w.writeSplat(data[index..], splat);
559 }
560 while (truncate >= data[index].len and index + 1 < data.len) {
561 truncate -= data[index].len;
562 index += 1;
563 }
564 }
565
566 // Deal with any left over splats
567 if (data.len != 0 and truncate < data[index].len * splat) {
568 assert(index == data.len - 1);
569 var remaining_splat = splat;
570 while (true) {
571 remaining_splat -= truncate / data[index].len;
572 truncate %= data[index].len;
573 if (remaining_splat == 0) break;
574 truncate += try w.writeSplat(&.{ data[index][truncate..], data[index] }, remaining_splat - 1);
575 }
576 }
577}
578
579test writeSplatAll {
580 var aw: Writer.Allocating = .init(testing.allocator);
581 defer aw.deinit();
582
583 var buffers = [_][]const u8{ "ba", "na" };
584 try aw.writer.writeSplatAll(&buffers, 2);
585 try testing.expectEqualStrings("banana", aw.writer.buffered());
586}
587
588test "writeSplatAll works with a single buffer" {
589 var aw: Writer.Allocating = .init(testing.allocator);
590 defer aw.deinit();
591
592 var message: [1][]const u8 = .{"hello"};
593 try aw.writer.writeSplatAll(&message, 3);
594 try testing.expectEqualStrings("hellohellohello", aw.writer.buffered());
595}
596
597/// Transfers `bytes` to the stream, calling `drain` at most once.
598///
599/// Returns the number of bytes transferred, which may be less than
600/// `bytes.len`, including zero.
601///
602/// A return value less than `bytes.len` does not indicate failure; a
603/// subsequent call may return nonzero, or fail with `error.WriteFailed`.
604///
605/// See also:
606/// * `writeAll`
607/// * `writeVec`
608pub fn write(w: *Writer, bytes: []const u8) Error!usize {
609 if (w.end + bytes.len <= w.buffer.len) {
610 @branchHint(.likely);
611 @memcpy(w.buffer[w.end..][0..bytes.len], bytes);
612 w.end += bytes.len;
613 return bytes.len;
614 }
615 return w.vtable.drain(w, &.{bytes}, 1);
616}
617
618/// Transfers `bytes` to the stream, calling `drain` as many times as necessary
619/// such that all `bytes` are transferred.
620///
621/// See also:
622/// * `print`
623/// * `writeVecAll`
624/// * `write`
625pub fn writeAll(w: *Writer, bytes: []const u8) Error!void {
626 var index: usize = 0;
627 while (index < bytes.len) index += try w.write(bytes[index..]);
628}
629
630/// Renders `fmt` string with `args`, calling `w` with slices of bytes.
631///
632/// The format string must be comptime-known and may contain placeholders
633/// following this format:
634/// ```
635/// {[argument][specifier]:[fill][alignment][width].[precision]}
636/// ```
637///
638/// Above, each word including its surrounding [ and ] is a parameter to be replaced with:
639///
640/// - **argument** is either the numeric index or the field name of the argument that should be inserted.
641/// - When using a field name, the field name (an identifier) must be enclosed in square
642/// brackets, e.g. `{[score]...}` as opposed to the numeric index form which can be written e.g. `{2...}`.
643/// - **specifier** is a type-dependent formatting option that determines how a type should formatted (see below).
644/// - **fill** is a single byte which is used to pad formatted numbers.
645/// - **alignment** is one of the three bytes '<', '^', or '>' to make numbers
646/// left, center, or right-aligned, respectively.
647/// - Not all specifiers support alignment.
648/// - Alignment is not Unicode-aware; appropriate only when used with raw
649/// bytes or ASCII.
650/// - **width** is the total size of the field in bytes, only applicable to
651/// number formatting.
652/// - **precision** specifies how many decimals a formatted number should have.
653///
654/// Most of the parameters are optional and may be omitted. The separators (':'
655/// and '.') may be omitted when all parameters afterwards are omitted.
656///
657/// The **fill** parameter is an exception. If a non-zero **fill** character is
658/// required at the same time as **width** is specified, **alignment** is
659/// required, otherwise the digit following ':' is interpreted as **width**.
660///
661/// **specifier** supports:
662/// - "x" and "X": numeric value in hexadecimal notation, or string in hexadecimal bytes
663/// - "s":
664/// - for pointer-to-many and C pointers of u8, print as a C-string using zero-termination
665/// - for slices of u8, print the entire slice as a string without zero-termination
666/// - "t":
667/// - for enums and tagged unions: prints the tag name
668/// - for error sets: prints the error name
669/// - "b64": string as standard base64
670/// - "e": floating point value in scientific notation
671/// - "d": numeric value in decimal notation
672/// - "b": integer value in binary notation
673/// - "o": integer value in octal notation
674/// - "c": integer as an ASCII character. Integer type must have 8 bits at max.
675/// - "u": integer as an UTF-8 sequence. Integer type must have 21 bits at max.
676/// - "B": bytes in SI units (decimal)
677/// - "Bi": bytes in IEC units (binary)
678/// - "?": optional value as either the unwrapped value, or `null`; may be
679/// followed by a format specifier for the underlying value.
680/// - "!": error union value as either the unwrapped value, or the formatted
681/// error value; may be followed by a format specifier for the underlying
682/// value.
683/// - "*": the address of the value instead of the value itself.
684/// - "any": a value of any type using its default format.
685/// - "f": delegates to the `format` method of the type, passing `*Writer` and
686/// expecting `Error!void` returned.
687/// - "q": prints as a double-quote escaped string. Inside the double-quoted
688/// string, everything is passed through unmodified, except for the following
689/// transformations:
690/// - escaped: '\n', '\r', '\t', '\\', '"'
691/// - hex-encoded: ASCII control characters
692/// - "qf": delegates to the `format` method of the type, while double-quote
693/// escaping.
694///
695/// Literal curly braces can be escaped in the format string via doubling, e.g.
696/// "{{" or "}}".
697pub fn print(w: *Writer, comptime fmt: []const u8, args: anytype) Error!void {
698 const ArgsType = @TypeOf(args);
699 const args_type_info = @typeInfo(ArgsType);
700 if (args_type_info != .@"struct") {
701 @compileError("expected tuple or struct argument, found " ++ @typeName(ArgsType));
702 }
703
704 const field_names = args_type_info.@"struct".field_names;
705 const max_format_args = @typeInfo(std.fmt.ArgSetType).int.bits;
706 if (field_names.len > max_format_args) {
707 @compileError("32 arguments max are supported per format call");
708 }
709
710 @setEvalBranchQuota(@as(comptime_int, fmt.len) * 1000); // NOTE: We're upcasting as 16-bit usize overflows.
711 comptime var arg_state: std.fmt.ArgState = .{ .args_len = field_names.len };
712 comptime var i = 0;
713 comptime var literal: []const u8 = "";
714 inline while (true) {
715 const start_index = i;
716
717 inline while (i < fmt.len) : (i += 1) {
718 switch (fmt[i]) {
719 '{', '}' => break,
720 else => {},
721 }
722 }
723
724 comptime var end_index = i;
725 comptime var unescape_brace = false;
726
727 // Handle {{ and }}, those are un-escaped as single braces
728 if (i + 1 < fmt.len and fmt[i + 1] == fmt[i]) {
729 unescape_brace = true;
730 // Make the first brace part of the literal...
731 end_index += 1;
732 // ...and skip both
733 i += 2;
734 }
735
736 literal = literal ++ fmt[start_index..end_index];
737
738 // We've already skipped the other brace, restart the loop
739 if (unescape_brace) continue;
740
741 // Write out the literal
742 if (literal.len != 0) {
743 try w.writeAll(literal);
744 literal = "";
745 }
746
747 if (i >= fmt.len) break;
748
749 if (fmt[i] == '}') {
750 @compileError("missing opening {");
751 }
752
753 // Get past the {
754 comptime assert(fmt[i] == '{');
755 i += 1;
756
757 const fmt_begin = i;
758 // Find the closing brace
759 inline while (i < fmt.len and fmt[i] != '}') : (i += 1) {}
760 const fmt_end = i;
761
762 if (i >= fmt.len) {
763 @compileError("missing closing }");
764 }
765
766 // Get past the }
767 comptime assert(fmt[i] == '}');
768 i += 1;
769
770 const placeholder_array = fmt[fmt_begin..fmt_end].*;
771 const placeholder = comptime std.fmt.Placeholder.parse(&placeholder_array);
772 const arg_pos = comptime switch (placeholder.arg) {
773 .none => null,
774 .number => |pos| pos,
775 .named => |arg_name| std.meta.fieldIndex(ArgsType, arg_name) orelse
776 @compileError("no argument with name '" ++ arg_name ++ "'"),
777 };
778
779 const width = switch (placeholder.width) {
780 .none => null,
781 .number => |v| v,
782 .named => |arg_name| blk: {
783 const arg_i = comptime std.meta.fieldIndex(ArgsType, arg_name) orelse
784 @compileError("no argument with name '" ++ arg_name ++ "'");
785 _ = comptime arg_state.nextArg(arg_i) orelse @compileError("too few arguments");
786 break :blk @field(args, arg_name);
787 },
788 };
789
790 const precision = switch (placeholder.precision) {
791 .none => null,
792 .number => |v| v,
793 .named => |arg_name| blk: {
794 const arg_i = comptime std.meta.fieldIndex(ArgsType, arg_name) orelse
795 @compileError("no argument with name '" ++ arg_name ++ "'");
796 _ = comptime arg_state.nextArg(arg_i) orelse @compileError("too few arguments");
797 break :blk @field(args, arg_name);
798 },
799 };
800
801 const arg_to_print = comptime arg_state.nextArg(arg_pos) orelse
802 @compileError("too few arguments");
803
804 try w.printValue(
805 placeholder.specifier_arg,
806 .{
807 .fill = placeholder.fill,
808 .alignment = placeholder.alignment,
809 .width = width,
810 .precision = precision,
811 },
812 @field(args, field_names[arg_to_print]),
813 std.options.fmt_max_depth,
814 );
815 }
816
817 if (comptime arg_state.hasUnusedArgs()) {
818 const missing_count = arg_state.args_len - @popCount(arg_state.used_args);
819 switch (missing_count) {
820 0 => unreachable,
821 1 => @compileError("unused argument in '" ++ fmt ++ "'"),
822 else => @compileError(std.fmt.comptimePrint("{d}", .{missing_count}) ++ " unused arguments in '" ++ fmt ++ "'"),
823 }
824 }
825}
826
827/// Calls `drain` as many times as necessary such that `byte` is transferred.
828pub fn writeByte(w: *Writer, byte: u8) Error!void {
829 while (w.buffer.len - w.end == 0) {
830 const n = try w.vtable.drain(w, &.{&.{byte}}, 1);
831 if (n > 0) return;
832 } else {
833 @branchHint(.likely);
834 w.buffer[w.end] = byte;
835 w.end += 1;
836 }
837}
838
839/// On success, at least `preserve` bytes will remain buffered if there are
840/// enough buffered bytes to do so.
841/// The amount buffered by the writer after the call will only be less than
842/// `preserve` if `w.end + 1` is less than `preserve` before the call.
843/// The intentionally preserved bytes will include up to `preserve -| 1` bytes from
844/// the previously buffered bytes, plus the newly written byte.
845///
846/// Asserts buffer capacity is at least `preserve`.
847pub fn writeBytePreserve(w: *Writer, preserve: usize, byte: u8) Error!void {
848 if (w.buffer.len - w.end == 0) {
849 @branchHint(.unlikely);
850 try w.vtable.rebase(w, preserve -| 1, 1);
851 }
852 w.buffer[w.end] = byte;
853 w.end += 1;
854}
855
856/// Writes the same byte many times, performing the underlying write call as
857/// many times as necessary.
858pub fn splatByteAll(w: *Writer, byte: u8, n: usize) Error!void {
859 var remaining: usize = n;
860 while (remaining > 0) remaining -= try w.splatByte(byte, remaining);
861}
862
863test splatByteAll {
864 var aw: Writer.Allocating = .init(testing.allocator);
865 defer aw.deinit();
866
867 try aw.writer.splatByteAll('7', 45);
868 try testing.expectEqualStrings(&@as([45]u8, @splat('7')), aw.writer.buffered());
869}
870
871/// Writes the same byte many times, performing the underlying write call as
872/// many times as necessary.
873///
874/// On success, at least `preserve` bytes will remain buffered if there are
875/// enough buffered bytes to do so.
876/// The amount buffered by the writer after the call will only be less than
877/// `preserve` if `w.end + n` is less than `preserve` before the call.
878/// The intentionally preserved bytes will include up to `preserve -| n` bytes from
879/// the previously buffered bytes, plus `@min(n, preserve_len)` of the newly
880/// written bytes.
881///
882/// Asserts buffer capacity is at least `preserve`.
883/// `n` can be greater than the buffer capacity.
884pub fn splatBytePreserve(w: *Writer, preserve: usize, byte: u8, n: usize) Error!void {
885 const new_end = w.end + n;
886 if (new_end <= w.buffer.len) {
887 @memset(w.buffer[w.end..][0..n], byte);
888 w.end = new_end;
889 return;
890 }
891 // If `n` is large, we can ignore `preserve` up to a point.
892 var remaining = n;
893 while (remaining > preserve) {
894 assert(remaining != 0);
895 remaining -= try splatByte(w, byte, remaining - preserve);
896 if (w.end + remaining <= w.buffer.len) {
897 @memset(w.buffer[w.end..][0..remaining], byte);
898 w.end += remaining;
899 return;
900 }
901 }
902 // Ensure the contract of `rebase` is upheld.
903 assert(w.end + remaining > w.buffer.len);
904 // Offset the amount preserved by the amount we have left to splat
905 // since the remaining splat is always going to be part of that
906 // preservation.
907 try w.vtable.rebase(w, preserve -| remaining, remaining);
908 @memset(w.buffer[w.end..][0..remaining], byte);
909 w.end += remaining;
910}
911
912/// Writes the same byte many times, allowing short writes.
913///
914/// Does maximum of one underlying `VTable.drain`.
915pub fn splatByte(w: *Writer, byte: u8, n: usize) Error!usize {
916 if (w.end + n <= w.buffer.len) {
917 @branchHint(.likely);
918 @memset(w.buffer[w.end..][0..n], byte);
919 w.end += n;
920 return n;
921 }
922 return writeSplat(w, &.{&.{byte}}, n);
923}
924
925/// Writes the same slice many times, performing the underlying write call as
926/// many times as necessary.
927pub fn splatBytesAll(w: *Writer, bytes: []const u8, splat: usize) Error!void {
928 var remaining_bytes: usize = bytes.len * splat;
929 remaining_bytes -= try w.splatBytes(bytes, splat);
930 while (remaining_bytes > 0) {
931 const leftover_splat = remaining_bytes / bytes.len;
932 const leftover_bytes = remaining_bytes % bytes.len;
933 const buffers: [2][]const u8 = .{ bytes[bytes.len - leftover_bytes ..], bytes };
934 remaining_bytes -= try w.writeSplat(&buffers, leftover_splat);
935 }
936}
937
938test splatBytesAll {
939 var aw: Writer.Allocating = .init(testing.allocator);
940 defer aw.deinit();
941
942 try aw.writer.splatBytesAll("hello", 3);
943 try testing.expectEqualStrings("hellohellohello", aw.writer.buffered());
944}
945
946/// Writes the same slice many times, allowing short writes.
947///
948/// Does maximum of one underlying `VTable.drain`.
949pub fn splatBytes(w: *Writer, bytes: []const u8, n: usize) Error!usize {
950 return writeSplat(w, &.{bytes}, n);
951}
952
953/// Asserts the `buffer` was initialized with a capacity of at least `@sizeOf(T)` bytes.
954pub inline fn writeInt(w: *Writer, comptime T: type, value: T, endian: std.lang.Endian) Error!void {
955 var bytes: [@divExact(@typeInfo(T).int.bits, 8)]u8 = undefined;
956 std.mem.writeInt(std.math.ByteAlignedInt(@TypeOf(value)), &bytes, value, endian);
957 return w.writeAll(&bytes);
958}
959
960/// The function is inline to avoid the dead code in case `endian` is
961/// comptime-known and matches host endianness.
962pub inline fn writeStruct(w: *Writer, value: anytype, endian: std.lang.Endian) Error!void {
963 switch (@typeInfo(@TypeOf(value))) {
964 .@"struct" => |info| switch (info.layout) {
965 .auto => @compileError("ill-defined memory layout"),
966 .@"extern" => {
967 if (native_endian == endian) {
968 return w.writeAll(@ptrCast((&value)[0..1]));
969 } else {
970 var copy = value;
971 std.mem.byteSwapAllFields(@TypeOf(value), &copy);
972 return w.writeAll(@ptrCast((&copy)[0..1]));
973 }
974 },
975 .@"packed" => {
976 return writeInt(w, info.backing_integer.?, @bitCast(value), endian);
977 },
978 },
979 else => @compileError("not a struct"),
980 }
981}
982
983pub inline fn writeSliceEndian(
984 w: *Writer,
985 Elem: type,
986 slice: []const Elem,
987 endian: std.lang.Endian,
988) Error!void {
989 switch (@typeInfo(Elem)) {
990 .@"struct" => |info| comptime assert(info.layout != .auto),
991 .int, .@"enum" => {},
992 else => @compileError("ill-defined memory layout"),
993 }
994 if (native_endian == endian) {
995 return writeAll(w, @ptrCast(slice));
996 } else {
997 return writeSliceSwap(w, Elem, slice);
998 }
999}
1000
1001pub fn writeSliceSwap(w: *Writer, Elem: type, slice: []const Elem) Error!void {
1002 for (slice) |elem| {
1003 var tmp = elem;
1004 std.mem.byteSwapAllFields(Elem, &tmp);
1005 try w.writeAll(@ptrCast(&tmp));
1006 }
1007}
1008
1009/// Unlike `writeSplat` and `writeVec`, this function will call into `VTable`
1010/// even if there is enough buffer capacity for the file contents.
1011///
1012/// The caller is responsible for flushing. Although the buffer may be bypassed
1013/// as an optimization, this is not a guarantee.
1014///
1015/// Although it would be possible to eliminate `error.Unimplemented` from the
1016/// error set by reading directly into the buffer in such case, this is not
1017/// done because it is more efficient to do it higher up the call stack so that
1018/// the error does not occur with each write.
1019///
1020/// See `sendFileReading` for an alternative that does not have
1021/// `error.Unimplemented` in the error set.
1022pub fn sendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize {
1023 return w.vtable.sendFile(w, file_reader, limit);
1024}
1025
1026/// Returns how many bytes from `header` and `file_reader` were consumed.
1027///
1028/// `limit` only applies to `file_reader`.
1029pub fn sendFileHeader(
1030 w: *Writer,
1031 header: []const u8,
1032 file_reader: *File.Reader,
1033 limit: Limit,
1034) FileError!usize {
1035 const new_end = w.end + header.len;
1036 if (new_end <= w.buffer.len) {
1037 @memcpy(w.buffer[w.end..][0..header.len], header);
1038 w.end = new_end;
1039 const file_bytes = w.vtable.sendFile(w, file_reader, limit) catch |err| switch (err) {
1040 error.ReadFailed, error.WriteFailed => |e| return e,
1041 error.EndOfStream, error.Unimplemented => |e| {
1042 // These errors are non-fatal, so if we wrote any header bytes, we will report that
1043 // and suppress this error. Only if there was no header may we return the error.
1044 if (header.len != 0) return header.len;
1045 return e;
1046 },
1047 };
1048 return header.len + file_bytes;
1049 }
1050 const buffered_contents = limit.slice(file_reader.interface.buffered());
1051 const n = try w.vtable.drain(w, &.{ header, buffered_contents }, 1);
1052 file_reader.interface.toss(n -| header.len);
1053 return n;
1054}
1055
1056/// Asserts nonzero buffer capacity and nonzero `limit`.
1057pub fn sendFileReading(w: *Writer, file_reader: *File.Reader, limit: Limit) FileReadingError!usize {
1058 assert(limit != .nothing);
1059 const dest = limit.slice(try w.writableSliceGreedy(1));
1060 const n = try file_reader.interface.readSliceShort(dest);
1061 if (n == 0) return error.EndOfStream;
1062 w.advance(n);
1063 return n;
1064}
1065
1066/// Number of bytes logically written is returned. This excludes bytes from
1067/// `buffer` because they have already been logically written.
1068///
1069/// The caller is responsible for flushing. Although the buffer may be bypassed
1070/// as an optimization, this is not a guarantee.
1071///
1072/// Asserts nonzero buffer capacity.
1073pub fn sendFileAll(w: *Writer, file_reader: *File.Reader, limit: Limit) FileAllError!usize {
1074 // The fallback sendFileReadingAll() path asserts non-zero buffer capacity.
1075 // Explicitly assert it here as well to ensure the assert is hit even if
1076 // the fallback path is not taken.
1077 assert(w.buffer.len > 0);
1078 var remaining = @backingInt(limit);
1079 while (remaining > 0) {
1080 const n = sendFile(w, file_reader, .limited(remaining)) catch |err| switch (err) {
1081 error.EndOfStream => break,
1082 error.Unimplemented => {
1083 file_reader.mode = file_reader.mode.toSimple();
1084 remaining -= try w.sendFileReadingAll(file_reader, .limited(remaining));
1085 break;
1086 },
1087 else => |e| return e,
1088 };
1089 remaining -= n;
1090 }
1091 return @backingInt(limit) - remaining;
1092}
1093
1094/// Equivalent to `sendFileAll` but uses direct `pread` and `read` calls on
1095/// `file` rather than `sendFile`. This is generally used as a fallback when
1096/// the underlying implementation returns `error.Unimplemented`, which is why
1097/// that error code does not appear in this function's error set.
1098///
1099/// Asserts nonzero buffer capacity.
1100pub fn sendFileReadingAll(w: *Writer, file_reader: *File.Reader, limit: Limit) FileAllError!usize {
1101 var remaining = @backingInt(limit);
1102 while (remaining > 0) {
1103 remaining -= sendFileReading(w, file_reader, .limited(remaining)) catch |err| switch (err) {
1104 error.EndOfStream => break,
1105 else => |e| return e,
1106 };
1107 }
1108 return @backingInt(limit) - remaining;
1109}
1110
1111pub fn alignBuffer(
1112 w: *Writer,
1113 buffer: []const u8,
1114 width: usize,
1115 alignment: std.fmt.Alignment,
1116 fill: u8,
1117) Error!void {
1118 const padding = if (buffer.len < width) width - buffer.len else 0;
1119 if (padding == 0) {
1120 @branchHint(.likely);
1121 return w.writeAll(buffer);
1122 }
1123 switch (alignment) {
1124 .left => {
1125 try w.writeAll(buffer);
1126 try w.splatByteAll(fill, padding);
1127 },
1128 .center => {
1129 const left_padding = padding / 2;
1130 const right_padding = (padding + 1) / 2;
1131 try w.splatByteAll(fill, left_padding);
1132 try w.writeAll(buffer);
1133 try w.splatByteAll(fill, right_padding);
1134 },
1135 .right => {
1136 try w.splatByteAll(fill, padding);
1137 try w.writeAll(buffer);
1138 },
1139 }
1140}
1141
1142pub fn alignBufferOptions(w: *Writer, buffer: []const u8, options: std.fmt.Options) Error!void {
1143 return w.alignBuffer(buffer, options.width orelse buffer.len, options.alignment, options.fill);
1144}
1145
1146pub fn printAddress(w: *Writer, value: anytype) Error!void {
1147 const T = @TypeOf(value);
1148 switch (@typeInfo(T)) {
1149 .pointer => |info| {
1150 try w.writeAll(@typeName(info.child) ++ "@");
1151 const int = if (info.size == .slice) @intFromPtr(value.ptr) else @intFromPtr(value);
1152 return w.printInt(int, 16, .lower, .{});
1153 },
1154 .optional => |info| {
1155 if (@typeInfo(info.child) == .pointer) {
1156 try w.writeAll(@typeName(info.child) ++ "@");
1157 try w.printInt(@intFromPtr(value), 16, .lower, .{});
1158 return;
1159 }
1160 },
1161 else => {},
1162 }
1163
1164 @compileError("cannot format non-pointer type " ++ @typeName(T) ++ " with * specifier");
1165}
1166
1167/// Asserts `buffer` capacity of at least 2 if `value` is a union.
1168pub fn printValue(
1169 w: *Writer,
1170 comptime fmt: []const u8,
1171 options: std.fmt.Options,
1172 value: anytype,
1173 max_depth: usize,
1174) Error!void {
1175 const T = @TypeOf(value);
1176
1177 switch (fmt.len) {
1178 1 => switch (fmt[0]) {
1179 '*' => return w.printAddress(value),
1180 'f' => return value.format(w),
1181 'd' => switch (@typeInfo(T)) {
1182 .float, .comptime_float => return printFloat(w, value, options.toNumber(.decimal, .lower)),
1183 .int, .comptime_int => return printInt(w, value, 10, .lower, options),
1184 .@"struct" => return value.formatNumber(w, options.toNumber(.decimal, .lower)),
1185 .@"enum" => return printInt(w, @backingInt(value), 10, .lower, options),
1186 .vector => return printVector(w, fmt, options, value, max_depth),
1187 else => invalidFmtError(fmt, value),
1188 },
1189 'c' => return w.printAsciiChar(value, options),
1190 'u' => return w.printUnicodeCodepoint(value),
1191 'b' => switch (@typeInfo(T)) {
1192 .int, .comptime_int => return printInt(w, value, 2, .lower, options),
1193 .@"enum" => return printInt(w, @backingInt(value), 2, .lower, options),
1194 .@"struct" => return value.formatNumber(w, options.toNumber(.binary, .lower)),
1195 .vector => return printVector(w, fmt, options, value, max_depth),
1196 else => invalidFmtError(fmt, value),
1197 },
1198 'o' => switch (@typeInfo(T)) {
1199 .int, .comptime_int => return printInt(w, value, 8, .lower, options),
1200 .@"enum" => return printInt(w, @backingInt(value), 8, .lower, options),
1201 .@"struct" => return value.formatNumber(w, options.toNumber(.octal, .lower)),
1202 .vector => return printVector(w, fmt, options, value, max_depth),
1203 else => invalidFmtError(fmt, value),
1204 },
1205 'x' => switch (@typeInfo(T)) {
1206 .float, .comptime_float => return printFloatHexOptions(w, value, options.toNumber(.hex, .lower)),
1207 .int, .comptime_int => return printInt(w, value, 16, .lower, options),
1208 .@"enum" => return printInt(w, @backingInt(value), 16, .lower, options),
1209 .@"struct" => return value.formatNumber(w, options.toNumber(.hex, .lower)),
1210 .pointer => |info| switch (info.size) {
1211 .one, .slice => {
1212 const slice: []const u8 = value;
1213 optionsForbidden(options);
1214 return printHex(w, slice, .lower);
1215 },
1216 .many, .c => {
1217 const slice: [:0]const u8 = std.mem.span(value);
1218 optionsForbidden(options);
1219 return printHex(w, slice, .lower);
1220 },
1221 },
1222 .array => {
1223 const slice: []const u8 = &value;
1224 optionsForbidden(options);
1225 return printHex(w, slice, .lower);
1226 },
1227 .vector => return printVector(w, fmt, options, value, max_depth),
1228 else => invalidFmtError(fmt, value),
1229 },
1230 'X' => switch (@typeInfo(T)) {
1231 .float, .comptime_float => return printFloatHexOptions(w, value, options.toNumber(.hex, .upper)),
1232 .int, .comptime_int => return printInt(w, value, 16, .upper, options),
1233 .@"enum" => return printInt(w, @backingInt(value), 16, .upper, options),
1234 .@"struct" => return value.formatNumber(w, options.toNumber(.hex, .upper)),
1235 .pointer => |info| switch (info.size) {
1236 .one, .slice => {
1237 const slice: []const u8 = value;
1238 optionsForbidden(options);
1239 return printHex(w, slice, .upper);
1240 },
1241 .many, .c => {
1242 const slice: [:0]const u8 = std.mem.span(value);
1243 optionsForbidden(options);
1244 return printHex(w, slice, .upper);
1245 },
1246 },
1247 .array => {
1248 const slice: []const u8 = &value;
1249 optionsForbidden(options);
1250 return printHex(w, slice, .upper);
1251 },
1252 .vector => return printVector(w, fmt, options, value, max_depth),
1253 else => invalidFmtError(fmt, value),
1254 },
1255 's' => switch (@typeInfo(T)) {
1256 .pointer => |info| switch (info.size) {
1257 .one, .slice => {
1258 const slice: []const u8 = value;
1259 return w.alignBufferOptions(slice, options);
1260 },
1261 .many, .c => {
1262 const slice: [:0]const u8 = std.mem.span(value);
1263 return w.alignBufferOptions(slice, options);
1264 },
1265 },
1266 .array => {
1267 const slice: []const u8 = &value;
1268 return w.alignBufferOptions(slice, options);
1269 },
1270 else => invalidFmtError(fmt, value),
1271 },
1272 'q' => switch (@typeInfo(T)) {
1273 .pointer => |info| switch (info.size) {
1274 .one, .slice => return printStringEscaped(w, value),
1275 .many, .c => return printStringEscaped(w, std.mem.span(value)),
1276 },
1277 .array => return printStringEscaped(w, &value),
1278 else => invalidFmtError(fmt, value),
1279 },
1280 'B' => switch (@typeInfo(T)) {
1281 .int, .comptime_int => return w.printByteSize(value, .decimal, options),
1282 .@"struct" => return value.formatByteSize(w, .decimal),
1283 else => invalidFmtError(fmt, value),
1284 },
1285 'e' => switch (@typeInfo(T)) {
1286 .float, .comptime_float => return printFloat(w, value, options.toNumber(.scientific, .lower)),
1287 .@"struct" => return value.formatNumber(w, options.toNumber(.scientific, .lower)),
1288 else => invalidFmtError(fmt, value),
1289 },
1290 'E' => switch (@typeInfo(T)) {
1291 .float, .comptime_float => return printFloat(w, value, options.toNumber(.scientific, .upper)),
1292 .@"struct" => return value.formatNumber(w, options.toNumber(.scientific, .upper)),
1293 else => invalidFmtError(fmt, value),
1294 },
1295 't' => switch (@typeInfo(T)) {
1296 .error_set => return w.alignBufferOptions(@errorName(value), options),
1297 .@"enum", .enum_literal, .@"union" => return w.alignBufferOptions(@tagName(value), options),
1298 else => invalidFmtError(fmt, value),
1299 },
1300 else => {},
1301 },
1302 2 => switch (fmt[0]) {
1303 'B' => switch (fmt[1]) {
1304 'i' => switch (@typeInfo(T)) {
1305 .int, .comptime_int => return w.printByteSize(value, .binary, options),
1306 .@"struct" => return value.formatByteSize(w, .binary),
1307 else => invalidFmtError(fmt, value),
1308 },
1309 else => {},
1310 },
1311 'q' => switch (fmt[1]) {
1312 'f' => {
1313 try w.writeByte('"');
1314 var buffer: [64]u8 = undefined;
1315 var escaping_writer: std.zig.StringEscapeWriter = .init(w, &buffer);
1316 try value.format(&escaping_writer.writer);
1317 try escaping_writer.writer.flush();
1318 try w.writeByte('"');
1319 return;
1320 },
1321 else => {},
1322 },
1323 else => {},
1324 },
1325 3 => if (fmt[0] == 'b' and fmt[1] == '6' and fmt[2] == '4') switch (@typeInfo(T)) {
1326 .pointer => |info| switch (info.size) {
1327 .one, .slice => {
1328 const slice: []const u8 = value;
1329 optionsForbidden(options);
1330 return w.printBase64(slice);
1331 },
1332 .many, .c => {
1333 const slice: [:0]const u8 = std.mem.span(value);
1334 optionsForbidden(options);
1335 return w.printBase64(slice);
1336 },
1337 },
1338 .array => {
1339 const slice: []const u8 = &value;
1340 optionsForbidden(options);
1341 return w.printBase64(slice);
1342 },
1343 else => invalidFmtError(fmt, value),
1344 },
1345 else => {},
1346 }
1347
1348 const is_any = comptime std.mem.eql(u8, fmt, ANY);
1349
1350 switch (@typeInfo(T)) {
1351 .float, .comptime_float => {
1352 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1353 return printFloat(w, value, options.toNumber(.decimal, .lower));
1354 },
1355 .int, .comptime_int => {
1356 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1357 return printInt(w, value, 10, .lower, options);
1358 },
1359 .bool => {
1360 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1361 const string: []const u8 = if (value) "true" else "false";
1362 return w.alignBufferOptions(string, options);
1363 },
1364 .void => {
1365 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1366 return w.alignBufferOptions("void", options);
1367 },
1368 .optional => {
1369 const remaining_fmt = comptime if (fmt.len > 0 and fmt[0] == '?')
1370 stripOptionalOrErrorUnionSpec(fmt)
1371 else if (is_any)
1372 ANY
1373 else
1374 @compileError("cannot print optional without a specifier (i.e. {?} or {any})");
1375 if (value) |payload| {
1376 return w.printValue(remaining_fmt, options, payload, max_depth);
1377 } else {
1378 return w.alignBufferOptions("null", options);
1379 }
1380 },
1381 .error_union => {
1382 const remaining_fmt = comptime if (fmt.len > 0 and fmt[0] == '!')
1383 stripOptionalOrErrorUnionSpec(fmt)
1384 else if (is_any)
1385 ANY
1386 else
1387 @compileError("cannot print error union without a specifier (i.e. {!} or {any})");
1388 if (value) |payload| {
1389 return w.printValue(remaining_fmt, options, payload, max_depth);
1390 } else |err| {
1391 return w.printValue("", options, err, max_depth);
1392 }
1393 },
1394 .error_set => {
1395 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1396 optionsForbidden(options);
1397 return printErrorSet(w, value);
1398 },
1399 .@"enum" => |info| {
1400 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1401 optionsForbidden(options);
1402 if (info.mode == .exhaustive) {
1403 return printEnumExhaustive(w, value);
1404 } else {
1405 return printEnumNonexhaustive(w, value);
1406 }
1407 },
1408 .@"union" => |info| {
1409 if (!is_any) {
1410 if (fmt.len != 0) invalidFmtError(fmt, value);
1411 return printValue(w, ANY, options, value, max_depth);
1412 }
1413 if (max_depth == 0) {
1414 try w.writeAll(".{ ... }");
1415 return;
1416 }
1417 if (info.tag_type) |UnionTagType| {
1418 try w.writeAll(".{ .");
1419 try w.writeAll(@tagName(@as(UnionTagType, value)));
1420 try w.writeAll(" = ");
1421 inline for (info.field_names) |u_field_name| {
1422 if (value == @field(UnionTagType, u_field_name)) {
1423 try w.printValue(ANY, options, @field(value, u_field_name), max_depth - 1);
1424 }
1425 }
1426 try w.writeAll(" }");
1427 } else switch (info.layout) {
1428 .auto => {
1429 return w.writeAll(".{ ... }");
1430 },
1431 .@"extern", .@"packed" => {
1432 if (info.field_names.len == 0) return w.writeAll(".{}");
1433 try w.writeAll(".{ ");
1434 inline for (info.field_names, 1..) |field_name, i| {
1435 try w.writeByte('.');
1436 try w.writeAll(field_name);
1437 try w.writeAll(" = ");
1438 try w.printValue(ANY, options, @field(value, field_name), max_depth - 1);
1439 try w.writeAll(if (i < info.field_names.len) ", " else " }");
1440 }
1441 },
1442 }
1443 },
1444 .@"struct" => |info| {
1445 if (!is_any) {
1446 if (fmt.len != 0) invalidFmtError(fmt, value);
1447 return printValue(w, ANY, options, value, max_depth);
1448 }
1449 if (info.is_tuple) {
1450 // Skip the type and field names when formatting tuples.
1451 if (max_depth == 0) {
1452 try w.writeAll(".{ ... }");
1453 return;
1454 }
1455 try w.writeAll(".{");
1456 inline for (info.field_names, 0..) |f_name, i| {
1457 if (i == 0) {
1458 try w.writeAll(" ");
1459 } else {
1460 try w.writeAll(", ");
1461 }
1462 try w.printValue(ANY, options, @field(value, f_name), max_depth - 1);
1463 }
1464 try w.writeAll(" }");
1465 return;
1466 }
1467 if (max_depth == 0) {
1468 try w.writeAll(".{ ... }");
1469 return;
1470 }
1471 try w.writeAll(".{");
1472 inline for (info.field_names, 0..) |f_name, i| {
1473 if (i == 0) {
1474 try w.writeAll(" .");
1475 } else {
1476 try w.writeAll(", .");
1477 }
1478 try w.writeAll(f_name);
1479 try w.writeAll(" = ");
1480 try w.printValue(ANY, options, @field(value, f_name), max_depth - 1);
1481 }
1482 try w.writeAll(" }");
1483 },
1484 .pointer => |ptr_info| switch (ptr_info.size) {
1485 .one => switch (@typeInfo(ptr_info.child)) {
1486 .array => |array_info| return w.printValue(fmt, options, @as([]const array_info.child, value), max_depth),
1487 .@"enum", .@"union", .@"struct" => return w.printValue(fmt, options, value.*, max_depth),
1488 else => {
1489 var buffers: [2][]const u8 = .{ @typeName(ptr_info.child), "@" };
1490 try w.writeVecAll(&buffers);
1491 try w.printInt(@intFromPtr(value), 16, .lower, options);
1492 return;
1493 },
1494 },
1495 .many, .c => {
1496 if (!is_any) @compileError("cannot format pointer without a specifier (i.e. {s} or {*})");
1497 optionsForbidden(options);
1498 try w.printAddress(value);
1499 },
1500 .slice => {
1501 if (!is_any)
1502 @compileError("cannot format slice without a specifier (i.e. {s}, {x}, {b64}, or {any})");
1503 if (max_depth == 0) return w.writeAll("{ ... }");
1504 try w.writeAll("{ ");
1505 for (value, 0..) |elem, i| {
1506 try w.printValue(fmt, options, elem, max_depth - 1);
1507 if (i != value.len - 1) {
1508 try w.writeAll(", ");
1509 }
1510 }
1511 try w.writeAll(" }");
1512 },
1513 },
1514 .array => {
1515 if (!is_any) @compileError("cannot format array without a specifier (i.e. {s} or {any})");
1516 return printArray(w, fmt, options, &value, max_depth);
1517 },
1518 .vector => |vector| {
1519 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1520 const array: [vector.len]vector.child = value;
1521 return printArray(w, fmt, options, &array, max_depth);
1522 },
1523 .@"fn" => @compileError("unable to format function body type, use '*const " ++ @typeName(T) ++ "' for a function pointer type"),
1524 .type => {
1525 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1526 return w.alignBufferOptions(@typeName(value), options);
1527 },
1528 .enum_literal => {
1529 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1530 optionsForbidden(options);
1531 var vecs: [2][]const u8 = .{ ".", @tagName(value) };
1532 return w.writeVecAll(&vecs);
1533 },
1534 .null => {
1535 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1536 return w.alignBufferOptions("null", options);
1537 },
1538 else => @compileError("unable to format type '" ++ @typeName(T) ++ "'"),
1539 }
1540}
1541
1542fn optionsForbidden(options: std.fmt.Options) void {
1543 assert(options.precision == null);
1544 assert(options.width == null);
1545}
1546
1547fn printErrorSet(w: *Writer, error_set: anyerror) Error!void {
1548 var vecs: [2][]const u8 = .{ "error.", @errorName(error_set) };
1549 try w.writeVecAll(&vecs);
1550}
1551
1552fn printEnumExhaustive(w: *Writer, value: anytype) Error!void {
1553 var vecs: [2][]const u8 = .{ ".", @tagName(value) };
1554 try w.writeVecAll(&vecs);
1555}
1556
1557fn printEnumNonexhaustive(w: *Writer, value: anytype) Error!void {
1558 if (std.enums.tagName(@TypeOf(value), value)) |tag_name| {
1559 var vecs: [2][]const u8 = .{ ".", tag_name };
1560 try w.writeVecAll(&vecs);
1561 return;
1562 }
1563 try w.writeAll("@enumFromInt(");
1564 try w.printInt(@backingInt(value), 10, .lower, .{});
1565 try w.writeByte(')');
1566}
1567
1568/// Prints a double quote, then escapes a string according to Zig string
1569/// literal rules, then a double quote.
1570pub fn printStringEscaped(w: *Writer, bytes: []const u8) Error!void {
1571 try w.writeByte('"');
1572 try std.zig.stringEscape(bytes, w);
1573 try w.writeByte('"');
1574}
1575
1576pub fn printVector(
1577 w: *Writer,
1578 comptime fmt: []const u8,
1579 options: std.fmt.Options,
1580 value: anytype,
1581 max_depth: usize,
1582) Error!void {
1583 const vector = @typeInfo(@TypeOf(value)).vector;
1584 const array: [vector.len]vector.child = value;
1585 return printArray(w, fmt, options, &array, max_depth);
1586}
1587
1588pub fn printArray(
1589 w: *Writer,
1590 comptime fmt: []const u8,
1591 options: std.fmt.Options,
1592 ptr_to_array: anytype,
1593 max_depth: usize,
1594) Error!void {
1595 if (max_depth == 0) return w.writeAll("{ ... }");
1596 try w.writeAll("{ ");
1597 for (ptr_to_array, 0..) |elem, i| {
1598 try w.printValue(fmt, options, elem, max_depth - 1);
1599 if (i < ptr_to_array.len - 1) {
1600 try w.writeAll(", ");
1601 }
1602 }
1603 try w.writeAll(" }");
1604}
1605
1606// A wrapper around `printIntAny` to avoid the generic explosion of this
1607// function by funneling smaller integer types through `isize` and `usize`.
1608pub inline fn printInt(
1609 w: *Writer,
1610 value: anytype,
1611 base: u8,
1612 case: std.fmt.Case,
1613 options: std.fmt.Options,
1614) Error!void {
1615 switch (@TypeOf(value)) {
1616 isize, usize => {},
1617 comptime_int => {
1618 if (comptime std.math.cast(usize, value)) |x| return printIntAny(w, x, base, case, options);
1619 if (comptime std.math.cast(isize, value)) |x| return printIntAny(w, x, base, case, options);
1620 const Int = std.math.IntFittingRange(value, value);
1621 return printIntAny(w, @as(Int, value), base, case, options);
1622 },
1623 else => switch (@typeInfo(@TypeOf(value)).int.signedness) {
1624 .signed => if (std.math.cast(isize, value)) |x| return printIntAny(w, x, base, case, options),
1625 .unsigned => if (std.math.cast(usize, value)) |x| return printIntAny(w, x, base, case, options),
1626 },
1627 }
1628 return printIntAny(w, value, base, case, options);
1629}
1630
1631/// In general, prefer `printInt` to avoid generic explosion. However this
1632/// function may be used when optimal codegen for a particular integer type is
1633/// desired.
1634pub fn printIntAny(
1635 w: *Writer,
1636 value: anytype,
1637 base: u8,
1638 case: std.fmt.Case,
1639 options: std.fmt.Options,
1640) Error!void {
1641 assert(base >= 2);
1642 const value_info = @typeInfo(@TypeOf(value)).int;
1643
1644 // The type must have the same size as `base` or be wider in order for the
1645 // division to work
1646 const min_int_bits = comptime @max(value_info.bits, 8);
1647 const MinInt = @Int(.unsigned, min_int_bits);
1648
1649 const abs_value = @abs(value);
1650 // The worst case in terms of space needed is base 2, plus 1 for the sign
1651 var buf: [1 + @max(@as(comptime_int, value_info.bits), 1)]u8 = undefined;
1652
1653 var a: MinInt = abs_value;
1654 var index: usize = buf.len;
1655
1656 if (base == 10) {
1657 while (a >= 100) : (a = @divTrunc(a, 100)) {
1658 index -= 2;
1659 buf[index..][0..2].* = std.fmt.digits2(@intCast(a % 100));
1660 }
1661
1662 if (a < 10) {
1663 index -= 1;
1664 buf[index] = '0' + @as(u8, @intCast(a));
1665 } else {
1666 index -= 2;
1667 buf[index..][0..2].* = std.fmt.digits2(@intCast(a));
1668 }
1669 } else {
1670 while (true) {
1671 const digit = a % base;
1672 index -= 1;
1673 buf[index] = std.fmt.digitToChar(@intCast(digit), case);
1674 a /= base;
1675 if (a == 0) break;
1676 }
1677 }
1678
1679 if (value_info.signedness == .signed) {
1680 if (value < 0) {
1681 // Negative integer
1682 index -= 1;
1683 buf[index] = '-';
1684 } else if (options.width == null or options.width.? == 0) {
1685 // Positive integer, omit the plus sign
1686 } else {
1687 // Positive integer
1688 index -= 1;
1689 buf[index] = '+';
1690 }
1691 }
1692
1693 return w.alignBufferOptions(buf[index..], options);
1694}
1695
1696pub fn printAsciiChar(w: *Writer, c: u8, options: std.fmt.Options) Error!void {
1697 return w.alignBufferOptions(@as(*const [1]u8, &c), options);
1698}
1699
1700pub fn printAscii(w: *Writer, bytes: []const u8, options: std.fmt.Options) Error!void {
1701 return w.alignBufferOptions(bytes, options);
1702}
1703
1704pub fn printUnicodeCodepoint(w: *Writer, c: u21) Error!void {
1705 var buf: [4]u8 = undefined;
1706 const len = std.unicode.utf8Encode(c, &buf) catch |err| switch (err) {
1707 error.Utf8CannotEncodeSurrogateHalf, error.CodepointTooLarge => l: {
1708 buf[0..3].* = std.unicode.replacement_character_utf8;
1709 break :l 3;
1710 },
1711 };
1712 return w.writeAll(buf[0..len]);
1713}
1714
1715/// Uses a larger stack buffer; asserts mode is decimal or scientific.
1716pub fn printFloat(w: *Writer, value: anytype, options: std.fmt.Number) Error!void {
1717 const mode: std.fmt.float.Mode = switch (options.mode) {
1718 .decimal => .decimal,
1719 .scientific => .scientific,
1720 .binary, .octal, .hex => unreachable,
1721 };
1722 var buf: [std.fmt.float.bufferSize(.decimal, f64)]u8 = undefined;
1723 const s = std.fmt.float.render(&buf, value, .{
1724 .mode = mode,
1725 .precision = options.precision,
1726 }) catch |err| switch (err) {
1727 error.BufferTooSmall => "(float)",
1728 };
1729 return w.alignBuffer(s, options.width orelse s.len, options.alignment, options.fill);
1730}
1731
1732/// Uses a smaller stack buffer; asserts mode is not decimal or scientific.
1733pub fn printFloatHexOptions(w: *Writer, value: anytype, options: std.fmt.Number) Error!void {
1734 var buf: [50]u8 = undefined; // for aligning
1735 var sub_writer: Writer = .fixed(&buf);
1736 switch (options.mode) {
1737 .decimal => unreachable,
1738 .scientific => unreachable,
1739 .binary => @panic("TODO"),
1740 .octal => @panic("TODO"),
1741 .hex => {},
1742 }
1743 printFloatHex(&sub_writer, value, options.case, options.precision) catch unreachable; // buf is large enough
1744
1745 const printed = sub_writer.buffered();
1746 return w.alignBuffer(printed, options.width orelse printed.len, options.alignment, options.fill);
1747}
1748
1749pub fn printFloatHex(w: *Writer, value: anytype, case: std.fmt.Case, opt_precision: ?usize) Error!void {
1750 const v = switch (@TypeOf(value)) {
1751 // comptime_float internally is a f128; this preserves precision.
1752 comptime_float => @as(f128, value),
1753 else => value,
1754 };
1755
1756 if (std.math.signbit(v)) try w.writeByte('-');
1757 if (std.math.isNan(v)) return w.writeAll(switch (case) {
1758 .lower => "nan",
1759 .upper => "NAN",
1760 });
1761 if (std.math.isInf(v)) return w.writeAll(switch (case) {
1762 .lower => "inf",
1763 .upper => "INF",
1764 });
1765
1766 const T = @TypeOf(v);
1767 const TU = @Int(.unsigned, @bitSizeOf(T));
1768
1769 const mantissa_bits = std.math.floatMantissaBits(T);
1770 const fractional_bits = std.math.floatFractionalBits(T);
1771 const exponent_bits = std.math.floatExponentBits(T);
1772 const mantissa_mask = (1 << mantissa_bits) - 1;
1773 const exponent_mask = (1 << exponent_bits) - 1;
1774 const exponent_bias = (1 << (exponent_bits - 1)) - 1;
1775
1776 const as_bits: TU = @bitCast(v);
1777 var mantissa = as_bits & mantissa_mask;
1778 var exponent: i32 = @as(u16, @truncate((as_bits >> mantissa_bits) & exponent_mask));
1779
1780 const is_denormal = exponent == 0 and mantissa != 0;
1781 const is_zero = exponent == 0 and mantissa == 0;
1782
1783 if (is_zero) {
1784 // Handle this case here to simplify the logic below.
1785 try w.writeAll("0x0");
1786 if (opt_precision) |precision| {
1787 if (precision > 0) {
1788 try w.writeAll(".");
1789 try w.splatByteAll('0', precision);
1790 }
1791 } else {
1792 try w.writeAll(".0");
1793 }
1794 try w.writeAll("p0");
1795 return;
1796 }
1797
1798 if (is_denormal) {
1799 // Adjust the exponent for printing.
1800 exponent += 1;
1801 } else {
1802 if (fractional_bits == mantissa_bits)
1803 mantissa |= 1 << fractional_bits; // Add the implicit integer bit.
1804 }
1805
1806 const mantissa_digits = (fractional_bits + 3) / 4;
1807 // Fill in zeroes to round the fraction width to a multiple of 4.
1808 mantissa <<= mantissa_digits * 4 - fractional_bits;
1809
1810 if (opt_precision) |precision| {
1811 // Round if needed.
1812 if (precision < mantissa_digits) {
1813 // We always have at least 4 extra bits.
1814 var extra_bits = (mantissa_digits - precision) * 4;
1815 // The result LSB is the Guard bit, we need two more (Round and
1816 // Sticky) to round the value.
1817 while (extra_bits > 2) {
1818 mantissa = (mantissa >> 1) | (mantissa & 1);
1819 extra_bits -= 1;
1820 }
1821 // Round to nearest, tie to even.
1822 mantissa |= @intFromBool(mantissa & 0b100 != 0);
1823 mantissa += 1;
1824 // Drop the excess bits.
1825 mantissa >>= 2;
1826 // Restore the alignment.
1827 mantissa <<= @as(std.math.Log2Int(TU), @intCast((mantissa_digits - precision) * 4));
1828
1829 const overflow = mantissa & (1 << 1 + mantissa_digits * 4) != 0;
1830 // Prefer a normalized result in case of overflow.
1831 if (overflow) {
1832 mantissa >>= 1;
1833 exponent += 1;
1834 }
1835 }
1836 }
1837
1838 // +1 for the decimal part.
1839 var buf: [1 + mantissa_digits]u8 = undefined;
1840 assert(std.fmt.printInt(&buf, mantissa, 16, case, .{ .fill = '0', .width = 1 + mantissa_digits }) == buf.len);
1841
1842 try w.writeAll("0x");
1843 try w.writeByte(buf[0]);
1844 const trimmed = std.mem.trimEnd(u8, buf[1..], "0");
1845 if (opt_precision) |precision| {
1846 if (precision > 0) try w.writeAll(".");
1847 } else if (trimmed.len > 0) {
1848 try w.writeAll(".");
1849 }
1850 try w.writeAll(trimmed);
1851 // Add trailing zeros if explicitly requested.
1852 if (opt_precision) |precision| if (precision > 0) {
1853 if (precision > trimmed.len)
1854 try w.splatByteAll('0', precision - trimmed.len);
1855 };
1856 try w.writeAll("p");
1857 try w.printInt(exponent - exponent_bias, 10, case, .{});
1858}
1859
1860pub const ByteSizeUnits = enum {
1861 /// This formatter represents the number as multiple of 1000 and uses the SI
1862 /// measurement units (kB, MB, GB, ...).
1863 decimal,
1864 /// This formatter represents the number as multiple of 1024 and uses the IEC
1865 /// measurement units (KiB, MiB, GiB, ...).
1866 binary,
1867};
1868
1869/// Format option `precision` is ignored when `value` is less than 1kB
1870pub fn printByteSize(
1871 w: *Writer,
1872 value: u64,
1873 comptime units: ByteSizeUnits,
1874 options: std.fmt.Options,
1875) Error!void {
1876 if (value == 0) return w.alignBufferOptions("0B", options);
1877 // The worst case in terms of space needed is 32 bytes + 3 for the suffix.
1878 var buf: [std.fmt.float.min_buffer_size + 3]u8 = undefined;
1879
1880 const mags_si = " kMGTPEZY";
1881 const mags_iec = " KMGTPEZY";
1882
1883 const log2 = std.math.log2(value);
1884 const base = switch (units) {
1885 .decimal => 1000,
1886 .binary => 1024,
1887 };
1888 const magnitude = switch (units) {
1889 .decimal => @min(log2 / comptime std.math.log2(1000), mags_si.len - 1),
1890 .binary => @min(log2 / 10, mags_iec.len - 1),
1891 };
1892 const new_value = std.math.lossyCast(f64, value) / std.math.pow(f64, std.math.lossyCast(f64, base), std.math.lossyCast(f64, magnitude));
1893 const suffix = switch (units) {
1894 .decimal => mags_si[magnitude],
1895 .binary => mags_iec[magnitude],
1896 };
1897
1898 const s = switch (magnitude) {
1899 0 => buf[0..std.fmt.printInt(&buf, value, 10, .lower, .{})],
1900 else => std.fmt.float.render(&buf, new_value, .{ .mode = .decimal, .precision = options.precision }) catch |err| switch (err) {
1901 error.BufferTooSmall => unreachable,
1902 },
1903 };
1904
1905 var i: usize = s.len;
1906 if (suffix == ' ') {
1907 buf[i] = 'B';
1908 i += 1;
1909 } else switch (units) {
1910 .decimal => {
1911 buf[i..][0..2].* = [_]u8{ suffix, 'B' };
1912 i += 2;
1913 },
1914 .binary => {
1915 buf[i..][0..3].* = [_]u8{ suffix, 'i', 'B' };
1916 i += 3;
1917 },
1918 }
1919
1920 return w.alignBufferOptions(buf[0..i], options);
1921}
1922
1923// This ANY const is a workaround for: https://github.com/ziglang/zig/issues/7948
1924const ANY = "any";
1925
1926fn stripOptionalOrErrorUnionSpec(comptime fmt: []const u8) []const u8 {
1927 return if (std.mem.eql(u8, fmt[1..], ANY))
1928 ANY
1929 else
1930 fmt[1..];
1931}
1932
1933pub fn invalidFmtError(comptime fmt: []const u8, value: anytype) noreturn {
1934 @compileError("invalid format string '" ++ fmt ++ "' for type '" ++ @typeName(@TypeOf(value)) ++ "'");
1935}
1936
1937pub fn printHex(w: *Writer, bytes: []const u8, case: std.fmt.Case) Error!void {
1938 const charset = switch (case) {
1939 .upper => "0123456789ABCDEF",
1940 .lower => "0123456789abcdef",
1941 };
1942 for (bytes) |c| {
1943 try w.writeByte(charset[c >> 4]);
1944 try w.writeByte(charset[c & 15]);
1945 }
1946}
1947
1948pub fn printBase64(w: *Writer, bytes: []const u8) Error!void {
1949 var chunker = std.mem.window(u8, bytes, 3, 3);
1950 var temp: [5]u8 = undefined;
1951 while (chunker.next()) |chunk| {
1952 try w.writeAll(std.base64.standard.Encoder.encode(&temp, chunk));
1953 }
1954}
1955
1956/// Write a single unsigned integer as LEB128 to the given writer.
1957pub fn writeUleb128(w: *Writer, value: anytype) Error!void {
1958 try w.writeLeb128(switch (@typeInfo(@TypeOf(value))) {
1959 .comptime_int => @as(std.math.IntFittingRange(0, @abs(value)), value),
1960 .int => |value_info| switch (value_info.signedness) {
1961 .signed => @as(@Int(.unsigned, value_info.bits -| 1), @intCast(value)),
1962 .unsigned => value,
1963 },
1964 else => comptime unreachable,
1965 });
1966}
1967
1968/// Write a single signed integer as LEB128 to the given writer.
1969pub fn writeSleb128(w: *Writer, value: anytype) Error!void {
1970 try w.writeLeb128(switch (@typeInfo(@TypeOf(value))) {
1971 .comptime_int => @as(std.math.IntFittingRange(@min(value, -1), @max(0, value)), value),
1972 .int => |value_info| switch (value_info.signedness) {
1973 .signed => value,
1974 .unsigned => @as(@Int(.signed, value_info.bits + 1), value),
1975 },
1976 else => comptime unreachable,
1977 });
1978}
1979
1980/// Write a single integer as LEB128 to the given writer.
1981pub fn writeLeb128(w: *Writer, value: anytype) Error!void {
1982 const T = @TypeOf(value);
1983 const info = switch (@typeInfo(T)) {
1984 .int => |info| info,
1985 else => @compileError(@typeName(T) ++ " not supported"),
1986 };
1987
1988 const BoundInt = @Int(info.signedness, 7);
1989 if (info.bits <= 7 or (value >= std.math.minInt(BoundInt) and value <= std.math.maxInt(BoundInt))) {
1990 const Bits = @Int(info.signedness, 8);
1991 const byte = switch (info.signedness) {
1992 .signed => @as(Bits, @intCast(value)) & 0x7F,
1993 .unsigned => @as(Bits, @intCast(value)),
1994 };
1995 try w.writeByte(@bitCast(byte));
1996 return;
1997 }
1998
1999 const Byte = packed struct { bits: u7, more: bool };
2000 const Int = std.math.ByteAlignedInt(T);
2001
2002 const max_bytes = @divFloor(info.bits - 1, 7) + 1;
2003
2004 const sign_value = value >> (info.bits - 1);
2005 var val: Int = value;
2006 for (0..max_bytes) |_| {
2007 const more = switch (info.signedness) {
2008 .signed => val >> 6 != sign_value,
2009 .unsigned => val > std.math.maxInt(u7),
2010 };
2011
2012 try w.writeByte(@bitCast(@as(Byte, .{
2013 .bits = @intCast(val & 0x7F),
2014 .more = more,
2015 })));
2016
2017 if (!more) return;
2018
2019 val >>= 7;
2020 } else unreachable;
2021}
2022
2023test "serialize signed LEB128" {
2024 // Small values
2025 try testLeb128Encoding(i7, 9, "\x09");
2026 try testLeb128Encoding(i64, 125, "\xFD\x00");
2027
2028 try testLeb128Encoding(i7, -34, "\x5E");
2029 try testLeb128Encoding(i64, -3, "\x7D");
2030
2031 // Random values
2032 try testLeb128Encoding(i16, 19373, "\xAD\x97\x01");
2033 try testLeb128Encoding(i32, 1628839242, "\xCA\xBA\xD8\x88\x06");
2034 try testLeb128Encoding(i64, 3789169920125966546, "\xD2\xB1\xD0\xD5\xF6\xBE\xF5\xCA\x34");
2035 try testLeb128Encoding(i128, 704622239050934257305893323522763588, "\xC4\xD6\x83\xC7\xE3\x91\x95\xC3\x96\x80\x8D\xA5\xF5\xDF\xA3\xDA\x87\x01");
2036
2037 try testLeb128Encoding(i16, -14558, "\xA2\x8E\x7F");
2038 try testLeb128Encoding(i32, -1702738165, "\x8B\x8E\x89\xD4\x79");
2039 try testLeb128Encoding(i64, -1709126996960612298, "\xB6\xE0\x87\xB1\xD3\xC1\xFD\xA3\x68");
2040 try testLeb128Encoding(i128, -113498719181566012704681230050325944039, "\x99\xD2\x80\xBC\xE6\x95\xBC\xC8\xDE\xB4\x9D\x81\x9F\xCA\xC6\xF8\x9C\xD5\x7E");
2041
2042 // {min,max} values
2043 try testLeb128Encoding(i16, std.math.maxInt(i16), "\xFF\xFF\x01");
2044 try testLeb128Encoding(i32, std.math.maxInt(i32), "\xFF\xFF\xFF\xFF\x07");
2045 try testLeb128Encoding(i64, std.math.maxInt(i64), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00");
2046 try testLeb128Encoding(i128, std.math.maxInt(i128), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x01");
2047
2048 try testLeb128Encoding(i16, std.math.minInt(i16), "\x80\x80\x7E");
2049 try testLeb128Encoding(i32, std.math.minInt(i32), "\x80\x80\x80\x80\x78");
2050 try testLeb128Encoding(i64, std.math.minInt(i64), "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x7F");
2051 try testLeb128Encoding(i128, std.math.minInt(i128), "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x80\x7E");
2052
2053 // Specific cases
2054 try testLeb128Encoding(i8, 0, "\x00");
2055
2056 try testLeb128Encoding(i2, -1, "\x7F");
2057 try testLeb128Encoding(i8, -1, "\x7F");
2058
2059 try testLeb128Encoding(i2, 1, "\x01");
2060 try testLeb128Encoding(i8, 1, "\x01");
2061
2062 // Encode byte boundaries
2063 try testLeb128Encoding(i7, std.math.maxInt(i7), "\x3F");
2064 try testLeb128Encoding(i8, std.math.maxInt(i7) + 1, "\xC0\x00");
2065 try testLeb128Encoding(i14, std.math.maxInt(i14), "\xFF\x3F");
2066 try testLeb128Encoding(i15, std.math.maxInt(i14) + 1, "\x80\xC0\x00");
2067 try testLeb128Encoding(i49, std.math.maxInt(i49), "\xFF\xFF\xFF\xFF\xFF\xFF\x3F");
2068 try testLeb128Encoding(i50, std.math.maxInt(i49) + 1, "\x80\x80\x80\x80\x80\x80\xC0\x00");
2069 try testLeb128Encoding(i56, std.math.maxInt(i56), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x3F");
2070 try testLeb128Encoding(i57, std.math.maxInt(i56) + 1, "\x80\x80\x80\x80\x80\x80\x80\xC0\x00");
2071 try testLeb128Encoding(i63, std.math.maxInt(i63), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x3F");
2072 try testLeb128Encoding(i64, std.math.maxInt(i63) + 1, "\x80\x80\x80\x80\x80\x80\x80\x80\xC0\x00");
2073
2074 try testLeb128Encoding(i7, std.math.minInt(i7), "\x40");
2075 try testLeb128Encoding(i8, std.math.minInt(i7) - 1, "\xBF\x7F");
2076 try testLeb128Encoding(i14, std.math.minInt(i14), "\x80\x40");
2077 try testLeb128Encoding(i15, std.math.minInt(i14) - 1, "\xFF\xBF\x7F");
2078 try testLeb128Encoding(i49, std.math.minInt(i49), "\x80\x80\x80\x80\x80\x80\x40");
2079 try testLeb128Encoding(i50, std.math.minInt(i49) - 1, "\xFF\xFF\xFF\xFF\xFF\xFF\xBF\x7F");
2080 try testLeb128Encoding(i56, std.math.minInt(i56), "\x80\x80\x80\x80\x80\x80\x80\x40");
2081 try testLeb128Encoding(i57, std.math.minInt(i56) - 1, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xBF\x7F");
2082 try testLeb128Encoding(i63, std.math.minInt(i63), "\x80\x80\x80\x80\x80\x80\x80\x80\x40");
2083 try testLeb128Encoding(i64, std.math.minInt(i63) - 1, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xBF\x7F");
2084}
2085
2086test "serialize unsigned LEB128" {
2087 // Small values
2088 try testLeb128Encoding(u7, 12, "\x0C");
2089 try testLeb128Encoding(u64, 201, "\xC9\x01");
2090
2091 // Random values
2092 try testLeb128Encoding(u8, 254, "\xFE\x01");
2093 try testLeb128Encoding(u16, 30241, "\xA1\xEC\x01");
2094 try testLeb128Encoding(u32, 2173531193, "\xB9\xE8\xB5\x8C\x08");
2095 try testLeb128Encoding(u64, 18321125691115744902, "\x86\xDD\xF2\x81\xF2\xD7\xED\xA0\xFE\x01");
2096 try testLeb128Encoding(u128, 122619209508942982841456325819614676193, "\xE1\x89\xF3\xD9\xE3\xAD\xEC\xF4\x98\x95\xF8\xBB\xD7\xB8\xF2\xCC\xBF\xB8\x01");
2097
2098 // Max values
2099 try testLeb128Encoding(u8, std.math.maxInt(u8), "\xFF\x01");
2100 try testLeb128Encoding(u16, std.math.maxInt(u16), "\xFF\xFF\x03");
2101 try testLeb128Encoding(u32, std.math.maxInt(u32), "\xFF\xFF\xFF\xFF\x0F");
2102 try testLeb128Encoding(u64, std.math.maxInt(u64), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x01");
2103 try testLeb128Encoding(u128, std.math.maxInt(u128), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x03");
2104
2105 // Specific cases
2106 try testLeb128Encoding(u0, 0, "\x00");
2107 try testLeb128Encoding(u1, 0, "\x00");
2108 try testLeb128Encoding(u8, 0, "\x00");
2109
2110 try testLeb128Encoding(u1, 1, "\x01");
2111 try testLeb128Encoding(u8, 1, "\x01");
2112
2113 // Encode byte boundaries
2114 try testLeb128Encoding(u7, std.math.maxInt(u7), "\x7F");
2115 try testLeb128Encoding(u8, std.math.maxInt(u7) + 1, "\x80\x01");
2116 try testLeb128Encoding(u14, std.math.maxInt(u14), "\xFF\x7F");
2117 try testLeb128Encoding(u15, std.math.maxInt(u14) + 1, "\x80\x80\x01");
2118 try testLeb128Encoding(u49, std.math.maxInt(u49), "\xFF\xFF\xFF\xFF\xFF\xFF\x7F");
2119 try testLeb128Encoding(u50, std.math.maxInt(u49) + 1, "\x80\x80\x80\x80\x80\x80\x80\x01");
2120 try testLeb128Encoding(u56, std.math.maxInt(u56), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x7F");
2121 try testLeb128Encoding(u57, std.math.maxInt(u56) + 1, "\x80\x80\x80\x80\x80\x80\x80\x80\x01");
2122 try testLeb128Encoding(u63, std.math.maxInt(u63), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x7F");
2123 try testLeb128Encoding(u64, std.math.maxInt(u63) + 1, "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x01");
2124}
2125
2126fn testLeb128Encoding(comptime T: type, value: T, encoding: []const u8) !void {
2127 const info = @typeInfo(T).int;
2128 const max_bytes = @divFloor(info.bits -| 1, 7) + 1;
2129 var bytes: [max_bytes]u8 = undefined;
2130
2131 var fw: Writer = .fixed(&bytes);
2132 try writeLeb128(&fw, value);
2133
2134 try std.testing.expectEqualSlices(u8, encoding, fw.buffered());
2135}
2136
2137test "printValue max_depth" {
2138 const Vec2 = struct {
2139 const SelfType = @This();
2140 x: f32,
2141 y: f32,
2142
2143 pub fn format(self: SelfType, w: *Writer) Error!void {
2144 return w.print("({d:.3},{d:.3})", .{ self.x, self.y });
2145 }
2146 };
2147 const E = enum {
2148 One,
2149 Two,
2150 Three,
2151 };
2152 const TU = union(enum) {
2153 const SelfType = @This();
2154 float: f32,
2155 int: u32,
2156 ptr: ?*SelfType,
2157 };
2158 const S = struct {
2159 const SelfType = @This();
2160 a: ?*SelfType,
2161 tu: TU,
2162 e: E,
2163 vec: Vec2,
2164 };
2165
2166 var inst = S{
2167 .a = null,
2168 .tu = TU{ .ptr = null },
2169 .e = E.Two,
2170 .vec = Vec2{ .x = 10.2, .y = 2.22 },
2171 };
2172 inst.a = &inst;
2173 inst.tu.ptr = &inst.tu;
2174
2175 var buf: [1000]u8 = undefined;
2176 var w: Writer = .fixed(&buf);
2177 try w.printValue("", .{}, inst, 0);
2178 try testing.expectEqualStrings(".{ ... }", w.buffered());
2179
2180 w = .fixed(&buf);
2181 try w.printValue("", .{}, inst, 1);
2182 try testing.expectEqualStrings(".{ .a = .{ ... }, .tu = .{ ... }, .e = .Two, .vec = .{ ... } }", w.buffered());
2183
2184 w = .fixed(&buf);
2185 try w.printValue("", .{}, inst, 2);
2186 try testing.expectEqualStrings(".{ .a = .{ .a = .{ ... }, .tu = .{ ... }, .e = .Two, .vec = .{ ... } }, .tu = .{ .ptr = .{ ... } }, .e = .Two, .vec = .{ .x = 10.2, .y = 2.22 } }", w.buffered());
2187
2188 w = .fixed(&buf);
2189 try w.printValue("", .{}, inst, 3);
2190 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());
2191
2192 const vec: @Vector(4, i32) = .{ 1, 2, 3, 4 };
2193 w = .fixed(&buf);
2194 try w.printValue("", .{}, vec, 0);
2195 try testing.expectEqualStrings("{ ... }", w.buffered());
2196
2197 w = .fixed(&buf);
2198 try w.printValue("", .{}, vec, 1);
2199 try testing.expectEqualStrings("{ 1, 2, 3, 4 }", w.buffered());
2200}
2201
2202test printInt {
2203 try testPrintIntCase("-1", @as(i1, -1), 10, .lower, .{});
2204
2205 try testPrintIntCase("-101111000110000101001110", @as(i32, -12345678), 2, .lower, .{});
2206 try testPrintIntCase("-12345678", @as(i32, -12345678), 10, .lower, .{});
2207 try testPrintIntCase("-bc614e", @as(i32, -12345678), 16, .lower, .{});
2208 try testPrintIntCase("-BC614E", @as(i32, -12345678), 16, .upper, .{});
2209
2210 try testPrintIntCase("12345678", @as(u32, 12345678), 10, .upper, .{});
2211
2212 try testPrintIntCase(" 666", @as(u32, 666), 10, .lower, .{ .width = 6 });
2213 try testPrintIntCase(" 1234", @as(u32, 0x1234), 16, .lower, .{ .width = 6 });
2214 try testPrintIntCase("1234", @as(u32, 0x1234), 16, .lower, .{ .width = 1 });
2215
2216 try testPrintIntCase("+42", @as(i32, 42), 10, .lower, .{ .width = 3 });
2217 try testPrintIntCase("-42", @as(i32, -42), 10, .lower, .{ .width = 3 });
2218
2219 try testPrintIntCase("123456789123456789", @as(comptime_int, 123456789123456789), 10, .lower, .{});
2220}
2221
2222test "printFloat with comptime_float" {
2223 var buf: [20]u8 = undefined;
2224 var w: Writer = .fixed(&buf);
2225 try w.printFloat(@as(comptime_float, 1.0), std.fmt.Options.toNumber(.{}, .scientific, .lower));
2226 try testing.expectEqualStrings(w.buffered(), "1e0");
2227 try testing.expectFmt("1", "{}", .{1.0});
2228}
2229
2230test "{q} format string" {
2231 const data: []const u8 = "i\tlike\"cheese\x00\x05cheese";
2232 try testing.expectFmt("hello \"i\\tlike\\\"cheese\\x00\\x05cheese\" world", "hello {q} world", .{data});
2233}
2234
2235test "{qf} format string" {
2236 const data: []const u8 = "😎";
2237 try testing.expectFmt("hello \"@\\\"😎\\\"\" world", "hello {qf} world", .{std.zig.fmtId(data)});
2238}
2239
2240fn testPrintIntCase(expected: []const u8, value: anytype, base: u8, case: std.fmt.Case, options: std.fmt.Options) !void {
2241 var buffer: [100]u8 = undefined;
2242 var w: Writer = .fixed(&buffer);
2243 try w.printInt(value, base, case, options);
2244 try testing.expectEqualStrings(expected, w.buffered());
2245}
2246
2247test printByteSize {
2248 try testing.expectFmt("file size: 42B\n", "file size: {B}\n", .{42});
2249 try testing.expectFmt("file size: 42B\n", "file size: {Bi}\n", .{42});
2250 try testing.expectFmt("file size: 63MB\n", "file size: {B}\n", .{63 * 1000 * 1000});
2251 try testing.expectFmt("file size: 63MiB\n", "file size: {Bi}\n", .{63 * 1024 * 1024});
2252 try testing.expectFmt("file size: 42B\n", "file size: {B:.2}\n", .{42});
2253 try testing.expectFmt("file size: 42B\n", "file size: {B:>9.2}\n", .{42});
2254 try testing.expectFmt("file size: 66.06MB\n", "file size: {B:.2}\n", .{63 * 1024 * 1024});
2255 try testing.expectFmt("file size: 60.08MiB\n", "file size: {Bi:.2}\n", .{63 * 1000 * 1000});
2256 try testing.expectFmt("file size: =66.06MB=\n", "file size: {B:=^9.2}\n", .{63 * 1024 * 1024});
2257 try testing.expectFmt("file size: 66.06MB\n", "file size: {B: >9.2}\n", .{63 * 1024 * 1024});
2258 try testing.expectFmt("file size: 66.06MB \n", "file size: {B: <9.2}\n", .{63 * 1024 * 1024});
2259 try testing.expectFmt("file size: 0.01844674407370955ZB\n", "file size: {B}\n", .{std.math.maxInt(u64)});
2260}
2261
2262test "bytes.hex" {
2263 const some_bytes = "\xCA\xFE\xBA\xBE";
2264 try testing.expectFmt("lowercase: cafebabe\n", "lowercase: {x}\n", .{some_bytes});
2265 try testing.expectFmt("uppercase: CAFEBABE\n", "uppercase: {X}\n", .{some_bytes});
2266 try testing.expectFmt("uppercase: CAFE\n", "uppercase: {X}\n", .{some_bytes[0..2]});
2267 try testing.expectFmt("lowercase: babe\n", "lowercase: {x}\n", .{some_bytes[2..]});
2268 const bytes_with_zeros = "\x00\x0E\xBA\xBE";
2269 try testing.expectFmt("lowercase: 000ebabe\n", "lowercase: {x}\n", .{bytes_with_zeros});
2270}
2271
2272test "padding" {
2273 const foo: enum { foo } = .foo;
2274 try testing.expectFmt("tag: |foo |\n", "tag: |{t:<4}|\n", .{foo});
2275
2276 const bar: error{bar} = error.bar;
2277 try testing.expectFmt("error: |bar |\n", "error: |{t:<4}|\n", .{bar});
2278}
2279
2280test fixed {
2281 {
2282 var buf: [255]u8 = undefined;
2283 var w: Writer = .fixed(&buf);
2284 try w.print("{s}{s}!", .{ "Hello", "World" });
2285 try testing.expectEqualStrings("HelloWorld!", w.buffered());
2286 }
2287
2288 comptime {
2289 var buf: [255]u8 = undefined;
2290 var w: Writer = .fixed(&buf);
2291 try w.print("{s}{s}!", .{ "Hello", "World" });
2292 try testing.expectEqualStrings("HelloWorld!", w.buffered());
2293 }
2294}
2295
2296test "fixed output" {
2297 var buffer: [10]u8 = undefined;
2298 var w: Writer = .fixed(&buffer);
2299
2300 try w.writeAll("Hello");
2301 try testing.expect(std.mem.eql(u8, w.buffered(), "Hello"));
2302
2303 try w.writeAll("world");
2304 try testing.expect(std.mem.eql(u8, w.buffered(), "Helloworld"));
2305
2306 try testing.expectError(error.WriteFailed, w.writeAll("!"));
2307 try testing.expect(std.mem.eql(u8, w.buffered(), "Helloworld"));
2308
2309 w = .fixed(&buffer);
2310
2311 try testing.expect(w.buffered().len == 0);
2312
2313 try testing.expectError(error.WriteFailed, w.writeAll("Hello world!"));
2314 try testing.expect(std.mem.eql(u8, w.buffered(), "Hello worl"));
2315}
2316
2317test "writeSplat 0 len splat larger than capacity" {
2318 var buf: [8]u8 = undefined;
2319 var w: Writer = .fixed(&buf);
2320 const n = try w.writeSplat(&.{"something that overflows buf"}, 0);
2321 try testing.expectEqual(0, n);
2322}
2323
2324pub fn failingDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
2325 _ = w;
2326 _ = data;
2327 _ = splat;
2328 return error.WriteFailed;
2329}
2330
2331pub fn failingSendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize {
2332 _ = w;
2333 _ = file_reader;
2334 _ = limit;
2335 return error.WriteFailed;
2336}
2337
2338pub fn failingRebase(w: *Writer, preserve: usize, capacity: usize) Error!void {
2339 _ = w;
2340 _ = preserve;
2341 _ = capacity;
2342 return error.WriteFailed;
2343}
2344
2345pub const Discarding = struct {
2346 count: u64,
2347 writer: Writer,
2348
2349 pub fn init(buffer: []u8) Discarding {
2350 return .{
2351 .count = 0,
2352 .writer = .{
2353 .vtable = &.{
2354 .drain = Discarding.drain,
2355 .sendFile = Discarding.sendFile,
2356 },
2357 .buffer = buffer,
2358 },
2359 };
2360 }
2361
2362 /// Includes buffered data (no need to flush).
2363 pub fn fullCount(d: *const Discarding) u64 {
2364 return d.count + d.writer.end;
2365 }
2366
2367 pub fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
2368 const d: *Discarding = @alignCast(@fieldParentPtr("writer", w));
2369 const slice = data[0 .. data.len - 1];
2370 const pattern = data[slice.len];
2371 var written: usize = pattern.len * splat;
2372 for (slice) |bytes| written += bytes.len;
2373 d.count += w.end + written;
2374 w.end = 0;
2375 return written;
2376 }
2377
2378 pub fn sendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize {
2379 if (File.Handle == void) return error.Unimplemented;
2380 const d: *Discarding = @alignCast(@fieldParentPtr("writer", w));
2381 d.count += w.end;
2382 w.end = 0;
2383 if (limit == .nothing) return 0;
2384 if (file_reader.getSize()) |size| {
2385 const n = limit.minInt64(size - file_reader.pos);
2386 if (n == 0) return error.EndOfStream;
2387 file_reader.seekBy(@intCast(n)) catch return error.Unimplemented;
2388 w.end = 0;
2389 d.count += n;
2390 return n;
2391 } else |_| {
2392 // Error is observable on `file_reader` instance, and it is better to
2393 // treat the file as a pipe.
2394 return error.Unimplemented;
2395 }
2396 }
2397};
2398
2399/// Removes the first `n` bytes from `buffer` by shifting buffer contents,
2400/// returning how many bytes are left after consuming the entire buffer, or
2401/// zero if the entire buffer was not consumed.
2402///
2403/// Useful for `VTable.drain` function implementations to implement partial
2404/// drains.
2405pub fn consume(w: *Writer, n: usize) usize {
2406 if (n < w.end) {
2407 const remaining = w.buffer[n..w.end];
2408 @memmove(w.buffer[0..remaining.len], remaining);
2409 w.end = remaining.len;
2410 return 0;
2411 }
2412 defer w.end = 0;
2413 return n - w.end;
2414}
2415
2416/// Shortcut for setting `end` to zero and returning zero. Equivalent to
2417/// calling `consume` with `end`.
2418pub fn consumeAll(w: *Writer) usize {
2419 w.end = 0;
2420 return 0;
2421}
2422
2423/// For use when the `Writer` implementation can cannot offer a more efficient
2424/// implementation than a basic read/write loop on the file.
2425pub fn unimplementedSendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize {
2426 _ = w;
2427 _ = file_reader;
2428 _ = limit;
2429 return error.Unimplemented;
2430}
2431
2432/// When this function is called it usually means the buffer got full, so it's
2433/// time to return an error. However, we still need to make sure all of the
2434/// available buffer has been filled. Also, it may be called from `flush` in
2435/// which case it should return successfully.
2436pub fn fixedDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
2437 if (data.len == 0) return 0;
2438 for (data[0 .. data.len - 1]) |bytes| {
2439 const dest = w.buffer[w.end..];
2440 const len = @min(bytes.len, dest.len);
2441 @memcpy(dest[0..len], bytes[0..len]);
2442 w.end += len;
2443 if (bytes.len > dest.len) return error.WriteFailed;
2444 }
2445 const pattern = data[data.len - 1];
2446 const dest = w.buffer[w.end..];
2447 switch (pattern.len) {
2448 0 => return 0,
2449 1 => {
2450 assert(splat >= dest.len);
2451 @memset(dest, pattern[0]);
2452 w.end += dest.len;
2453 return error.WriteFailed;
2454 },
2455 else => {
2456 for (0..splat) |i| {
2457 const remaining = dest[i * pattern.len ..];
2458 const len = @min(pattern.len, remaining.len);
2459 @memcpy(remaining[0..len], pattern[0..len]);
2460 w.end += len;
2461 if (pattern.len > remaining.len) return error.WriteFailed;
2462 }
2463 unreachable;
2464 },
2465 }
2466}
2467
2468pub fn unreachableDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
2469 _ = w;
2470 _ = data;
2471 _ = splat;
2472 unreachable;
2473}
2474
2475pub fn unreachableRebase(w: *Writer, preserve: usize, capacity: usize) Error!void {
2476 _ = w;
2477 _ = preserve;
2478 _ = capacity;
2479 unreachable;
2480}
2481
2482pub fn fromArrayList(array_list: *ArrayList(u8)) Writer {
2483 defer array_list.* = .empty;
2484 array_list.pointer_stability.assertUnlocked();
2485 return .{
2486 .vtable = &.{
2487 .drain = fixedDrain,
2488 .flush = noopFlush,
2489 .rebase = failingRebase,
2490 },
2491 .buffer = array_list.allocatedSlice(),
2492 .end = array_list.items.len,
2493 };
2494}
2495
2496pub fn toArrayList(w: *Writer) ArrayList(u8) {
2497 const result: ArrayList(u8) = .{
2498 .items = w.buffer[0..w.end],
2499 .capacity = w.buffer.len,
2500 .pointer_stability = .{},
2501 };
2502 w.buffer = &.{};
2503 w.end = 0;
2504 return result;
2505}
2506
2507/// Provides a `Writer` implementation based on calling `Hasher.update`, sending
2508/// all data also to an underlying `Writer`.
2509///
2510/// When using this, the underlying writer is best unbuffered because all
2511/// writes are passed on directly to it.
2512///
2513/// This implementation makes suboptimal buffering decisions due to being
2514/// generic. A better solution will involve creating a writer for each hash
2515/// function, where the splat buffer can be tailored to the hash implementation
2516/// details.
2517///
2518/// Contrast with `Hashing` which terminates the stream pipeline.
2519pub fn Hashed(comptime Hasher: type) type {
2520 return struct {
2521 out: *Writer,
2522 hasher: Hasher,
2523 writer: Writer,
2524
2525 pub fn init(out: *Writer, buffer: []u8) @This() {
2526 return .initHasher(out, .{}, buffer);
2527 }
2528
2529 pub fn initHasher(out: *Writer, hasher: Hasher, buffer: []u8) @This() {
2530 return .{
2531 .out = out,
2532 .hasher = hasher,
2533 .writer = .{
2534 .buffer = buffer,
2535 .vtable = &.{ .drain = @This().drain },
2536 },
2537 };
2538 }
2539
2540 fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
2541 const this: *@This() = @alignCast(@fieldParentPtr("writer", w));
2542 const aux = w.buffered();
2543 const aux_n = try this.out.writeSplatHeader(aux, data, splat);
2544 if (aux_n < w.end) {
2545 this.hasher.update(w.buffer[0..aux_n]);
2546 const remaining = w.buffer[aux_n..w.end];
2547 @memmove(w.buffer[0..remaining.len], remaining);
2548 w.end = remaining.len;
2549 return 0;
2550 }
2551 this.hasher.update(aux);
2552 const n = aux_n - w.end;
2553 w.end = 0;
2554 var remaining: usize = n;
2555 for (data[0 .. data.len - 1]) |slice| {
2556 if (remaining <= slice.len) {
2557 this.hasher.update(slice[0..remaining]);
2558 return n;
2559 }
2560 remaining -= slice.len;
2561 this.hasher.update(slice);
2562 }
2563 const pattern = data[data.len - 1];
2564 assert(remaining <= splat * pattern.len);
2565 switch (pattern.len) {
2566 0 => {
2567 assert(remaining == 0);
2568 },
2569 1 => {
2570 var buffer: [64]u8 = undefined;
2571 @memset(&buffer, pattern[0]);
2572 while (remaining > 0) {
2573 const update_len = @min(remaining, buffer.len);
2574 this.hasher.update(buffer[0..update_len]);
2575 remaining -= update_len;
2576 }
2577 },
2578 else => {
2579 while (remaining > 0) {
2580 const update_len = @min(remaining, pattern.len);
2581 this.hasher.update(pattern[0..update_len]);
2582 remaining -= update_len;
2583 }
2584 },
2585 }
2586 return n;
2587 }
2588 };
2589}
2590
2591/// Provides a `Writer` implementation based on calling `Hasher.update`,
2592/// discarding all data.
2593///
2594/// This implementation makes suboptimal buffering decisions due to being
2595/// generic. A better solution will involve creating a writer for each hash
2596/// function, where the splat buffer can be tailored to the hash implementation
2597/// details.
2598///
2599/// The total number of bytes written is stored in `hasher`.
2600///
2601/// Contrast with `Hashed` which also passes the data to an underlying stream.
2602pub fn Hashing(comptime Hasher: type) type {
2603 return struct {
2604 hasher: Hasher,
2605 writer: Writer,
2606
2607 pub fn init(buffer: []u8) @This() {
2608 return .initHasher(.init(.{}), buffer);
2609 }
2610
2611 pub fn initHasher(hasher: Hasher, buffer: []u8) @This() {
2612 return .{
2613 .hasher = hasher,
2614 .writer = .{
2615 .buffer = buffer,
2616 .vtable = &.{ .drain = @This().drain },
2617 },
2618 };
2619 }
2620
2621 fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
2622 const this: *@This() = @alignCast(@fieldParentPtr("writer", w));
2623 this.hasher.update(w.buffered());
2624 w.end = 0;
2625 var n: usize = 0;
2626 for (data[0 .. data.len - 1]) |slice| {
2627 this.hasher.update(slice);
2628 n += slice.len;
2629 }
2630 for (0..splat) |_| this.hasher.update(data[data.len - 1]);
2631 return n + splat * data[data.len - 1].len;
2632 }
2633 };
2634}
2635
2636/// Maintains `Writer` state such that it writes to the unused capacity of an
2637/// array list, filling it up completely before making a call through the
2638/// vtable, causing a resize. Consequently, the same, optimized, non-generic
2639/// machine code that uses `Writer`, such as formatted printing, takes
2640/// the hot paths when using this API.
2641///
2642/// When using this API, it is not necessary to call `flush`.
2643pub const Allocating = struct {
2644 allocator: Allocator,
2645 writer: Writer,
2646 alignment: std.mem.Alignment,
2647
2648 pub fn init(allocator: Allocator) Allocating {
2649 return .initAligned(allocator, .of(u8));
2650 }
2651
2652 pub fn initAligned(allocator: Allocator, alignment: std.mem.Alignment) Allocating {
2653 return .{
2654 .allocator = allocator,
2655 .writer = .{
2656 .buffer = &.{},
2657 .vtable = &vtable,
2658 },
2659 .alignment = alignment,
2660 };
2661 }
2662
2663 pub fn initCapacity(allocator: Allocator, capacity: usize) error{OutOfMemory}!Allocating {
2664 return .{
2665 .allocator = allocator,
2666 .writer = .{
2667 .buffer = if (capacity == 0)
2668 &.{}
2669 else
2670 (allocator.rawAlloc(capacity, .of(u8), @returnAddress()) orelse
2671 return error.OutOfMemory)[0..capacity],
2672 .vtable = &vtable,
2673 },
2674 .alignment = .of(u8),
2675 };
2676 }
2677
2678 pub fn initOwnedSlice(allocator: Allocator, slice: []u8) Allocating {
2679 return initOwnedSliceAligned(allocator, .of(u8), slice);
2680 }
2681
2682 pub fn initOwnedSliceAligned(
2683 allocator: Allocator,
2684 comptime alignment: std.mem.Alignment,
2685 slice: []align(alignment.toByteUnits()) u8,
2686 ) Allocating {
2687 return .{
2688 .allocator = allocator,
2689 .writer = .{
2690 .buffer = slice,
2691 .vtable = &vtable,
2692 },
2693 .alignment = alignment,
2694 };
2695 }
2696
2697 /// Replaces `array_list` with empty, taking ownership of the memory.
2698 pub fn fromArrayList(allocator: Allocator, array_list: *ArrayList(u8)) Allocating {
2699 return fromArrayListAligned(allocator, .of(u8), array_list);
2700 }
2701
2702 /// Replaces `array_list` with empty, taking ownership of the memory.
2703 pub fn fromArrayListAligned(
2704 allocator: Allocator,
2705 comptime alignment: std.mem.Alignment,
2706 array_list: *std.array_list.Aligned(u8, alignment),
2707 ) Allocating {
2708 defer array_list.* = .empty;
2709 return .{
2710 .allocator = allocator,
2711 .writer = .{
2712 .vtable = &vtable,
2713 .buffer = array_list.allocatedSlice(),
2714 .end = array_list.items.len,
2715 },
2716 .alignment = alignment,
2717 };
2718 }
2719
2720 const vtable: VTable = .{
2721 .drain = Allocating.drain,
2722 .sendFile = Allocating.sendFile,
2723 .flush = noopFlush,
2724 .rebase = growingRebase,
2725 };
2726
2727 pub fn deinit(a: *Allocating) void {
2728 if (a.writer.buffer.len == 0) return;
2729 a.allocator.rawFree(a.writer.buffer, a.alignment, @returnAddress());
2730 a.* = undefined;
2731 }
2732
2733 /// Returns an array list that takes ownership of the allocated memory.
2734 /// Resets the `Allocating` to an empty state.
2735 pub fn toArrayList(a: *Allocating) ArrayList(u8) {
2736 return toArrayListAligned(a, .of(u8));
2737 }
2738
2739 /// Returns an array list that takes ownership of the allocated memory.
2740 /// Resets the `Allocating` to an empty state.
2741 pub fn toArrayListAligned(
2742 a: *Allocating,
2743 comptime alignment: std.mem.Alignment,
2744 ) std.array_list.Aligned(u8, alignment) {
2745 assert(a.alignment == alignment); // Required for Allocator correctness.
2746 const w = &a.writer;
2747 const result: std.array_list.Aligned(u8, alignment) = .{
2748 .items = @alignCast(w.buffer[0..w.end]),
2749 .capacity = w.buffer.len,
2750 .pointer_stability = .{},
2751 };
2752 w.buffer = &.{};
2753 w.end = 0;
2754 return result;
2755 }
2756
2757 pub fn ensureUnusedCapacity(a: *Allocating, additional_count: usize) Allocator.Error!void {
2758 const new_capacity = std.math.add(usize, a.writer.end, additional_count) catch return error.OutOfMemory;
2759 return ensureTotalCapacity(a, new_capacity);
2760 }
2761
2762 pub fn ensureTotalCapacity(a: *Allocating, new_capacity: usize) Allocator.Error!void {
2763 // Protects growing unnecessarily since better_capacity will be larger.
2764 if (a.writer.buffer.len >= new_capacity) return;
2765 const better_capacity = ArrayList(u8).growCapacity(new_capacity);
2766 return ensureTotalCapacityPrecise(a, better_capacity);
2767 }
2768
2769 pub fn ensureTotalCapacityPrecise(a: *Allocating, new_capacity: usize) Allocator.Error!void {
2770 const old_memory = a.writer.buffer;
2771 if (old_memory.len >= new_capacity) return;
2772 assert(new_capacity != 0);
2773 const alignment = a.alignment;
2774 if (old_memory.len > 0) {
2775 if (a.allocator.rawRemap(old_memory, alignment, new_capacity, @returnAddress())) |new| {
2776 a.writer.buffer = new[0..new_capacity];
2777 return;
2778 }
2779 }
2780 const new_memory = (a.allocator.rawAlloc(new_capacity, alignment, @returnAddress()) orelse
2781 return error.OutOfMemory)[0..new_capacity];
2782 const saved = old_memory[0..a.writer.end];
2783 @memcpy(new_memory[0..saved.len], saved);
2784 if (old_memory.len != 0) a.allocator.rawFree(old_memory, alignment, @returnAddress());
2785 a.writer.buffer = new_memory;
2786 }
2787
2788 pub fn toOwnedSlice(a: *Allocating) Allocator.Error![]u8 {
2789 const old_memory = a.writer.buffer;
2790 const alignment = a.alignment;
2791 const buffered_len = a.writer.end;
2792
2793 if (old_memory.len > 0) {
2794 if (buffered_len == 0) {
2795 a.allocator.rawFree(old_memory, alignment, @returnAddress());
2796 a.writer.buffer = &.{};
2797 a.writer.end = 0;
2798 return old_memory[0..0];
2799 } else if (a.allocator.rawRemap(old_memory, alignment, buffered_len, @returnAddress())) |new| {
2800 a.writer.buffer = &.{};
2801 a.writer.end = 0;
2802 return new[0..buffered_len];
2803 }
2804 }
2805
2806 if (buffered_len == 0)
2807 return a.writer.buffer[0..0];
2808
2809 const new_memory = (a.allocator.rawAlloc(buffered_len, alignment, @returnAddress()) orelse
2810 return error.OutOfMemory)[0..buffered_len];
2811 @memcpy(new_memory, old_memory[0..buffered_len]);
2812 if (old_memory.len != 0) a.allocator.rawFree(old_memory, alignment, @returnAddress());
2813 a.writer.buffer = &.{};
2814 a.writer.end = 0;
2815 return new_memory;
2816 }
2817
2818 pub fn toOwnedSliceSentinel(a: *Allocating, comptime sentinel: u8) Allocator.Error![:sentinel]u8 {
2819 // This addition can never overflow because `a.writer.buffer` can never occupy the whole address space.
2820 try ensureTotalCapacityPrecise(a, a.writer.end + 1);
2821 a.writer.buffer[a.writer.end] = sentinel;
2822 a.writer.end += 1;
2823 errdefer a.writer.end -= 1;
2824 const result = try toOwnedSlice(a);
2825 return result[0 .. result.len - 1 :sentinel];
2826 }
2827
2828 pub fn written(a: *Allocating) []u8 {
2829 return a.writer.buffered();
2830 }
2831
2832 pub fn shrinkRetainingCapacity(a: *Allocating, new_len: usize) void {
2833 a.writer.end = new_len;
2834 }
2835
2836 pub fn clearRetainingCapacity(a: *Allocating) void {
2837 a.shrinkRetainingCapacity(0);
2838 }
2839
2840 fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
2841 const a: *Allocating = @fieldParentPtr("writer", w);
2842 assert(data.len != 0);
2843 const count = countSplat(data, splat);
2844 a.ensureUnusedCapacity(count + 1) catch return error.WriteFailed;
2845 for (data[0 .. data.len - 1]) |bytes| {
2846 @memcpy(a.writer.buffer[a.writer.end..][0..bytes.len], bytes);
2847 a.writer.end += bytes.len;
2848 }
2849 const pattern = data[data.len - 1];
2850 switch (pattern.len) {
2851 0 => {},
2852 1 => {
2853 @memset(a.writer.buffer[a.writer.end..][0..splat], pattern[0]);
2854 a.writer.end += splat;
2855 },
2856 else => for (0..splat) |_| {
2857 @memcpy(a.writer.buffer[a.writer.end..][0..pattern.len], pattern);
2858 a.writer.end += pattern.len;
2859 },
2860 }
2861 return count;
2862 }
2863
2864 fn sendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize {
2865 if (File.Handle == void) return error.Unimplemented;
2866 if (limit == .nothing) return 0;
2867 const a: *Allocating = @fieldParentPtr("writer", w);
2868 const pos = file_reader.logicalPos();
2869 const additional, const exact = if (file_reader.getSize()) |size|
2870 .{ size - pos, true }
2871 else |_|
2872 .{ std.atomic.cache_line, false };
2873 if (additional == 0) return error.EndOfStream;
2874 a.ensureUnusedCapacity(limit.minInt64(additional)) catch return error.WriteFailed;
2875 const buffer = a.writer.buffer[a.writer.end..];
2876 const dest = if (exact) buffer[0..limit.minInt64(additional)] else limit.slice(buffer);
2877 const n = try file_reader.interface.readSliceShort(dest);
2878 if (n == 0) return error.EndOfStream;
2879 a.writer.end += n;
2880 return n;
2881 }
2882
2883 fn growingRebase(w: *Writer, preserve: usize, minimum_len: usize) Error!void {
2884 const a: *Allocating = @fieldParentPtr("writer", w);
2885 const total = std.math.add(usize, preserve, minimum_len) catch return error.WriteFailed;
2886 a.ensureTotalCapacity(total) catch return error.WriteFailed;
2887 a.ensureUnusedCapacity(minimum_len) catch return error.WriteFailed;
2888 }
2889
2890 fn testAllocating(comptime alignment: std.mem.Alignment) !void {
2891 var a: Allocating = .initAligned(testing.allocator, alignment);
2892 defer a.deinit();
2893 const w = &a.writer;
2894
2895 const x: i32 = 42;
2896 const y: i32 = 1234;
2897 try w.print("x: {}\ny: {}\n", .{ x, y });
2898 const expected = "x: 42\ny: 1234\n";
2899 try testing.expectEqualSlices(u8, expected, a.written());
2900
2901 // exercise *Aligned methods
2902 var l = a.toArrayListAligned(alignment);
2903 defer l.deinit(testing.allocator);
2904 try testing.expectEqualSlices(u8, expected, l.items);
2905 a = .fromArrayListAligned(testing.allocator, alignment, &l);
2906 try testing.expectEqualSlices(u8, expected, a.written());
2907 const slice: []align(alignment.toByteUnits()) u8 = @alignCast(try a.toOwnedSlice());
2908 try testing.expectEqualSlices(u8, expected, slice);
2909 a = .initOwnedSliceAligned(testing.allocator, alignment, slice);
2910 try testing.expectEqualSlices(u8, expected, a.writer.buffer);
2911 }
2912
2913 test Allocating {
2914 try testAllocating(.@"1");
2915 try testAllocating(.@"4");
2916 try testAllocating(.@"8");
2917 try testAllocating(.@"16");
2918 try testAllocating(.@"32");
2919 try testAllocating(.@"64");
2920 }
2921};
2922
2923test "discarding sendFile" {
2924 const io = testing.io;
2925
2926 var tmp_dir = testing.tmpDir(.{});
2927 defer tmp_dir.cleanup();
2928
2929 const file = try tmp_dir.dir.createFile(io, "input.txt", .{ .read = true });
2930 defer file.close(io);
2931 var r_buffer: [256]u8 = undefined;
2932 var file_writer: File.Writer = .init(file, io, &r_buffer);
2933 try file_writer.interface.writeByte('h');
2934 try file_writer.interface.flush();
2935
2936 var file_reader = file_writer.moveToReader();
2937 try file_reader.seekTo(0);
2938
2939 var w_buffer: [256]u8 = undefined;
2940 var discarding: Writer.Discarding = .init(&w_buffer);
2941
2942 _ = try file_reader.interface.streamRemaining(&discarding.writer);
2943}
2944
2945test "allocating sendFile" {
2946 const io = testing.io;
2947
2948 var tmp_dir = testing.tmpDir(.{});
2949 defer tmp_dir.cleanup();
2950
2951 const file = try tmp_dir.dir.createFile(io, "input.txt", .{ .read = true });
2952 defer file.close(io);
2953 var r_buffer: [2]u8 = undefined;
2954 var file_writer: File.Writer = .init(file, io, &r_buffer);
2955 try file_writer.interface.writeAll("abcd");
2956 try file_writer.interface.flush();
2957
2958 var file_reader = file_writer.moveToReader();
2959 try file_reader.seekTo(0);
2960 try file_reader.interface.fill(2);
2961
2962 var allocating: Writer.Allocating = .init(testing.allocator);
2963 defer allocating.deinit();
2964 try allocating.ensureUnusedCapacity(1);
2965 try testing.expectEqual(4, allocating.writer.sendFileAll(&file_reader, .unlimited));
2966 try testing.expectEqualStrings("abcd", allocating.writer.buffered());
2967}
2968
2969test sendFileReading {
2970 const io = testing.io;
2971
2972 var tmp_dir = testing.tmpDir(.{});
2973 defer tmp_dir.cleanup();
2974
2975 const file = try tmp_dir.dir.createFile(io, "input.txt", .{ .read = true });
2976 defer file.close(io);
2977 var r_buffer: [2]u8 = undefined;
2978 var file_writer: File.Writer = .init(file, io, &r_buffer);
2979 try file_writer.interface.writeAll("abcd");
2980 try file_writer.interface.flush();
2981
2982 var file_reader = file_writer.moveToReader();
2983 try file_reader.seekTo(0);
2984 try file_reader.interface.fill(2);
2985
2986 var w_buffer: [1]u8 = undefined;
2987 var discarding: Writer.Discarding = .init(&w_buffer);
2988 try testing.expectEqual(4, discarding.writer.sendFileReadingAll(&file_reader, .unlimited));
2989}
2990
2991test writeStruct {
2992 var buffer: [16]u8 = undefined;
2993 const S = extern struct { a: u64, b: u32, c: u32 };
2994 const s: S = .{ .a = 1, .b = 2, .c = 3 };
2995 {
2996 var w: Writer = .fixed(&buffer);
2997 try w.writeStruct(s, .little);
2998 try testing.expectEqualSlices(u8, &.{
2999 1, 0, 0, 0, 0, 0, 0, 0, //
3000 2, 0, 0, 0, //
3001 3, 0, 0, 0, //
3002 }, &buffer);
3003 }
3004 {
3005 var w: Writer = .fixed(&buffer);
3006 try w.writeStruct(s, .big);
3007 try testing.expectEqualSlices(u8, &.{
3008 0, 0, 0, 0, 0, 0, 0, 1, //
3009 0, 0, 0, 2, //
3010 0, 0, 0, 3, //
3011 }, &buffer);
3012 }
3013}
3014
3015test writeSliceEndian {
3016 var buffer: [5]u8 align(2) = undefined;
3017 var w: Writer = .fixed(&buffer);
3018 try w.writeByte('x');
3019 const array: [2]u16 = .{ 0x1234, 0x5678 };
3020 try writeSliceEndian(&w, u16, &array, .big);
3021 try testing.expectEqualSlices(u8, &.{ 'x', 0x12, 0x34, 0x56, 0x78 }, &buffer);
3022}
3023
3024test "writableSlice with fixed writer" {
3025 var buf: [2]u8 = undefined;
3026 var w: std.Io.Writer = .fixed(&buf);
3027 try w.writeByte(1);
3028 try std.testing.expectError(error.WriteFailed, w.writableSlice(2));
3029}
3030
3031test splatBytePreserve {
3032 try testSplatBytePreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 5, .splat_len = 5 });
3033 try testSplatBytePreserve(.{ .buf_len = 10, .fill_len = 9, .preserve = 5, .splat_len = 2 });
3034 try testSplatBytePreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 5, .splat_len = 6 });
3035 try testSplatBytePreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 6, .splat_len = 6 });
3036 try testSplatBytePreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 5, .splat_len = 10 });
3037 try testSplatBytePreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 6, .splat_len = 10 });
3038 try testSplatBytePreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 6, .splat_len = 11 });
3039 try testSplatBytePreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 6, .splat_len = 80 });
3040 try testSplatBytePreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 6, .splat_len = 85 });
3041 try testSplatBytePreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 10, .splat_len = 6 });
3042 try testSplatBytePreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 10, .splat_len = 11 });
3043 try testSplatBytePreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 10, .splat_len = 80 });
3044 try testSplatBytePreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 10, .splat_len = 85 });
3045}
3046
3047fn testSplatBytePreserve(options: struct { buf_len: u4, fill_len: u4, preserve: u4, splat_len: u8 }) !void {
3048 assert(options.fill_len <= options.buf_len);
3049 assert(options.preserve <= options.buf_len);
3050
3051 const fill_buf = "abcdefghijklmno";
3052 const fill = fill_buf[0..options.fill_len];
3053 var expected_out_buf: [256]u8 = @splat('X');
3054 @memcpy(expected_out_buf[0..options.fill_len], fill);
3055 const expected_out = expected_out_buf[0 .. options.fill_len + options.splat_len];
3056 const expected_preserved = expected_out[expected_out.len -| options.preserve..];
3057
3058 var out_buf: [256]u8 = undefined;
3059 var fw: Writer = .fixed(&out_buf);
3060 var indirect_buffer: [16]u8 = undefined;
3061 var twi: std.testing.WriterIndirect = .init(&fw, indirect_buffer[0..options.buf_len]);
3062 const w = &twi.interface;
3063
3064 try w.writeAll(fill);
3065 try w.splatBytePreserve(options.preserve, 'X', options.splat_len);
3066
3067 try std.testing.expectEqualStrings(expected_preserved, w.buffer[w.end -| options.preserve..w.end]);
3068
3069 try w.flush();
3070
3071 try std.testing.expectEqualStrings(expected_out, fw.buffer[0..fw.end]);
3072}