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