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