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), &copy);
 852                    return w.writeAll(@ptrCast((&copy)[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}