| ... | ... | @@ -4,7 +4,7 @@ const native_endian = builtin.target.cpu.arch.endian(); |
| 4 | 4 | const Writer = @This(); |
| 5 | 5 | const std = @import("../std.zig"); |
| 6 | 6 | const assert = std.debug.assert; |
| 7 | | const Limit = std.io.Limit; |
| 7 | const Limit = std.Io.Limit; |
| 8 | 8 | const File = std.fs.File; |
| 9 | 9 | const testing = std.testing; |
| 10 | 10 | const Allocator = std.mem.Allocator; |
| ... | ... | @@ -76,6 +76,14 @@ pub const VTable = struct { |
| 76 | 76 | /// There may be subsequent calls to `drain` and `sendFile` after a `flush` |
| 77 | 77 | /// operation. |
| 78 | 78 | flush: *const fn (w: *Writer) Error!void = defaultFlush, |
| 79 | |
| 80 | /// Ensures `capacity` more bytes can be buffered without rebasing. |
| 81 | /// |
| 82 | /// The most recent `preserve` bytes must remain buffered. |
| 83 | /// |
| 84 | /// Only called when `capacity` bytes cannot fit into the unused capacity |
| 85 | /// of `buffer`. |
| 86 | rebase: *const fn (w: *Writer, preserve: usize, capacity: usize) Error!void = defaultRebase, |
| 79 | 87 | }; |
| 80 | 88 | |
| 81 | 89 | pub const Error = error{ |
| ... | ... | @@ -117,6 +125,7 @@ pub fn fixed(buffer: []u8) Writer { |
| 117 | 125 | .vtable = &.{ |
| 118 | 126 | .drain = fixedDrain, |
| 119 | 127 | .flush = noopFlush, |
| 128 | .rebase = failingRebase, |
| 120 | 129 | }, |
| 121 | 130 | .buffer = buffer, |
| 122 | 131 | }; |
| ... | ... | @@ -130,6 +139,7 @@ pub const failing: Writer = .{ |
| 130 | 139 | .vtable = &.{ |
| 131 | 140 | .drain = failingDrain, |
| 132 | 141 | .sendFile = failingSendFile, |
| 142 | .rebase = failingRebase, |
| 133 | 143 | }, |
| 134 | 144 | }; |
| 135 | 145 | |
| ... | ... | @@ -276,7 +286,7 @@ fn writeSplatHeaderLimitFinish( |
| 276 | 286 | |
| 277 | 287 | test "writeSplatHeader splatting avoids buffer aliasing temptation" { |
| 278 | 288 | const initial_buf = try testing.allocator.alloc(u8, 8); |
| 279 | | var aw: std.io.Writer.Allocating = .initOwnedSlice(testing.allocator, initial_buf); |
| 289 | var aw: Allocating = .initOwnedSlice(testing.allocator, initial_buf); |
| 280 | 290 | defer aw.deinit(); |
| 281 | 291 | // This test assumes 8 vector buffer in this function. |
| 282 | 292 | const n = try aw.writer.writeSplatHeader("header which is longer than buf ", &.{ |
| ... | ... | @@ -307,24 +317,41 @@ pub fn noopFlush(w: *Writer) Error!void { |
| 307 | 317 | |
| 308 | 318 | test "fixed buffer flush" { |
| 309 | 319 | var buffer: [1]u8 = undefined; |
| 310 | | var writer: std.io.Writer = .fixed(&buffer); |
| 320 | var writer: Writer = .fixed(&buffer); |
| 311 | 321 | |
| 312 | 322 | try writer.writeByte(10); |
| 313 | 323 | try writer.flush(); |
| 314 | 324 | try testing.expectEqual(10, buffer[0]); |
| 315 | 325 | } |
| 316 | 326 | |
| 317 | | /// Calls `VTable.drain` but hides the last `preserve_len` bytes from the |
| 318 | | /// implementation, keeping them buffered. |
| 319 | | pub fn drainPreserve(w: *Writer, preserve_len: usize) Error!void { |
| 320 | | const preserved_head = w.end -| preserve_len; |
| 321 | | const preserved_tail = w.end; |
| 322 | | const preserved_len = preserved_tail - preserved_head; |
| 323 | | w.end = preserved_head; |
| 324 | | defer w.end += preserved_len; |
| 325 | | assert(0 == try w.vtable.drain(w, &.{""}, 1)); |
| 326 | | assert(w.end <= preserved_head + preserved_len); |
| 327 | | @memmove(w.buffer[w.end..][0..preserved_len], w.buffer[preserved_head..preserved_tail]); |
| 327 | pub fn rebase(w: *Writer, preserve: usize, unused_capacity_len: usize) Error!void { |
| 328 | if (w.buffer.len - w.end >= unused_capacity_len) { |
| 329 | @branchHint(.likely); |
| 330 | return; |
| 331 | } |
| 332 | try w.vtable.rebase(w, preserve, unused_capacity_len); |
| 333 | } |
| 334 | |
| 335 | pub fn defaultRebase(w: *Writer, preserve: usize, minimum_len: usize) Error!void { |
| 336 | while (w.buffer.len - w.end < minimum_len) { |
| 337 | { |
| 338 | // TODO: instead of this logic that "hides" data from |
| 339 | // the implementation, introduce a seek index to Writer |
| 340 | const preserved_head = w.end -| preserve; |
| 341 | const preserved_tail = w.end; |
| 342 | const preserved_len = preserved_tail - preserved_head; |
| 343 | w.end = preserved_head; |
| 344 | defer w.end += preserved_len; |
| 345 | assert(0 == try w.vtable.drain(w, &.{""}, 1)); |
| 346 | assert(w.end <= preserved_head + preserved_len); |
| 347 | @memmove(w.buffer[w.end..][0..preserved_len], w.buffer[preserved_head..preserved_tail]); |
| 348 | } |
| 349 | |
| 350 | // If the loop condition was false this assertion would have passed |
| 351 | // anyway. Otherwise, give the implementation a chance to grow the |
| 352 | // buffer before asserting on the buffer length. |
| 353 | assert(w.buffer.len - preserve >= minimum_len); |
| 354 | } |
| 328 | 355 | } |
| 329 | 356 | |
| 330 | 357 | pub fn unusedCapacitySlice(w: *const Writer) []u8 { |
| ... | ... | @@ -353,53 +380,44 @@ pub fn writableSlice(w: *Writer, len: usize) Error![]u8 { |
| 353 | 380 | return big_slice[0..len]; |
| 354 | 381 | } |
| 355 | 382 | |
| 356 | | /// Asserts the provided buffer has total capacity enough for `minimum_length`. |
| 383 | /// Asserts the provided buffer has total capacity enough for `minimum_len`. |
| 357 | 384 | /// |
| 358 | 385 | /// Does not `advance` the buffer end position. |
| 359 | 386 | /// |
| 360 | | /// If `minimum_length` is zero, this is equivalent to `unusedCapacitySlice`. |
| 361 | | pub fn writableSliceGreedy(w: *Writer, minimum_length: usize) Error![]u8 { |
| 362 | | while (w.buffer.len - w.end < minimum_length) { |
| 363 | | assert(0 == try w.vtable.drain(w, &.{""}, 1)); |
| 364 | | // If the loop condition was false this assertion would have passed |
| 365 | | // anyway. Otherwise, give the implementation a chance to grow the |
| 366 | | // buffer before asserting on the buffer length. |
| 367 | | assert(w.buffer.len >= minimum_length); |
| 368 | | } else { |
| 369 | | @branchHint(.likely); |
| 370 | | return w.buffer[w.end..]; |
| 371 | | } |
| 387 | /// If `minimum_len` is zero, this is equivalent to `unusedCapacitySlice`. |
| 388 | pub fn writableSliceGreedy(w: *Writer, minimum_len: usize) Error![]u8 { |
| 389 | return writableSliceGreedyPreserve(w, 0, minimum_len); |
| 372 | 390 | } |
| 373 | 391 | |
| 374 | | /// Asserts the provided buffer has total capacity enough for `minimum_length` |
| 375 | | /// and `preserve_len` combined. |
| 392 | /// Asserts the provided buffer has total capacity enough for `minimum_len` |
| 393 | /// and `preserve` combined. |
| 376 | 394 | /// |
| 377 | 395 | /// Does not `advance` the buffer end position. |
| 378 | 396 | /// |
| 379 | | /// When draining the buffer, ensures that at least `preserve_len` bytes |
| 397 | /// When draining the buffer, ensures that at least `preserve` bytes |
| 380 | 398 | /// remain buffered. |
| 381 | 399 | /// |
| 382 | | /// If `preserve_len` is zero, this is equivalent to `writableSliceGreedy`. |
| 383 | | pub fn writableSliceGreedyPreserve(w: *Writer, preserve_len: usize, minimum_length: usize) Error![]u8 { |
| 384 | | assert(w.buffer.len >= preserve_len + minimum_length); |
| 385 | | while (w.buffer.len - w.end < minimum_length) { |
| 386 | | try drainPreserve(w, preserve_len); |
| 387 | | } else { |
| 400 | /// If `preserve` is zero, this is equivalent to `writableSliceGreedy`. |
| 401 | pub fn writableSliceGreedyPreserve(w: *Writer, preserve: usize, minimum_len: usize) Error![]u8 { |
| 402 | if (w.buffer.len - w.end >= minimum_len) { |
| 388 | 403 | @branchHint(.likely); |
| 389 | 404 | return w.buffer[w.end..]; |
| 390 | 405 | } |
| 406 | try rebase(w, preserve, minimum_len); |
| 407 | assert(w.buffer.len >= preserve + minimum_len); |
| 408 | return w.buffer[w.end..]; |
| 391 | 409 | } |
| 392 | 410 | |
| 393 | 411 | /// Asserts the provided buffer has total capacity enough for `len`. |
| 394 | 412 | /// |
| 395 | 413 | /// Advances the buffer end position by `len`. |
| 396 | 414 | /// |
| 397 | | /// When draining the buffer, ensures that at least `preserve_len` bytes |
| 415 | /// When draining the buffer, ensures that at least `preserve` bytes |
| 398 | 416 | /// remain buffered. |
| 399 | 417 | /// |
| 400 | | /// If `preserve_len` is zero, this is equivalent to `writableSlice`. |
| 401 | | pub fn writableSlicePreserve(w: *Writer, preserve_len: usize, len: usize) Error![]u8 { |
| 402 | | const big_slice = try w.writableSliceGreedyPreserve(preserve_len, len); |
| 418 | /// If `preserve` is zero, this is equivalent to `writableSlice`. |
| 419 | pub fn writableSlicePreserve(w: *Writer, preserve: usize, len: usize) Error![]u8 { |
| 420 | const big_slice = try w.writableSliceGreedyPreserve(preserve, len); |
| 403 | 421 | advance(w, len); |
| 404 | 422 | return big_slice[0..len]; |
| 405 | 423 | } |
| ... | ... | @@ -708,16 +726,18 @@ pub fn writeByte(w: *Writer, byte: u8) Error!void { |
| 708 | 726 | } |
| 709 | 727 | } |
| 710 | 728 | |
| 711 | | /// When draining the buffer, ensures that at least `preserve_len` bytes |
| 729 | /// When draining the buffer, ensures that at least `preserve` bytes |
| 712 | 730 | /// remain buffered. |
| 713 | | pub fn writeBytePreserve(w: *Writer, preserve_len: usize, byte: u8) Error!void { |
| 714 | | while (w.buffer.len - w.end == 0) { |
| 715 | | try drainPreserve(w, preserve_len); |
| 716 | | } else { |
| 731 | pub fn writeBytePreserve(w: *Writer, preserve: usize, byte: u8) Error!void { |
| 732 | if (w.buffer.len - w.end != 0) { |
| 717 | 733 | @branchHint(.likely); |
| 718 | 734 | w.buffer[w.end] = byte; |
| 719 | 735 | w.end += 1; |
| 736 | return; |
| 720 | 737 | } |
| 738 | try w.vtable.rebase(w, preserve, 1); |
| 739 | w.buffer[w.end] = byte; |
| 740 | w.end += 1; |
| 721 | 741 | } |
| 722 | 742 | |
| 723 | 743 | /// Writes the same byte many times, performing the underlying write call as |
| ... | ... | @@ -735,18 +755,18 @@ test splatByteAll { |
| 735 | 755 | try testing.expectEqualStrings("7" ** 45, aw.writer.buffered()); |
| 736 | 756 | } |
| 737 | 757 | |
| 738 | | pub fn splatBytePreserve(w: *Writer, preserve_len: usize, byte: u8, n: usize) Error!void { |
| 758 | pub fn splatBytePreserve(w: *Writer, preserve: usize, byte: u8, n: usize) Error!void { |
| 739 | 759 | const new_end = w.end + n; |
| 740 | 760 | if (new_end <= w.buffer.len) { |
| 741 | 761 | @memset(w.buffer[w.end..][0..n], byte); |
| 742 | 762 | w.end = new_end; |
| 743 | 763 | return; |
| 744 | 764 | } |
| 745 | | // If `n` is large, we can ignore `preserve_len` up to a point. |
| 765 | // If `n` is large, we can ignore `preserve` up to a point. |
| 746 | 766 | var remaining = n; |
| 747 | | while (remaining > preserve_len) { |
| 767 | while (remaining > preserve) { |
| 748 | 768 | assert(remaining != 0); |
| 749 | | remaining -= try splatByte(w, byte, remaining - preserve_len); |
| 769 | remaining -= try splatByte(w, byte, remaining - preserve); |
| 750 | 770 | if (w.end + remaining <= w.buffer.len) { |
| 751 | 771 | @memset(w.buffer[w.end..][0..remaining], byte); |
| 752 | 772 | w.end += remaining; |
| ... | ... | @@ -754,9 +774,9 @@ pub fn splatBytePreserve(w: *Writer, preserve_len: usize, byte: u8, n: usize) Er |
| 754 | 774 | } |
| 755 | 775 | } |
| 756 | 776 | // All the next bytes received must be preserved. |
| 757 | | if (preserve_len < w.end) { |
| 758 | | @memmove(w.buffer[0..preserve_len], w.buffer[w.end - preserve_len ..][0..preserve_len]); |
| 759 | | w.end = preserve_len; |
| 777 | if (preserve < w.end) { |
| 778 | @memmove(w.buffer[0..preserve], w.buffer[w.end - preserve ..][0..preserve]); |
| 779 | w.end = preserve; |
| 760 | 780 | } |
| 761 | 781 | while (remaining > 0) remaining -= try w.splatByte(byte, remaining); |
| 762 | 782 | } |
| ... | ... | @@ -1667,7 +1687,7 @@ pub const ByteSizeUnits = enum { |
| 1667 | 1687 | |
| 1668 | 1688 | /// Format option `precision` is ignored when `value` is less than 1kB |
| 1669 | 1689 | pub fn printByteSize( |
| 1670 | | w: *std.io.Writer, |
| 1690 | w: *Writer, |
| 1671 | 1691 | value: u64, |
| 1672 | 1692 | comptime units: ByteSizeUnits, |
| 1673 | 1693 | options: std.fmt.Options, |
| ... | ... | @@ -2169,7 +2189,7 @@ test "fixed output" { |
| 2169 | 2189 | |
| 2170 | 2190 | test "writeSplat 0 len splat larger than capacity" { |
| 2171 | 2191 | var buf: [8]u8 = undefined; |
| 2172 | | var w: std.io.Writer = .fixed(&buf); |
| 2192 | var w: Writer = .fixed(&buf); |
| 2173 | 2193 | const n = try w.writeSplat(&.{"something that overflows buf"}, 0); |
| 2174 | 2194 | try testing.expectEqual(0, n); |
| 2175 | 2195 | } |
| ... | ... | @@ -2188,6 +2208,13 @@ pub fn failingSendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) File |
| 2188 | 2208 | return error.WriteFailed; |
| 2189 | 2209 | } |
| 2190 | 2210 | |
| 2211 | pub fn failingRebase(w: *Writer, preserve: usize, capacity: usize) Error!void { |
| 2212 | _ = w; |
| 2213 | _ = preserve; |
| 2214 | _ = capacity; |
| 2215 | return error.WriteFailed; |
| 2216 | } |
| 2217 | |
| 2191 | 2218 | pub const Discarding = struct { |
| 2192 | 2219 | count: u64, |
| 2193 | 2220 | writer: Writer, |
| ... | ... | @@ -2455,7 +2482,7 @@ pub fn Hashing(comptime Hasher: type) type { |
| 2455 | 2482 | /// Maintains `Writer` state such that it writes to the unused capacity of an |
| 2456 | 2483 | /// array list, filling it up completely before making a call through the |
| 2457 | 2484 | /// vtable, causing a resize. Consequently, the same, optimized, non-generic |
| 2458 | | /// machine code that uses `std.io.Reader`, such as formatted printing, takes |
| 2485 | /// machine code that uses `std.Io.Reader`, such as formatted printing, takes |
| 2459 | 2486 | /// the hot paths when using this API. |
| 2460 | 2487 | /// |
| 2461 | 2488 | /// When using this API, it is not necessary to call `flush`. |
| ... | ... | @@ -2514,6 +2541,7 @@ pub const Allocating = struct { |
| 2514 | 2541 | .drain = Allocating.drain, |
| 2515 | 2542 | .sendFile = Allocating.sendFile, |
| 2516 | 2543 | .flush = noopFlush, |
| 2544 | .rebase = growingRebase, |
| 2517 | 2545 | }; |
| 2518 | 2546 | |
| 2519 | 2547 | pub fn deinit(a: *Allocating) void { |
| ... | ... | @@ -2595,7 +2623,7 @@ pub const Allocating = struct { |
| 2595 | 2623 | return list.items.len - start_len; |
| 2596 | 2624 | } |
| 2597 | 2625 | |
| 2598 | | fn sendFile(w: *Writer, file_reader: *File.Reader, limit: std.io.Limit) FileError!usize { |
| 2626 | fn sendFile(w: *Writer, file_reader: *File.Reader, limit: Limit) FileError!usize { |
| 2599 | 2627 | if (File.Handle == void) return error.Unimplemented; |
| 2600 | 2628 | if (limit == .nothing) return 0; |
| 2601 | 2629 | const a: *Allocating = @fieldParentPtr("writer", w); |
| ... | ... | @@ -2612,6 +2640,15 @@ pub const Allocating = struct { |
| 2612 | 2640 | return n; |
| 2613 | 2641 | } |
| 2614 | 2642 | |
| 2643 | fn growingRebase(w: *Writer, preserve: usize, minimum_len: usize) Error!void { |
| 2644 | _ = preserve; // This implementation always preserves the entire buffer. |
| 2645 | const a: *Allocating = @fieldParentPtr("writer", w); |
| 2646 | const gpa = a.allocator; |
| 2647 | var list = a.toArrayList(); |
| 2648 | defer setArrayList(a, list); |
| 2649 | list.ensureUnusedCapacity(gpa, minimum_len) catch return error.WriteFailed; |
| 2650 | } |
| 2651 | |
| 2615 | 2652 | fn setArrayList(a: *Allocating, list: std.ArrayListUnmanaged(u8)) void { |
| 2616 | 2653 | a.writer.buffer = list.allocatedSlice(); |
| 2617 | 2654 | a.writer.end = list.items.len; |
| ... | ... | @@ -2645,7 +2682,7 @@ test "discarding sendFile" { |
| 2645 | 2682 | try file_reader.seekTo(0); |
| 2646 | 2683 | |
| 2647 | 2684 | var w_buffer: [256]u8 = undefined; |
| 2648 | | var discarding: std.io.Writer.Discarding = .init(&w_buffer); |
| 2685 | var discarding: Writer.Discarding = .init(&w_buffer); |
| 2649 | 2686 | |
| 2650 | 2687 | _ = try file_reader.interface.streamRemaining(&discarding.writer); |
| 2651 | 2688 | } |
| ... | ... | @@ -2664,7 +2701,7 @@ test "allocating sendFile" { |
| 2664 | 2701 | var file_reader = file_writer.moveToReader(); |
| 2665 | 2702 | try file_reader.seekTo(0); |
| 2666 | 2703 | |
| 2667 | | var allocating: std.io.Writer.Allocating = .init(testing.allocator); |
| 2704 | var allocating: Writer.Allocating = .init(testing.allocator); |
| 2668 | 2705 | defer allocating.deinit(); |
| 2669 | 2706 | |
| 2670 | 2707 | _ = try file_reader.interface.streamRemaining(&allocating.writer); |
| ... | ... | @@ -2702,3 +2739,10 @@ test writeSliceEndian { |
| 2702 | 2739 | try writeSliceEndian(&w, u16, &array, .big); |
| 2703 | 2740 | try testing.expectEqualSlices(u8, &.{ 'x', 0x12, 0x34, 0x56, 0x78 }, &buffer); |
| 2704 | 2741 | } |
| 2742 | |
| 2743 | test "writableSlice with fixed writer" { |
| 2744 | var buf: [2]u8 = undefined; |
| 2745 | var w: std.Io.Writer = .fixed(&buf); |
| 2746 | try w.writeByte(1); |
| 2747 | try std.testing.expectError(error.WriteFailed, w.writableSlice(2)); |
| 2748 | } |