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 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(.likely);
850 w.buffer[w.end] = byte;
851 w.end += 1;
852 return;
853 }
854 try w.vtable.rebase(w, preserve -| 1, 1);
855 w.buffer[w.end] = byte;
856 w.end += 1;
857}
858
859/// Writes the same byte many times, performing the underlying write call as
860/// many times as necessary.
861pub fn splatByteAll(w: *Writer, byte: u8, n: usize) Error!void {
862 var remaining: usize = n;
863 while (remaining > 0) remaining -= try w.splatByte(byte, remaining);
864}
865
866test splatByteAll {
867 var aw: Writer.Allocating = .init(testing.allocator);
868 defer aw.deinit();
869
870 try aw.writer.splatByteAll('7', 45);
871 try testing.expectEqualStrings(&@as([45]u8, @splat('7')), aw.writer.buffered());
872}
873
874/// Writes the same byte many times, performing the underlying write call as
875/// many times as necessary.
876///
877/// On success, at least `preserve` bytes will remain buffered if there are
878/// enough buffered bytes to do so.
879/// The amount buffered by the writer after the call will only be less than
880/// `preserve` if `w.end + n` is less than `preserve` before the call.
881/// The intentionally preserved bytes will include up to `preserve -| n` bytes from
882/// the previously buffered bytes, plus `@min(n, preserve_len)` of the newly
883/// written bytes.
884///
885/// Asserts buffer capacity is at least `preserve`.
886/// `n` can be greater than the buffer capacity.
887pub fn splatBytePreserve(w: *Writer, preserve: usize, byte: u8, n: usize) Error!void {
888 const new_end = w.end + n;
889 if (new_end <= w.buffer.len) {
890 @memset(w.buffer[w.end..][0..n], byte);
891 w.end = new_end;
892 return;
893 }
894 // If `n` is large, we can ignore `preserve` up to a point.
895 var remaining = n;
896 while (remaining > preserve) {
897 assert(remaining != 0);
898 remaining -= try splatByte(w, byte, remaining - preserve);
899 if (w.end + remaining <= w.buffer.len) {
900 @memset(w.buffer[w.end..][0..remaining], byte);
901 w.end += remaining;
902 return;
903 }
904 }
905 // Ensure the contract of `rebase` is upheld.
906 assert(w.end + remaining > w.buffer.len);
907 // Offset the amount preserved by the amount we have left to splat
908 // since the remaining splat is always going to be part of that
909 // preservation.
910 try w.vtable.rebase(w, preserve -| remaining, remaining);
911 @memset(w.buffer[w.end..][0..remaining], byte);
912 w.end += remaining;
913}
914
915/// Writes the same byte many times, allowing short writes.
916///
917/// Does maximum of one underlying `VTable.drain`.
918pub fn splatByte(w: *Writer, byte: u8, n: usize) Error!usize {
919 if (w.end + n <= w.buffer.len) {
920 @branchHint(.likely);
921 @memset(w.buffer[w.end..][0..n], byte);
922 w.end += n;
923 return n;
924 }
925 return writeSplat(w, &.{&.{byte}}, n);
926}
927
928/// Writes the same slice many times, performing the underlying write call as
929/// many times as necessary.
930pub fn splatBytesAll(w: *Writer, bytes: []const u8, splat: usize) Error!void {
931 var remaining_bytes: usize = bytes.len * splat;
932 remaining_bytes -= try w.splatBytes(bytes, splat);
933 while (remaining_bytes > 0) {
934 const leftover_splat = remaining_bytes / bytes.len;
935 const leftover_bytes = remaining_bytes % bytes.len;
936 const buffers: [2][]const u8 = .{ bytes[bytes.len - leftover_bytes ..], bytes };
937 remaining_bytes -= try w.writeSplat(&buffers, leftover_splat);
938 }
939}
940
941test splatBytesAll {
942 var aw: Writer.Allocating = .init(testing.allocator);
943 defer aw.deinit();
944
945 try aw.writer.splatBytesAll("hello", 3);
946 try testing.expectEqualStrings("hellohellohello", aw.writer.buffered());
947}
948
949/// Writes the same slice many times, allowing short writes.
950///
951/// Does maximum of one underlying `VTable.drain`.
952pub fn splatBytes(w: *Writer, bytes: []const u8, n: usize) Error!usize {
953 return writeSplat(w, &.{bytes}, n);
954}
955
956/// Asserts the `buffer` was initialized with a capacity of at least `@sizeOf(T)` bytes.
957pub inline fn writeInt(w: *Writer, comptime T: type, value: T, endian: std.lang.Endian) Error!void {
958 var bytes: [@divExact(@typeInfo(T).int.bits, 8)]u8 = undefined;
959 std.mem.writeInt(std.math.ByteAlignedInt(@TypeOf(value)), &bytes, value, endian);
960 return w.writeAll(&bytes);
961}
962
963/// The function is inline to avoid the dead code in case `endian` is
964/// comptime-known and matches host endianness.
965pub inline fn writeStruct(w: *Writer, value: anytype, endian: std.lang.Endian) Error!void {
966 switch (@typeInfo(@TypeOf(value))) {
967 .@"struct" => |info| switch (info.layout) {
968 .auto => @compileError("ill-defined memory layout"),
969 .@"extern" => {
970 if (native_endian == endian) {
971 return w.writeAll(@ptrCast((&value)[0..1]));
972 } else {
973 var copy = value;
974 std.mem.byteSwapAllFields(@TypeOf(value), &copy);
975 return w.writeAll(@ptrCast((&copy)[0..1]));
976 }
977 },
978 .@"packed" => {
979 return writeInt(w, info.backing_integer.?, @bitCast(value), endian);
980 },
981 },
982 else => @compileError("not a struct"),
983 }
984}
985
986pub inline fn writeSliceEndian(
987 w: *Writer,
988 Elem: type,
989 slice: []const Elem,
990 endian: std.lang.Endian,
991) Error!void {
992 switch (@typeInfo(Elem)) {
993 .@"struct" => |info| comptime assert(info.layout != .auto),
994 .int, .@"enum" => {},
995 else => @compileError("ill-defined memory layout"),
996 }
997 if (native_endian == endian) {
998 return writeAll(w, @ptrCast(slice));
999 } else {
1000 return writeSliceSwap(w, Elem, slice);
1001 }
1002}
1003
1004pub fn writeSliceSwap(w: *Writer, Elem: type, slice: []const Elem) Error!void {
1005 for (slice) |elem| {
1006 var tmp = elem;
1007 std.mem.byteSwapAllFields(Elem, &tmp);
1008 try w.writeAll(@ptrCast(&tmp));
1009 }
1010}
1011
1012/// Unlike `writeSplat` and `writeVec`, this function will call into `VTable`
1013/// even if there is enough buffer capacity for the file contents.
1014///
1015/// The caller is responsible for flushing. Although the buffer may be bypassed
1016/// as an optimization, this is not a guarantee.
1017///
1018/// Although it would be possible to eliminate `error.Unimplemented` from the
1019/// error set by reading directly into the buffer in such case, this is not
1020/// done because it is more efficient to do it higher up the call stack so that
1021/// the error does not occur with each write.
1022///
1023/// See `sendFileReading` for an alternative that does not have
1024/// `error.Unimplemented` in the error set.
1025pub fn sendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize {
1026 return w.vtable.sendFile(w, file_reader, limit);
1027}
1028
1029/// Returns how many bytes from `header` and `file_reader` were consumed.
1030///
1031/// `limit` only applies to `file_reader`.
1032pub fn sendFileHeader(
1033 w: *Writer,
1034 header: []const u8,
1035 file_reader: *File.Reader,
1036 limit: Limit,
1037) FileError!usize {
1038 const new_end = w.end + header.len;
1039 if (new_end <= w.buffer.len) {
1040 @memcpy(w.buffer[w.end..][0..header.len], header);
1041 w.end = new_end;
1042 const file_bytes = w.vtable.sendFile(w, file_reader, limit) catch |err| switch (err) {
1043 error.ReadFailed, error.WriteFailed => |e| return e,
1044 error.EndOfStream, error.Unimplemented => |e| {
1045 // These errors are non-fatal, so if we wrote any header bytes, we will report that
1046 // and suppress this error. Only if there was no header may we return the error.
1047 if (header.len != 0) return header.len;
1048 return e;
1049 },
1050 };
1051 return header.len + file_bytes;
1052 }
1053 const buffered_contents = limit.slice(file_reader.interface.buffered());
1054 const n = try w.vtable.drain(w, &.{ header, buffered_contents }, 1);
1055 file_reader.interface.toss(n -| header.len);
1056 return n;
1057}
1058
1059/// Asserts nonzero buffer capacity and nonzero `limit`.
1060pub fn sendFileReading(w: *Writer, file_reader: *File.Reader, limit: Limit) FileReadingError!usize {
1061 assert(limit != .nothing);
1062 const dest = limit.slice(try w.writableSliceGreedy(1));
1063 const n = try file_reader.interface.readSliceShort(dest);
1064 if (n == 0) return error.EndOfStream;
1065 w.advance(n);
1066 return n;
1067}
1068
1069/// Number of bytes logically written is returned. This excludes bytes from
1070/// `buffer` because they have already been logically written.
1071///
1072/// The caller is responsible for flushing. Although the buffer may be bypassed
1073/// as an optimization, this is not a guarantee.
1074///
1075/// Asserts nonzero buffer capacity.
1076pub fn sendFileAll(w: *Writer, file_reader: *File.Reader, limit: Limit) FileAllError!usize {
1077 // The fallback sendFileReadingAll() path asserts non-zero buffer capacity.
1078 // Explicitly assert it here as well to ensure the assert is hit even if
1079 // the fallback path is not taken.
1080 assert(w.buffer.len > 0);
1081 var remaining = @backingInt(limit);
1082 while (remaining > 0) {
1083 const n = sendFile(w, file_reader, .limited(remaining)) catch |err| switch (err) {
1084 error.EndOfStream => break,
1085 error.Unimplemented => {
1086 file_reader.mode = file_reader.mode.toSimple();
1087 remaining -= try w.sendFileReadingAll(file_reader, .limited(remaining));
1088 break;
1089 },
1090 else => |e| return e,
1091 };
1092 remaining -= n;
1093 }
1094 return @backingInt(limit) - remaining;
1095}
1096
1097/// Equivalent to `sendFileAll` but uses direct `pread` and `read` calls on
1098/// `file` rather than `sendFile`. This is generally used as a fallback when
1099/// the underlying implementation returns `error.Unimplemented`, which is why
1100/// that error code does not appear in this function's error set.
1101///
1102/// Asserts nonzero buffer capacity.
1103pub fn sendFileReadingAll(w: *Writer, file_reader: *File.Reader, limit: Limit) FileAllError!usize {
1104 var remaining = @backingInt(limit);
1105 while (remaining > 0) {
1106 remaining -= sendFileReading(w, file_reader, .limited(remaining)) catch |err| switch (err) {
1107 error.EndOfStream => break,
1108 else => |e| return e,
1109 };
1110 }
1111 return @backingInt(limit) - remaining;
1112}
1113
1114pub fn alignBuffer(
1115 w: *Writer,
1116 buffer: []const u8,
1117 width: usize,
1118 alignment: std.fmt.Alignment,
1119 fill: u8,
1120) Error!void {
1121 const padding = if (buffer.len < width) width - buffer.len else 0;
1122 if (padding == 0) {
1123 @branchHint(.likely);
1124 return w.writeAll(buffer);
1125 }
1126 switch (alignment) {
1127 .left => {
1128 try w.writeAll(buffer);
1129 try w.splatByteAll(fill, padding);
1130 },
1131 .center => {
1132 const left_padding = padding / 2;
1133 const right_padding = (padding + 1) / 2;
1134 try w.splatByteAll(fill, left_padding);
1135 try w.writeAll(buffer);
1136 try w.splatByteAll(fill, right_padding);
1137 },
1138 .right => {
1139 try w.splatByteAll(fill, padding);
1140 try w.writeAll(buffer);
1141 },
1142 }
1143}
1144
1145pub fn alignBufferOptions(w: *Writer, buffer: []const u8, options: std.fmt.Options) Error!void {
1146 return w.alignBuffer(buffer, options.width orelse buffer.len, options.alignment, options.fill);
1147}
1148
1149pub fn printAddress(w: *Writer, value: anytype) Error!void {
1150 const T = @TypeOf(value);
1151 switch (@typeInfo(T)) {
1152 .pointer => |info| {
1153 try w.writeAll(@typeName(info.child) ++ "@");
1154 const int = if (info.size == .slice) @intFromPtr(value.ptr) else @intFromPtr(value);
1155 return w.printInt(int, 16, .lower, .{});
1156 },
1157 .optional => |info| {
1158 if (@typeInfo(info.child) == .pointer) {
1159 try w.writeAll(@typeName(info.child) ++ "@");
1160 try w.printInt(@intFromPtr(value), 16, .lower, .{});
1161 return;
1162 }
1163 },
1164 else => {},
1165 }
1166
1167 @compileError("cannot format non-pointer type " ++ @typeName(T) ++ " with * specifier");
1168}
1169
1170/// Asserts `buffer` capacity of at least 2 if `value` is a union.
1171pub fn printValue(
1172 w: *Writer,
1173 comptime fmt: []const u8,
1174 options: std.fmt.Options,
1175 value: anytype,
1176 max_depth: usize,
1177) Error!void {
1178 const T = @TypeOf(value);
1179
1180 switch (fmt.len) {
1181 1 => switch (fmt[0]) {
1182 '*' => return w.printAddress(value),
1183 'f' => return value.format(w),
1184 'd' => switch (@typeInfo(T)) {
1185 .float, .comptime_float => return printFloat(w, value, options.toNumber(.decimal, .lower)),
1186 .int, .comptime_int => return printInt(w, value, 10, .lower, options),
1187 .@"struct" => return value.formatNumber(w, options.toNumber(.decimal, .lower)),
1188 .@"enum" => return printInt(w, @backingInt(value), 10, .lower, options),
1189 .vector => return printVector(w, fmt, options, value, max_depth),
1190 else => invalidFmtError(fmt, value),
1191 },
1192 'c' => return w.printAsciiChar(value, options),
1193 'u' => return w.printUnicodeCodepoint(value),
1194 'b' => switch (@typeInfo(T)) {
1195 .int, .comptime_int => return printInt(w, value, 2, .lower, options),
1196 .@"enum" => return printInt(w, @backingInt(value), 2, .lower, options),
1197 .@"struct" => return value.formatNumber(w, options.toNumber(.binary, .lower)),
1198 .vector => return printVector(w, fmt, options, value, max_depth),
1199 else => invalidFmtError(fmt, value),
1200 },
1201 'o' => switch (@typeInfo(T)) {
1202 .int, .comptime_int => return printInt(w, value, 8, .lower, options),
1203 .@"enum" => return printInt(w, @backingInt(value), 8, .lower, options),
1204 .@"struct" => return value.formatNumber(w, options.toNumber(.octal, .lower)),
1205 .vector => return printVector(w, fmt, options, value, max_depth),
1206 else => invalidFmtError(fmt, value),
1207 },
1208 'x' => switch (@typeInfo(T)) {
1209 .float, .comptime_float => return printFloatHexOptions(w, value, options.toNumber(.hex, .lower)),
1210 .int, .comptime_int => return printInt(w, value, 16, .lower, options),
1211 .@"enum" => return printInt(w, @backingInt(value), 16, .lower, options),
1212 .@"struct" => return value.formatNumber(w, options.toNumber(.hex, .lower)),
1213 .pointer => |info| switch (info.size) {
1214 .one, .slice => {
1215 const slice: []const u8 = value;
1216 optionsForbidden(options);
1217 return printHex(w, slice, .lower);
1218 },
1219 .many, .c => {
1220 const slice: [:0]const u8 = std.mem.span(value);
1221 optionsForbidden(options);
1222 return printHex(w, slice, .lower);
1223 },
1224 },
1225 .array => {
1226 const slice: []const u8 = &value;
1227 optionsForbidden(options);
1228 return printHex(w, slice, .lower);
1229 },
1230 .vector => return printVector(w, fmt, options, value, max_depth),
1231 else => invalidFmtError(fmt, value),
1232 },
1233 'X' => switch (@typeInfo(T)) {
1234 .float, .comptime_float => return printFloatHexOptions(w, value, options.toNumber(.hex, .upper)),
1235 .int, .comptime_int => return printInt(w, value, 16, .upper, options),
1236 .@"enum" => return printInt(w, @backingInt(value), 16, .upper, options),
1237 .@"struct" => return value.formatNumber(w, options.toNumber(.hex, .upper)),
1238 .pointer => |info| switch (info.size) {
1239 .one, .slice => {
1240 const slice: []const u8 = value;
1241 optionsForbidden(options);
1242 return printHex(w, slice, .upper);
1243 },
1244 .many, .c => {
1245 const slice: [:0]const u8 = std.mem.span(value);
1246 optionsForbidden(options);
1247 return printHex(w, slice, .upper);
1248 },
1249 },
1250 .array => {
1251 const slice: []const u8 = &value;
1252 optionsForbidden(options);
1253 return printHex(w, slice, .upper);
1254 },
1255 .vector => return printVector(w, fmt, options, value, max_depth),
1256 else => invalidFmtError(fmt, value),
1257 },
1258 's' => switch (@typeInfo(T)) {
1259 .pointer => |info| switch (info.size) {
1260 .one, .slice => {
1261 const slice: []const u8 = value;
1262 return w.alignBufferOptions(slice, options);
1263 },
1264 .many, .c => {
1265 const slice: [:0]const u8 = std.mem.span(value);
1266 return w.alignBufferOptions(slice, options);
1267 },
1268 },
1269 .array => {
1270 const slice: []const u8 = &value;
1271 return w.alignBufferOptions(slice, options);
1272 },
1273 else => invalidFmtError(fmt, value),
1274 },
1275 'q' => switch (@typeInfo(T)) {
1276 .pointer => |info| switch (info.size) {
1277 .one, .slice => return printStringEscaped(w, value),
1278 .many, .c => return printStringEscaped(w, std.mem.span(value)),
1279 },
1280 .array => return printStringEscaped(w, &value),
1281 else => invalidFmtError(fmt, value),
1282 },
1283 'B' => switch (@typeInfo(T)) {
1284 .int, .comptime_int => return w.printByteSize(value, .decimal, options),
1285 .@"struct" => return value.formatByteSize(w, .decimal),
1286 else => invalidFmtError(fmt, value),
1287 },
1288 'e' => switch (@typeInfo(T)) {
1289 .float, .comptime_float => return printFloat(w, value, options.toNumber(.scientific, .lower)),
1290 .@"struct" => return value.formatNumber(w, options.toNumber(.scientific, .lower)),
1291 else => invalidFmtError(fmt, value),
1292 },
1293 'E' => switch (@typeInfo(T)) {
1294 .float, .comptime_float => return printFloat(w, value, options.toNumber(.scientific, .upper)),
1295 .@"struct" => return value.formatNumber(w, options.toNumber(.scientific, .upper)),
1296 else => invalidFmtError(fmt, value),
1297 },
1298 't' => switch (@typeInfo(T)) {
1299 .error_set => return w.alignBufferOptions(@errorName(value), options),
1300 .@"enum", .enum_literal, .@"union" => return w.alignBufferOptions(@tagName(value), options),
1301 else => invalidFmtError(fmt, value),
1302 },
1303 else => {},
1304 },
1305 2 => switch (fmt[0]) {
1306 'B' => switch (fmt[1]) {
1307 'i' => switch (@typeInfo(T)) {
1308 .int, .comptime_int => return w.printByteSize(value, .binary, options),
1309 .@"struct" => return value.formatByteSize(w, .binary),
1310 else => invalidFmtError(fmt, value),
1311 },
1312 else => {},
1313 },
1314 'q' => switch (fmt[1]) {
1315 'f' => {
1316 try w.writeByte('"');
1317 var buffer: [64]u8 = undefined;
1318 var escaping_writer: std.zig.StringEscapeWriter = .init(w, &buffer);
1319 try value.format(&escaping_writer.writer);
1320 try escaping_writer.writer.flush();
1321 try w.writeByte('"');
1322 return;
1323 },
1324 else => {},
1325 },
1326 else => {},
1327 },
1328 3 => if (fmt[0] == 'b' and fmt[1] == '6' and fmt[2] == '4') switch (@typeInfo(T)) {
1329 .pointer => |info| switch (info.size) {
1330 .one, .slice => {
1331 const slice: []const u8 = value;
1332 optionsForbidden(options);
1333 return w.printBase64(slice);
1334 },
1335 .many, .c => {
1336 const slice: [:0]const u8 = std.mem.span(value);
1337 optionsForbidden(options);
1338 return w.printBase64(slice);
1339 },
1340 },
1341 .array => {
1342 const slice: []const u8 = &value;
1343 optionsForbidden(options);
1344 return w.printBase64(slice);
1345 },
1346 else => invalidFmtError(fmt, value),
1347 },
1348 else => {},
1349 }
1350
1351 const is_any = comptime std.mem.eql(u8, fmt, ANY);
1352
1353 switch (@typeInfo(T)) {
1354 .float, .comptime_float => {
1355 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1356 return printFloat(w, value, options.toNumber(.decimal, .lower));
1357 },
1358 .int, .comptime_int => {
1359 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1360 return printInt(w, value, 10, .lower, options);
1361 },
1362 .bool => {
1363 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1364 const string: []const u8 = if (value) "true" else "false";
1365 return w.alignBufferOptions(string, options);
1366 },
1367 .void => {
1368 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1369 return w.alignBufferOptions("void", options);
1370 },
1371 .optional => {
1372 const remaining_fmt = comptime if (fmt.len > 0 and fmt[0] == '?')
1373 stripOptionalOrErrorUnionSpec(fmt)
1374 else if (is_any)
1375 ANY
1376 else
1377 @compileError("cannot print optional without a specifier (i.e. {?} or {any})");
1378 if (value) |payload| {
1379 return w.printValue(remaining_fmt, options, payload, max_depth);
1380 } else {
1381 return w.alignBufferOptions("null", options);
1382 }
1383 },
1384 .error_union => {
1385 const remaining_fmt = comptime if (fmt.len > 0 and fmt[0] == '!')
1386 stripOptionalOrErrorUnionSpec(fmt)
1387 else if (is_any)
1388 ANY
1389 else
1390 @compileError("cannot print error union without a specifier (i.e. {!} or {any})");
1391 if (value) |payload| {
1392 return w.printValue(remaining_fmt, options, payload, max_depth);
1393 } else |err| {
1394 return w.printValue("", options, err, max_depth);
1395 }
1396 },
1397 .error_set => {
1398 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1399 optionsForbidden(options);
1400 return printErrorSet(w, value);
1401 },
1402 .@"enum" => |info| {
1403 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1404 optionsForbidden(options);
1405 if (info.mode == .exhaustive) {
1406 return printEnumExhaustive(w, value);
1407 } else {
1408 return printEnumNonexhaustive(w, value);
1409 }
1410 },
1411 .@"union" => |info| {
1412 if (!is_any) {
1413 if (fmt.len != 0) invalidFmtError(fmt, value);
1414 return printValue(w, ANY, options, value, max_depth);
1415 }
1416 if (max_depth == 0) {
1417 try w.writeAll(".{ ... }");
1418 return;
1419 }
1420 if (info.tag_type) |UnionTagType| {
1421 try w.writeAll(".{ .");
1422 try w.writeAll(@tagName(@as(UnionTagType, value)));
1423 try w.writeAll(" = ");
1424 inline for (info.field_names) |u_field_name| {
1425 if (value == @field(UnionTagType, u_field_name)) {
1426 try w.printValue(ANY, options, @field(value, u_field_name), max_depth - 1);
1427 }
1428 }
1429 try w.writeAll(" }");
1430 } else switch (info.layout) {
1431 .auto => {
1432 return w.writeAll(".{ ... }");
1433 },
1434 .@"extern", .@"packed" => {
1435 if (info.field_names.len == 0) return w.writeAll(".{}");
1436 try w.writeAll(".{ ");
1437 inline for (info.field_names, 1..) |field_name, i| {
1438 try w.writeByte('.');
1439 try w.writeAll(field_name);
1440 try w.writeAll(" = ");
1441 try w.printValue(ANY, options, @field(value, field_name), max_depth - 1);
1442 try w.writeAll(if (i < info.field_names.len) ", " else " }");
1443 }
1444 },
1445 }
1446 },
1447 .@"struct" => |info| {
1448 if (!is_any) {
1449 if (fmt.len != 0) invalidFmtError(fmt, value);
1450 return printValue(w, ANY, options, value, max_depth);
1451 }
1452 if (info.is_tuple) {
1453 // Skip the type and field names when formatting tuples.
1454 if (max_depth == 0) {
1455 try w.writeAll(".{ ... }");
1456 return;
1457 }
1458 try w.writeAll(".{");
1459 inline for (info.field_names, 0..) |f_name, i| {
1460 if (i == 0) {
1461 try w.writeAll(" ");
1462 } else {
1463 try w.writeAll(", ");
1464 }
1465 try w.printValue(ANY, options, @field(value, f_name), max_depth - 1);
1466 }
1467 try w.writeAll(" }");
1468 return;
1469 }
1470 if (max_depth == 0) {
1471 try w.writeAll(".{ ... }");
1472 return;
1473 }
1474 try w.writeAll(".{");
1475 inline for (info.field_names, 0..) |f_name, i| {
1476 if (i == 0) {
1477 try w.writeAll(" .");
1478 } else {
1479 try w.writeAll(", .");
1480 }
1481 try w.writeAll(f_name);
1482 try w.writeAll(" = ");
1483 try w.printValue(ANY, options, @field(value, f_name), max_depth - 1);
1484 }
1485 try w.writeAll(" }");
1486 },
1487 .pointer => |ptr_info| switch (ptr_info.size) {
1488 .one => switch (@typeInfo(ptr_info.child)) {
1489 .array => |array_info| return w.printValue(fmt, options, @as([]const array_info.child, value), max_depth),
1490 .@"enum", .@"union", .@"struct" => return w.printValue(fmt, options, value.*, max_depth),
1491 else => {
1492 var buffers: [2][]const u8 = .{ @typeName(ptr_info.child), "@" };
1493 try w.writeVecAll(&buffers);
1494 try w.printInt(@intFromPtr(value), 16, .lower, options);
1495 return;
1496 },
1497 },
1498 .many, .c => {
1499 if (!is_any) @compileError("cannot format pointer without a specifier (i.e. {s} or {*})");
1500 optionsForbidden(options);
1501 try w.printAddress(value);
1502 },
1503 .slice => {
1504 if (!is_any)
1505 @compileError("cannot format slice without a specifier (i.e. {s}, {x}, {b64}, or {any})");
1506 if (max_depth == 0) return w.writeAll("{ ... }");
1507 try w.writeAll("{ ");
1508 for (value, 0..) |elem, i| {
1509 try w.printValue(fmt, options, elem, max_depth - 1);
1510 if (i != value.len - 1) {
1511 try w.writeAll(", ");
1512 }
1513 }
1514 try w.writeAll(" }");
1515 },
1516 },
1517 .array => {
1518 if (!is_any) @compileError("cannot format array without a specifier (i.e. {s} or {any})");
1519 return printArray(w, fmt, options, &value, max_depth);
1520 },
1521 .vector => |vector| {
1522 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1523 const array: [vector.len]vector.child = value;
1524 return printArray(w, fmt, options, &array, max_depth);
1525 },
1526 .@"fn" => @compileError("unable to format function body type, use '*const " ++ @typeName(T) ++ "' for a function pointer type"),
1527 .type => {
1528 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1529 return w.alignBufferOptions(@typeName(value), options);
1530 },
1531 .enum_literal => {
1532 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1533 optionsForbidden(options);
1534 var vecs: [2][]const u8 = .{ ".", @tagName(value) };
1535 return w.writeVecAll(&vecs);
1536 },
1537 .null => {
1538 if (!is_any and fmt.len != 0) invalidFmtError(fmt, value);
1539 return w.alignBufferOptions("null", options);
1540 },
1541 else => @compileError("unable to format type '" ++ @typeName(T) ++ "'"),
1542 }
1543}
1544
1545fn optionsForbidden(options: std.fmt.Options) void {
1546 assert(options.precision == null);
1547 assert(options.width == null);
1548}
1549
1550fn printErrorSet(w: *Writer, error_set: anyerror) Error!void {
1551 var vecs: [2][]const u8 = .{ "error.", @errorName(error_set) };
1552 try w.writeVecAll(&vecs);
1553}
1554
1555fn printEnumExhaustive(w: *Writer, value: anytype) Error!void {
1556 var vecs: [2][]const u8 = .{ ".", @tagName(value) };
1557 try w.writeVecAll(&vecs);
1558}
1559
1560fn printEnumNonexhaustive(w: *Writer, value: anytype) Error!void {
1561 if (std.enums.tagName(@TypeOf(value), value)) |tag_name| {
1562 var vecs: [2][]const u8 = .{ ".", tag_name };
1563 try w.writeVecAll(&vecs);
1564 return;
1565 }
1566 try w.writeAll("@enumFromInt(");
1567 try w.printInt(@backingInt(value), 10, .lower, .{});
1568 try w.writeByte(')');
1569}
1570
1571/// Prints a double quote, then escapes a string according to Zig string
1572/// literal rules, then a double quote.
1573pub fn printStringEscaped(w: *Writer, bytes: []const u8) Error!void {
1574 try w.writeByte('"');
1575 try std.zig.stringEscape(bytes, w);
1576 try w.writeByte('"');
1577}
1578
1579pub fn printVector(
1580 w: *Writer,
1581 comptime fmt: []const u8,
1582 options: std.fmt.Options,
1583 value: anytype,
1584 max_depth: usize,
1585) Error!void {
1586 const vector = @typeInfo(@TypeOf(value)).vector;
1587 const array: [vector.len]vector.child = value;
1588 return printArray(w, fmt, options, &array, max_depth);
1589}
1590
1591pub fn printArray(
1592 w: *Writer,
1593 comptime fmt: []const u8,
1594 options: std.fmt.Options,
1595 ptr_to_array: anytype,
1596 max_depth: usize,
1597) Error!void {
1598 if (max_depth == 0) return w.writeAll("{ ... }");
1599 try w.writeAll("{ ");
1600 for (ptr_to_array, 0..) |elem, i| {
1601 try w.printValue(fmt, options, elem, max_depth - 1);
1602 if (i < ptr_to_array.len - 1) {
1603 try w.writeAll(", ");
1604 }
1605 }
1606 try w.writeAll(" }");
1607}
1608
1609// A wrapper around `printIntAny` to avoid the generic explosion of this
1610// function by funneling smaller integer types through `isize` and `usize`.
1611pub inline fn printInt(
1612 w: *Writer,
1613 value: anytype,
1614 base: u8,
1615 case: std.fmt.Case,
1616 options: std.fmt.Options,
1617) Error!void {
1618 switch (@TypeOf(value)) {
1619 isize, usize => {},
1620 comptime_int => {
1621 if (comptime std.math.cast(usize, value)) |x| return printIntAny(w, x, base, case, options);
1622 if (comptime std.math.cast(isize, value)) |x| return printIntAny(w, x, base, case, options);
1623 const Int = std.math.IntFittingRange(value, value);
1624 return printIntAny(w, @as(Int, value), base, case, options);
1625 },
1626 else => switch (@typeInfo(@TypeOf(value)).int.signedness) {
1627 .signed => if (std.math.cast(isize, value)) |x| return printIntAny(w, x, base, case, options),
1628 .unsigned => if (std.math.cast(usize, value)) |x| return printIntAny(w, x, base, case, options),
1629 },
1630 }
1631 return printIntAny(w, value, base, case, options);
1632}
1633
1634/// In general, prefer `printInt` to avoid generic explosion. However this
1635/// function may be used when optimal codegen for a particular integer type is
1636/// desired.
1637pub fn printIntAny(
1638 w: *Writer,
1639 value: anytype,
1640 base: u8,
1641 case: std.fmt.Case,
1642 options: std.fmt.Options,
1643) Error!void {
1644 assert(base >= 2);
1645 const value_info = @typeInfo(@TypeOf(value)).int;
1646
1647 // The type must have the same size as `base` or be wider in order for the
1648 // division to work
1649 const min_int_bits = comptime @max(value_info.bits, 8);
1650 const MinInt = @Int(.unsigned, min_int_bits);
1651
1652 const abs_value = @abs(value);
1653 // The worst case in terms of space needed is base 2, plus 1 for the sign
1654 var buf: [1 + @max(@as(comptime_int, value_info.bits), 1)]u8 = undefined;
1655
1656 var a: MinInt = abs_value;
1657 var index: usize = buf.len;
1658
1659 if (base == 10) {
1660 while (a >= 100) : (a = @divTrunc(a, 100)) {
1661 index -= 2;
1662 buf[index..][0..2].* = std.fmt.digits2(@intCast(a % 100));
1663 }
1664
1665 if (a < 10) {
1666 index -= 1;
1667 buf[index] = '0' + @as(u8, @intCast(a));
1668 } else {
1669 index -= 2;
1670 buf[index..][0..2].* = std.fmt.digits2(@intCast(a));
1671 }
1672 } else {
1673 while (true) {
1674 const digit = a % base;
1675 index -= 1;
1676 buf[index] = std.fmt.digitToChar(@intCast(digit), case);
1677 a /= base;
1678 if (a == 0) break;
1679 }
1680 }
1681
1682 if (value_info.signedness == .signed) {
1683 if (value < 0) {
1684 // Negative integer
1685 index -= 1;
1686 buf[index] = '-';
1687 } else if (options.width == null or options.width.? == 0) {
1688 // Positive integer, omit the plus sign
1689 } else {
1690 // Positive integer
1691 index -= 1;
1692 buf[index] = '+';
1693 }
1694 }
1695
1696 return w.alignBufferOptions(buf[index..], options);
1697}
1698
1699pub fn printAsciiChar(w: *Writer, c: u8, options: std.fmt.Options) Error!void {
1700 return w.alignBufferOptions(@as(*const [1]u8, &c), options);
1701}
1702
1703pub fn printAscii(w: *Writer, bytes: []const u8, options: std.fmt.Options) Error!void {
1704 return w.alignBufferOptions(bytes, options);
1705}
1706
1707pub fn printUnicodeCodepoint(w: *Writer, c: u21) Error!void {
1708 var buf: [4]u8 = undefined;
1709 const len = std.unicode.utf8Encode(c, &buf) catch |err| switch (err) {
1710 error.Utf8CannotEncodeSurrogateHalf, error.CodepointTooLarge => l: {
1711 buf[0..3].* = std.unicode.replacement_character_utf8;
1712 break :l 3;
1713 },
1714 };
1715 return w.writeAll(buf[0..len]);
1716}
1717
1718/// Uses a larger stack buffer; asserts mode is decimal or scientific.
1719pub fn printFloat(w: *Writer, value: anytype, options: std.fmt.Number) Error!void {
1720 const mode: std.fmt.float.Mode = switch (options.mode) {
1721 .decimal => .decimal,
1722 .scientific => .scientific,
1723 .binary, .octal, .hex => unreachable,
1724 };
1725 var buf: [std.fmt.float.bufferSize(.decimal, f64)]u8 = undefined;
1726 const s = std.fmt.float.render(&buf, value, .{
1727 .mode = mode,
1728 .precision = options.precision,
1729 }) catch |err| switch (err) {
1730 error.BufferTooSmall => "(float)",
1731 };
1732 return w.alignBuffer(s, options.width orelse s.len, options.alignment, options.fill);
1733}
1734
1735/// Uses a smaller stack buffer; asserts mode is not decimal or scientific.
1736pub fn printFloatHexOptions(w: *Writer, value: anytype, options: std.fmt.Number) Error!void {
1737 var buf: [50]u8 = undefined; // for aligning
1738 var sub_writer: Writer = .fixed(&buf);
1739 switch (options.mode) {
1740 .decimal => unreachable,
1741 .scientific => unreachable,
1742 .binary => @panic("TODO"),
1743 .octal => @panic("TODO"),
1744 .hex => {},
1745 }
1746 printFloatHex(&sub_writer, value, options.case, options.precision) catch unreachable; // buf is large enough
1747
1748 const printed = sub_writer.buffered();
1749 return w.alignBuffer(printed, options.width orelse printed.len, options.alignment, options.fill);
1750}
1751
1752pub fn printFloatHex(w: *Writer, value: anytype, case: std.fmt.Case, opt_precision: ?usize) Error!void {
1753 const v = switch (@TypeOf(value)) {
1754 // comptime_float internally is a f128; this preserves precision.
1755 comptime_float => @as(f128, value),
1756 else => value,
1757 };
1758
1759 if (std.math.signbit(v)) try w.writeByte('-');
1760 if (std.math.isNan(v)) return w.writeAll(switch (case) {
1761 .lower => "nan",
1762 .upper => "NAN",
1763 });
1764 if (std.math.isInf(v)) return w.writeAll(switch (case) {
1765 .lower => "inf",
1766 .upper => "INF",
1767 });
1768
1769 const T = @TypeOf(v);
1770 const TU = @Int(.unsigned, @bitSizeOf(T));
1771
1772 const mantissa_bits = std.math.floatMantissaBits(T);
1773 const fractional_bits = std.math.floatFractionalBits(T);
1774 const exponent_bits = std.math.floatExponentBits(T);
1775 const mantissa_mask = (1 << mantissa_bits) - 1;
1776 const exponent_mask = (1 << exponent_bits) - 1;
1777 const exponent_bias = (1 << (exponent_bits - 1)) - 1;
1778
1779 const as_bits: TU = @bitCast(v);
1780 var mantissa = as_bits & mantissa_mask;
1781 var exponent: i32 = @as(u16, @truncate((as_bits >> mantissa_bits) & exponent_mask));
1782
1783 const is_denormal = exponent == 0 and mantissa != 0;
1784 const is_zero = exponent == 0 and mantissa == 0;
1785
1786 if (is_zero) {
1787 // Handle this case here to simplify the logic below.
1788 try w.writeAll("0x0");
1789 if (opt_precision) |precision| {
1790 if (precision > 0) {
1791 try w.writeAll(".");
1792 try w.splatByteAll('0', precision);
1793 }
1794 } else {
1795 try w.writeAll(".0");
1796 }
1797 try w.writeAll("p0");
1798 return;
1799 }
1800
1801 if (is_denormal) {
1802 // Adjust the exponent for printing.
1803 exponent += 1;
1804 } else {
1805 if (fractional_bits == mantissa_bits)
1806 mantissa |= 1 << fractional_bits; // Add the implicit integer bit.
1807 }
1808
1809 const mantissa_digits = (fractional_bits + 3) / 4;
1810 // Fill in zeroes to round the fraction width to a multiple of 4.
1811 mantissa <<= mantissa_digits * 4 - fractional_bits;
1812
1813 if (opt_precision) |precision| {
1814 // Round if needed.
1815 if (precision < mantissa_digits) {
1816 // We always have at least 4 extra bits.
1817 var extra_bits = (mantissa_digits - precision) * 4;
1818 // The result LSB is the Guard bit, we need two more (Round and
1819 // Sticky) to round the value.
1820 while (extra_bits > 2) {
1821 mantissa = (mantissa >> 1) | (mantissa & 1);
1822 extra_bits -= 1;
1823 }
1824 // Round to nearest, tie to even.
1825 mantissa |= @intFromBool(mantissa & 0b100 != 0);
1826 mantissa += 1;
1827 // Drop the excess bits.
1828 mantissa >>= 2;
1829 // Restore the alignment.
1830 mantissa <<= @as(std.math.Log2Int(TU), @intCast((mantissa_digits - precision) * 4));
1831
1832 const overflow = mantissa & (1 << 1 + mantissa_digits * 4) != 0;
1833 // Prefer a normalized result in case of overflow.
1834 if (overflow) {
1835 mantissa >>= 1;
1836 exponent += 1;
1837 }
1838 }
1839 }
1840
1841 // +1 for the decimal part.
1842 var buf: [1 + mantissa_digits]u8 = undefined;
1843 assert(std.fmt.printInt(&buf, mantissa, 16, case, .{ .fill = '0', .width = 1 + mantissa_digits }) == buf.len);
1844
1845 try w.writeAll("0x");
1846 try w.writeByte(buf[0]);
1847 const trimmed = std.mem.trimEnd(u8, buf[1..], "0");
1848 if (opt_precision) |precision| {
1849 if (precision > 0) try w.writeAll(".");
1850 } else if (trimmed.len > 0) {
1851 try w.writeAll(".");
1852 }
1853 try w.writeAll(trimmed);
1854 // Add trailing zeros if explicitly requested.
1855 if (opt_precision) |precision| if (precision > 0) {
1856 if (precision > trimmed.len)
1857 try w.splatByteAll('0', precision - trimmed.len);
1858 };
1859 try w.writeAll("p");
1860 try w.printInt(exponent - exponent_bias, 10, case, .{});
1861}
1862
1863pub const ByteSizeUnits = enum {
1864 /// This formatter represents the number as multiple of 1000 and uses the SI
1865 /// measurement units (kB, MB, GB, ...).
1866 decimal,
1867 /// This formatter represents the number as multiple of 1024 and uses the IEC
1868 /// measurement units (KiB, MiB, GiB, ...).
1869 binary,
1870};
1871
1872/// Format option `precision` is ignored when `value` is less than 1kB
1873pub fn printByteSize(
1874 w: *Writer,
1875 value: u64,
1876 comptime units: ByteSizeUnits,
1877 options: std.fmt.Options,
1878) Error!void {
1879 if (value == 0) return w.alignBufferOptions("0B", options);
1880 // The worst case in terms of space needed is 32 bytes + 3 for the suffix.
1881 var buf: [std.fmt.float.min_buffer_size + 3]u8 = undefined;
1882
1883 const mags_si = " kMGTPEZY";
1884 const mags_iec = " KMGTPEZY";
1885
1886 const log2 = std.math.log2(value);
1887 const base = switch (units) {
1888 .decimal => 1000,
1889 .binary => 1024,
1890 };
1891 const magnitude = switch (units) {
1892 .decimal => @min(log2 / comptime std.math.log2(1000), mags_si.len - 1),
1893 .binary => @min(log2 / 10, mags_iec.len - 1),
1894 };
1895 const new_value = std.math.lossyCast(f64, value) / std.math.pow(f64, std.math.lossyCast(f64, base), std.math.lossyCast(f64, magnitude));
1896 const suffix = switch (units) {
1897 .decimal => mags_si[magnitude],
1898 .binary => mags_iec[magnitude],
1899 };
1900
1901 const s = switch (magnitude) {
1902 0 => buf[0..std.fmt.printInt(&buf, value, 10, .lower, .{})],
1903 else => std.fmt.float.render(&buf, new_value, .{ .mode = .decimal, .precision = options.precision }) catch |err| switch (err) {
1904 error.BufferTooSmall => unreachable,
1905 },
1906 };
1907
1908 var i: usize = s.len;
1909 if (suffix == ' ') {
1910 buf[i] = 'B';
1911 i += 1;
1912 } else switch (units) {
1913 .decimal => {
1914 buf[i..][0..2].* = [_]u8{ suffix, 'B' };
1915 i += 2;
1916 },
1917 .binary => {
1918 buf[i..][0..3].* = [_]u8{ suffix, 'i', 'B' };
1919 i += 3;
1920 },
1921 }
1922
1923 return w.alignBufferOptions(buf[0..i], options);
1924}
1925
1926// This ANY const is a workaround for: https://github.com/ziglang/zig/issues/7948
1927const ANY = "any";
1928
1929fn stripOptionalOrErrorUnionSpec(comptime fmt: []const u8) []const u8 {
1930 return if (std.mem.eql(u8, fmt[1..], ANY))
1931 ANY
1932 else
1933 fmt[1..];
1934}
1935
1936pub fn invalidFmtError(comptime fmt: []const u8, value: anytype) noreturn {
1937 @compileError("invalid format string '" ++ fmt ++ "' for type '" ++ @typeName(@TypeOf(value)) ++ "'");
1938}
1939
1940pub fn printHex(w: *Writer, bytes: []const u8, case: std.fmt.Case) Error!void {
1941 const charset = switch (case) {
1942 .upper => "0123456789ABCDEF",
1943 .lower => "0123456789abcdef",
1944 };
1945 for (bytes) |c| {
1946 try w.writeByte(charset[c >> 4]);
1947 try w.writeByte(charset[c & 15]);
1948 }
1949}
1950
1951pub fn printBase64(w: *Writer, bytes: []const u8) Error!void {
1952 var chunker = std.mem.window(u8, bytes, 3, 3);
1953 var temp: [5]u8 = undefined;
1954 while (chunker.next()) |chunk| {
1955 try w.writeAll(std.base64.standard.Encoder.encode(&temp, chunk));
1956 }
1957}
1958
1959/// Write a single unsigned integer as LEB128 to the given writer.
1960pub fn writeUleb128(w: *Writer, value: anytype) Error!void {
1961 try w.writeLeb128(switch (@typeInfo(@TypeOf(value))) {
1962 .comptime_int => @as(std.math.IntFittingRange(0, @abs(value)), value),
1963 .int => |value_info| switch (value_info.signedness) {
1964 .signed => @as(@Int(.unsigned, value_info.bits -| 1), @intCast(value)),
1965 .unsigned => value,
1966 },
1967 else => comptime unreachable,
1968 });
1969}
1970
1971/// Write a single signed integer as LEB128 to the given writer.
1972pub fn writeSleb128(w: *Writer, value: anytype) Error!void {
1973 try w.writeLeb128(switch (@typeInfo(@TypeOf(value))) {
1974 .comptime_int => @as(std.math.IntFittingRange(@min(value, -1), @max(0, value)), value),
1975 .int => |value_info| switch (value_info.signedness) {
1976 .signed => value,
1977 .unsigned => @as(@Int(.signed, value_info.bits + 1), value),
1978 },
1979 else => comptime unreachable,
1980 });
1981}
1982
1983/// Write a single integer as LEB128 to the given writer.
1984pub fn writeLeb128(w: *Writer, value: anytype) Error!void {
1985 const T = @TypeOf(value);
1986 const info = switch (@typeInfo(T)) {
1987 .int => |info| info,
1988 else => @compileError(@typeName(T) ++ " not supported"),
1989 };
1990
1991 const BoundInt = @Int(info.signedness, 7);
1992 if (info.bits <= 7 or (value >= std.math.minInt(BoundInt) and value <= std.math.maxInt(BoundInt))) {
1993 const Bits = @Int(info.signedness, 8);
1994 const byte = switch (info.signedness) {
1995 .signed => @as(Bits, @intCast(value)) & 0x7F,
1996 .unsigned => @as(Bits, @intCast(value)),
1997 };
1998 try w.writeByte(@bitCast(byte));
1999 return;
2000 }
2001
2002 const Byte = packed struct { bits: u7, more: bool };
2003 const Int = std.math.ByteAlignedInt(T);
2004
2005 const max_bytes = @divFloor(info.bits - 1, 7) + 1;
2006
2007 const sign_value = value >> (info.bits - 1);
2008 var val: Int = value;
2009 for (0..max_bytes) |_| {
2010 const more = switch (info.signedness) {
2011 .signed => val >> 6 != sign_value,
2012 .unsigned => val > std.math.maxInt(u7),
2013 };
2014
2015 try w.writeByte(@bitCast(@as(Byte, .{
2016 .bits = @intCast(val & 0x7F),
2017 .more = more,
2018 })));
2019
2020 if (!more) return;
2021
2022 val >>= 7;
2023 } else unreachable;
2024}
2025
2026test "serialize signed LEB128" {
2027 // Small values
2028 try testLeb128Encoding(i7, 9, "\x09");
2029 try testLeb128Encoding(i64, 125, "\xFD\x00");
2030
2031 try testLeb128Encoding(i7, -34, "\x5E");
2032 try testLeb128Encoding(i64, -3, "\x7D");
2033
2034 // Random values
2035 try testLeb128Encoding(i16, 19373, "\xAD\x97\x01");
2036 try testLeb128Encoding(i32, 1628839242, "\xCA\xBA\xD8\x88\x06");
2037 try testLeb128Encoding(i64, 3789169920125966546, "\xD2\xB1\xD0\xD5\xF6\xBE\xF5\xCA\x34");
2038 try testLeb128Encoding(i128, 704622239050934257305893323522763588, "\xC4\xD6\x83\xC7\xE3\x91\x95\xC3\x96\x80\x8D\xA5\xF5\xDF\xA3\xDA\x87\x01");
2039
2040 try testLeb128Encoding(i16, -14558, "\xA2\x8E\x7F");
2041 try testLeb128Encoding(i32, -1702738165, "\x8B\x8E\x89\xD4\x79");
2042 try testLeb128Encoding(i64, -1709126996960612298, "\xB6\xE0\x87\xB1\xD3\xC1\xFD\xA3\x68");
2043 try testLeb128Encoding(i128, -113498719181566012704681230050325944039, "\x99\xD2\x80\xBC\xE6\x95\xBC\xC8\xDE\xB4\x9D\x81\x9F\xCA\xC6\xF8\x9C\xD5\x7E");
2044
2045 // {min,max} values
2046 try testLeb128Encoding(i16, std.math.maxInt(i16), "\xFF\xFF\x01");
2047 try testLeb128Encoding(i32, std.math.maxInt(i32), "\xFF\xFF\xFF\xFF\x07");
2048 try testLeb128Encoding(i64, std.math.maxInt(i64), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00");
2049 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");
2050
2051 try testLeb128Encoding(i16, std.math.minInt(i16), "\x80\x80\x7E");
2052 try testLeb128Encoding(i32, std.math.minInt(i32), "\x80\x80\x80\x80\x78");
2053 try testLeb128Encoding(i64, std.math.minInt(i64), "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x7F");
2054 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");
2055
2056 // Specific cases
2057 try testLeb128Encoding(i8, 0, "\x00");
2058
2059 try testLeb128Encoding(i2, -1, "\x7F");
2060 try testLeb128Encoding(i8, -1, "\x7F");
2061
2062 try testLeb128Encoding(i2, 1, "\x01");
2063 try testLeb128Encoding(i8, 1, "\x01");
2064
2065 // Encode byte boundaries
2066 try testLeb128Encoding(i7, std.math.maxInt(i7), "\x3F");
2067 try testLeb128Encoding(i8, std.math.maxInt(i7) + 1, "\xC0\x00");
2068 try testLeb128Encoding(i14, std.math.maxInt(i14), "\xFF\x3F");
2069 try testLeb128Encoding(i15, std.math.maxInt(i14) + 1, "\x80\xC0\x00");
2070 try testLeb128Encoding(i49, std.math.maxInt(i49), "\xFF\xFF\xFF\xFF\xFF\xFF\x3F");
2071 try testLeb128Encoding(i50, std.math.maxInt(i49) + 1, "\x80\x80\x80\x80\x80\x80\xC0\x00");
2072 try testLeb128Encoding(i56, std.math.maxInt(i56), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x3F");
2073 try testLeb128Encoding(i57, std.math.maxInt(i56) + 1, "\x80\x80\x80\x80\x80\x80\x80\xC0\x00");
2074 try testLeb128Encoding(i63, std.math.maxInt(i63), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x3F");
2075 try testLeb128Encoding(i64, std.math.maxInt(i63) + 1, "\x80\x80\x80\x80\x80\x80\x80\x80\xC0\x00");
2076
2077 try testLeb128Encoding(i7, std.math.minInt(i7), "\x40");
2078 try testLeb128Encoding(i8, std.math.minInt(i7) - 1, "\xBF\x7F");
2079 try testLeb128Encoding(i14, std.math.minInt(i14), "\x80\x40");
2080 try testLeb128Encoding(i15, std.math.minInt(i14) - 1, "\xFF\xBF\x7F");
2081 try testLeb128Encoding(i49, std.math.minInt(i49), "\x80\x80\x80\x80\x80\x80\x40");
2082 try testLeb128Encoding(i50, std.math.minInt(i49) - 1, "\xFF\xFF\xFF\xFF\xFF\xFF\xBF\x7F");
2083 try testLeb128Encoding(i56, std.math.minInt(i56), "\x80\x80\x80\x80\x80\x80\x80\x40");
2084 try testLeb128Encoding(i57, std.math.minInt(i56) - 1, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xBF\x7F");
2085 try testLeb128Encoding(i63, std.math.minInt(i63), "\x80\x80\x80\x80\x80\x80\x80\x80\x40");
2086 try testLeb128Encoding(i64, std.math.minInt(i63) - 1, "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xBF\x7F");
2087}
2088
2089test "serialize unsigned LEB128" {
2090 // Small values
2091 try testLeb128Encoding(u7, 12, "\x0C");
2092 try testLeb128Encoding(u64, 201, "\xC9\x01");
2093
2094 // Random values
2095 try testLeb128Encoding(u8, 254, "\xFE\x01");
2096 try testLeb128Encoding(u16, 30241, "\xA1\xEC\x01");
2097 try testLeb128Encoding(u32, 2173531193, "\xB9\xE8\xB5\x8C\x08");
2098 try testLeb128Encoding(u64, 18321125691115744902, "\x86\xDD\xF2\x81\xF2\xD7\xED\xA0\xFE\x01");
2099 try testLeb128Encoding(u128, 122619209508942982841456325819614676193, "\xE1\x89\xF3\xD9\xE3\xAD\xEC\xF4\x98\x95\xF8\xBB\xD7\xB8\xF2\xCC\xBF\xB8\x01");
2100
2101 // Max values
2102 try testLeb128Encoding(u8, std.math.maxInt(u8), "\xFF\x01");
2103 try testLeb128Encoding(u16, std.math.maxInt(u16), "\xFF\xFF\x03");
2104 try testLeb128Encoding(u32, std.math.maxInt(u32), "\xFF\xFF\xFF\xFF\x0F");
2105 try testLeb128Encoding(u64, std.math.maxInt(u64), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x01");
2106 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");
2107
2108 // Specific cases
2109 try testLeb128Encoding(u0, 0, "\x00");
2110 try testLeb128Encoding(u1, 0, "\x00");
2111 try testLeb128Encoding(u8, 0, "\x00");
2112
2113 try testLeb128Encoding(u1, 1, "\x01");
2114 try testLeb128Encoding(u8, 1, "\x01");
2115
2116 // Encode byte boundaries
2117 try testLeb128Encoding(u7, std.math.maxInt(u7), "\x7F");
2118 try testLeb128Encoding(u8, std.math.maxInt(u7) + 1, "\x80\x01");
2119 try testLeb128Encoding(u14, std.math.maxInt(u14), "\xFF\x7F");
2120 try testLeb128Encoding(u15, std.math.maxInt(u14) + 1, "\x80\x80\x01");
2121 try testLeb128Encoding(u49, std.math.maxInt(u49), "\xFF\xFF\xFF\xFF\xFF\xFF\x7F");
2122 try testLeb128Encoding(u50, std.math.maxInt(u49) + 1, "\x80\x80\x80\x80\x80\x80\x80\x01");
2123 try testLeb128Encoding(u56, std.math.maxInt(u56), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x7F");
2124 try testLeb128Encoding(u57, std.math.maxInt(u56) + 1, "\x80\x80\x80\x80\x80\x80\x80\x80\x01");
2125 try testLeb128Encoding(u63, std.math.maxInt(u63), "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x7F");
2126 try testLeb128Encoding(u64, std.math.maxInt(u63) + 1, "\x80\x80\x80\x80\x80\x80\x80\x80\x80\x01");
2127}
2128
2129fn testLeb128Encoding(comptime T: type, value: T, encoding: []const u8) !void {
2130 const info = @typeInfo(T).int;
2131 const max_bytes = @divFloor(info.bits -| 1, 7) + 1;
2132 var bytes: [max_bytes]u8 = undefined;
2133
2134 var fw: Writer = .fixed(&bytes);
2135 try writeLeb128(&fw, value);
2136
2137 try std.testing.expectEqualSlices(u8, encoding, fw.buffered());
2138}
2139
2140test "printValue max_depth" {
2141 const Vec2 = struct {
2142 const SelfType = @This();
2143 x: f32,
2144 y: f32,
2145
2146 pub fn format(self: SelfType, w: *Writer) Error!void {
2147 return w.print("({d:.3},{d:.3})", .{ self.x, self.y });
2148 }
2149 };
2150 const E = enum {
2151 One,
2152 Two,
2153 Three,
2154 };
2155 const TU = union(enum) {
2156 const SelfType = @This();
2157 float: f32,
2158 int: u32,
2159 ptr: ?*SelfType,
2160 };
2161 const S = struct {
2162 const SelfType = @This();
2163 a: ?*SelfType,
2164 tu: TU,
2165 e: E,
2166 vec: Vec2,
2167 };
2168
2169 var inst = S{
2170 .a = null,
2171 .tu = TU{ .ptr = null },
2172 .e = E.Two,
2173 .vec = Vec2{ .x = 10.2, .y = 2.22 },
2174 };
2175 inst.a = &inst;
2176 inst.tu.ptr = &inst.tu;
2177
2178 var buf: [1000]u8 = undefined;
2179 var w: Writer = .fixed(&buf);
2180 try w.printValue("", .{}, inst, 0);
2181 try testing.expectEqualStrings(".{ ... }", w.buffered());
2182
2183 w = .fixed(&buf);
2184 try w.printValue("", .{}, inst, 1);
2185 try testing.expectEqualStrings(".{ .a = .{ ... }, .tu = .{ ... }, .e = .Two, .vec = .{ ... } }", w.buffered());
2186
2187 w = .fixed(&buf);
2188 try w.printValue("", .{}, inst, 2);
2189 try testing.expectEqualStrings(".{ .a = .{ .a = .{ ... }, .tu = .{ ... }, .e = .Two, .vec = .{ ... } }, .tu = .{ .ptr = .{ ... } }, .e = .Two, .vec = .{ .x = 10.2, .y = 2.22 } }", w.buffered());
2190
2191 w = .fixed(&buf);
2192 try w.printValue("", .{}, inst, 3);
2193 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());
2194
2195 const vec: @Vector(4, i32) = .{ 1, 2, 3, 4 };
2196 w = .fixed(&buf);
2197 try w.printValue("", .{}, vec, 0);
2198 try testing.expectEqualStrings("{ ... }", w.buffered());
2199
2200 w = .fixed(&buf);
2201 try w.printValue("", .{}, vec, 1);
2202 try testing.expectEqualStrings("{ 1, 2, 3, 4 }", w.buffered());
2203}
2204
2205test printInt {
2206 try testPrintIntCase("-1", @as(i1, -1), 10, .lower, .{});
2207
2208 try testPrintIntCase("-101111000110000101001110", @as(i32, -12345678), 2, .lower, .{});
2209 try testPrintIntCase("-12345678", @as(i32, -12345678), 10, .lower, .{});
2210 try testPrintIntCase("-bc614e", @as(i32, -12345678), 16, .lower, .{});
2211 try testPrintIntCase("-BC614E", @as(i32, -12345678), 16, .upper, .{});
2212
2213 try testPrintIntCase("12345678", @as(u32, 12345678), 10, .upper, .{});
2214
2215 try testPrintIntCase(" 666", @as(u32, 666), 10, .lower, .{ .width = 6 });
2216 try testPrintIntCase(" 1234", @as(u32, 0x1234), 16, .lower, .{ .width = 6 });
2217 try testPrintIntCase("1234", @as(u32, 0x1234), 16, .lower, .{ .width = 1 });
2218
2219 try testPrintIntCase("+42", @as(i32, 42), 10, .lower, .{ .width = 3 });
2220 try testPrintIntCase("-42", @as(i32, -42), 10, .lower, .{ .width = 3 });
2221
2222 try testPrintIntCase("123456789123456789", @as(comptime_int, 123456789123456789), 10, .lower, .{});
2223}
2224
2225test "printFloat with comptime_float" {
2226 var buf: [20]u8 = undefined;
2227 var w: Writer = .fixed(&buf);
2228 try w.printFloat(@as(comptime_float, 1.0), std.fmt.Options.toNumber(.{}, .scientific, .lower));
2229 try testing.expectEqualStrings(w.buffered(), "1e0");
2230 try testing.expectFmt("1", "{}", .{1.0});
2231}
2232
2233test "{q} format string" {
2234 const data: []const u8 = "i\tlike\"cheese\x00\x05cheese";
2235 try testing.expectFmt("hello \"i\\tlike\\\"cheese\\x00\\x05cheese\" world", "hello {q} world", .{data});
2236}
2237
2238test "{qf} format string" {
2239 const data: []const u8 = "😎";
2240 try testing.expectFmt("hello \"@\\\"😎\\\"\" world", "hello {qf} world", .{std.zig.fmtId(data)});
2241}
2242
2243fn testPrintIntCase(expected: []const u8, value: anytype, base: u8, case: std.fmt.Case, options: std.fmt.Options) !void {
2244 var buffer: [100]u8 = undefined;
2245 var w: Writer = .fixed(&buffer);
2246 try w.printInt(value, base, case, options);
2247 try testing.expectEqualStrings(expected, w.buffered());
2248}
2249
2250test printByteSize {
2251 try testing.expectFmt("file size: 42B\n", "file size: {B}\n", .{42});
2252 try testing.expectFmt("file size: 42B\n", "file size: {Bi}\n", .{42});
2253 try testing.expectFmt("file size: 63MB\n", "file size: {B}\n", .{63 * 1000 * 1000});
2254 try testing.expectFmt("file size: 63MiB\n", "file size: {Bi}\n", .{63 * 1024 * 1024});
2255 try testing.expectFmt("file size: 42B\n", "file size: {B:.2}\n", .{42});
2256 try testing.expectFmt("file size: 42B\n", "file size: {B:>9.2}\n", .{42});
2257 try testing.expectFmt("file size: 66.06MB\n", "file size: {B:.2}\n", .{63 * 1024 * 1024});
2258 try testing.expectFmt("file size: 60.08MiB\n", "file size: {Bi:.2}\n", .{63 * 1000 * 1000});
2259 try testing.expectFmt("file size: =66.06MB=\n", "file size: {B:=^9.2}\n", .{63 * 1024 * 1024});
2260 try testing.expectFmt("file size: 66.06MB\n", "file size: {B: >9.2}\n", .{63 * 1024 * 1024});
2261 try testing.expectFmt("file size: 66.06MB \n", "file size: {B: <9.2}\n", .{63 * 1024 * 1024});
2262 try testing.expectFmt("file size: 0.01844674407370955ZB\n", "file size: {B}\n", .{std.math.maxInt(u64)});
2263}
2264
2265test "bytes.hex" {
2266 const some_bytes = "\xCA\xFE\xBA\xBE";
2267 try testing.expectFmt("lowercase: cafebabe\n", "lowercase: {x}\n", .{some_bytes});
2268 try testing.expectFmt("uppercase: CAFEBABE\n", "uppercase: {X}\n", .{some_bytes});
2269 try testing.expectFmt("uppercase: CAFE\n", "uppercase: {X}\n", .{some_bytes[0..2]});
2270 try testing.expectFmt("lowercase: babe\n", "lowercase: {x}\n", .{some_bytes[2..]});
2271 const bytes_with_zeros = "\x00\x0E\xBA\xBE";
2272 try testing.expectFmt("lowercase: 000ebabe\n", "lowercase: {x}\n", .{bytes_with_zeros});
2273}
2274
2275test "padding" {
2276 const foo: enum { foo } = .foo;
2277 try testing.expectFmt("tag: |foo |\n", "tag: |{t:<4}|\n", .{foo});
2278
2279 const bar: error{bar} = error.bar;
2280 try testing.expectFmt("error: |bar |\n", "error: |{t:<4}|\n", .{bar});
2281}
2282
2283test fixed {
2284 {
2285 var buf: [255]u8 = undefined;
2286 var w: Writer = .fixed(&buf);
2287 try w.print("{s}{s}!", .{ "Hello", "World" });
2288 try testing.expectEqualStrings("HelloWorld!", w.buffered());
2289 }
2290
2291 comptime {
2292 var buf: [255]u8 = undefined;
2293 var w: Writer = .fixed(&buf);
2294 try w.print("{s}{s}!", .{ "Hello", "World" });
2295 try testing.expectEqualStrings("HelloWorld!", w.buffered());
2296 }
2297}
2298
2299test "fixed output" {
2300 var buffer: [10]u8 = undefined;
2301 var w: Writer = .fixed(&buffer);
2302
2303 try w.writeAll("Hello");
2304 try testing.expect(std.mem.eql(u8, w.buffered(), "Hello"));
2305
2306 try w.writeAll("world");
2307 try testing.expect(std.mem.eql(u8, w.buffered(), "Helloworld"));
2308
2309 try testing.expectError(error.WriteFailed, w.writeAll("!"));
2310 try testing.expect(std.mem.eql(u8, w.buffered(), "Helloworld"));
2311
2312 w = .fixed(&buffer);
2313
2314 try testing.expect(w.buffered().len == 0);
2315
2316 try testing.expectError(error.WriteFailed, w.writeAll("Hello world!"));
2317 try testing.expect(std.mem.eql(u8, w.buffered(), "Hello worl"));
2318}
2319
2320test "writeSplat 0 len splat larger than capacity" {
2321 var buf: [8]u8 = undefined;
2322 var w: Writer = .fixed(&buf);
2323 const n = try w.writeSplat(&.{"something that overflows buf"}, 0);
2324 try testing.expectEqual(0, n);
2325}
2326
2327pub fn failingDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
2328 _ = w;
2329 _ = data;
2330 _ = splat;
2331 return error.WriteFailed;
2332}
2333
2334pub fn failingSendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize {
2335 _ = w;
2336 _ = file_reader;
2337 _ = limit;
2338 return error.WriteFailed;
2339}
2340
2341pub fn failingRebase(w: *Writer, preserve: usize, capacity: usize) Error!void {
2342 _ = w;
2343 _ = preserve;
2344 _ = capacity;
2345 return error.WriteFailed;
2346}
2347
2348pub const Discarding = struct {
2349 count: u64,
2350 writer: Writer,
2351
2352 pub fn init(buffer: []u8) Discarding {
2353 return .{
2354 .count = 0,
2355 .writer = .{
2356 .vtable = &.{
2357 .drain = Discarding.drain,
2358 .sendFile = Discarding.sendFile,
2359 },
2360 .buffer = buffer,
2361 },
2362 };
2363 }
2364
2365 /// Includes buffered data (no need to flush).
2366 pub fn fullCount(d: *const Discarding) u64 {
2367 return d.count + d.writer.end;
2368 }
2369
2370 pub fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
2371 const d: *Discarding = @alignCast(@fieldParentPtr("writer", w));
2372 const slice = data[0 .. data.len - 1];
2373 const pattern = data[slice.len];
2374 var written: usize = pattern.len * splat;
2375 for (slice) |bytes| written += bytes.len;
2376 d.count += w.end + written;
2377 w.end = 0;
2378 return written;
2379 }
2380
2381 pub fn sendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize {
2382 if (File.Handle == void) return error.Unimplemented;
2383 const d: *Discarding = @alignCast(@fieldParentPtr("writer", w));
2384 d.count += w.end;
2385 w.end = 0;
2386 if (limit == .nothing) return 0;
2387 if (file_reader.getSize()) |size| {
2388 const n = limit.minInt64(size - file_reader.pos);
2389 if (n == 0) return error.EndOfStream;
2390 file_reader.seekBy(@intCast(n)) catch return error.Unimplemented;
2391 w.end = 0;
2392 d.count += n;
2393 return n;
2394 } else |_| {
2395 // Error is observable on `file_reader` instance, and it is better to
2396 // treat the file as a pipe.
2397 return error.Unimplemented;
2398 }
2399 }
2400};
2401
2402/// Removes the first `n` bytes from `buffer` by shifting buffer contents,
2403/// returning how many bytes are left after consuming the entire buffer, or
2404/// zero if the entire buffer was not consumed.
2405///
2406/// Useful for `VTable.drain` function implementations to implement partial
2407/// drains.
2408pub fn consume(w: *Writer, n: usize) usize {
2409 if (n < w.end) {
2410 const remaining = w.buffer[n..w.end];
2411 @memmove(w.buffer[0..remaining.len], remaining);
2412 w.end = remaining.len;
2413 return 0;
2414 }
2415 defer w.end = 0;
2416 return n - w.end;
2417}
2418
2419/// Shortcut for setting `end` to zero and returning zero. Equivalent to
2420/// calling `consume` with `end`.
2421pub fn consumeAll(w: *Writer) usize {
2422 w.end = 0;
2423 return 0;
2424}
2425
2426/// For use when the `Writer` implementation can cannot offer a more efficient
2427/// implementation than a basic read/write loop on the file.
2428pub fn unimplementedSendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize {
2429 _ = w;
2430 _ = file_reader;
2431 _ = limit;
2432 return error.Unimplemented;
2433}
2434
2435/// When this function is called it usually means the buffer got full, so it's
2436/// time to return an error. However, we still need to make sure all of the
2437/// available buffer has been filled. Also, it may be called from `flush` in
2438/// which case it should return successfully.
2439pub fn fixedDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
2440 if (data.len == 0) return 0;
2441 for (data[0 .. data.len - 1]) |bytes| {
2442 const dest = w.buffer[w.end..];
2443 const len = @min(bytes.len, dest.len);
2444 @memcpy(dest[0..len], bytes[0..len]);
2445 w.end += len;
2446 if (bytes.len > dest.len) return error.WriteFailed;
2447 }
2448 const pattern = data[data.len - 1];
2449 const dest = w.buffer[w.end..];
2450 switch (pattern.len) {
2451 0 => return 0,
2452 1 => {
2453 assert(splat >= dest.len);
2454 @memset(dest, pattern[0]);
2455 w.end += dest.len;
2456 return error.WriteFailed;
2457 },
2458 else => {
2459 for (0..splat) |i| {
2460 const remaining = dest[i * pattern.len ..];
2461 const len = @min(pattern.len, remaining.len);
2462 @memcpy(remaining[0..len], pattern[0..len]);
2463 w.end += len;
2464 if (pattern.len > remaining.len) return error.WriteFailed;
2465 }
2466 unreachable;
2467 },
2468 }
2469}
2470
2471pub fn unreachableDrain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
2472 _ = w;
2473 _ = data;
2474 _ = splat;
2475 unreachable;
2476}
2477
2478pub fn unreachableRebase(w: *Writer, preserve: usize, capacity: usize) Error!void {
2479 _ = w;
2480 _ = preserve;
2481 _ = capacity;
2482 unreachable;
2483}
2484
2485pub fn fromArrayList(array_list: *ArrayList(u8)) Writer {
2486 defer array_list.* = .empty;
2487 array_list.pointer_stability.assertUnlocked();
2488 return .{
2489 .vtable = &.{
2490 .drain = fixedDrain,
2491 .flush = noopFlush,
2492 .rebase = failingRebase,
2493 },
2494 .buffer = array_list.allocatedSlice(),
2495 .end = array_list.items.len,
2496 };
2497}
2498
2499pub fn toArrayList(w: *Writer) ArrayList(u8) {
2500 const result: ArrayList(u8) = .{
2501 .items = w.buffer[0..w.end],
2502 .capacity = w.buffer.len,
2503 .pointer_stability = .{},
2504 };
2505 w.buffer = &.{};
2506 w.end = 0;
2507 return result;
2508}
2509
2510/// Provides a `Writer` implementation based on calling `Hasher.update`, sending
2511/// all data also to an underlying `Writer`.
2512///
2513/// When using this, the underlying writer is best unbuffered because all
2514/// writes are passed on directly to it.
2515///
2516/// This implementation makes suboptimal buffering decisions due to being
2517/// generic. A better solution will involve creating a writer for each hash
2518/// function, where the splat buffer can be tailored to the hash implementation
2519/// details.
2520///
2521/// Contrast with `Hashing` which terminates the stream pipeline.
2522pub fn Hashed(comptime Hasher: type) type {
2523 return struct {
2524 out: *Writer,
2525 hasher: Hasher,
2526 writer: Writer,
2527
2528 pub fn init(out: *Writer, buffer: []u8) @This() {
2529 return .initHasher(out, .{}, buffer);
2530 }
2531
2532 pub fn initHasher(out: *Writer, hasher: Hasher, buffer: []u8) @This() {
2533 return .{
2534 .out = out,
2535 .hasher = hasher,
2536 .writer = .{
2537 .buffer = buffer,
2538 .vtable = &.{ .drain = @This().drain },
2539 },
2540 };
2541 }
2542
2543 fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
2544 const this: *@This() = @alignCast(@fieldParentPtr("writer", w));
2545 const aux = w.buffered();
2546 const aux_n = try this.out.writeSplatHeader(aux, data, splat);
2547 if (aux_n < w.end) {
2548 this.hasher.update(w.buffer[0..aux_n]);
2549 const remaining = w.buffer[aux_n..w.end];
2550 @memmove(w.buffer[0..remaining.len], remaining);
2551 w.end = remaining.len;
2552 return 0;
2553 }
2554 this.hasher.update(aux);
2555 const n = aux_n - w.end;
2556 w.end = 0;
2557 var remaining: usize = n;
2558 for (data[0 .. data.len - 1]) |slice| {
2559 if (remaining <= slice.len) {
2560 this.hasher.update(slice[0..remaining]);
2561 return n;
2562 }
2563 remaining -= slice.len;
2564 this.hasher.update(slice);
2565 }
2566 const pattern = data[data.len - 1];
2567 assert(remaining <= splat * pattern.len);
2568 switch (pattern.len) {
2569 0 => {
2570 assert(remaining == 0);
2571 },
2572 1 => {
2573 var buffer: [64]u8 = undefined;
2574 @memset(&buffer, pattern[0]);
2575 while (remaining > 0) {
2576 const update_len = @min(remaining, buffer.len);
2577 this.hasher.update(buffer[0..update_len]);
2578 remaining -= update_len;
2579 }
2580 },
2581 else => {
2582 while (remaining > 0) {
2583 const update_len = @min(remaining, pattern.len);
2584 this.hasher.update(pattern[0..update_len]);
2585 remaining -= update_len;
2586 }
2587 },
2588 }
2589 return n;
2590 }
2591 };
2592}
2593
2594/// Provides a `Writer` implementation based on calling `Hasher.update`,
2595/// discarding all data.
2596///
2597/// This implementation makes suboptimal buffering decisions due to being
2598/// generic. A better solution will involve creating a writer for each hash
2599/// function, where the splat buffer can be tailored to the hash implementation
2600/// details.
2601///
2602/// The total number of bytes written is stored in `hasher`.
2603///
2604/// Contrast with `Hashed` which also passes the data to an underlying stream.
2605pub fn Hashing(comptime Hasher: type) type {
2606 return struct {
2607 hasher: Hasher,
2608 writer: Writer,
2609
2610 pub fn init(buffer: []u8) @This() {
2611 return .initHasher(.init(.{}), buffer);
2612 }
2613
2614 pub fn initHasher(hasher: Hasher, buffer: []u8) @This() {
2615 return .{
2616 .hasher = hasher,
2617 .writer = .{
2618 .buffer = buffer,
2619 .vtable = &.{ .drain = @This().drain },
2620 },
2621 };
2622 }
2623
2624 fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
2625 const this: *@This() = @alignCast(@fieldParentPtr("writer", w));
2626 this.hasher.update(w.buffered());
2627 w.end = 0;
2628 var n: usize = 0;
2629 for (data[0 .. data.len - 1]) |slice| {
2630 this.hasher.update(slice);
2631 n += slice.len;
2632 }
2633 for (0..splat) |_| this.hasher.update(data[data.len - 1]);
2634 return n + splat * data[data.len - 1].len;
2635 }
2636 };
2637}
2638
2639/// Maintains `Writer` state such that it writes to the unused capacity of an
2640/// array list, filling it up completely before making a call through the
2641/// vtable, causing a resize. Consequently, the same, optimized, non-generic
2642/// machine code that uses `Writer`, such as formatted printing, takes
2643/// the hot paths when using this API.
2644///
2645/// When using this API, it is not necessary to call `flush`.
2646pub const Allocating = struct {
2647 allocator: Allocator,
2648 writer: Writer,
2649 alignment: std.mem.Alignment,
2650
2651 pub fn init(allocator: Allocator) Allocating {
2652 return .initAligned(allocator, .of(u8));
2653 }
2654
2655 pub fn initAligned(allocator: Allocator, alignment: std.mem.Alignment) Allocating {
2656 return .{
2657 .allocator = allocator,
2658 .writer = .{
2659 .buffer = &.{},
2660 .vtable = &vtable,
2661 },
2662 .alignment = alignment,
2663 };
2664 }
2665
2666 pub fn initCapacity(allocator: Allocator, capacity: usize) error{OutOfMemory}!Allocating {
2667 return .{
2668 .allocator = allocator,
2669 .writer = .{
2670 .buffer = if (capacity == 0)
2671 &.{}
2672 else
2673 (allocator.rawAlloc(capacity, .of(u8), @returnAddress()) orelse
2674 return error.OutOfMemory)[0..capacity],
2675 .vtable = &vtable,
2676 },
2677 .alignment = .of(u8),
2678 };
2679 }
2680
2681 pub fn initOwnedSlice(allocator: Allocator, slice: []u8) Allocating {
2682 return initOwnedSliceAligned(allocator, .of(u8), slice);
2683 }
2684
2685 pub fn initOwnedSliceAligned(
2686 allocator: Allocator,
2687 comptime alignment: std.mem.Alignment,
2688 slice: []align(alignment.toByteUnits()) u8,
2689 ) Allocating {
2690 return .{
2691 .allocator = allocator,
2692 .writer = .{
2693 .buffer = slice,
2694 .vtable = &vtable,
2695 },
2696 .alignment = alignment,
2697 };
2698 }
2699
2700 /// Replaces `array_list` with empty, taking ownership of the memory.
2701 pub fn fromArrayList(allocator: Allocator, array_list: *ArrayList(u8)) Allocating {
2702 return fromArrayListAligned(allocator, .of(u8), array_list);
2703 }
2704
2705 /// Replaces `array_list` with empty, taking ownership of the memory.
2706 pub fn fromArrayListAligned(
2707 allocator: Allocator,
2708 comptime alignment: std.mem.Alignment,
2709 array_list: *std.array_list.Aligned(u8, alignment),
2710 ) Allocating {
2711 defer array_list.* = .empty;
2712 return .{
2713 .allocator = allocator,
2714 .writer = .{
2715 .vtable = &vtable,
2716 .buffer = array_list.allocatedSlice(),
2717 .end = array_list.items.len,
2718 },
2719 .alignment = alignment,
2720 };
2721 }
2722
2723 const vtable: VTable = .{
2724 .drain = Allocating.drain,
2725 .sendFile = Allocating.sendFile,
2726 .flush = noopFlush,
2727 .rebase = growingRebase,
2728 };
2729
2730 pub fn deinit(a: *Allocating) void {
2731 if (a.writer.buffer.len == 0) return;
2732 a.allocator.rawFree(a.writer.buffer, a.alignment, @returnAddress());
2733 a.* = undefined;
2734 }
2735
2736 /// Returns an array list that takes ownership of the allocated memory.
2737 /// Resets the `Allocating` to an empty state.
2738 pub fn toArrayList(a: *Allocating) ArrayList(u8) {
2739 return toArrayListAligned(a, .of(u8));
2740 }
2741
2742 /// Returns an array list that takes ownership of the allocated memory.
2743 /// Resets the `Allocating` to an empty state.
2744 pub fn toArrayListAligned(
2745 a: *Allocating,
2746 comptime alignment: std.mem.Alignment,
2747 ) std.array_list.Aligned(u8, alignment) {
2748 assert(a.alignment == alignment); // Required for Allocator correctness.
2749 const w = &a.writer;
2750 const result: std.array_list.Aligned(u8, alignment) = .{
2751 .items = @alignCast(w.buffer[0..w.end]),
2752 .capacity = w.buffer.len,
2753 .pointer_stability = .{},
2754 };
2755 w.buffer = &.{};
2756 w.end = 0;
2757 return result;
2758 }
2759
2760 pub fn ensureUnusedCapacity(a: *Allocating, additional_count: usize) Allocator.Error!void {
2761 const new_capacity = std.math.add(usize, a.writer.end, additional_count) catch return error.OutOfMemory;
2762 return ensureTotalCapacity(a, new_capacity);
2763 }
2764
2765 pub fn ensureTotalCapacity(a: *Allocating, new_capacity: usize) Allocator.Error!void {
2766 // Protects growing unnecessarily since better_capacity will be larger.
2767 if (a.writer.buffer.len >= new_capacity) return;
2768 const better_capacity = ArrayList(u8).growCapacity(new_capacity);
2769 return ensureTotalCapacityPrecise(a, better_capacity);
2770 }
2771
2772 pub fn ensureTotalCapacityPrecise(a: *Allocating, new_capacity: usize) Allocator.Error!void {
2773 const old_memory = a.writer.buffer;
2774 if (old_memory.len >= new_capacity) return;
2775 assert(new_capacity != 0);
2776 const alignment = a.alignment;
2777 if (old_memory.len > 0) {
2778 if (a.allocator.rawRemap(old_memory, alignment, new_capacity, @returnAddress())) |new| {
2779 a.writer.buffer = new[0..new_capacity];
2780 return;
2781 }
2782 }
2783 const new_memory = (a.allocator.rawAlloc(new_capacity, alignment, @returnAddress()) orelse
2784 return error.OutOfMemory)[0..new_capacity];
2785 const saved = old_memory[0..a.writer.end];
2786 @memcpy(new_memory[0..saved.len], saved);
2787 if (old_memory.len != 0) a.allocator.rawFree(old_memory, alignment, @returnAddress());
2788 a.writer.buffer = new_memory;
2789 }
2790
2791 pub fn toOwnedSlice(a: *Allocating) Allocator.Error![]u8 {
2792 const old_memory = a.writer.buffer;
2793 const alignment = a.alignment;
2794 const buffered_len = a.writer.end;
2795
2796 if (old_memory.len > 0) {
2797 if (buffered_len == 0) {
2798 a.allocator.rawFree(old_memory, alignment, @returnAddress());
2799 a.writer.buffer = &.{};
2800 a.writer.end = 0;
2801 return old_memory[0..0];
2802 } else if (a.allocator.rawRemap(old_memory, alignment, buffered_len, @returnAddress())) |new| {
2803 a.writer.buffer = &.{};
2804 a.writer.end = 0;
2805 return new[0..buffered_len];
2806 }
2807 }
2808
2809 if (buffered_len == 0)
2810 return a.writer.buffer[0..0];
2811
2812 const new_memory = (a.allocator.rawAlloc(buffered_len, alignment, @returnAddress()) orelse
2813 return error.OutOfMemory)[0..buffered_len];
2814 @memcpy(new_memory, old_memory[0..buffered_len]);
2815 if (old_memory.len != 0) a.allocator.rawFree(old_memory, alignment, @returnAddress());
2816 a.writer.buffer = &.{};
2817 a.writer.end = 0;
2818 return new_memory;
2819 }
2820
2821 pub fn toOwnedSliceSentinel(a: *Allocating, comptime sentinel: u8) Allocator.Error![:sentinel]u8 {
2822 // This addition can never overflow because `a.writer.buffer` can never occupy the whole address space.
2823 try ensureTotalCapacityPrecise(a, a.writer.end + 1);
2824 a.writer.buffer[a.writer.end] = sentinel;
2825 a.writer.end += 1;
2826 errdefer a.writer.end -= 1;
2827 const result = try toOwnedSlice(a);
2828 return result[0 .. result.len - 1 :sentinel];
2829 }
2830
2831 pub fn written(a: *Allocating) []u8 {
2832 return a.writer.buffered();
2833 }
2834
2835 pub fn shrinkRetainingCapacity(a: *Allocating, new_len: usize) void {
2836 a.writer.end = new_len;
2837 }
2838
2839 pub fn clearRetainingCapacity(a: *Allocating) void {
2840 a.shrinkRetainingCapacity(0);
2841 }
2842
2843 fn drain(w: *Writer, data: []const []const u8, splat: usize) Error!usize {
2844 const a: *Allocating = @fieldParentPtr("writer", w);
2845 assert(data.len != 0);
2846 const count = countSplat(data, splat);
2847 a.ensureUnusedCapacity(count + 1) catch return error.WriteFailed;
2848 for (data[0 .. data.len - 1]) |bytes| {
2849 @memcpy(a.writer.buffer[a.writer.end..][0..bytes.len], bytes);
2850 a.writer.end += bytes.len;
2851 }
2852 const pattern = data[data.len - 1];
2853 switch (pattern.len) {
2854 0 => {},
2855 1 => {
2856 @memset(a.writer.buffer[a.writer.end..][0..splat], pattern[0]);
2857 a.writer.end += splat;
2858 },
2859 else => for (0..splat) |_| {
2860 @memcpy(a.writer.buffer[a.writer.end..][0..pattern.len], pattern);
2861 a.writer.end += pattern.len;
2862 },
2863 }
2864 return count;
2865 }
2866
2867 fn sendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize {
2868 if (File.Handle == void) return error.Unimplemented;
2869 if (limit == .nothing) return 0;
2870 const a: *Allocating = @fieldParentPtr("writer", w);
2871 const pos = file_reader.logicalPos();
2872 const additional, const exact = if (file_reader.getSize()) |size|
2873 .{ size - pos, true }
2874 else |_|
2875 .{ std.atomic.cache_line, false };
2876 if (additional == 0) return error.EndOfStream;
2877 a.ensureUnusedCapacity(limit.minInt64(additional)) catch return error.WriteFailed;
2878 const buffer = a.writer.buffer[a.writer.end..];
2879 const dest = if (exact) buffer[0..limit.minInt64(additional)] else limit.slice(buffer);
2880 const n = try file_reader.interface.readSliceShort(dest);
2881 if (n == 0) return error.EndOfStream;
2882 a.writer.end += n;
2883 return n;
2884 }
2885
2886 fn growingRebase(w: *Writer, preserve: usize, minimum_len: usize) Error!void {
2887 const a: *Allocating = @fieldParentPtr("writer", w);
2888 const total = std.math.add(usize, preserve, minimum_len) catch return error.WriteFailed;
2889 a.ensureTotalCapacity(total) catch return error.WriteFailed;
2890 a.ensureUnusedCapacity(minimum_len) catch return error.WriteFailed;
2891 }
2892
2893 fn testAllocating(comptime alignment: std.mem.Alignment) !void {
2894 var a: Allocating = .initAligned(testing.allocator, alignment);
2895 defer a.deinit();
2896 const w = &a.writer;
2897
2898 const x: i32 = 42;
2899 const y: i32 = 1234;
2900 try w.print("x: {}\ny: {}\n", .{ x, y });
2901 const expected = "x: 42\ny: 1234\n";
2902 try testing.expectEqualSlices(u8, expected, a.written());
2903
2904 // exercise *Aligned methods
2905 var l = a.toArrayListAligned(alignment);
2906 defer l.deinit(testing.allocator);
2907 try testing.expectEqualSlices(u8, expected, l.items);
2908 a = .fromArrayListAligned(testing.allocator, alignment, &l);
2909 try testing.expectEqualSlices(u8, expected, a.written());
2910 const slice: []align(alignment.toByteUnits()) u8 = @alignCast(try a.toOwnedSlice());
2911 try testing.expectEqualSlices(u8, expected, slice);
2912 a = .initOwnedSliceAligned(testing.allocator, alignment, slice);
2913 try testing.expectEqualSlices(u8, expected, a.writer.buffer);
2914 }
2915
2916 test Allocating {
2917 try testAllocating(.@"1");
2918 try testAllocating(.@"4");
2919 try testAllocating(.@"8");
2920 try testAllocating(.@"16");
2921 try testAllocating(.@"32");
2922 try testAllocating(.@"64");
2923 }
2924};
2925
2926test "discarding sendFile" {
2927 const io = testing.io;
2928
2929 var tmp_dir = testing.tmpDir(.{});
2930 defer tmp_dir.cleanup();
2931
2932 const file = try tmp_dir.dir.createFile(io, "input.txt", .{ .read = true });
2933 defer file.close(io);
2934 var r_buffer: [256]u8 = undefined;
2935 var file_writer: File.Writer = .init(file, io, &r_buffer);
2936 try file_writer.interface.writeByte('h');
2937 try file_writer.interface.flush();
2938
2939 var file_reader = file_writer.moveToReader();
2940 try file_reader.seekTo(0);
2941
2942 var w_buffer: [256]u8 = undefined;
2943 var discarding: Writer.Discarding = .init(&w_buffer);
2944
2945 _ = try file_reader.interface.streamRemaining(&discarding.writer);
2946}
2947
2948test "allocating sendFile" {
2949 const io = testing.io;
2950
2951 var tmp_dir = testing.tmpDir(.{});
2952 defer tmp_dir.cleanup();
2953
2954 const file = try tmp_dir.dir.createFile(io, "input.txt", .{ .read = true });
2955 defer file.close(io);
2956 var r_buffer: [2]u8 = undefined;
2957 var file_writer: File.Writer = .init(file, io, &r_buffer);
2958 try file_writer.interface.writeAll("abcd");
2959 try file_writer.interface.flush();
2960
2961 var file_reader = file_writer.moveToReader();
2962 try file_reader.seekTo(0);
2963 try file_reader.interface.fill(2);
2964
2965 var allocating: Writer.Allocating = .init(testing.allocator);
2966 defer allocating.deinit();
2967 try allocating.ensureUnusedCapacity(1);
2968 try testing.expectEqual(4, allocating.writer.sendFileAll(&file_reader, .unlimited));
2969 try testing.expectEqualStrings("abcd", allocating.writer.buffered());
2970}
2971
2972test sendFileReading {
2973 const io = testing.io;
2974
2975 var tmp_dir = testing.tmpDir(.{});
2976 defer tmp_dir.cleanup();
2977
2978 const file = try tmp_dir.dir.createFile(io, "input.txt", .{ .read = true });
2979 defer file.close(io);
2980 var r_buffer: [2]u8 = undefined;
2981 var file_writer: File.Writer = .init(file, io, &r_buffer);
2982 try file_writer.interface.writeAll("abcd");
2983 try file_writer.interface.flush();
2984
2985 var file_reader = file_writer.moveToReader();
2986 try file_reader.seekTo(0);
2987 try file_reader.interface.fill(2);
2988
2989 var w_buffer: [1]u8 = undefined;
2990 var discarding: Writer.Discarding = .init(&w_buffer);
2991 try testing.expectEqual(4, discarding.writer.sendFileReadingAll(&file_reader, .unlimited));
2992}
2993
2994test writeStruct {
2995 var buffer: [16]u8 = undefined;
2996 const S = extern struct { a: u64, b: u32, c: u32 };
2997 const s: S = .{ .a = 1, .b = 2, .c = 3 };
2998 {
2999 var w: Writer = .fixed(&buffer);
3000 try w.writeStruct(s, .little);
3001 try testing.expectEqualSlices(u8, &.{
3002 1, 0, 0, 0, 0, 0, 0, 0, //
3003 2, 0, 0, 0, //
3004 3, 0, 0, 0, //
3005 }, &buffer);
3006 }
3007 {
3008 var w: Writer = .fixed(&buffer);
3009 try w.writeStruct(s, .big);
3010 try testing.expectEqualSlices(u8, &.{
3011 0, 0, 0, 0, 0, 0, 0, 1, //
3012 0, 0, 0, 2, //
3013 0, 0, 0, 3, //
3014 }, &buffer);
3015 }
3016}
3017
3018test writeSliceEndian {
3019 var buffer: [5]u8 align(2) = undefined;
3020 var w: Writer = .fixed(&buffer);
3021 try w.writeByte('x');
3022 const array: [2]u16 = .{ 0x1234, 0x5678 };
3023 try writeSliceEndian(&w, u16, &array, .big);
3024 try testing.expectEqualSlices(u8, &.{ 'x', 0x12, 0x34, 0x56, 0x78 }, &buffer);
3025}
3026
3027test "writableSlice with fixed writer" {
3028 var buf: [2]u8 = undefined;
3029 var w: std.Io.Writer = .fixed(&buf);
3030 try w.writeByte(1);
3031 try std.testing.expectError(error.WriteFailed, w.writableSlice(2));
3032}
3033
3034test splatBytePreserve {
3035 try testSplatBytePreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 5, .splat_len = 5 });
3036 try testSplatBytePreserve(.{ .buf_len = 10, .fill_len = 9, .preserve = 5, .splat_len = 2 });
3037 try testSplatBytePreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 5, .splat_len = 6 });
3038 try testSplatBytePreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 6, .splat_len = 6 });
3039 try testSplatBytePreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 5, .splat_len = 10 });
3040 try testSplatBytePreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 6, .splat_len = 10 });
3041 try testSplatBytePreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 6, .splat_len = 11 });
3042 try testSplatBytePreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 6, .splat_len = 80 });
3043 try testSplatBytePreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 6, .splat_len = 85 });
3044 try testSplatBytePreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 10, .splat_len = 6 });
3045 try testSplatBytePreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 10, .splat_len = 11 });
3046 try testSplatBytePreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 10, .splat_len = 80 });
3047 try testSplatBytePreserve(.{ .buf_len = 10, .fill_len = 5, .preserve = 10, .splat_len = 85 });
3048}
3049
3050fn testSplatBytePreserve(options: struct { buf_len: u4, fill_len: u4, preserve: u4, splat_len: u8 }) !void {
3051 assert(options.fill_len <= options.buf_len);
3052 assert(options.preserve <= options.buf_len);
3053
3054 const fill_buf = "abcdefghijklmno";
3055 const fill = fill_buf[0..options.fill_len];
3056 var expected_out_buf: [256]u8 = @splat('X');
3057 @memcpy(expected_out_buf[0..options.fill_len], fill);
3058 const expected_out = expected_out_buf[0 .. options.fill_len + options.splat_len];
3059 const expected_preserved = expected_out[expected_out.len -| options.preserve..];
3060
3061 var out_buf: [256]u8 = undefined;
3062 var fw: Writer = .fixed(&out_buf);
3063 var indirect_buffer: [16]u8 = undefined;
3064 var twi: std.testing.WriterIndirect = .init(&fw, indirect_buffer[0..options.buf_len]);
3065 const w = &twi.interface;
3066
3067 try w.writeAll(fill);
3068 try w.splatBytePreserve(options.preserve, 'X', options.splat_len);
3069
3070 try std.testing.expectEqualStrings(expected_preserved, w.buffer[w.end -| options.preserve..w.end]);
3071
3072 try w.flush();
3073
3074 try std.testing.expectEqualStrings(expected_out, fw.buffer[0..fw.end]);
3075}